diff --git a/hive-bash-mcp/src/runner.rs b/hive-bash-mcp/src/runner.rs index a2d6674d..424bb9f9 100644 --- a/hive-bash-mcp/src/runner.rs +++ b/hive-bash-mcp/src/runner.rs @@ -517,34 +517,6 @@ fn poll_once(socket: &Path, claimed: &Arc>>) { // Task execution // --------------------------------------------------------------------------- -/// Max characters kept from the command's first line when building the -/// "running" todo summary (see `short_cmd_label`). -const CMD_LABEL_MAX_CHARS: usize = 100; - -/// Build a short, single-line label for `cmd` to use in the "running" -/// todo summary. `cmd` can be an entire multi-line script (heredocs are -/// a common agent pattern) — embedding it whole balloons the todo body -/// in every UI that renders it, so this keeps only the first non-blank -/// line, char-truncated to `CMD_LABEL_MAX_CHARS`, with a trailing `…` -/// whenever either the line itself was cut or more lines follow. The -/// full command is still on disk in the task file for `status`/`view`. -fn short_cmd_label(cmd: &str) -> String { - let mut lines = cmd.lines().map(str::trim).filter(|l| !l.is_empty()); - let first = lines.next().unwrap_or(""); - let more_lines = lines.next().is_some(); - let char_truncated = first.chars().count() > CMD_LABEL_MAX_CHARS; - let label: String = if char_truncated { - first.chars().take(CMD_LABEL_MAX_CHARS).collect() - } else { - first.to_owned() - }; - if char_truncated || more_lines { - format!("{label}…") - } else { - label - } -} - async fn run_task(mut task: TaskFile, socket: &Path) { let id = task.id.clone(); tracing::info!(id = %id, cmd = %task.cmd, "bash_runner: starting task"); @@ -554,12 +526,15 @@ async fn run_task(mut task: TaskFile, socket: &Path) { if let Err(e) = write_task(&task) { tracing::warn!(id = %id, error = ?e, "bash_runner: write running state failed"); } - upsert_bash_todo( - socket, - &id, - format!("bash task `{id}` running: `{}`", short_cmd_label(&task.cmd)), - ) - .await; + // No command text in the summary: `id` is already the human-chosen + // `name` when the caller passed one, and even the auto-generated + // hex id was already returned to the agent in the `run` tool's own + // result — the todo only needs to say which task, not repeat the + // command. An unbounded raw command (e.g. a multi-line heredoc + // script) would balloon every UI that renders todo summaries; the + // full command stays on disk in the task file for `status`/`view` + // instead. + upsert_bash_todo(socket, &id, format!("bash task `{id}` running")).await; // Best-effort: tally the normalised command head for the /stats // "favorite tools" view. Counted once per execution, regardless of // exit status. Never fails the task. @@ -858,43 +833,10 @@ fn done_summary(id: &str, summary: &str, output: Option<(bool, bool)>) -> String #[cfg(test)] mod tests { - use super::{CMD_LABEL_MAX_CHARS, short_cmd_label, validate_task_name}; + use super::validate_task_name; use crate::test_util::with_harness_dir; use hive_types::Ident; - #[test] - fn short_cmd_label_short_single_line_is_unchanged() { - assert_eq!(short_cmd_label("echo hi"), "echo hi"); - } - - #[test] - fn short_cmd_label_trims_and_skips_leading_blank_lines() { - assert_eq!(short_cmd_label("\n\n echo hi \nmore stuff"), "echo hi…"); - } - - #[test] - fn short_cmd_label_flags_multiline_even_when_first_line_is_short() { - // The whole point: a short first line followed by a long heredoc - // body must still read as truncated, not as the complete command. - let cmd = "cat > script.sh <<'SH'\n#!/usr/bin/env bash\necho hi\nSH\n"; - let label = short_cmd_label(cmd); - assert_eq!(label, "cat > script.sh <<'SH'…"); - } - - #[test] - fn short_cmd_label_char_truncates_a_long_single_line() { - let long = "x".repeat(CMD_LABEL_MAX_CHARS + 50); - let label = short_cmd_label(&long); - assert_eq!(label.chars().count(), CMD_LABEL_MAX_CHARS + 1); // +1 for the `…` - assert!(label.ends_with('…')); - } - - #[test] - fn short_cmd_label_handles_empty_command() { - assert_eq!(short_cmd_label(""), ""); - assert_eq!(short_cmd_label(" \n \n"), ""); - } - #[test] fn accepts_ident_names() { for ok in ["build", "ci-check", "t1", "task-123", "abc"] {