When /init runs inside a git repository, automatically append .deepseek/
to .gitignore so that workspace-local state (instructions, snapshots,
pastes) is not accidentally committed.
The helper ensure_deepseek_gitignored() checks for an existing .git
directory, reads the current .gitignore (if any), and appends the entry
only when it is not already present. Non-fatal if the file cannot be
written (e.g. read-only filesystem).
Includes four tests covering creation, append, idempotency, and
non-git-repo skip behaviour.
Fixes#1326
Sort discovered tools by name in three places so the prompt prefix
the model sees is deterministic across runs regardless of server
pagination order:
- McpConnection::discover_tools — after all pages collected
- McpPool::all_tools — after iterating connections
- McpPool::to_api_tools — final block sent to the model
Adds a regression test that exercises a 2-page paginated discovery
with reverse-ordered tools and asserts the result is sorted.
Adapts the production sort idea from @hxy91819's PR; the test
infrastructure here uses the existing ScriptedValueTransport rather
than introducing a parallel MockTransport with a different trait
signature.
Co-Authored-By: hxy91819 <hxy91819@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Error messages containing environment variable names like
DEEPSEEK_ALLOW_INSECURE_HTTP were mangled by markdown rendering:
the inline _italic_ parser consumed underscored segments (e.g.
_ALLOW_ → italic ALLOW without underscores), resulting in the
illegible DEEPSEEKALLOWINSECURE_HTTP displayed to the user.
Switch HistoryCell::Error from render_message (which routes through
markdown_render::render_markdown_tagged) to wrap_plain_line, which
renders the message body verbatim while preserving the existing
severity label, bold prefix, and continuation-rail layout.
Closes#1303
Extract the duplicated try_lock retry loop into a generic helper method
retry_lock<T>() that retries up to N times with 1ms pauses. This improves
readability and makes the retry pattern reusable.
Addresses review feedback from gemini-code-assist.
The /clear command was not reliably clearing the Todos sidebar because
clear_todos() used a single try_lock() attempt. When the engine held
the mutex during tool execution, the lock acquisition failed and todos
persisted across /clear commands.
Replace the non-blocking try_lock with a retry loop (up to 100 attempts,
1ms apart) so /clear waits briefly for the mutex and always clears todos.
Also add a regression test clear_todos_resets_todos_list that seeds todos
and asserts they are empty after /clear.
Fixes#1258
Replace the upstream inert-scrollbar test (transcript_scrollbar_gutter_
is_not_draggable) with one that asserts the gutter starts a scrollbar
drag rather than a text selection, matching the intended behaviour
where the visible scrollbar thumb remains interactive.
The other 17 test failures in the CI run are pre-existing Windows/local
environment issues (Python REPL / skills fixture pollution) unrelated
to this change.
- Wire mouse_hits_transcript_scrollbar into the Left-Down handler so the
scrollbar thumb remains draggable after rebase onto upstream/main.
- Fix clippy::useless_format in memory-overhead test.
Simulate a 30-turn complex session (user → thinking → assistant →
tools) and assert the rail_prefix_widths vector stays under 1 MB even
in pathological sessions. The test reports exact memory figures via
eprintln! for diagnostic visibility.
Replace the hardcoded three-glyph TOOL_CARD_RAIL_PREFIXES set (only
covered tool-card rails) with iterative structural detection that
handles all TUI decoration glyphs:
- Iterates through consecutive leading decorative spans so tool headers
with multiple prefix spans (e.g. "• ▶ run issue") are fully stripped.
- Pattern A: "<glyph>[<glyph>…]<space>" where all non-space chars are
drawing characters — covers single-glyph (▏, ▶, ⌕) and multi-glyph
prefixes (⋮⋮).
- Pattern B: "<glyph>" + lone space span — covers assistant/user glyphs
(●, ▎).
- Covers Box Drawing, Block Elements, Geometric Shapes, and individual
chars used as TUI decoration (•, …, ·, ⌕, ⋮).
Store rail-prefix widths in TranscriptViewCache so the copy path reads
metadata rather than guessing from glyphs.
When the user holds the left mouse button and drags past the top or
bottom of the transcript rect, advance the viewport on a fixed cadence
so a long passage can be selected in one drag. Also strip the visual
tool-card left-rail glyph (`╭ │ ╰`) from copied text so it does not
leak into the clipboard.
Auto-scroll:
* New `SelectionAutoscroll` state on `ViewportState` records direction
and the last in-bounds column while a drag is held outside the rect.
* Armed/disarmed by the existing `Drag(Left)` handler; cleared on
`Up(Left)`, on a fresh `Down(Left)`, and when the cursor returns
inside the viewport.
* A new per-loop helper `tick_selection_autoscroll` advances
`pending_scroll_delta` by ±1 line every 30 ms (~33 lines/sec) and
extends the selection head to the matching edge row, so the visible
selection rect stays glued to the cursor edge.
* The main loop's `poll_timeout` is clamped to the next autoscroll
tick so the loop wakes up on cadence even with no input events.
Copy artifact:
* `line_to_plain_for_copy` returns `(plain_text, rail_prefix_width)`,
stripping a leading rail glyph span when present.
* `selection_to_text` shifts recorded selection columns left by the
rail width before slicing so visible selection bounds still match.
Tests:
* `drag_above/below_viewport_arms_autoscroll_*` — verify direction
and clamped column for vertical-out drags.
* `drag_back_inside_disarms_autoscroll` — re-entering clears state.
* `mouse_up_clears_selection_autoscroll` — release ends autoscroll.
* `tick_selection_autoscroll_advances_pending_scroll_when_due` —
cadence advances scroll and head; `_respects_cadence` blocks early
ticks; `_clears_when_drag_ended` self-heals if drag state is lost.
* `line_to_plain_for_copy_*` — rail glyph stripping for top/middle/
bottom rails plus negative tests for plain spans, OSC-8 wrappers,
and lines whose plain text legitimately starts with `│`.
* Strengthened `selection_to_text_copies_rendered_transcript_block`
to assert no `│ ` line-prefix leaks.
The earlier build-script fix only watched .git/HEAD, which catches
branch switches and detached-HEAD moves but NOT git commit on the
current branch — the commit updates the underlying ref file
(refs/heads/<name> or packed-refs after pack-refs), and HEAD itself
stays unchanged. So the embedded short-SHA in deepseek --version went
stale on the same-branch-commit case the fix was supposed to cover.
Resolve the symbolic ref at build time and watch:
- the loose ref file (refs/heads/<branch>)
- packed-refs (Cargo treats a non-existent rerun-if-changed path as
always-changed, which covers the loose to packed transition after
git pack-refs)
Detached HEAD is unchanged: HEAD itself contains a SHA, no symbolic
deref happens, and HEAD-as-watched still triggers on every move.
Adds parse_symbolic_ref + 4 unit tests covering: stripped prefix,
no trailing newline, detached SHA, empty input.
Smoke verified: with the previous fix, an empty commit on the same
branch did not bust the cache. With this commit, it does.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The stdio MCP spawn site discarded server stderr (`Stdio::null`), so a
server that crashed after \`initialize\` left only "Stdio transport
closed" — useless for debugging.
Pipe stderr now and drain it into a bounded ring buffer
(\`StderrTail\`, capped at 64 lines) via a tokio task. When the read
side fails (EOF or IO error), the transport error includes the
captured stderr tail so callers see why the server died.
Adds a unix-only regression test that spawns
\`sh -c 'echo >&2; exit 1'\` and asserts the stderr line propagates
through the recv() error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The application's drag-select clamps to the transcript area; selection
that appeared to cross into the right sidebar on Windows was actually
the terminal handling the drag natively because Windows defaulted
`mouse_capture = false`. That default was set to dodge the
mouse-mode-leak failure mode from #878 / #898 on legacy conhost, but
modern Windows Terminal handles mouse mode cleanly.
`default_mouse_capture_enabled` now branches on `WT_SESSION`:
- Set (Windows Terminal): default on, sidebar isolation works.
- Unset (legacy conhost / older Windows hosts): default off, same as
before — explicit `--mouse-capture` or `[tui] mouse_capture = true`
still opts in.
Adds two regression tests on the Windows path covering both branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three additional cases to the rail-on-code-block guard:
- multi-line SQL fence after an intro paragraph (issue #1212 repro)
- fenced block with an embedded blank line (different wrap branch)
- a single source line long enough to wrap inside the fence
The existing fix in caf77949d already covers these, but the original
test only asserted on a 2-line fence with no wrap. The wider coverage
locks the current behavior so a future markdown-render rewrite can't
silently regress the visible \`▏\` rail back into code output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`changelog_entry_exists_for_current_package_version` reached for
`CARGO_MANIFEST_DIR/../../CHANGELOG.md`, which assumes a workspace
checkout. Running the test from a packaged crate (no parent workspace)
panicked instead of skipping.
Walk up from `CARGO_MANIFEST_DIR` looking for `CHANGELOG.md`. Inside
the workspace it still finds the top-level file; outside the workspace
the gate quietly skips with a printed note instead of panicking.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`build.rs` only declared `rerun-if-env-changed` for `DEEPSEEK_BUILD_SHA`
and `GITHUB_SHA`. With no `rerun-if-changed` directives, Cargo cached
the build-script output across commits and the embedded short-SHA in
`DEEPSEEK_BUILD_VERSION` (visible in `--version`) went stale until the
next `cargo clean`.
Add a `cargo:rerun-if-changed=<workspace>/.git/HEAD` directive in both
the `cli` and `tui` build scripts. Handle both layouts: a regular
checkout (`.git` is a directory) and a worktree (`.git` is a pointer
file containing `gitdir: <path>`), so the SHA stays current in either
case.
Verified: touching `.git/HEAD` now triggers a recompile of the affected
crate; `--version` reflects the current commit on the next build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two responsibly-disclosed security fixes:
- GHSA-88gh-2526-gfrr (@JafarAkhondali)
- GHSA-72w5-pf8h-xfp4 (@47Cid)
Plus version bump, CHANGELOG, regression tests for both.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Crossterm routes the same logical commands through the WinAPI console
backend on Windows, so EnableFocusChange / Push keyboard flags /
EnableMouseCapture / EnableBracketedPaste never reach the writer as
ANSI bytes there. Gate the byte-level test with cfg(not(windows)) and
add a windows-only smoke test that just exercises the function for
panic-freedom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "message" arm wrapped its work in `if !data.trim().is_empty()`,
which clippy::collapsible_match flags. Move the predicate into a
match guard so the inner branch is the only body.
No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the common "re-arm keyboard enhancement, mouse capture, bracketed
paste, and focus events" sequence used by app startup, FocusGained
recovery, and resume_terminal into a single best-effort helper.
The FocusGained handler previously only re-pushed keyboard enhancement
flags + (after v0.8.24) mouse capture, missing bracketed paste and focus
events. Each new mode added at startup was a fresh gap waiting to be
discovered when a user Cmd+Tabbed away. Co-locating the four flags in
one canonical helper means future mode additions are one edit, not three.
Adds a unit test that pins the gating (mouse + bracketed paste honor
their booleans; keyboard + focus are unconditional) by writing into an
in-memory buffer and asserting on the CSI sequences crossterm emits.
Test: cargo test -p deepseek-tui --bin deepseek-tui --locked
The previous render_table_row truncated cell content with `…` whenever
a cell's display width exceeded `(terminal_width - 7) / num_cols`. In
narrow terminals or with verbose English/Chinese instructional tables
(common in LLM responses), users would see only the first ~30
characters of meaningful content per cell with the rest silently lost
— not visible by scrolling, not recoverable.
Replace the truncation with a word-wrapping renderer that preserves
the full cell content across multiple visual lines while keeping the
column separators (`│`) aligned on every wrapped continuation line.
The row's visual height becomes the height of the tallest column;
shorter columns get blank-padded continuation rows so column edges
stay aligned.
Algorithm:
- wrap_cell_text splits on whitespace and packs words greedily until
the next word wouldn't fit; words wider than col_width are
hard-broken at character boundaries so wrapping always makes
progress (URLs, paths).
- render_table_row pre-wraps every cell, computes the row height as
max(cell_segments_len), then emits N visual lines with each cell's
segment-or-empty padded to col_width and separated by `│`.
Adds two regression tests covering: long cells preserve content (no
`…`) and wrapped continuation lines retain column separators.
* fix(client): stabilize reasoning_content replay for prompt cache
- stop gating assistant reasoning_content on whether a later user turn
exists; the field now depends only on the stored message itself
- preserve historical message bytes across turns so DeepSeek's prefix
cache stays warm on every text-reply follow-up
- add a byte-stability regression test and update the prior-non-tool
reasoning test to assert the new contract
* style(client): rustfmt single-line let binding
- collapse a two-line `let mut has_reasoning = ...` into a single line
so `cargo fmt --all -- --check` passes
Previously, Home/End without modifiers scrolled the transcript
instead of moving the cursor to the start/end of the input line.
This is unexpected for most users who expect standard text editing
behavior.
Now:
- Home/End (no modifier) → move cursor to start/end of input
- Ctrl+Home/End → scroll transcript to top/bottom
Closes#1234
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-authored-by: heloanc <heloanc@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Closes#1266 (DeepWiki at https://mcp.deepwiki.com/mcp). URL-based MCP servers now try the modern Streamable HTTP transport first — POST JSON-RPC to the base URL with `Accept: application/json, text/event-stream`, accept either JSON or SSE response — and fall back to the older SSE endpoint-discovery flow on incompatible status codes (404/405/406/415/501). Existing SSE servers keep working via the fallback. Single-file change in `crates/tui/src/mcp.rs` with a tokio-based end-to-end test that exercises the full handshake.
Thanks @reidliu41.
Closes#1273. 中止 implies a temporarily-stoppable action; 终止 is the definitive 'end the turn' semantics that matches the English 'Abort the turn'. Updates two strings in the approval dialog (`option_abort` and the footer controls hint).
Thanks @Liu-Vince.
Add direct POST-based MCP transport for Streamable HTTP servers while keeping
the existing SSE endpoint-discovery path as a fallback. Parse both JSON and
text/event-stream responses so servers like DeepWiki can be validated and used.
Size the config key column from the available setting keys so long keys like
paste_burst_detection remain fully visible without pushing the value and scope
columns out of alignment.
Add a regression test covering long config keys and scope column alignment.
Pre-existing gap exposed during v0.8.24 testing: when the user clicks
away (Cmd+Tab, opens the screenshot tool, etc.) and clicks back, some
terminals drop the application's mouse-tracking mode. The
\`FocusGained\` handler at \`ui.rs::1599\` already re-pushed keyboard
enhancement flags to recover IME state — extend the same recovery to
\`EnableMouseCapture\` so wheel scroll keeps working after a focus
round-trip. Gated on \`app.use_mouse_capture\` so explicit
\`--no-mouse-capture\` users aren't re-enabled against their will.
中止 implies a temporary pause that can be resumed; 终止 means a
definitive end, which matches the English "Abort the turn" / "abort"
semantics of this action.
Update both the option label ("终止本轮") and the Esc key footer hint
("Esc:终止") in the approval/elevation widget.
Fixes#1273
Merge of PR #1196 by wplll. Adds:
Cache-aware prompt layering:
- PromptBuilder struct separates prompt construction from inspection
- System prompt split into named layers with stability classification
- Layers classified as static/history/dynamic for cache debugging
/cache inspect command:
- SHA-256 hashes of each rendered prompt layer
- Base static prefix hash vs full request prefix hash
- Static prefix stability status across turns
- First-divergence tracking from previous request
Wire payload optimization:
- Tool result budget: large outputs compacted before API request
- Tool result dedup: repeated outputs replaced by compact refs
- Turn metadata dedup: repeated <turn_meta> blocks deduplicated
- Wire-only: local session messages remain unchanged
Project context pack:
- Deterministic workspace summary injected into stable prefix
- Configurable via [context] project_pack = false
Cache warmup and improved footer cache display.
Thanks to wplll for the contribution.
Previously clear_todos() only cleared the plan_state — the SharedTodoList
was never touched. Now it clears both: the todo list (via try_lock on
self.todos) and the plan state. The caller in commands/core.rs already
called clear_todos() on /clear; it just had nothing to clear.
Adds a directive to the ## Language section telling the model that
project context (AGENTS.md, auto-generated instructions, file trees,
skill descriptions) is NOT a language signal. Chinese filenames in a
repo should not bias the model toward Chinese replies when the user
writes in English.
Adds a disk-space guard before each snapshot: if the snapshot directory
exceeds MAX_SNAPSHOT_SIZE_MB (500 MB), the oldest snapshots are pruned
aggressively to stay under PRUNE_TARGET_MB (400 MB). If pruning cannot
get under the limit, the history is wiped so the next snapshot starts
fresh.
The guard runs in snapshot() before git add/write-tree/commit-tree so
disk pressure is relieved BEFORE taking another snapshot.
Includes dir_size_mb() helper and regression tests.
Thanks to Giggitycountless for the original max_size_mb proposal
(PR #1131).
MCP servers are allowed by spec to paginate list responses. The old
implementation made a single request and stopped, silently dropping
subsequent pages. Servers that paginate at fewer items than their
total tool count (e.g. gbrain at 5 per page) would appear to expose
only those first few tools.
All four discovery methods now follow nextCursor until the server
signals no more pages, accumulating results across all pages:
- discover_tools
- discover_resources
- discover_resource_templates
- discover_prompts
Thanks to Liu-Vince for the original diagnosis and fix (PR #1256).
Files inside .deepseek/, .cursor/, .claude/, and .agents/ were invisible
to @-mention Tab-completion when those directories were listed in
.gitignore (the common case). The walk_for_completions and
build_file_index paths now walk these directories separately with
gitignore disabled, merging results into the main candidate list.
Also excludes .deepseek/snapshots/ from the dot-dir walk so the snapshot
side repo (which can reach hundreds of GB, see #1112) is never indexed.
The fix applies to:
- @-mention Tab-completion (walk_for_completions)
- Fuzzy file resolution (build_file_index)
- Ctrl+P file picker (collect_candidates)
Windows ships curl built against Schannel, which performs mandatory
certificate-revocation checks. When the user's network can't reach
OCSP/CRL responders (corporate firewalls, captive portals, IPv6
hiccups, some ISPs), the TLS handshake fails with
\`CRYPT_E_NO_REVOCATION_CHECK (0x80092012)\` and \`deepseek update\`
is unable to fetch \`api.github.com\` or download release assets.
Add \`--ssl-no-revoke\` to every curl invocation issued by the
self-update path on Windows. Other platforms (OpenSSL/LibreSSL on
macOS/Linux/BSD) continue to use no extra flags. Helper is a pure
function over the OS string, so both branches are unit-tested.
Replaces the autofix in 326a1da (reverted in 51f2f04b4), which mistakenly
required `workspace` to live under the global config's skills directory
and therefore caused `resolve_skills_dir` to *always* fall back to the
global skills dir — silently breaking workspace-local `.agents/skills`
and `./skills` loading.
Apply the right containment check instead: canonicalize the workspace
once, canonicalize each candidate, and require the candidate to
`.starts_with` the canonicalized workspace before returning it. A
`skills` symlink that escapes the workspace (e.g. to `/etc`) now causes
the candidate to be rejected and resolution to fall back to the
configured global skills directory, preserving real symlink-escape
defense without regressing the feature.
Adds three regression tests: positive case for `.agents/skills`,
positive case for the `./skills` fallback, and a Unix-only test that
constructs a symlink-escaping `skills` directory and asserts it is
rejected.