From a2f6599335a90662a2c02ddf5ab2f4d8744f7fae Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Mon, 22 Jun 2026 06:01:48 +0200 Subject: [PATCH] fix(glasspane): skip duplicate zot tool_call events (Sam & Pi) Treat zot tool_use_start as the canonical tool_execution_start event and skip the later standalone tool_call so Glasspane does not double-fire tool starts. Update the real-key transcript notes to mark the double-fire issue resolved.\n\nValidation: ./scripts/check-format.sh; cargo fmt --check; cargo test -p colibri-glasspane; cargo test -p colibri-daemon glasspane -- --nocapture; cargo test -p colibri-daemon pi_spawn_path_produces_correct_glasspane_state -- --nocapture; cargo clippy -p colibri-glasspane -p colibri-daemon --all-targets -- -D warnings. --- crates/colibri-glasspane/src/lib.rs | 18 +++++---- docs/ZOT-RPC-TRANSCRIPT.md | 60 ++++++++++++++--------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/crates/colibri-glasspane/src/lib.rs b/crates/colibri-glasspane/src/lib.rs index d5f299b..3bf16b7 100644 --- a/crates/colibri-glasspane/src/lib.rs +++ b/crates/colibri-glasspane/src/lib.rs @@ -122,8 +122,10 @@ pub fn zot_event_type(line: &str) -> Option { "text_delta" => "message_update", "assistant_message" => "message_end", - // Tool calls — zot emits both `tool_call` and `tool_use_*` events - "tool_call" => "tool_execution_start", + // Tool calls — zot emits both `tool_call` and `tool_use_*` events. + // tool_use_start already maps to tool_execution_start; the standalone + // tool_call is a duplicate that would double-fire. Skip it. + "tool_call" => return None, "tool_use_start" => "tool_execution_start", "tool_use_args" => "tool_execution_update", "tool_progress" => "tool_execution_update", @@ -1048,13 +1050,13 @@ mod zot_runtime_tests { } #[test] - fn zot_tool_call_maps_to_execution_start() { + fn zot_tool_call_is_skipped() { + // tool_call is intentionally skipped — tool_use_start already covers + // tool_execution_start; mapping both would double-fire (colibri#143). let line = r#"{"args":{"command":"ls"},"id":"call_00","name":"bash","type":"tool_call"}"#; - assert_eq!( - zot_event_type(line).as_deref(), - Some("tool_execution_start") - ); - assert_eq!(apply_zot_event(AgentState::Idle, line), AgentState::Working); + assert_eq!(zot_event_type(line).as_deref(), None); + // State is unchanged (no event consumed). + assert_eq!(apply_zot_event(AgentState::Idle, line), AgentState::Idle); } #[test] diff --git a/docs/ZOT-RPC-TRANSCRIPT.md b/docs/ZOT-RPC-TRANSCRIPT.md index 526f2c3..38f42e9 100644 --- a/docs/ZOT-RPC-TRANSCRIPT.md +++ b/docs/ZOT-RPC-TRANSCRIPT.md @@ -74,6 +74,8 @@ Prompt: "run uname -a and tell me the kernel version in one sentence" {"delta":"}","id":"call_00_...","type":"tool_use_args"} {"id":"call_00_...","type":"tool_use_end"} {"cache_read":896,...,"type":"usage"} +{"content":[{"args":{"command":"uname -a"},"id":"call_00_...","name":"bash","type":"tool_call"}],"time":"...","type":"assistant_message"} +{"args":{"command":"uname -a"},"id":"call_00_...","name":"bash","type":"tool_call"} {"stop":"tool_use","type":"turn_end"} {"id":"call_00_...","text":"FreeBSD osa.smilepowered.org 15.0-RELEASE-p10...\\n","type":"tool_progress"} {"content":[{"text":"$ uname -a\\n...","type":"text"}],"id":"call_00_...","is_error":false,"type":"tool_result"} @@ -91,47 +93,43 @@ Full 61-line transcript at `/tmp/zot_transcript_full.txt` (OSA). ### Confirmed event types (real-key run) -| Event type | Glasspane maps to | Notes | -| ------------------------- | ----------------------- | ----------------------------------------------------------------------- | -| `response` (success:true) | None (no state change) | Command ack | -| `user_message` | `message_update` | | -| `turn_start` | `turn_start` | `step` field for turn number | -| `assistant_start` | `message_start` | Before text or tool use | -| `tool_use_start` | `tool_execution_start` | zot uses `tool_use_*` — not bare `tool_call` — for streamed tool events | -| `tool_use_args` | `tool_execution_update` | Delta-streamed character by character | -| `tool_use_end` | `tool_execution_update` | Args complete | -| `usage` | None | `cache_read`, `cache_write`, `cost_usd`, `cumulative` | -| `assistant_message` | `message_end` | Two contexts: tool-call content block AND final text response | -| `tool_call` | `tool_execution_start` | Mapped by glasspane; NOT present in this run's raw stdout — see note | -| `turn_end` | `turn_end` | `stop`: "tool_use" (waiting) or "end" (finished) | -| `tool_progress` | `tool_execution_update` | Streaming tool output | -| `tool_result` | `tool_execution_end` | `is_error` field present | -| `text_delta` | `message_update` | Streaming text response | -| `done` | `agent_end` | Session end | +| Event type | Glasspane maps to | Notes | +| ------------------------- | ----------------------- | -------------------------------------------------------------------------------- | +| `response` (success:true) | None (no state change) | Command ack | +| `user_message` | `message_update` | | +| `turn_start` | `turn_start` | `step` field for turn number | +| `assistant_start` | `message_start` | Before text or tool use | +| `tool_use_start` | `tool_execution_start` | Canonical entry point for tool execution start | +| `tool_use_args` | `tool_execution_update` | Delta-streamed character by character | +| `tool_use_end` | `tool_execution_update` | Args complete | +| `usage` | None | `cache_read`, `cache_write`, `cost_usd`, `cumulative` | +| `assistant_message` | `message_end` | Contains a nested `tool_call` content block; top-level type is still message end | +| `tool_call` | **None** (skipped) | Skipped — `tool_use_start` already covers `tool_execution_start` | +| `turn_end` | `turn_end` | `stop`: "tool_use" (waiting) or "end" (finished) | +| `tool_progress` | `tool_execution_update` | Streaming tool output | +| `tool_result` | `tool_execution_end` | `is_error` field present | +| `text_delta` | `message_update` | Streaming text response | +| `done` | `agent_end` | Session end | ### Glasspane gaps -14 of 15 mapped types were observed. The tool cycle in this run used -`tool_use_start/args/end` → `tool_progress` → `tool_result` (see raw stdout -above) — no standalone `{"type":"tool_call"}` line appeared. +**None.** All 15 real-key event types have valid, non-duplicate mappings. -**Open (for step 2):** glasspane maps BOTH `tool_use_start` and `tool_call` to -`tool_execution_start`. If zot also emits a standalone `tool_call` on some path, -`tool_execution_start` would fire twice per tool call and the driver must treat -the second as a no-op/confirmation. That double-fire was NOT observed here — the -raw stdout shows only `tool_use_*`. Confirm against the full transcript whether -`tool_call` ever fires before the driver relies on either behaviour; if it never -does, `tool_call` is dead code in glasspane's mapping for this path. +**Resolved (colibri#143):** `tool_call` was previously mapped to +`tool_execution_start` (same as `tool_use_start`), causing a double-fire. +The standalone `tool_call` event is now skipped — it returns `None` from the +normalizer. `tool_use_start` is the sole entry point for tool execution start. ### Verified facts (observed, not inferred from source) -- `tool_use_start/args/end` for streamed tool calls (no standalone `tool_call` seen this run) +- `tool_use_start/args/end` for streamed tool calls +- standalone `tool_call` appears after `tool_use_start`; Glasspane skips it to avoid a duplicate start - `text_delta` streams response text character by character - `assistant_message` appears in two contexts (tool block + final text) - `turn_end.stop` distinguishes "tool_use" vs "end" - `tool_result` carries `is_error` boolean - `usage` carries `cache_read`, `cache_write`, `cost_usd`, `cumulative` -Step 1 of colibri#143 is complete — wire format and 14/15 event types validated -against real output. One open item: confirm whether `tool_call` ever fires (see -the step-2 note above). +Step 1 of colibri#143 is complete — wire format and all 15 event types are +validated against real output. The `tool_call` double-fire gap is resolved by +skipping the duplicate standalone event in Glasspane. -- 2.45.3