mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
Adopts main's tool-call permission gate (#5712) onto the goose loop. The conflicts were semantic, not textual: #5712 landed +2494/-299 inside `crates/buzz-agent`, the crate this branch rewrote, so five of main's files (`llm.rs`, `mcp.rs`, `fake_llm.rs`, `regressions.rs`, and main's `agent.rs` loop) no longer exist here and their share of the feature had to be re-expressed against goose. Taken from main unchanged: `permission.rs` (the broker), `wire.rs`'s `session/request_permission` shapes, `Inbound::Response`, `send_checked`, and `write_frames`. Re-expressed: * The gate moved from main's `RunCtx::execute_parallel` to `tools::run_one`, which is where this branch dispatches model-issued calls. It runs after the `tool_call` announcement and before `Agent::dispatch_tool_call`, so a denied call still reaches a terminal wire state. * The broker takes goose's `CancellationToken` instead of a `watch::Receiver<bool>`, and an `AskSubject` instead of buzz's deleted `ToolCall` type. * Argument-shape validation is dropped: it guarded main's `mcp.rs`, and goose's `CallToolRequestParams::arguments` is already `Option<Map>`, so a non-object cannot be represented. * `max_pending_permissions` / `permission_timeout` follow this crate's `env_parse`-with-default style rather than main's validated `parse_env`. Two behaviour notes worth review: * `load_skill` is now **gated**, where main exempted it as an in-process built-in. Under goose it is a real tool on the skills platform extension, dispatched through the same path as any MCP tool. Exempting it would need an allowlist keyed on tool *name* — which an untrusted MCP server controls, and could register its own `load_skill` to inherit. Test renamed accordingly. * Connection teardown already cancels every session (`serve`'s shutdown), so main's `cancel_all_sessions` plus writer-death select arm had no separate work to do here; the broker's wire-closed path covers the rest. Test harness changes: * `tests/common/mod.rs`'s fake LLM now answers SSE. goose's openai-compatible provider requests `stream: true` and parses `chat.completion.chunk`; against main's plain `chat.completion` body every turn ended `end_turn` with no tool call and no ask, which reads as a broken gate rather than a broken fixture. Its `/models` lookup is also answered without popping the canned queue. * `tests/approve/mod.rs` auto-approves for the nine suites whose subject is not the boundary, selecting by option `kind` rather than a hardcoded `optionId`. * `cancel.rs` now answers the ask and waits for `FAKE_MCP_CALL_RECEIVED` before cancelling. Without that the cancel resolved the *permission wait*, the tool was never dispatched, and `cancel_propagates_notifications_cancelled_to_mcp` failed — a test named "cancel mid tool call" was exercising "cancel before tool call". Verified at this tree: `buzz-agent` full suite 111 unit + 36 integration (including all 9 boundary tests) pass, `clippy --all-targets -D warnings` clean, `cargo fmt --check` clean, `cargo build --workspace` clean, and both `Cargo.lock` files resolve `--locked` (8 goose deps in root, 0 in desktop). Co-authored-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Michael Neale <michael.neale@gmail.com>