Override `supports_parallel()` to return `true` in `WebSearchTool`,
allowing the engine to batch multiple concurrent web_search calls
into a `FuturesUnordered` parallel group instead of serializing them.
The tool is already read-only, auto-approved, and non-interactive —
parallel-safe by all other criteria. This change removes the final
gate (`supports_parallel() -> false` default) so co-issued searches
run concurrently rather than one-at-a-time.
Closes the ~55s serial wall-clock for 3 simultaneous web searches
(now ~20s, the slowest individual call).
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit a7dcf63c556268b53ff430747ae2e141e4cd4451)
When MCP servers return tool schemas, the field order within each schema
object and the order of entries in required / dependentRequired arrays
can vary across reconnections. This causes the serialized tool catalog
bytes to change even when the logical schema is unchanged, busting
DeepSeek's KV prefix cache.
Add schema_canonicalize::canonicalize_schema which recursively:
- Sorts every required array alphabetically
- Sorts every dependentRequired sub-array alphabetically
- Rebuilds object keys in alphabetical order
- Recurses into all nested objects and arrays
The canonicalize step runs after schema_sanitize in build_api_tools,
so each tool's input_schema is first cleaned then byte-stabilized.
The existing OnceLock api_cache pins the result, ensuring the tool
catalog bytes are identical across reads and across process restarts.
8 unit tests cover: required sorting, dependentRequired sorting,
equivalent-ordering byte match, recursive nesting, empty schemas,
deeply nested schemas, non-required array preservation, and key
ordering.
(cherry picked from commit 7cee9cd5e12a74e8072bf2f6a1b18555ed0db0bf)
Addresses chatgpt-codex review: the previous full serde_json::to_string
included internal-only fields (allowed_callers, defer_loading,
input_examples, cache_control) that are never sent to the chat API.
This caused spurious drift detection when those fields changed.
- New tool_to_api_json() helper mirrors tool_to_chat() serialization:
only type, name, description, parameters, strict
- Doc comment fixed: 'sorted by name' → 'sorted lexicographically
by JSON text' (greptile review)
Phase 1.5 — upgrade PrefixFingerprint::compute() to hash the full tool
JSON serialization (name + description + schema) instead of just tool
names. This catches schema/description drift in addition to name changes.
- Serialize each tool via serde_json::to_string, sort by name, join
- New test: fingerprint_detects_schema_change_not_just_name_change
- All 21 prefix_cache tests pass
- Aligned with prompt_zones.rs tool_catalog_digest approach
Treat a missing tui.status_items field as None even when the [tui]
table exists, preserving the documented default footer behavior.
Add a regression test for configs that define [tui] without status_items.
with_agent_tools() unconditionally registered web_search/fetch_url/web.run
(via with_web_tools) and apply_patch (via with_patch_tools), but tool_setup.rs
conditionally registered them again based on Feature::WebSearch and
Feature::ApplyPatch flags. This caused double registration (overwritten
with a warning log on the second insert).
Changes:
- Remove with_web_tools() and with_patch_tools() from with_agent_tools()
- Move finance tool out of with_web_tools() into its own with_finance_tool()
(finance is market data, not web search — it should not be gated behind
the web-search feature flag)
- Add with_finance_tool() to with_agent_tools() so finance stays always
available
- Update tests: new test for with_finance_tool(), updated web_tools test
to verify finance is no longer in the web group
- Fix false 'Turn stalled' during long active turns with running tools.
Add turn_last_activity_at tracking and active-tool awareness to
reconcile_turn_liveness(). Three new tests cover the fix.
- Remove Qwen 3.7 Max OpenRouter preset from registry, picker, docs,
and tests. Qwen 3.7 Max is a hosted model; the preset will return
when an open-weight Qwen 3.7 release ships. MiniMax M3 remains as
a full 1M-context multimodal route.
- Sync root CHANGELOG to crates/tui/CHANGELOG for crates.io packaging.
Update docs/CONFIGURATION.md, docs/PROVIDERS.md, and README to
reflect the Qwen 3.7 removal. Regenerate web facts timestamp.
All dead_code in prompt_zones.rs is intentional — these types are
scaffolding awaiting future integration. Pre-existing schema_migration
warnings remain as-is.
Narrower slice per Hmbown's review: typed zone structs as foundation
without wiring into the request path. Future phases will integrate
AppendLog/TurnScratch/ThreeZoneRequest into turn_loop.
- prompt_zones.rs (663 lines, 16 tests): PinnedPrefix / FrozenPrefix /
PrefixDrift (ready), AppendLog / TurnScratch / ThreeZoneRequest
(scaffolding, #[allow(dead_code)])
- FrozenPrefix: full tool JSON hash (name+desc+schema), raw-text
fast-path in verify(), cache_control preserved in build_messages()
- /cache zones subcommand with three-zone status display
- merge_compaction_summary: zone affiliation doc comment
- No turn_loop/session changes — engine continues using
PrefixStabilityManager / MessageRequest as before
Clear stale busy state and retry/title animations on local cancel.\n\nLocal verification:\n- cargo test -p codewhale-tui\n- codewhale doctor\n- codewhale --provider deepseek --model deepseek-v4-pro exec "Reply with exactly: OK"
* docs(state): add doc comments to all public types
* docs(execpolicy): add doc comments to all public types
* test(web): add unit tests for pure helper functions
Add vitest configuration and tests for:
- relativeTime: time formatting (just now, minutes, hours, days, months, years)
- lastPageFromLink: GitHub Link header pagination parsing
These are the first tests for the web frontend. The test framework
(vitest) was already in package.json but had no config or test files.
---------
Co-authored-by: Hu Qiantao <huqiantao@HudeMacBook-Air.local>
* fix(web_search): add timeout floor and retry for Volcengine provider
Volcengine's Responses API pipeline (web search + model inference +
JSON generation) can exceed the default 15 s timeout on complex
queries, causing ~50% of requests to fail with transport errors.
Changes:
- Enforce a 60 s minimum timeout for the Volcengine provider
- Separate connect_timeout (15 s) from total request timeout
- Retry transient transport errors up to 3 times (1 s / 2 s backoff)
- Add TCP keepalive, HTTP/2 keepalive, and User-Agent headers
matching the patterns used in client.rs
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: raise Volcengine timeout floor to 90 s
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: correct stale comment — floor is 90 s, not 60 s
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web_search): tighten Volcengine retry semantics
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Hunter B <hmbown@gmail.com>
* refactor(palette): remove unused backward-compat aliases and add module docs
- Remove DEEPSEEK_AQUA_RGB, DEEPSEEK_NAVY_RGB, DEEPSEEK_AQUA, DEEPSEEK_NAVY
(unused backward-compatible aliases with no references in production code)
- Add module-level doc comment explaining the three-layer palette organization:
RGB tuples, semantic Color constants, and backward-compat aliases
- Note that some constants are kept for design-system completeness
* fix: remove deprecated color audit test (DEEPSEEK_AQUA no longer exists)
* fix: remove unused import in palette_audit test
---------
Co-authored-by: Hu Qiantao <huqiantao@HudeMacBook-Air.local>
* fix: use effective_model_for_budget instead of raw model in compaction_config
When model is set to 'auto', self.model holds the literal string 'auto',
which gets passed to the API as the model name in compaction requests.
DeepSeek's API rejects 'auto' with HTTP 400 since it's not a recognized
model ID.
effective_model_for_budget() resolves 'auto' to the last effective model
or falls back to DEFAULT_TEXT_MODEL, ensuring compaction always sends a
concrete model name.
* test(tui): cover auto model compaction config
---------
Co-authored-by: codgo <anbiaoren@gie777.com>
Co-authored-by: Hunter B <hmbown@gmail.com>
* docs(secrets): add doc comments to all public types
* docs(protocol): add doc comments to all public types
* style(protocol): run rustfmt on docs
---------
Co-authored-by: Hu Qiantao <huqiantao@HudeMacBook-Air.local>
Co-authored-by: Hunter B <hmbown@gmail.com>