fix(glasspane): skip duplicate zot tool_call events (Sam & Pi) #147
2 changed files with 39 additions and 39 deletions
|
|
@ -122,8 +122,10 @@ pub fn zot_event_type(line: &str) -> Option<String> {
|
|||
"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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue