feat(shuma): captura acotada + reproceso de salidas vía stdin

shuma-exec: cota dura de memoria. CommandSpec.capture_limit (bytes):
pasado el tope se emite RunEvent::Truncated una vez y el resto se
descarta —pero el pipe se sigue drenando, así el proceso no se
bloquea y termina normal. CommandSpec.stdin_data alimenta un texto
por la entrada estándar (escrito en su propio hilo).

shuma-session: CommandRun.truncated.

shuma-shell: tope de captura de 8 MiB por comando. Cada card con
salida muestra «⤳ reprocesar» — al pulsarlo, el próximo comando
filtra esa salida capturada (vía stdin) sin re-ejecutar el original;
un banner marca el modo. Las cards truncadas avisan «⚠ truncado».

shuma-exec: 12 tests (incluye truncado y reproceso por stdin).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-20 19:53:42 +00:00
parent 0740d2e2af
commit b4be5e1c72
4 changed files with 281 additions and 42 deletions
@@ -60,6 +60,8 @@ pub struct CommandRun {
pub exit_code: Option<i32>,
/// Salida — cada línea sabe si es de stdout o de stderr.
pub output: Vec<OutputLine>,
/// `true` si la salida superó el tope de captura y se descartó parte.
pub truncated: bool,
/// Segundo Unix en que arrancó.
pub started_at: u64,
/// Segundo Unix en que terminó.
@@ -167,12 +169,20 @@ impl WorkSession {
status: RunStatus::Running,
exit_code: None,
output: Vec::new(),
truncated: false,
started_at: now,
finished_at: None,
});
id
}
/// Marca que la salida de un comando se truncó al tope de captura.
pub fn mark_truncated(&mut self, id: RunId) {
if let Some(r) = self.run_mut(id) {
r.truncated = true;
}
}
pub fn run(&self, id: RunId) -> Option<&CommandRun> {
self.history.iter().find(|r| r.id == id)
}