- workspace.package.version: 0.8.29 → 0.8.30
- per-crate path-dependency version pins: 0.8.29 → 0.8.30
- npm/deepseek-tui: version + deepseekBinaryVersion → 0.8.30
- Cargo.lock refreshed via `cargo update --workspace --offline`
- CHANGELOG: `[Unreleased]` → `[0.8.30] - 2026-05-11` with the full
release-theme paragraph and the new "Changed" section for the
Alt+<key> unification
Verified with `./scripts/release/check-versions.sh`:
Version state OK: workspace=0.8.30, npm=0.8.30, lockfile in sync.
Workspace + per-crate path-dep version pins, npm wrapper, and
deepseekBinaryVersion all advance 0.8.28 -> 0.8.29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Workspace + per-crate path-dep version pins, npm wrapper, and
`deepseekBinaryVersion` all advance from 0.8.27 → 0.8.28. Lockfile
refreshed via `cargo update --workspace --offline`.
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>
- Bump workspace version 0.8.22 → 0.8.23 across Cargo.toml, every per-crate
path-dependency pin, npm/deepseek-tui/package.json (both `version` and
`deepseekBinaryVersion`), and Cargo.lock.
- Add a 0.8.23 CHANGELOG entry covering the security hardening stack
(sanitized child env, plan-mode tool surface, sub-agent approvals,
symlink walks, runtime API auth, shell safety classification, MCP
config path traversal), the macOS Keychain prompt fix, the #1244 MCP
spawn error visibility + env passthrough work, the compact-thinking UX
change, and a Known issues callout for mid-run MCP stderr.
- Backfill missing CHANGELOG entries for v0.8.21 (community-heavy
release, contributors credited) and v0.8.22 (fetch_url redirect
validation). The gap was unintentional, so contributor work is being
reflected in-repo now.
- Add docs/RELEASE_CHECKLIST.md so future releases gate on the
CHANGELOG/version/preflight steps explicitly.
* fix(config): keep DeepSeek beta endpoint for legacy cn alias
* fix(ci): filter download-artifact to deepseek* pattern
Prevents the release aggregation job from picking up non-binary
artifacts (e.g. Docker .dockerbuild cache layers) that cause the
checksum manifest to include spurious entries and the Release to
carry files it shouldn't.
* fix(tui): enable focus events to restore IME after app-switch
On macOS, switching away (Cmd+Tab) and back suspends the IME compositor.
Without focus-event handling, the TUI never signals readiness to the
terminal, so CJK input methods (Pinyin, Zhuyin, etc.) stop working.
- EnableFocusChange on startup so the terminal reports FocusGained/FocusLost
- Re-push KeyboardEnhancementFlags on FocusGained (some terminals reset
the enhanced keyboard mode on focus-loss)
- DisableFocusChange on shutdown for clean terminal handoff
* chore: cargo fmt
* docs: add DataWhale and DeepSeek to acknowledgments
* docs: fix DeepSeek name etymology in acknowledgments
* fix(tui): recapture viewport on focus restore
* docs: thank DeepSeek and DataWhale bilingually
A community-driven reliability release. Plan-mode safety, paste-Enter
auto-submit, slash-menu skills coverage, the deepseek-cn endpoint
preset, and a handful of platform / streaming / gateway-compat fixes,
plus a small PTY-driven QA harness.
See CHANGELOG.md for the full annotated change list with credits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47 fmt drifts had accumulated from the squash-merged community PRs on
this branch (#653, #654, #655, #645, #658, #668, #659, #661, #660,
#667, #656). Pure formatting — no behavioural changes — applied via
`cargo fmt --all` to satisfy CI's `cargo fmt --all -- --check` gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#651: fix test assertion — section_bg now Color::Reset (was DEEPSEEK_INK)
#645: replace expect() with Result in OpenSandboxBackend::new()
#653: correct resolve_prefixes docstring to describe deny-always-wins
Final step in the v0.8.11 patch release. Bumps the workspace
`Cargo.toml`, all 9 internal path-dep version pins, and
`npm/deepseek-tui/package.json` to **0.8.11**. `Cargo.lock`
regenerated alongside.
The v0.8.11 CHANGELOG entry already landed on `main` via the
cache-maxing overhaul PR (#684). This commit only stamps the
version. Together they ship:
* **Cache-maxing for V4 1M context** — engine no longer rebuilds the
system prompt on every turn (#684's `Session::last_system_prompt_hash`),
the volatile working-set summary moved out of the system prompt
into per-turn `<turn_meta>` on the latest user message, the tool
array is anchored with `cache_control: ephemeral`, and the
`messages_with_turn_metadata` injection skips tool-result
messages so the assistant→tool_result invariant stays intact.
* **500K compaction floor** — automatic compaction refuses below
500K tokens via `MINIMUM_AUTO_COMPACTION_TOKENS`. Manual
`/compact` bypasses (explicit user agency).
* **Token-only compaction trigger** — dropped
`CompactionConfig::message_threshold` and the message-count
branch in `should_compact`; that 128K-era heuristic only fired
on long sessions of small messages, exactly the case where
rewriting the V4 prefix cache is most wasteful.
* **Legacy 128K naming** — `DEFAULT_CONTEXT_WINDOW_TOKENS` →
`LEGACY_DEEPSEEK_CONTEXT_WINDOW_TOKENS`.
* **`npm install` resilience** — `install.js` now retries with
exponential backoff, enforces per-attempt timeout + 30 s stall
detector, honors `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` (pure
Node, no new dependencies), and prints download progress to
stderr. Driven by a community report that `npm install` took 18
minutes through a CN npm mirror; the GitHub Releases binary
fetch was the bottleneck and CN mirrors don't proxy GitHub.
Verified locally:
* cargo fmt --all -- --check ✓
* cargo clippy --workspace --all-targets --all-features
--locked -- -D warnings ✓
* cargo test --workspace --all-features --locked ✓
* parity gates (snapshot, parity_protocol, parity_state) ✓
* bash scripts/release/check-versions.sh ✓
(workspace=0.8.11, npm=0.8.11, lockfile in sync)
* node scripts/release/npm-wrapper-smoke.js ✓
Reminder for the maintainer at release time: the npm publish is
manual and requires 2FA OTP on every publish. After this PR
merges and the GitHub Release is fully drafted by `auto-tag.yml`,
publish from a developer machine:
cd npm/deepseek-tui
npm publish --access public
The `prepublishOnly` hook checks all eight binaries plus the
SHA256 manifest are present on the GitHub Release before letting
`npm publish` proceed, so this must happen *after* the GitHub
Release is finalized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Workspace `version = "0.8.8"` in root `Cargo.toml`.
- 31 internal `deepseek-*` path-dep version pins across the
9 crates that declare them.
- `npm/deepseek-tui/package.json` `version` and
`deepseekBinaryVersion` both updated.
- `Cargo.lock` regenerated for the new workspace version.
- `CHANGELOG.md` `[Unreleased]` heading promoted to
`[0.8.8] - 2026-05-03`.
`scripts/release/check-versions.sh` reports the workspace, npm
wrapper, and lockfile all aligned. Pushing this to `main` should
fire `auto-tag.yml`, which creates the `v0.8.8` tag with
`RELEASE_TAG_PAT`. The tag triggers `release.yml` to build the
matrix and draft the GitHub Release. The npm wrapper publish
remains manual (npm 2FA OTP requirement).
What ships in v0.8.8
====================
The full polish stack already merged via PRs #514 (stabilization),
#515 (OSC 8 hyperlinks), #517 (inline diff render), #518 (user
memory MVP), #519 (foreground polish + per-project overlay +
security + Windows redraw fix), and #508 (Linux ARM64 prebuilts +
install docs). See `CHANGELOG.md` and the README "What's new in
v0.8.8" section for the full list.
Merge the v0.8.6 feature batch and release hardening.\n\nIncludes the full #373-#380/#382-#402 milestone scope, version bump to 0.8.6, secure /share temp-file handling, Windows-safe self-update replacement, and CI portability fixes.\n\nRemote PR checks passed on the final head before merge.
Workspace + npm wrapper + every internal crate path-dep pin moved from
0.8.4 → 0.8.5. scripts/release/check-versions.sh confirms parity across
the three sources. cargo build / clippy / test all clean.
Pushing this commit to main is the trigger for auto-tag.yml to create
the v0.8.5 tag, which fires release.yml to build the cross-platform
matrix and draft the GitHub Release. The npm publish remains a manual
follow-up (2FA on every publish, no automation token provisioned).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pricing): extend V4 Pro 75% discount expiry to 2026-05-31 15:59 UTC
DeepSeek extended the promotional discount past the original 2026-05-05
cutoff. Without this update the TUI would have started showing 4× the
actual billed cost on May 6.
Source: https://api-docs.deepseek.com/quick_start/pricing — "extended
until 2026/05/31 15:59 UTC".
Adds a regression test pinning the new active window so a future revert
to the May 5 date trips the suite immediately.
Closes#267
* chore: remove stale TODO(integrate) markers from already-integrated modules
Five `// TODO(integrate)` comments and one matching "Not yet integrated"
note were misleading anyone grepping for integration work. Each module
is in fact wired up:
- execpolicy/mod.rs → tools/shell.rs:1322 (load_default_policy)
- sandbox/mod.rs → tools/shell.rs:28, main.rs:2647, tui/approval.rs:30
- sandbox/policy.rs → main.rs:2752, tui/approval.rs:30 (SandboxPolicy)
- command_safety.rs → tools/shell.rs:1321, tools/tasks.rs:13,
tools/approval_cache.rs:26
- tui/streaming/mod.rs → tui/app.rs:38 (StreamingState)
The remaining TODO at mcp.rs:1771 covers a separate "wire legacy sync API
into CLI subcommands or remove" decision and is left in place.
Closes#266
* docs(release): add install + dual-binary template to GitHub Release page
Closes#265.
The Release page used the auto-generated commit-title body. New users
hitting the Release page from Twitter / npm-search had no on-page
guidance that the dispatcher (`deepseek`) and the TUI runtime
(`deepseek-tui`) ship as two binaries that must coexist; #258 was an
external user spending 11 minutes figuring this out and #272 was the
follow-on confusion.
The new body covers:
- npm wrapper as the recommended install
- `cargo install deepseek-tui-cli deepseek-tui --locked` (both crates)
- Manual download with a per-platform table showing both artifacts
- sha256 verify using the existing `deepseek-artifacts-sha256.txt`
- Changelog link
* feat(debug): add /cache command surfacing per-turn DeepSeek cache hit/miss
Step 1 of #263. Without per-turn telemetry the prefix-cache audit is
unfounded speculation; the rest of the issue's investigation steps
depend on this surface.
The DeepSeek API already returns `prompt_cache_hit_tokens` and
`prompt_cache_miss_tokens` per turn, and we already store the *latest*
on App. This adds a 50-turn ring (`turn_cache_history`) populated at
the same site as `last_prompt_cache_*_tokens`, plus a `/cache [count]`
slash command that renders a fixed-width table of the last N turns
with per-turn ratios and a session aggregate. Default count is 10;
larger values clamp to the ring size.
Edge cases the formatter handles:
- No telemetry yet → friendly "no turns recorded" message
- `cache_hit_tokens = None` (provider didn't report) → row renders all
em-dashes and is excluded from session aggregates so one missing-
telemetry turn can't make the average ratio look broken.
- `cache_hit_tokens = Some, cache_miss_tokens = None` → infer miss as
`input − hit` and mark the cell with `*`. Footer documents the
asterisk.
- Ring at cap (50) → push evicts oldest.
Tests cover all four paths plus the cap.
* test(prompts): add cache-prefix stability harness for #263 step 2
The DeepSeek prefix-cache only hits while the byte prefix of each
request matches the prior call. Anything in the cached prefix that
varies turn-to-turn for unchanged inputs is a cache buster.
Adds a focused harness next to the production surface so the property
is regression-guarded:
1. `first_divergence(a, b)` helper that returns the first divergent
byte position with a `±32 byte` window of context, used by the
custom assertion `assert_byte_identical`. Future suspect tests can
reuse this to surface "where" rather than just "fail".
2. `compose_prompt_is_byte_stable_across_calls` — sweeps every
(mode, personality) pair and pins that two consecutive calls
produce identical bytes. Rules out suspect #4 (mode-prompt churn).
3. `system_prompt_for_mode_with_context_is_byte_stable_for_unchanged_workspace`
— the call site `engine.rs::build_tool_context` actually invokes,
pinned for an empty workspace across all three modes.
4. `system_prompt_with_working_set_summary_is_byte_stable_for_constant_summary`
— pins that the surrounding prompt construction faithfully embeds
the working_set summary it's given without injecting extra
non-determinism. (The actual working_set summary stability lives
in `working_set.rs` and is the next investigation target — see
issue note in PR description.)
Foundation for the suspect-by-suspect bisection in the rest of #263.
* fix(secrets): never overwrite the secrets file when load_unlocked errors
`FileKeyringStore::set` and `delete` did
`self.load_unlocked().unwrap_or_default()`, which wiped every existing
secret if the read failed for any reason other than \"file is missing\":
- file mode != 0600 (`InsecurePermissions`) — easy on headless / CI
environments where a permissive umask got applied
- corrupt JSON
- transient I/O error
In all of those, the next `store_unlocked` overwrote the file with an
empty-or-single-entry blob and reset perms to 0600, silently losing
every other provider's key.
Switch both call sites to `?`. `load_unlocked` already returns
`Ok(default)` for a missing file, so the first-write-creates-the-file
ergonomic is preserved (covered by the new
`file_store_set_still_creates_file_when_missing` test).
Adds four regression tests:
- set: insecure perms surface InsecurePermissions and leave the file
byte-identical.
- delete: same.
- set: corrupt JSON surfaces the parse error and leaves the file
byte-identical.
- set: missing file path still works (idempotence guard).
Closes#281
* fix(cache): make tool catalog byte-stable across calls and sessions
DeepSeek's KV prefix cache hits on the longest matching byte prefix of
the request. Two places in the tool-array path were silently introducing
divergence:
1. `ToolRegistry::to_api_tools()` iterated `self.tools.values()` directly.
Rust's default `HashMap` is seeded with `RandomState` per process, so
every `deepseek` launch produced a different tool order — the cross-
session resume case (the one with the biggest cache wins) never hit.
2. `active_tool_list_from_catalog()` filtered the catalog `Vec` by the
active set in catalog order. When ToolSearch activated a previously-
deferred tool mid-conversation, the new tool appeared at its catalog
index, shifting every later tool's byte offset and busting the cached
prefix from there onwards.
Fixes:
- `to_api_tools()` now sorts by tool name before emitting the API tool
array. Stable across calls AND across launches.
- `build_model_tool_catalog()` sorts each partition (built-ins first,
contiguous; MCP tools after, also alphabetical). Mirrors Claude Code's
`assembleToolPool` strategy where they explicitly call out cache
stability as the reason: "a flat sort would interleave MCP tools into
built-ins and invalidate all downstream cache keys whenever an MCP
tool sorts between existing built-ins."
- `active_tool_list_from_catalog()` puts always-loaded tools in catalog
order at the head and deferred-but-now-active tools at the tail. A
deferred-tool activation during ToolSearch no longer shifts earlier
tools' positions.
Adds three regression tests:
- `to_api_tools_emits_alphabetical_order_regardless_of_registration_order`
- `model_tool_catalog_sorts_each_partition_for_prefix_cache_stability`
- `active_tool_list_pushes_deferred_activations_to_the_tail`
Refs #263. Findings produced by reading reference Claude Code source
side-by-side with our request-building flow; full delta analysis in
the PR description.
* fix(sandbox): elevate Agent-mode shell sandbox to allow network access
The seatbelt-default policy is `WorkspaceWrite { network_access: false }`,
which on macOS emits `(deny default)` with no `(allow network-outbound)` /
`(allow system-socket)`. Every outbound socket call from a sandboxed
shell command — including `getaddrinfo` for DNS — gets denied by the
kernel. Symptom: "DNS resolution failed" for any URL the model tries to
reach via curl, yt-dlp, package managers, etc.
Engine.build_tool_context only elevated the policy in Yolo mode, leaving
Agent mode (the default) stuck on the strict default. That's tighter
than competitors (Claude Code, Codex) without buying any safety the
application-level NetworkPolicy or the approval flow doesn't already
provide.
Switch the elevation to a `match` so:
- Plan → no elevation (read-only investigation; shell tool not registered)
- Agent → WorkspaceWrite { network_access: true, … }
- Yolo → WorkspaceWrite { network_access: true, … } (unchanged)
Adds `agent_and_yolo_modes_elevate_shell_sandbox_to_allow_network` so a
future revert to the no-network default trips CI immediately.
Closes#273
* fix(skills): treat bare github.com/<owner>/<repo> URLs as GitHubRepo
Closes#269.
`/skill install https://github.com/obra/superpowers` failed on every
platform with `invalid gzip header`. Root cause: `InstallSource::parse`
matched any `https://`-prefixed spec as `DirectUrl`, so the installer
downloaded the HTML repo page (200 OK, `text/html`) and tried to
gzip-decode HTML. The user reported it from Win11 + PowerShell but the
parse path is platform-independent.
Recognize bare GitHub repo URLs in `InstallSource::parse`:
- `https://github.com/<owner>/<repo>`
- `https://github.com/<owner>/<repo>/`
- `https://github.com/<owner>/<repo>.git`
- `https://github.com/<owner>/<repo>.git/`
- `https://www.github.com/<owner>/<repo>`
- `http://github.com/<owner>/<repo>` (legacy)
…all route to the existing `GitHubRepo` source, which already produces
`https://github.com/<repo>/archive/refs/heads/{main,master}.tar.gz`
candidates with proper fallback. URLs with a third path segment
(`/archive/...`, `/blob/...`, `/tree/...`) keep going through
`DirectUrl` because the user picked that exact path.
Adds two regression tests: one asserting the seven recognised forms
all canonicalize to `github:obra/superpowers`, and one pinning the
sub-resource paths to `DirectUrl`.
* fix(cache): drop volatile fields from working_set summary block (#280) (#287)
The working-set summary lands inside the system prompt before the
historical conversation, so any byte that drifts there cache-misses
everything that follows in DeepSeek's KV prefix cache. Two sources of
turn-over-turn drift are removed:
1. The rendered line is now `- {path} ({kind})`. The previous form
interpolated `entry.touches` and `self.turn - entry.last_turn`,
both of which advance on every user message even when no new
paths are observed.
2. A new `sorted_for_prompt` helper sorts by (touches DESC, path ASC)
instead of the turn-aware `sorted_entries`. The recency bonus in
`score_entry` crosses bucket boundaries as turns advance, so even
without rendering `last seen` the order — and which entries cross
the `max_prompt_entries` cutoff — drifted. Compaction pinning
still uses `sorted_entries` because it genuinely wants recency.
Adds a regression test that observes a fixed message set, calls
`summary_block` before and after `next_turn()`, and asserts the two
outputs are byte-identical. The shared `first_divergence` /
`assert_byte_identical` helpers (from #279) move from `prompts::tests`
into `test_support` so working_set tests can reuse them.
Closes#280.
* fix(cache): memoise tool catalog so descriptions stay byte-stable (#289)
`to_api_tools` previously re-sampled `tool.description()` and
`tool.input_schema()` on every call. Native tools return `&'static str`
and a `json!` literal, so the bytes were stable in practice — but the
`McpToolAdapter` returns `self.tool.description.as_deref()`, which can
drift when the upstream MCP server reconnects with a different
description string. Any drift mid-session rewrites the tool catalog
that lands in the cached prefix and busts every byte that follows.
Adds an `api_cache: OnceLock<Vec<Tool>>` field on `ToolRegistry`. The
first `to_api_tools` call materialises the catalog; subsequent calls
return a clone of the cached vector. Mutations (`register`, `remove`,
`clear`) reset the field so the next read rebuilds. Mirrors
reference-cc's `getToolSchemaCache` (`utils/api.ts:119–208`).
Tests:
- `to_api_tools_pins_description_bytes_across_calls` registers a tool
whose `description()` advances through a script of pre-built strings
on each call. After the cache is populated, the second `to_api_tools`
read returns the original description because `description()` is no
longer invoked. Without the cache the second read would return the
next script entry.
- `register_invalidates_api_tools_cache` registers a tool, snapshots,
registers another, snapshots again, and asserts the second snapshot
reflects both tools (cache rebuilt) and that the varying tool's
description advanced (proving the rebuild actually re-sampled).
- `remove_and_clear_invalidate_api_tools_cache` covers the other two
invalidation paths.
* fix(cache): sort project_tree and summarize_project output (#290)
Both helpers walked the workspace via `ignore::WalkBuilder::build()`
and emitted entries in the OS readdir order — non-deterministic across
filesystems (htree-hash on ext4, insertion-order on APFS, etc.). Their
output lands in the fallback branch of the system prompt's project
context (when the workspace has no AGENTS.md / CLAUDE.md) and inside
the `project_map` tool surface, both of which feed the cached prefix.
`summarize_project` now sorts the collected key-files list before the
type-detection logic and the fallback `Project with key files: …` join.
`project_tree` collects `(rel_path, is_dir)` tuples, sorts by full
path, and only then formats the indented tree. Sorting by full path
preserves the visual tree shape — `"src" < "src/lib.rs"` because the
shorter string compares less — while making siblings deterministic.
Tests cover sibling order, parent-before-children invariant, byte
stability across two consecutive calls, and the fallback `Project
with key files:` branch (the only branch where the joined order
escapes into output without further sorting downstream).
* fix(client): unique fallback id for parallel streaming tool calls (#291)
When a streamed tool_call delta omits the `id` field, the chat-completion
decoder used to fall back to the literal string `"tool_call"` for every
call. With the V4 API's native parallel tool calls (multiple tool_calls
in one delta), every parallel call ended up with the same fallback id —
downstream tool-result routing then matched the first call's result
twice and the second call hung waiting for an answer that never arrived.
The fallback now indexes by the assigned `content_block` position,
producing `"call_0"`, `"call_1"`, … within a single response. Upstream-
supplied ids are still forwarded verbatim; only the fallback path
changes.
Tests pin both invariants:
- `decoder_assigns_unique_fallback_ids_to_parallel_tool_calls_missing_id`
feeds two tool calls without `id` in one delta and asserts they get
distinct ids.
- `decoder_preserves_upstream_tool_call_id_when_present` keeps the
forward-as-is path honest.
* fix(cache): place handoff and working_set after static prompt blocks (#292)
* fix(cache): drop volatile fields from working_set summary block (#280)
The working-set summary lands inside the system prompt before the
historical conversation, so any byte that drifts there cache-misses
everything that follows in DeepSeek's KV prefix cache. Two sources of
turn-over-turn drift are removed:
1. The rendered line is now `- {path} ({kind})`. The previous form
interpolated `entry.touches` and `self.turn - entry.last_turn`,
both of which advance on every user message even when no new
paths are observed.
2. A new `sorted_for_prompt` helper sorts by (touches DESC, path ASC)
instead of the turn-aware `sorted_entries`. The recency bonus in
`score_entry` crosses bucket boundaries as turns advance, so even
without rendering `last seen` the order — and which entries cross
the `max_prompt_entries` cutoff — drifted. Compaction pinning
still uses `sorted_entries` because it genuinely wants recency.
Adds a regression test that observes a fixed message set, calls
`summary_block` before and after `next_turn()`, and asserts the two
outputs are byte-identical. The shared `first_divergence` /
`assert_byte_identical` helpers (from #279) move from `prompts::tests`
into `test_support` so working_set tests can reuse them.
Closes#280.
* fix(cache): place handoff and working_set after static prompt blocks
`system_prompt_for_mode_with_context_and_skills` previously interleaved
volatile content into the static prefix:
1. mode prompt static
2. project context static
3. working_set_summary ← volatile
4. skills_block static
5. handoff_block ← volatile
6. ## Context Management static
7. COMPACT_TEMPLATE static
Anything past byte (3) cache-missed every time the working-set drifted
or `/compact` rewrote `.deepseek/handoff.md` — including the static
`## Context Management` and `## Compaction Handoff` blocks behind them.
New order keeps every static block in the cached prefix and pushes the
two volatile blocks to the end:
1. mode prompt
2. project context (or fallback automap)
3. skills block
4. ## Context Management (Agent / Yolo only)
5. COMPACT_TEMPLATE
── volatile boundary ──
6. handoff block
7. working-set summary
Adds a doc comment on the function describing the volatile-content-last
invariant so future contributors don't reintroduce churn into the
prefix. Adds two regression tests:
- `system_prompt_with_handoff_file_is_byte_stable_when_file_is_unchanged`
pins the handoff path with a fixture file.
- `handoff_and_working_set_appear_after_static_blocks` asserts the
ordering invariant directly so a future reorder fails loudly.
Reference: Claude Code's own prompt builder marks this same boundary
with a `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` constant; we don't introduce
the abstraction yet but match the principle.
* feat(i18n): localize slash command help (Phase 1a, #285) (#294)
Adds 44 new MessageIds, one per slash command, and translations to all
four shipped locales (en/ja/zh-Hans/pt-BR). Refactors CommandInfo so the
English description now lives in localization.rs (single source of
truth) instead of being duplicated on the struct, and threads the
active Locale through the three render surfaces:
- crates/tui/src/tui/views/help.rs (the ?/F1/Ctrl+/ help overlay)
- crates/tui/src/tui/command_palette.rs (Ctrl+K palette)
- crates/tui/src/commands/core.rs (the /help text command)
Usage strings (e.g. /cache [count]) stay English by design — they're
placeholder syntax, not natural language.
The existing locale-coverage test
(`shipped_first_pack_has_no_missing_core_messages`) already iterates
ALL_MESSAGE_IDS across Locale::shipped(), so the 44 new IDs are
automatically required to be present in all four locale arms or CI
fails.
This is the first of several incremental Phase 1 PRs. Phase 1b covers
the debug commands (/tokens /cost /cache), 1c the footer hints, and
1d doctor output. Phases 2–3 cover onboarding and error surfaces.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): localize /tokens /cost /cache debug output (Phase 1b, #285) (#295)
Adds 13 new MessageIds covering the report templates and the
sub-strings shared across them, with translations for all four
shipped locales (en/ja/zh-Hans/pt-BR):
- CmdTokensReport, CmdTokensContextWithWindow, CmdTokensContextUnknownWindow
- CmdTokensCacheBoth, CmdTokensCacheHitOnly, CmdTokensCacheMissOnly
- CmdTokensNotReported
- CmdCostReport
- CmdCacheNoData, CmdCacheHeader, CmdCacheTotals, CmdCacheFootnote, CmdCacheAdvice
Each template uses {placeholder} substitution via String::replace
rather than format!, since format! requires a literal — the
locale-resolved &'static str isn't one. The placeholder convention
({active}, {hit}, {miss}, …) means a translator can re-order or
restructure a sentence freely without changing the call site.
Helpers `token_count`, `active_context_summary`, `cache_summary`, and
`format_cache_history` now take `Locale` so each can resolve their
templates from the same source of truth.
The English templates byte-match the previous hardcoded format strings
so the existing 16 debug-command tests pass unchanged.
Column headers in the cache table (`turn in out hit miss …`)
are intentionally NOT localized — the body rows are formatted with
fixed column widths and translating the header words would break
alignment. Numbers, ratios, and the model id stay in English form.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): localize footer state + help section labels (Phase 1c, #285) (#296)
Adds 11 new MessageIds covering visible footer chrome and the help-overlay
section headings, with translations for all four shipped locales:
Footer:
- FooterWorking — animated `working` / `working.` / … pulse
- FooterAgentSingular / FooterAgentsPlural — the sub-agent count chip
- FooterPressCtrlCAgain — the quit-confirmation toast
Help overlay sections (`?` / `F1` / `Ctrl+/`):
- HelpSectionNavigation, HelpSectionEditing, HelpSectionActions,
HelpSectionModes, HelpSectionSessions, HelpSectionClipboard,
HelpSectionHelp
`KeybindingSection::label` now takes Locale and returns tr(locale, …).
`footer_working_label` and `footer_agents_chip` likewise take Locale; the
two production callsites in tui/ui.rs pass `app.ui_locale`.
The mode chip itself (agent / yolo / plan) intentionally stays English —
those are brand/acronym labels, and translating them would mean explaining
to maintainers what `代理` means in a bug report.
The keybinding catalog DESCRIPTIONS (41 entries) are not translated in this
PR — those are technical prose that would dwarf the rest of i18n work and
can ship in v0.8.5. Section labels are translated so the help overlay
groups read as expected in any locale.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(commands): smoke-test that every / command dispatches to a handler (#299)
Adds two parallel-safe smoke tests in `crates/tui/src/commands/mod.rs`
that iterate the COMMANDS registry and verify every command — and every
declared alias — dispatches to a real handler. A dispatch miss surfaces
as the fall-through `Unknown command:` error message in `execute`,
which used to be invisible until a user typed the command and saw the
"did you mean" suggestion fire on a registered command.
The tests build a workspace-isolated app via `tempfile::TempDir` so
side-effecting handlers (`/init` writing AGENTS.md, `/save` and
`/export` writing files) do not pollute `crates/tui/` when CI runs from
there. `/save` and `/export` get an explicit tempdir-relative path
because their no-arg defaults still resolve relative to `cwd`.
`/restore` is skipped — it shells out to git for the snapshot repo and
its own dedicated tests in `commands/restore.rs` already serialize on
the global env mutex via `scoped_home`. The existing coverage there is
sufficient.
Closes a gap surfaced when verifying that the v0.8.4 i18n refactor
(#294, #295, #296) did not silently break any slash-command dispatch.
All 44 commands and their aliases pass (16 aliases on top of the
44 names; `/restore` is the only skip).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(release): bump version to 0.8.4 (#297)
CHANGELOG entry covers the v0.8.4 work landed since 0.8.3:
- Localization Phase 1 (#285) — slash command help (#294), debug command
output (#295), footer state and help-overlay section labels (#296).
Adds 68 new MessageIds across all four shipped locales (en/ja/zh-Hans/pt-BR).
- Cache-prefix stability (#263) — five companion fixes (#287, #288→#292,
#289, #290, #291) that keep the DeepSeek prefix cache stable across turns.
- Plus the items already in [Unreleased]: agent-mode network exec (#272),
/skill GitHub URL parsing (#269), and the V4 Pro discount expiry extension
(#267).
Bumps:
- Cargo.toml workspace version 0.8.3 → 0.8.4
- npm/deepseek-tui/package.json version + deepseekBinaryVersion 0.8.3 → 0.8.4
- Cargo.lock regenerated from the new workspace version.
Phase 1d (doctor output), Phase 2 (onboarding/init/missing-companion),
and Phase 3 (tool errors / sandbox denials / approvals) deferred to v0.8.5.
The shipped Phase 1 surfaces (slash commands, debug telemetry, footer
chrome) cover the highest-traffic UI paths Chinese users see first.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(release): bump internal path-dep versions + repair doc link (#301)
CI on PR #300 (release feat/v0.8.4 → main) flagged two regressions
introduced by the 0.8.4 version bump:
1. Version drift — path-dependency `version = "0.8.3"` references
inside the workspace crates (10 crates: agent, app-server, cli,
config, core, execpolicy, hooks, mcp, tools, tui) did not move with
the workspace `[workspace.package] version = "0.8.4"`. The CI guard
`scripts/release/check-versions.sh` requires they match.
2. Broken intra-doc-link `[crate::localization::english]` in the
CommandInfo doc comment — `english` is private. Replaced with a
reference to the public `description_for` accessor and the public
`tr()` function.
Verified with:
- scripts/release/check-versions.sh — Version state OK.
- RUSTDOCFLAGS=-Dwarnings cargo doc --workspace --no-deps — green.
- cargo fmt + clippy + test all green.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps workspace, all internal path-deps, and npm wrapper (version +
deepseekBinaryVersion) from 0.8.1 → 0.8.2. Lockfile re-locked offline
to keep the registry index untouched.
Triggers auto-tag.yml on push, which creates v0.8.2 and fires
release.yml to build cross-platform binaries and draft the GitHub
Release. npm publish remains manual per CLAUDE.md release runbook.
Note: npm registry already has 0.8.2 published (with binaryVersion
0.8.1 from an earlier checkpoint). That release keeps working unchanged
because v0.8.1 binaries stay on GitHub. Repo state aligns to 0.8.2 so
the version-drift gate passes; next npm publish (which will need to be
0.8.3 since 0.8.2 is taken) will pick up binaryVersion=0.8.2 and pull
the new binaries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps the workspace/npm wrapper to 0.8.0 and fixes completed background shell jobs retaining live process handles, which could cause Too many open files, checkpoint save failures, shell spawn failures, and lag around send/close/Esc. Also includes Windows REPL bootstrap timeout hardening and Cargo/TUNA mirror install docs.
Includes:
- Post-turn freeze fix (reorder maybe_advance_cycle before TurnComplete)
- Enter/steering fix (QueueFollowUp when model is streaming)
- Esc fanout hardening (idempotent finalize methods)
- cargo fmt pass on new code
- CHANGELOG, README, and version bump across workspace + npm
* wip(v0.7.7): handoff baseline of partial sub-agent stabilization
Captures uncommitted work-in-progress on the v0.7.7 stabilization lane
so subsequent fixes have a stable starting point. Subsequent commits
finish the canonical SubAgentJob/SwarmJob model, fix sidebar/transcript/
footer agreement, copy/paste/cancel contract, checklist rendering, shell
summary preservation, monotonic spend, and version provenance.
Refs #235#236#237#238#239#240#241#242#243#244#245
* release: bump workspace version to 0.7.7 (#245)
Refs #245
* fix(v0.7.7): canonical swarm card binding, monotonic spend, checklist + shell summary
- Add `swarm_card_index: HashMap<swarm_id, history_index>` so overlapping
fanouts each project to their own FanoutCard. Eliminates the screenshot
contradiction where a stale background swarm's progress clobbered a
newer card (#236, #238).
- Suppress fanout-class tools (`agent_swarm`, `spawn_agents_on_csv`,
`rlm`, `agent_spawn`) from `active_tool_status_label` so the footer no
longer reports "tool agent_swarm · 1 active" while sidebar+card show
the actual worker counts (#236, #238).
- Add `App::displayed_session_cost` + `displayed_cost_high_water` so the
visible session+sub-agent total is monotonic across reconciliation
events (cache discounts, provisional → final). New tests: monotonicity
under negative reconciliation; duplicate dedup keeps display steady (#244).
- Preserve high-signal summary lines from the truncated tail of shell
output: `test result:`, `failures:`, `error[E…]`, `Finished`,
`Compiling`, panic markers. Stops the agent re-running cargo gates
just to see pass/fail under truncation (#242).
- Render `checklist_write` / `todo_*` results as a purpose-built
checklist card with completed/total + percent header, per-item status
markers, and a collapsing affordance for long lists. Plumbed through
the existing `GenericToolCell` so no new variant threading is needed (#241).
Refs #236#238#241#242#244
* fix(v0.7.7): Esc clears active tool entries optimistically (#243)
When Esc cancels the foreground turn we now finalize the active cell
immediately rather than waiting for the engine's TurnComplete echo to
drain. This stops the footer "tool ... · X active" chip from briefly
contradicting the cancelled state, and frees the composer for the next
message.
Background `block:false` swarms are intentionally NOT killed here — they
remain durable, tracked through `swarm_jobs` and `swarm_card_index` so
their FanoutCard updates as workers land. Subsequent `swarm_status` /
`swarm_result` / `swarm_cancel` tool calls see the canonical store.
New focused test verifies: after Esc, `active_cell` is None, the
background swarm record is preserved, and `is_loading` is cleared so
the composer can submit immediately.
Refs #243
* fix(v0.7.7): Windows .exe lookup + post-turn snapshot detach (#247, #234)
#247 — npm-distributed Windows package failed at runtime because the
Rust dispatcher's `delegate_to_tui` / `delegate_simple_tui` looked for a
sibling named exactly "deepseek-tui", while the actual file shipped by
`scripts/install.js` is `deepseek-tui.exe`. Replace both lookups with
`locate_sibling_tui_binary`, which:
- Honours `DEEPSEEK_TUI_BIN` for explicit overrides
- Tries `deepseek-tui{EXE_SUFFIX}` first (`.exe` on Windows, "" elsewhere)
- Falls back to suffix-less `deepseek-tui` on Windows so users who
applied the issue's manual workaround still launch successfully
- Emits a platform-correct error path in the bail message
Tests: `sibling_tui_candidate_picks_platform_correct_name`,
`sibling_tui_candidate_windows_falls_back_to_suffixless` (windows-only),
`locate_sibling_tui_binary_honours_env_override`.
#234 — Detach the post-turn workspace snapshot so `git add -A && git
commit` no longer pins the engine loop after `Event::TurnComplete`.
The snapshot still runs on `tokio::task::spawn_blocking`, but the
engine no longer awaits its `JoinHandle`, so the UI accepts input
(text, copy, paste, selection) without waiting for the bookkeeping to
finish. Cycle advance and pre-turn snapshot remain awaited — they are
correctness-sensitive and the cycle path already emits a status chip
("↻ context refreshing…") so the user has visible feedback.
Refs #234#247
* chore(v0.7.7): bump npm package version 0.7.6 → 0.7.7
Required by `scripts/release/check-versions.sh` ("Version drift" CI
gate); the workspace was bumped to 0.7.7 but `npm/deepseek-tui/package.json`
still reported 0.7.6, blocking PR #246 from going green.
Refs #245
- Bump workspace version to 0.7.6 (Cargo.toml + all crate internal dep pins)
- Bump npm wrapper version and deepseekBinaryVersion to 0.7.6
- Add v0.7.6 changelog entry: localization, paste burst, history search,
pending input preview, grouped /config editor, searchable help overlay,
Alt+↑ edit-last-queued, composer attachment management
- Update README with v0.7.6 features (localization, paste, history search)
- Archive v0.7.5 implementation plan to docs/archive/
- Update Cargo.lock
Issues #202, #203, #204, #205:
- Cycle/seam triggers use active request input size + response
headroom reserve, not lifetime cumulative API usage.
- V4 hard-cycle headroom calibrated around fixed TURN_MAX_OUTPUT_TOKENS
plus CONTEXT_HEADROOM_TOKENS safety buffer.
- /tokens, /cost, footer/header labels, and docs now separate
active context, turn telemetry, cumulative usage, cache hit/miss,
context percent, and cost.
- Foreground exec_shell timeout output tells the model the process
was killed and suggests task_shell_start or background exec_shell
plus poll/wait.
- Added regression tests for active-token basis, V4 headroom,
seam trigger basis, footer label behavior, and shell timeout
recovery metadata.
- Preserved #200/#201 policy: V4 default is append-only,
prefix-cache preserving; replacement compaction, Flash seams,
and capacity intervention remain opt-in.