Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,12 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
`~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
- **`crates/cli/src/tools.rs`** — agent tool schemas + execution: `read_file`, `create_directory`,
`list_directory`, `search_files`, `glob`, `read_website`, `create_file`, `edit_file`,
`multi_edit`, `delete_file`, `run_command`, `write_todos`, `remember`. Add a tool in both the
spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
`multi_edit`, `delete_file`, `run_command`, `command_output`, `kill_command`, `monitor_command`,
`write_todos`, `remember`. Add a tool in both the
spec list (`all_tools`) and the execute `match` (`execute_tool`). `monitor_command` waits on a background task in one call and reports what it has so far
through the `ToolProgress` seam (`register_tool_progress`; `main.rs` registers `AcpToolProgress`,
which sends a `tool_call_update` replacing the card's content; a no-op in the TUI and headless).
`run_command` also enforces
commit attribution: when a command creates a new commit that lacks the
`Co-Authored-By: siGit Code` trailer (`commit_co_author_trailer()`, which reads
`siGit Code v<version>-<acp|tui|headless>` from the surface `main` sets via `set_surface`), it amends the trailer in —
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## Unreleased

### Added

- **`monitor_command` follows a background task in one tool call.** It waits
until the task exits, a line of new output matches an optional `until`
pattern, or a timeout passes (300 s by default, 600 s at most). In an
editor it is a single card that updates with the task's output as it
arrives, instead of a column of `command_output` cards that also used up
the turn's tool rounds (#253).

### Changed

- Cards for `command_output`, `monitor_command` and `kill_command` name the
task and its command.

## 1.7.5

A new siGit Code Cloud tier, and two fixes for signing in and switching
Expand Down
18 changes: 18 additions & 0 deletions crates/cli/src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ pub(crate) fn tool_title(name: &str, arguments_json: &str) -> String {
str_arg("path")
}
"search_files" | "glob" => str_arg("pattern"),
"command_output" | "monitor_command" | "kill_command" => {
let id = args.as_ref().and_then(|a| a.get("task_id")?.as_u64());
id.map(|id| match crate::tools::background_task_command(id) {
Some(command) => format!("task {id} (`{command}`)"),
None => format!("task {id}"),
})
}
_ => None,
};
(name.to_string(), summary)
Expand Down Expand Up @@ -4145,6 +4152,17 @@ mod tests {
);
}

#[test]
fn tool_title_task_tools_name_the_task() {
for name in ["command_output", "monitor_command", "kill_command"] {
assert_eq!(
tool_title(name, r#"{"task_id":999999}"#),
format!("{name} · task 999999")
);
}
assert_eq!(tool_title("monitor_command", "{}"), "monitor_command");
}

#[test]
fn tool_title_file_tools_show_the_path() {
for name in [
Expand Down
30 changes: 27 additions & 3 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ fn tool_kind_for(tool_name: &str) -> ToolKind {
ToolKind::Edit
}
"delete_file" => ToolKind::Delete,
"run_command" | "kill_command" => ToolKind::Execute,
"run_command" | "kill_command" | "monitor_command" => ToolKind::Execute,
"read_file" | "list_directory" | "command_output" => ToolKind::Read,
"search_files" | "glob" | "web_search" => ToolKind::Search,
"read_website" => ToolKind::Fetch,
Expand Down Expand Up @@ -1007,6 +1007,26 @@ impl client_fs::ClientFileSystem for AcpClientFs {
}
}

/// Live tool-card updates over the ACP connection (see `tools::ToolProgress`).
struct AcpToolProgress {
cx: ConnectionTo<Client>,
}

impl tools::ToolProgress for AcpToolProgress {
fn update(&self, session_id: &str, tool_call_id: &str, text: &str) {
let update = SessionUpdate::ToolCallUpdate(ToolCallUpdate::new(
tool_call_id.to_string(),
ToolCallUpdateFields::new().content(vec![tool_output_content(text)]),
));
if let Err(error) = self
.cx
.send_notification(SessionNotification::new(session_id.to_string(), update))
{
log::warn!("could not update tool call {tool_call_id}: {error}");
}
}
}

/// The editor's terminal, reached over the ACP connection. Registered with
/// `client_terminal` at `initialize` when the client advertises `terminal`.
struct AcpClientTerminal {
Expand Down Expand Up @@ -2216,6 +2236,8 @@ impl SiGitAgent {
client_terminal::register(Arc::new(AcpClientTerminal { cx: cx.clone() }), terminal);
}

tools::register_tool_progress(Arc::new(AcpToolProgress { cx: cx.clone() }));

// URL-mode elicitation is where `authenticate` sends the user to sign
// in. Form mode is never used: the spec forbids collecting
// credentials in a form, and nothing else here asks the user for
Expand Down Expand Up @@ -3737,10 +3759,12 @@ impl SiGitAgent {
.and_modify(|count| *count += 1)
.or_insert(1);
// Repeated status checks are expected for a live background
// command. command_output long-polls and the overall round cap
// command. command_output and monitor_command wait, and the overall round cap
// still bounds a confused model without cutting off a real
// build or release while it is in progress.
let repeated = *repeat_count >= 3 && tc.name != "command_output";
let repeated = *repeat_count >= 3
&& tc.name != "command_output"
&& tc.name != "monitor_command";
if repeated {
force_text = true;
stopped_repeating = Some(tc.name.clone());
Expand Down
4 changes: 3 additions & 1 deletion crates/cli/src/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ pub enum Decision {
pub fn classify(tool_name: &str) -> ToolRisk {
match tool_name {
"read_file" | "list_directory" | "search_files" | "glob" | "read_website"
| "write_todos" | "skill" | "task" | "command_output" | "web_search" => ToolRisk::ReadOnly,
| "write_todos" | "skill" | "task" | "command_output" | "monitor_command"
| "web_search" => ToolRisk::ReadOnly,
_ => {
if let Some(bare) = crate::mcp::official_tool_suffix(tool_name)
&& (bare.starts_with("list_")
Expand Down Expand Up @@ -544,6 +545,7 @@ mod tests {
"skill",
"task",
"command_output",
"monitor_command",
"web_search",
] {
assert_eq!(classify(tool), ToolRisk::ReadOnly, "{tool}");
Expand Down
Loading
Loading