Based on the user's actual deepseek-v4-flash debug log (Responses protocol):
- real_log_captures_reasoning_and_tools: replays turn 1 of the real SSE
(reasoning item + reasoning_text.delta + two function calls) and asserts the
Done message is [Thinking, ToolCall, ToolCall] with usage mapped
(input 850 / output 157 / cache-read 768)
- agent_multi_turn_replays_reasoning: drives a 3-turn agent over the real
protocol (turn 1 + turn 2 from the log, plus a terminating text turn),
asserting thinking lands in the transcript and the second request's replay
carries a reasoning item with the captured text and matching call ids
MockTransport gains requests() to inspect all recorded requests.
The real endpoint log (deepseek-v4-flash) shows the Responses protocol: the
thinking arrives as a 'reasoning' output item plus response.reasoning_text.delta
events, which we ignored — so the thinking never reached the transcript and
the replayed history omitted it ('reasoning_text must be passed back', 400).
- output_item.added with item.type 'reasoning' opens a thinking block;
response.reasoning_text.delta appends to it
- messages_to_responses now replays assistant thinking as a 'reasoning' item
with a reasoning_text part (before the text item)
- regression tests: reasoning capture and reasoning-item replay
- (the earlier chat-completions reasoning_content/text fixes remain for the
chat protocol)
The replay error ('reasoning_text in thinking mode must be passed back')
persisted because the endpoint emits reasoning under a name other than
reasoning_content — e.g. reasoning_text — which we never captured, so the
thinking never reached the transcript.
- capture reasoning from reasoning_content / reasoning_text / reasoning /
thinking (first non-empty wins)
- echo the captured reasoning back as BOTH reasoning_content and
reasoning_text on assistant messages
- FOCUS_DEBUG_FILE=<path> appends every raw network chunk to a file for
diagnosing provider-protocol mismatches (both providers)
- regression tests: reasoning_text capture, dual-field replay
DeepSeek's thinking mode rejects a replayed assistant message that omits its
reasoning ('the reasoning_text must be passed back to the API', HTTP 400).
The Chat Completions serializer now includes thinking blocks as
reasoning_content on assistant messages; OpenAI official never produces
thinking blocks, so the field is only sent when present — safe for both.
Regression test: a replayed assistant carries reasoning_content.
StreamEvent::Error is encoded by the agent loop as an assistant message with
stop_reason=error and error_message, but the UI only rendered the header and
the (empty) content, hiding the actual message (e.g. a provider HTTP 400
detail). Now:
- UiBlock::Error renders error_message in red, for both committed and live
assistant messages
- on job Done, the error is also surfaced into the status bar and a note,
even when the job itself reported no error (it was encoded as a message)
- regression test: an error message renders its concrete text
- Braille spinner driven by a per-frame counter, shown in both the status
bar (bottom) and at the bottom of the message area while a job runs
- phase detection (RunState::working_phase): running tool > thinking >
streaming > generic, so the hint reads e.g. "⠹ shell: cargo test…"
- testable via the injected clock (tests/state_tests.rs)
- ratatui + crossterm chat UI: streaming assistant text, multi-line input,
bottom status bar (model / protocol / session id / context usage)
- thinking blocks collapse to a summary with duration + estimated tokens;
tool calls collapse to a concrete summary (path / command) + duration;
click or Tab expands the details
- config modal (/config): provider, OpenAI protocol, baseUrl, apiKey, model,
contextWindow — persisted to ~/.focus/config.json; session list (/sessions)
on the harness JSONL tree; /new, /compact, /help
- auto-compaction (80% threshold, no confirmation) + dynamic system prompt
with usage info run in a background job (fake-provider-testable)
- Esc aborts a run (abandons the job thread; bounded by the provider timeout)
- tests: config codec, summaries/durations/wrap/caret, UI state machine with
an injected clock, job flow with a fake provider, session persistence
Terminal interaction (raw mode, incremental redraw, scrolling, mouse, keys)
is a genuine need that pure std cannot reasonably solve, so AGENTS.md §2.1
now exempts ratatui + crossterm for focus-tui only (transitives follow).
Workspace gains the focus-tui member and the 7-crate layout is documented.
All test cases live in tests/<module>_tests.rs and access only public API;
internals are either tested through the public API or promoted to pub. The
sole exception is #[cfg(test)] test infrastructure (e.g. the permissive TLS
config in focus-transport).
Per AGENTS.md §5.1 every test must live under crates/<name>/tests/, so the
inline #[cfg(test)] modules in harness/tools/providers/transport are gone:
- harness: session_tests / compaction_tests / prompt_tests (days_to_ymd made pub)
- tools: read / write / edit / shell tests (Tool trait imported explicitly)
- providers: config_tests; request-body and stop-reason coverage folded into
integration tests via mock-recorded requests and SSE events (private builders
no longer tested directly)
- transport: ChunkedDecoder made pub (with Default), decoder tests moved into
transport_tests.rs
The only #[cfg(test)] left in src/ is the test-only TLS infra in tls.rs.
- drop focus-cli from workspace members; remove tokio from focus-tools and
focus-harness (sync std I/O); add focus-json to focus-tools
- AGENTS.md: reflect 6-crate layout, focus-cli removal, TUI coming later
- lockfile updated for the new dependency graph
- read: line-range reads, binary detection
- write: overwrite with parent-dir creation
- edit: pi-style exact oldString/newString replacement with occurrence
- shell: bash on Linux, PowerShell (cmd fallback) on Windows; streaming
line updates via ToolUpdateSink, kill-on-timeout
- sync std I/O consistent with the core Tool trait; no tokio needed
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.