The v0.8.38 upgrade dramatically changed two user-visible behaviors that
were not intended as regressions:
- The /model picker was reworked (#1201/#1632) to make a blocking network
fetch on open and replace the curated tier list with the raw provider
catalog. Revert model_picker.rs and the OpenModelPicker handler to the
v0.8.37 instant curated picker. The /models command still lists the live
catalog.
- #1617 rekeyed the approval cache to an exact full-argument fingerprint,
which also dropped the v0.8.37 arity-aware command-family grouping for
"approve for session". Reintroduce build_approval_grouping_key (the lossy
v0.8.37 logic) for approvals while keeping the exact key for denials, so
denying one call no longer over-blocks later differing calls.
https://claude.ai/code/session_01NDuRxM56o17SE7SDLcTFYT
When an OpenAI-compatible backend (vLLM, Ollama, LM Studio, Together AI,
self-hosted vLLM/SGLang, etc.) streams an assistant message containing
multiple tool_calls in a single round, only the **last** tool's
`Event::ToolCallStarted` was firing. The preceding N-1 tool calls
executed and produced tool_result events, but never announced their
start to consumers (TUI / runtime API / embedder bridges), leaving them
with N orphan tool_result blocks and no matching tool_use blocks in the
assistant history.
## Reproduction
```text
backend dispatches: 7 × write_file + 1 × exec_shell
log shows: 7 × ApprovalRequired events ✓
listeners receive: 1 × chat:tool_start, 7 × chat:tool_end
session history: 1 tool_use + 7 tool_result (6 orphans)
```
Tested against vLLM 0.7 + Qwen3.6-35B-A3B with a "scaffold 7-file Tauri
template" prompt. Any model+backend combo that emits batch tool_calls
trips this — typical when a single LLM round asks for multiple parallel
file writes or edits.
## Root cause
`run_turn` tracked the currently-streaming tool block with a single
`current_tool_index: Option<usize>`. The Anthropic-style adapter
(non-streaming response → events at `chat.rs::L1807`) emits
Start/Stop pairs in lockstep so the slot never overlaps. But the
OpenAI streaming parser (`chat.rs::L1954-2064`) emits every
`ContentBlockStart::ToolUse` as soon as a tool_call delta lands, then
batches every `ContentBlockStop` at `finish_reason`:
```text
Start { index: 0 } // tool #1
Delta { index: 0, .. }
Start { index: 1 } // tool #2 — overwrites current_tool_index
Delta { index: 1, .. }
…
Start { index: 6 } // current_tool_index = Some(6)
Delta { index: 6, .. }
Stop { index: 0 } // take() returns Some(6) ← wrong tool!
Stop { index: 1 } // take() returns None
Stop { index: 2 } // take() returns None
…
```
The first `Stop` consumes the last index and emits `ToolCallStarted`
for the wrong `tool_uses` entry; every subsequent `Stop` finds the
slot already `None` and skips the entire `if let Some(index) = …`
branch, dropping the announcement.
## Fix
Replace the single slot with `HashMap<u32 block_index, usize
tool_uses_idx>`:
- `ContentBlockStart::ToolUse` and `::ServerToolUse` insert the
`(event.index → tool_uses.len())` mapping.
- `InputJsonDelta` looks up by the `ContentBlockDelta` outer index.
- `ContentBlockStop` removes by the stop's index, so each Stop routes
to its own `tool_uses` entry regardless of arrival order.
Routing no longer depends on `current_block_kind` (which has the same
single-slot overwrite problem); `current_tool_indices.remove(&index)`
returning `Some(_)` already proves the Stop belongs to a tool block.
## Tests
Added `batch_tool_calls_preserve_all_tool_use_indices` in
`core/engine/turn_loop.rs::tests` — feeds 7 Starts and 7 Stops through
the same `HashMap` API used by `run_turn`, asserts every index round-trips.
Manual end-to-end verification: vLLM + Qwen3.6-35B + 7-file Tauri
template prompt → frontend `messages` history now contains all 7
`write_file` tool_use blocks paired with their tool_result blocks.
Co-authored-by: hexin <he.xin@h3c.com>
Add a release follow-up job that updates the Homebrew tap from the checksum manifest when a tap token is configured.
The job now skips before checkout/download/update when neither HOMEBREW_TAP_PAT nor RELEASE_TAG_PAT is configured, so missing tap credentials do not fail an otherwise successful release.
Closes#1602.
Co-authored-by: Zhiping <2716057626@qq.com>
Co-authored-by: Oliver-ZPLiu <47081637+Oliver-ZPLiu@users.noreply.github.com>
Map legacy DeepSeek CN provider names back to the canonical Deepseek provider in both manual parsing and TOML deserialization.
Co-authored-by: qiyan233 <qiyan233@users.noreply.github.com>
Hard-wrap overlong CJK/no-whitespace runs in diff and pager text wrappers so they do not overflow the right edge.
Fixes#1571.
Co-authored-by: Aitensa <1900013029@pku.edu.cn>
Deduplicate official DeepSeek model completions and normalize known prefixed aliases to the bare model IDs expected by official DeepSeek providers, while preserving provider-specific IDs for compatible backends.\n\nFixes #1594.\n\nCo-authored-by: reidliu41 <reid201711@gmail.com>
Make the translation client optional so missing or invalid API configuration does not crash startup before onboarding can render.\n\nCo-authored-by: Crvena <kuragectl@gmail.com>
Capture and replay Mcp-Session-Id for Streamable HTTP transports, and apply configured custom headers to the GET preflight.\n\nCloses #1629.\n\nCo-authored-by: Zhiping <2716057626@qq.com>
Write the Kitty keyboard protocol probe (ESC[>0u) on Windows instead of enabling disambiguation flags that crossterm does not decode there. Fixes#1599.
Make the sidebar expiry test avoid subtracting from a fresh Instant on Windows runners, falling back to a short sleep only when an older Instant cannot be represented.
Hard-break oversized streaming tokens so CJK runs, URLs, and other no-whitespace content stay within the target width. Includes regression coverage for long CJK text and first-token overflow.
Add the Feishu/Lark long-connection bridge, Tencent Lighthouse runbooks, CNB mirror guidance, CNB tag release pipeline, and China-friendly update fallback documentation for the v0.8.37 line.
Summary:
- restore auto sidebar focus when Ctrl+Alt+0 is pressed from hidden state
- preserve existing hide behavior from visible sidebar states
- add regression coverage
Validation: CI green before merge.
Summary:
- include generic tool input in approval-cache fingerprints
- keep exact repeat denials stable
- prevent one denied generic tool call from blocking future distinct calls
Validation: CI green before merge.
Summary:
- concise live shell/tool labels
- collapsed pending CI polling rows
- hardened stale task-panel timing test
Validation: CI green before merge.
Horace Liu (@liuhq) contributed Nix package support and install
documentation in the v0.8.34 cycle but was inadvertently omitted from
that release's changelog and the README contributor list. This commit
adds them to both.