Commit Graph

1009 Commits

Author SHA1 Message Date
Paulo Aboim Pinto ba8b4b7adf fix: remove skip-past-newline from move_cursor_line_end
Per gemini-code-assist review on #1749: End on a newline character
should stay at the end of the current line (idempotent), not skip
to the next line.  Removes the non-standard skip-past-newline logic
and updates the associated tests.
2026-05-18 23:07:13 +08:00
Paulo Aboim Pinto f77a07e207 feat: Home/End jump to line start/end in multiline composer
Plain Home and End now navigate within the current line instead of
jumping to the absolute start/end of the entire input.  Ctrl+A and
Ctrl+E remain as absolute start/end shortcuts.

- Add move_cursor_line_start() / move_cursor_line_end() to App
- Wire Home -> move_cursor_line_start(), End -> move_cursor_line_end()
- On single-line input the new methods behave identically to the
  absolute versions (no behaviour change)
- End on a newline character skips to the end of the next line
- 14 tests covering multiline, singleline, and edge cases
2026-05-18 23:07:13 +08:00
Zhongyue Lin 0ce41505bc fix(shell): robust cmd detection + stronger test assertion (review #1744)
Address gemini-code-assist review on PR #1744 (two MEDIUM):

- cmd detection used program.eq_ignore_ascii_case("cmd"), which fails
  for a full path (C:\Windows\System32\cmd.exe) or a .exe suffix, so the
  raw_arg quoting fix would not apply. Use Path::file_stem() instead
  (fully-qualified std::path::Path -> no unused import off-Windows).
- Strengthen the Windows block of issue_1691_quoted_commit_message_round_trips
  to assert argv content equals spec.args, not just arg count, so the
  raw_arg payload (quotes preserved, no extra escaping) is actually
  verified. sandbox/mod.rs already asserts content -- left untouched.

Windows paths are cfg-gated (compile-checked on macOS, executed on
Windows CI). macOS build + clippy clean.

Refs #1691.
2026-05-18 23:06:52 +08:00
Zhongyue Lin b3223cd48d fix(shell): preserve quoted args on Windows cmd /C invocation (#1691)
git commit -m "feat: complete sub-pages" failed on Windows with
'pathspec sub-pages" did not match' because the quoted -m message was
split on spaces. Root cause is not a tokenizer bug: CommandSpec::shell()
builds 'cmd /C "chcp 65001 >/dev/null & <command>"' and std::process::Command
applies MSVCRT escaping (" -> \"); cmd.exe does not use MSVCRT parsing,
so the quoting is destroyed and git receives feat:/complete/sub-pages"
as separate pathspecs. The Unix sh -c path was already correct.

Add a cfg-split push_shell_args() replacing cmd.args() at the 3 std
spawn sites in shell.rs. On Windows, for the cmd /C <payload> shape
only, pass /C and payload via CommandExt::raw_arg so the string reaches
cmd.exe verbatim (as a terminal does); other programs keep normal
escaping. Non-Windows is a faithful pass-through (byte-for-byte
unchanged). portable_pty path intentionally untouched (out of scope).

The Unix path is provably unchanged (tested); the Windows raw_arg
runtime correctness is only verifiable on a Windows runner -- flagged in
the PR for Windows CI verification per the #1736 Windows policy.

Refs #1691, #1736.
2026-05-18 23:06:52 +08:00
Zhongyue Lin 351898d05f fix(tui): emit thinking-only notice only at true turn end (review #1742)
Address gemini-code-assist review on PR #1742 (MEDIUM): the status was
emitted at the assistant-persist site, but the same turn can still
CONTINUE for pending steers or sub-agent completions -- the user would
see a spurious 'turn ended without output' notice immediately before the
turn resumed.

Capture thinking_only_no_sendable at the persist site (no emission
there) and decide at the end of the tool_uses.is_empty() path, just
before the terminal break -- reachable only when there were no pending
steers, no sub-agent completions, and we were not holding for running
children. Extend should_emit_thinking_only_status with steers_pending
and holding_for_subagents (false if either), recomputed live at the
decision point as defense-in-depth. Unit test updated with the two new
no-emit cases.

Refs #1727.
2026-05-18 23:06:52 +08:00
Zhongyue Lin 4ab7c53442 fix(tui): surface thinking-only turns instead of silently ending (#1727)
When a model streams a turn with only a reasoning block (empty content,
no tool_calls -- e.g. gpt-oss via ollama's harmony->OpenAI shim mapping
to reasoning_content), has_sendable_assistant_content is false: the
if-only persist branch was skipped, NO event was emitted, and the turn
fell through to break. The UI spinner hung with no reply and no error.

Add an else-if at the same persist site that, only on a clean end
(tool_uses empty, turn_error.is_none(), not cancelled), warns and emits
an Event::status notice telling the user the turn ended and they can
retry. No assistant message is persisted (prior behavior preserved); no
retry/re-prompt/placeholder policy is added (out of scope). Guard
ordering extracted into a pure should_emit_thinking_only_status() helper
with a unit test, matching the existing should_hold_turn_for_subagents
style. Maintainer audit #1736 confirms #1727 is not shipped in v0.8.39
and release-blocking.

Refs #1727, #1736.
2026-05-18 23:06:52 +08:00
Zhongyue Lin 323f43df60 fix(client): align stream reasoning classification with replay (review #1743)
Address gemini-code-assist review on PR #1743:

- HIGH: should_replay_reasoning_content_for_provider was made model-aware
  in the previous commit, but handle_chat_completion_stream still computed
  is_reasoning_model = requires_reasoning_content(model) &&
  provider_accepts_reasoning_content(provider). On the openai provider +
  a DeepSeek model that was false during SSE parsing, so reasoning tokens
  were stored as content (not reasoning_content) and the next request
  still 400'd -- the fix was incomplete. Extract is_reasoning_model_for_stream()
  and route the stream call site through it; add an equivalence test
  locking it to the replay predicate so the two paths can't drift.
- MEDIUM: rename generic_openai_provider_drops_deepseek_reasoning_content
  -> generic_openai_provider_drops_reasoning_content_for_non_deepseek_models
  (now uses gpt-4o; old name was misleading).

Non-DeepSeek models on any provider are unaffected (#1542 not regressed).

Refs #1739, #1694, #1542.
2026-05-18 23:06:52 +08:00
Zhongyue Lin 1a5ee2f67d fix(client): replay reasoning_content for DeepSeek models on openai provider (#1739)
should_replay_reasoning_content_for_provider() returned false whenever
provider_accepts_reasoning_content(provider) was false (true for
ApiProvider::Openai) without checking the model. This single gate feeds
both build_for_provider (include_reasoning) and
sanitize_thinking_mode_messages, so a DeepSeek reasoning model on the
generic openai provider (DeepSeek-compatible endpoint) had all
reasoning_content stripped -> the DeepSeek thinking-mode API 400s
('reasoning_content in the thinking mode must be passed back'). This is
the over-aggressive half of ac01b225 (fix #1542).

Gate the early return on the model too:
!provider_accepts_reasoning_content(provider) && !requires_reasoning_content(model).
Known DeepSeek reasoning models replay regardless of provider; genuine
non-DeepSeek models on openai still strip (effort=off still wins). #1542
not regressed (provider_accepts_reasoning_content untouched).

Two pre-existing client.rs tests asserted the buggy case (deepseek-v4-pro
on Openai -> dropped); retargeted to gpt-4o to preserve their #1542
intent without encoding the bug. New positive/negative coverage in
chat.rs.

Refs #1739, #1694, #1542, #1736.
2026-05-18 23:06:52 +08:00
Zhongyue Lin 3adc05d627 style(config): mark unreachable DeepSeek match arms (review #1740)
Address gemini-code-assist review on PR #1740 (MEDIUM, clarity): the
non-DeepSeek else-branch match listed ApiProvider::Deepseek /
DeepseekCN arms that are logically unreachable (handled by the if
branch above). Collapse to a single
'Deepseek | DeepseekCN => unreachable!(...)' arm so the intent is
explicit for future maintainers. No behavior change.

Refs #1714.
2026-05-18 23:06:52 +08:00
Zhongyue Lin b156d7da33 fix(config): honor explicit custom model for non-DeepSeek providers (#1714)
The CLI --model handoff only exports DEEPSEEK_MODEL, which apply_env_overrides
funneled into the DeepSeek-only root default_text_model slot. default_model()
then treated it as a normalizable weak default and fell back to a
DeepSeek/provider default for an unrecognized custom model on a non-DeepSeek
provider, so e.g. --provider openai --model MiniMax-M2.7 silently sent a
DeepSeek model to the endpoint.

Route DEEPSEEK_MODEL into the active provider's model slot for non-DeepSeek
providers (mirroring the existing OPENAI_MODEL branch), and return an explicit
non-DeepSeek-alias model verbatim from default_model(). DeepSeek/DeepSeekCN
behavior is unchanged. Adds a regression test next to
openai_provider_accepts_custom_model_and_base_url.

Refs #1714, #1739, #1736.
2026-05-18 23:06:52 +08:00
Paulo Aboim Pinto 1874359ac5 fix: stop RUST_LOG from leaking tracing messages into TUI alt-screen on Windows
Only DEEPSEEK_LOG_LEVEL should gate verbose CLI output. RUST_LOG controls
the tracing subscriber independently (file logging). On Windows stderr is
not redirected to the log file, so coupling the two causes tracing log
messages to leak into the TUI alt-screen, corrupting the display.

Closes #1774
2026-05-18 23:06:52 +08:00
jayzhu 5c452328ab fix: defer clipboard init to avoid blocking TUI startup on X11-less hosts
On Linux, `arboard::Clipboard::new()` opens a blocking connect() to the
X11 Unix socket. When no X server is running (headless, WSL2 without
WSLg), the call hangs indefinitely. Because raw mode and the alternate
screen are already active at that point, Ctrl+C no longer generates
SIGINT and the event loop hasn't started yet — leaving the user with a
blank screen and no way to exit.

Move clipboard initialization from `ClipboardHandler::new()` (called
synchronously during App construction) to a lazy `ensure_clipboard()`
that runs on first read/write with a 500 ms timeout. If the X11
connection doesn't respond in time, the handler stays in fallback mode
and `write_text` falls through to the existing OSC 52 / pbcopy /
PowerShell paths.
2026-05-18 23:06:52 +08:00
Hunter Bown 373fbd95a0 chore(release): prepare v0.8.39
Bump workspace, inter-crate, and npm package versions 0.8.38 -> 0.8.39.

Roll CHANGELOG [Unreleased] into [0.8.39] with all fixes:
- Revert v0.8.38 /model picker rework (back to instant curated picker)
- Restore approval grouping (lossy v0.8.37 logic for approvals, exact key for denials)
- Thinking-only turn surface fix (#1727)
- ACP server JSON-RPC id stringification (#1696)
- Chat client: reasoning_content for generic providers (#1673)
- Compaction: user text query preservation (#1704)
- Engine: system prompt override survival (#1688)
- Pager: G/End overshoot fix (#1706), mouse scroll (#1716)
- Composer: scroll with text (#1677), multiline arrows (#1721)
- macOS system theme detection (#1670)
- rlm_open blank source fields (#1712)
- Terminal resize paging fix (#1724)
- Docker first-run permission (#1684)
- README Rust 1.88+ requirement note (#1718)

Tests: 3149 passed, 0 failed (deepseek-tui crate)
clippy: clean on --all-targets --all-features
2026-05-17 16:36:21 +08:00
Claude 81bc2da069 fix(tui): revert v0.8.38 /model picker rework and restore approval grouping
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
2026-05-17 16:15:17 +08:00
Hunter Bown f0a7e15d30 fix(feishu): guard thread store startup order (#1700) 2026-05-16 18:25:23 -05:00
Hunter Bown 5401eaae08 chore(release): prepare v0.8.38 (#1698) 2026-05-15 18:08:58 -05:00
hexin a528ea9824 fix(streaming): preserve all tool_calls in OpenAI batch responses (#1686)
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>
2026-05-15 17:55:44 -05:00
Hunter Bown b080891efa fix(tui): count loop guard blocks as failures (#1658) 2026-05-15 17:43:07 -05:00
Hunter Bown b834548897 fix(tui): calm legacy Windows console rendering (#1655) 2026-05-14 19:00:52 -05:00
Hunter Bown 7c8c71eb03 fix(tui): default Windows composer arrows to scroll 2026-05-14 16:39:51 -05:00
Hunter Bown 2a4022acbe docs: refresh README setup guidance 2026-05-14 16:38:11 -05:00
Hunter Bown f8a4dee173 fix(tui): preserve provider-selected models 2026-05-14 16:18:18 -05:00
Hunter Bown a21d34181b docs: sync packaged changelog 2026-05-14 15:25:40 -05:00
Hunter Bown 668121ccc5 style: rustfmt markdown renderer 2026-05-14 15:25:40 -05:00
Hunter Bown e12b4f18e1 fix(tui): keep wrapped OSC 8 links whole 2026-05-14 15:25:40 -05:00
Hunter Bown f23c9828b7 fix(tui): restore terminal modes on early exit
Harvests the cleanup-guard portion of PR #1630 by @duanchao-lab while preserving provider-aware onboarding startup.
2026-05-14 15:25:40 -05:00
Hunter Bown ae9e4b4b24 fix(client): omit strict OpenAI-incompatible fields 2026-05-14 15:25:40 -05:00
Hunter Bown 4c32a316be chore(release): prepare v0.8.37 2026-05-14 14:37:14 -05:00
qiyan233 cc67454f1f fix(config): accept legacy DeepSeek CN provider aliases
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>
2026-05-14 14:00:35 -05:00
Hunter Bown b84fa98153 fix(tui): compile URL opener on unsupported targets
Gate browser-launch command construction to supported desktop targets and return the existing unsupported-platform error elsewhere.

Fixes #1639.
2026-05-14 14:00:27 -05:00
Eosin Ai f7eb17b00f fix(tui): wrap CJK runs in diff and pager
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>
2026-05-14 13:46:58 -05:00
Reid eef16f45fd fix(model): canonicalize DeepSeek model completions
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>
2026-05-14 13:39:31 -05:00
MidoriKurage a87c50b044 fix(tui): keep onboarding alive without an API key
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>
2026-05-14 13:34:33 -05:00
ZzzPL ece805568b fix(mcp): persist Streamable HTTP session IDs
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>
2026-05-14 13:25:22 -05:00
Gordon 13e7957621 fix(input): avoid enabling CSI-u flags on Windows
Write the Kitty keyboard protocol probe (ESC[>0u) on Windows instead of enabling disambiguation flags that crossterm does not decode there. Fixes #1599.
2026-05-14 07:41:09 -05:00
Hunter Bown 89e78d75db test(tui): avoid Windows Instant underflow
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.
2026-05-14 07:33:02 -05:00
Reid 8fd82be1ee fix(streaming): wrap overlong no-whitespace text
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.
2026-05-14 07:11:38 -05:00
jieshu666 96369a8d51 fix(tui): reduce full-repaint flicker
Avoid an intermediate flush between terminal origin reset and clear so slow terminals do not render the transient reset state.
2026-05-14 07:04:50 -05:00
Vishnu 4a617b1b2c fix(tui): restore terminal on SIGINT and SIGTERM
Restore terminal modes on abnormal signal exit and share the emergency restore path with the panic hook. Fixes #1583.
2026-05-14 07:03:54 -05:00
axobase001 7d3a36ddbc fix(shell): preserve proxy env for child tasks
Allow standard proxy environment variables through the child task environment filter, with regression coverage for upper- and lower-case forms.
2026-05-14 07:03:12 -05:00
Hunter Bown 9483248a9f feat(feishu): carry Lighthouse bridge into v0.8.37
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.
2026-05-14 03:56:03 -05:00
Hunter Bown 019d55694a fix(tui): treat absolute slash paths as messages
Let absolute filesystem paths like /usr/bin pass through the composer as user text instead of being parsed as slash commands.
2026-05-14 03:43:35 -05:00
Hunter Bown 11c655b32a fix(tui): refresh mcp discovery on manager open
Refresh deferred MCP discovery when the manager opens so the sidebar count reflects deferred tools already available to the model.
2026-05-14 03:43:32 -05:00
Hunter Bown a3f88bf6cf fix(search): default web search to bing (#1619)
Summary:
- add Bing as explicit default web_search provider
- keep explicit DuckDuckGo configuration supported
- update docs/help/config examples

Validation: CI green before merge.
2026-05-14 03:31:15 -05:00
Hunter Bown d46f7415b3 fix(tui): let ctrl-alt-0 restore hidden sidebar (#1621)
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.
2026-05-14 03:30:44 -05:00
Hunter Bown f060386927 fix(approval): fingerprint generic tool denials (#1623)
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.
2026-05-14 03:30:11 -05:00
Hunter Bown 9eb588c383 fix(tui): summarize live tool status noise (#1618)
Summary:
- concise live shell/tool labels
- collapsed pending CI polling rows
- hardened stale task-panel timing test

Validation: CI green before merge.
2026-05-14 03:21:57 -05:00
Hunter Bown d5c45d962d chore(release): prepare v0.8.36
Squash merge of work/v0.8.36-cache-hygiene into main.

All preflight gates passed: version-drift/check/lint/test (3073 pass, 0 fail) / CodeQL / GitGuardian / npm-smoke. Preparing the v0.8.36 release tag.
2026-05-14 00:31:18 -05:00
Hunter Bown d5051429dd ci(release): harden changelog drift checks 2026-05-13 18:09:57 -05:00
Hunter Bown 2739f8ee08 fix(tui): clarify sidebar state and settings wiring 2026-05-13 18:09:41 -05:00