The agent passed None for on_update, so tools (e.g. shell) never forwarded
their incremental output — the UI only saw the final result. Now:
- EventSink: Send + Sync (the forwarding closure must satisfy ToolUpdateSink)
- ToolUpdateSink becomes lifetime-parameterized (bare trait-object aliases
default to 'static, which forbade borrowing the caller's sink); all tool
impls updated to &ToolUpdateSink<'_>
- Agent::execute_single forwards each tool update as a ToolExecutionUpdate
event (Mutex provides interior mutability inside the Fn closure)
- TUI: running tools show the tail of the output live (newest lines), done
tools show the head + remaining-line hint
regression test: a streaming tool's on_update texts appear as agent events in
order.
Multi-turn tool calls collided on replay: the reducer regenerated call_0,
call_1, ... every turn while ignoring the API's real call ids, so replayed
history had duplicate function_call/function_call_output ids that the API
cannot pair (visible once a conversation has two tool-using turns).
- ProviderEventReducer::tool_call_start_with_id(index, name, id) keeps the
API's id; tool_call_start delegates with an auto-generated fallback
- Responses API: use item.call_id; Chat Completions: use tool_calls[].id
- regressions: real-log test asserts the captured ids, agent replay test
asserts ids stay unique across three turns; reducer id-override test
focus-cli is deleted entirely (the TUI will be a new crate later); the old
Anthropic/OpenAI/Zai provider sources and the core mock module are removed as
part of the rewrite.