Post-merge review feedback on #583 surfaced four small accuracy gaps:
1. The narrative docs in `docs/CONFIGURATION.md` and the inline comment
in `config.example.toml` said the notification fires "when a turn
takes longer than a threshold" — but the call site in
`tui/ui.rs:928` is gated on `TurnOutcomeStatus::Completed`. Failed
and cancelled turns are silent on purpose. Spell that out so users
don't expect alerts on long failures.
2. The `notify_done` rustdoc still summarised `Auto` as "Osc9 for known
terminals, Bel otherwise" — internally inconsistent with the new
Windows-aware fallback documented one screen earlier on the
`Method::Auto` enum and on `resolve_method`. Update the public
rustdoc to point at the canonical resolution table on
`resolve_method` and call out the `Off`-on-Windows branch.
3. The `## Key Reference` list in `docs/CONFIGURATION.md` had no entries
for `[notifications].method`, `[notifications].threshold_secs`, or
`[notifications].include_summary`. Other features with a dedicated
subsection (e.g. `[memory].enabled`) are listed there too, so readers
scanning the canonical key list could not discover the notification
knobs. Added the three keys with cross-references to the
Notifications subsection.
4. The Windows-only test only covered the unknown-`TERM_PROGRAM` →
`Off` fallback. The positive path (known OSC-9 terminal still
resolves to `Osc9`) was only tested via `iTerm.app`, which is a
macOS-only program — Windows CI would still pass if the `WezTerm`
arm of the match disappeared. Added
`auto_detect_picks_osc9_for_wezterm_on_windows` so the
WezTerm-on-Windows compatibility guarantee is exercised on the
Windows runner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Windows, the audio stack maps BEL (`\x07`) to the
`SystemAsterisk` / `MB_OK` chime — the same sound applications use
for error popups. So with the previous `Method::Auto` fallback to
`Bel`, every successful turn-completion notification ended up
sounding identical to a software error.
Reported by a community user who described it as "the popup-error
sound from a CAD program I used to use" (#583).
resolve_method() now returns `Off` instead of `Bel` on Windows for
unknown TERM_PROGRAM values. Known OSC-9-capable terminals
(`iTerm.app`, `Ghostty`, `WezTerm`) still resolve to `Osc9` on
every platform, so users running WezTerm on Windows keep getting
real notifications. macOS and Linux behaviour is unchanged.
Windows users who actively want an audible cue can opt back in by
setting `[notifications].method = "bel"` in `~/.deepseek/config.toml`.
Also:
- Documents `[notifications]` in `docs/CONFIGURATION.md` with an
explicit Windows note (the schema was previously undocumented).
- Updates the inline comment in `config.example.toml` so users
reading the seed config see the platform-specific behaviour.
- Splits the existing `auto_detect_picks_bel_for_unknown` test
into a Unix variant (`#[cfg(not(target_os = "windows"))]`) and
adds a new Windows-gated test that asserts the `Off` fallback,
so CI's Windows runner exercises the platform-specific path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- add /memory help and clearer invalid-subcommand guidance
- register /memory in shared slash-command help
- align memory docs with current behavior and config
- add focused tests for help and discovery
Resolves the post-#514/#517/#518 conflicts:
- CHANGELOG.md: kept both polish-stack and Linux ARM64 entries under
[Unreleased]; reordered so the ARM64/install-message Changed/Docs
sections precede the Releases footer.
- config.example.toml: kept both the `instructions = [...]` example
and the `[memory]` opt-in stanza in sequence.
- crates/tui/src/config.rs: kept both `instructions_paths()` (#454)
and `memory_enabled()` (#489) on the Config impl.
- crates/tui/src/prompts.rs: extended
`system_prompt_for_mode_with_context_and_skills` to take BOTH
`instructions: Option<&[PathBuf]>` and `user_memory_block:
Option<&str>`. Section 2.5a renders instructions; 2.5b renders the
memory block — both above the skills block so KV prefix caching
still wins.
- crates/tui/src/core/engine.rs: thread both args through the two
call sites.
- crates/tui/src/prompts.rs: update the `system_prompt_for_mode_with_context`
forwarder and the test caller to pass `None` for the new arg.
- .gitignore: ignore `.claude/*.local.md` and `*.local.json` so
local ralph / Claude-Code notes can't leak into commits.
Folds in two valid suggestions from the gemini-code-assist review on #519:
- `client.rs`: collapse the duplicated `LlmError → label` match and the
`human_retry_reason` body into a single
`retry_reason_label_and_human(err) -> (&'static str, String)` helper.
- `widgets/footer.rs::retry_banner_spans`: merge the two separate
`match &props.retry` blocks into one that returns both `(label, color)`.
Behavior is unchanged; refactor is a pure DRY win.
Corporate users behind TLS-inspecting proxies (Zscaler, Netskope,
Palo Alto, in-house mitmproxy fleets) need to add the proxy's
intermediate CA to the trusted-roots set so the deepseek client
doesn't fail with `unable to get local issuer certificate`.
The reqwest builder already trusts the platform's system store
via native-tls. This adds opt-in support for the conventional
`SSL_CERT_FILE` env var so users can point at their own bundle:
* New `add_extra_root_certs(builder, path)` helper reads the
file, tries `Certificate::from_pem_bundle` (covers single-cert
files too), falls back to `from_der` for binary cert files.
* Wired into `build_http_client` when `SSL_CERT_FILE` is set
and non-empty. Failures log a warning via the existing
`logging::warn` channel and return the builder unchanged —
the existing system trust still applies, so a malformed env
var degrades gracefully instead of bricking the launch.
* Each successful load logs `info` with the cert count so
operators can confirm their bundle was picked up.
Documented in `docs/CONFIGURATION.md`'s environment-variables
list alongside the existing TLS-related notes.
No new dependency — reqwest's `native-tls` feature already
exposes `Certificate::from_pem_bundle` / `from_der`.
Catches up `docs/CONFIGURATION.md` with the v0.8.8 polish stack so
operators have one source of truth for the new surfaces:
* `NO_ANIMATIONS` env override (#450) joins the existing
environment-variable list, with a cross-reference to
`docs/ACCESSIBILITY.md`.
* New `### Instruction sources` section documents the
`instructions = [...]` config field (#454): expansion rules,
100 KiB per-file cap with `[…elided]` marker, missing-file
warning behavior, and the project-wholesale-replaces-user
override semantics.
* New `### /hooks listing` section documents the read-only
slash command (#460 MVP) so users know how to introspect
configured lifecycle hooks without `cat`-ing config.toml.
* New `### Composer stash` section documents Ctrl+S +
`/stash list|pop|clear` (#440) including the 200-entry cap
and multiline preservation.
Pure documentation; no code changes. Existing prompt-stability
and config-loading tests are unaffected.
The memory MVP shipped in PR #518 added three surfaces (\`# \` quick-add,
\`/memory\` slash command, \`remember\` model tool) plus the opt-in
toggle, but the only user-facing reference today is the one-line
mention of \`memory_path\` in CONFIGURATION.md and the \`#489\` cross-
reference in SUBAGENTS.md. This commit adds a dedicated user-facing
doc covering the whole feature.
Coverage:
- Why opt-in by default
- How to enable (env var + config.toml)
- What the system prompt block looks like
- Three ways to add to memory:
1. \`# foo\` composer prefix (#492)
2. \`/memory\` slash command (#491) — show / path / clear / edit
3. \`remember\` tool (#489) — model-callable, auto-approved
- File format — timestamped Markdown bullets, hand-editable
- What stays out of memory — secrets / transient state / long
instructions / conversation snippets
- Privacy and scope — per-user, never uploaded, provider-agnostic
- Configuration reference — settings table with defaults and overrides
Cross-link added in CONFIGURATION.md so the existing \`memory_path\`
mention now points at the full feature doc.
No Rust code changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The project-config overlay (`<workspace>/.deepseek/config.toml` merged
on top of the user's global `~/.deepseek/config.toml`) was already
wired but only carried four string fields: model, api_key, base_url,
reasoning_effort. The use cases users actually file under #485 — "this
repo wants a different sandbox / approval policy / MCP server set / hard
sub-agent cap" — weren't covered.
### What ships
Adds the following keys to the project overlay, all merged with
identical "non-empty wins" semantics for strings:
- `provider` — pick a different backend per repo (e.g. `nvidia-nim` for
an enterprise repo, `deepseek-cn` for a CN-team repo).
- `approval_policy` — `never` / `on-request` / `untrusted` for repos
with strict policies.
- `sandbox_mode` — `read-only` / `workspace-write` / `danger-full-access`.
- `mcp_config_path` — per-repo MCP server set without touching the
user's global file.
- `notes_path` — keep notes in-repo for projects where the notes tool
is part of the dev workflow.
Plus two non-string fields:
- `max_subagents` (positive integer; clamped to `1..=MAX_SUBAGENTS=20`).
- `allow_shell` (bool).
### What stays user-global
`skills_dir`, `hooks`, `[capacity]`, `[retry]`, `[memory]`, etc. — those
are user-shaped settings, not repo-shaped. If a future use case
demands per-project values for any of them, a follow-up PR can extend
the overlay rather than letting the boundary blur.
### Tests
- 8 new tests in `project_config_tests` covering: provider+model,
approval+sandbox, max_subagents+allow_shell, max_subagents
clamping, negative-max_subagents rejection, missing config file
pass-through, malformed TOML pass-through, and empty-string
no-op.
### Docs
- New "Per-project overlay (#485)" section in `docs/CONFIGURATION.md`
with a table of supported keys and the rationale for which fields
stay user-global.
### Verification
cargo fmt --all -- --check ✓
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings ✓
cargo test --workspace --all-features --locked ✓ 1828 + supporting (was 1820)
Closes#485
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modern terminals (iTerm2, Terminal.app 13+, Ghostty, Kitty, WezTerm,
Alacritty, recent gnome-terminal/konsole) make a URL clickable when it's
wrapped in:
\x1b]8;;TARGET\x1b\\LABEL\x1b]8;;\x1b\\
Terminals that don't understand the sequence simply render the visible
LABEL and ignore the escape, so emitting OSC 8 is a strict UX upgrade
for supporting terminals and a no-op for the rest.
### What's wired
- New `crates/tui/src/tui/osc8.rs` module with `wrap_link(target, label)`,
`strip_into(s, &mut out)`, and a process-wide `ENABLED` AtomicBool that
defaults to `true`.
- `markdown_render::render_line_with_links` now wraps recognized URLs
(`http(s)://…`) in OSC 8 when the runtime flag is on. Display width is
computed from the bare URL — the escapes are zero-width on supporting
terminals.
- `ui_text::line_to_string` and `line_to_plain` strip OSC 8 wrappers when
the span content contains an escape, so selection / clipboard output
carries clean URLs and not the raw escape codes.
- `[tui] osc8_links: bool` config (default `true`) added to `TuiConfig`,
documented in `docs/CONFIGURATION.md`, and surfaced in
`config.example.toml`. `run_tui` applies it at startup.
### Tests
- 7 unit tests in `osc8::tests` covering wrap, strip-with-ESC-terminator,
strip-with-BEL-terminator, plain passthrough, mixed escapes, default
state, and round-trip set/unset.
- 2 markdown_render tests proving URLs in paragraph blocks emit the OSC 8
wrapper when enabled and emit plain text when disabled.
- 2 ui_text tests proving `line_to_plain` strips OSC 8 wrappers from spans
and passes plain spans through unchanged.
Tests that touch the global ENABLED flag serialize through a static
Mutex inside the test module so cargo's parallel runner can't observe a
torn read.
### Verification
cargo fmt --all -- --check ✓
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings ✓
cargo test --workspace --all-features --locked ✓ (1820 + supporting; was 1809)
Closes#498
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles the v0.8.8 stabilization fixes that were already implemented in the
working tree, plus the workflow/doc reconciliation called out in #507.
### Sub-agent runtime fixes
- **#509** Default sub-agent cap raised to 10 (configurable via
`[subagents].max_concurrent` in `config.toml`, hard ceiling 20). The
running-count calculation now ignores non-running, no-handle, and finished
handles so completed agents stop counting against the cap.
- **#510** `SharedSubAgentManager` is now `Arc<RwLock<...>>`; the read paths
that previously held a `Mutex` for inspection now take a read lock,
eliminating the multi-agent fan-out UI freeze.
- **#511** `compact_tool_result_for_context` summarizes `agent_result` /
`agent_wait` payloads before they are folded into the parent context.
- **#512** RLM tool cards map to `ToolFamily::Rlm` and render `rlm`, not
`swarm`. Stale "swarm" wording cleaned in docs/comments/tests.
- **#513** (foreground stopgap only) Foreground RLM work is visible in the
Agents sidebar projection. Full async RLM lifecycle remains v0.8.9 — the
issue stays open with a refined scope.
### TUI / UX fixes
- **#487** Offline composer queue is now session-scoped; legacy unscoped
queues fail closed.
- **#488** Composer Option+Backspace deletes by word; cross-platform key
routing helpers added.
- **#443/#444** Keyboard enhancement flags pop on normal AND panic exit; the
raw-mode startup probe is now bounded by a configurable timeout.
- **#449** Production footer reads statusline colors from `app.ui_theme`
rather than the bespoke palette.
- **#506** `display_path_with_home` no longer mutates `HOME` in tests; the
flake on shared-env CI is gone.
### Self-update / packaging
- **#503** `update.rs` arch mapping uses release-asset naming (`arm64`/`x64`)
instead of the raw Rust constants. The platform-asset selector also rejects
`.sha256` siblings as primary binaries. Tests now live alongside the source
in `mod tests` (the `#[path]`-based integration test was removed because it
duplicated test runs and forced a `pub(crate)` helper that no real caller
used).
- **`Max 5 in flight` wording updated** in `agent_spawn` description,
`prompts/base.md`, and `docs/TOOL_SURFACE.md` so the model sees the real
default cap (10) and the configuration knob name.
### CI / release docs (#507)
- Pruned three duplicated/dead workflows: `crates-publish.yml`, `parity.yml`,
`publish-npm.yml`. Their gates already run in `ci.yml` for every push/PR.
- `release.yml` build job now allows `parity` to be skipped (it only runs on
tag push), unblocking `workflow_dispatch` reruns. The job still fails
closed on a real parity failure.
- `RELEASE_RUNBOOK.md` reconciled: crate publishing is documented as the
manual `scripts/release/publish-crates.sh` flow (no automated workflow);
references to the deleted workflows removed.
- `CLAUDE.md` notes the `RELEASE_TAG_PAT` requirement for the auto-tag →
release.yml chain (without it, the tag is created but `release.yml` does
not fire) and documents the `workflow_dispatch` parity-skip behavior.
### Docs
- `docs/COMPETITIVE_ANALYSIS.md` added — capability matrix vs OpenCode and
Codex CLI, gap analysis, and recommended implementation order.
### Verification (this branch)
- `cargo fmt --all -- --check` ✓
- `cargo check --workspace --all-targets --locked` ✓
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` ✓
- `cargo test --workspace --all-features --locked` ✓ (1809 + supporting)
- Parity gates ✓ (snapshot, parity_protocol, parity_state)
- `cargo build --release --locked -p deepseek-tui-cli -p deepseek-tui` ✓
- Lockfile drift guard ✓
- `deepseek doctor --json` clean
- `deepseek eval` (offline harness) success=true, 0 tool errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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
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.
Adds a compact `setup --status` view, a `setup --clean` for regenerable
session checkpoints, and `--tools`/`--plugins` scaffolding for
~/.deepseek/{tools,plugins} so the extension model has a documented home
that doctor can count. `doctor --json` lands as a CI-safe alternative to
the human-readable doctor (skips the live API probe).
Also locks down the engine's hostility to fake tool-call wrappers:
filter_tool_call_delta and the marker constants are now testable, the
streaming loop emits one compact status notice per turn when it strips
a wrapper, and a new protocol_recovery integration test asserts that
the legacy text parser never turns <function_calls> into a real tool
call. Adds 23 unit tests + 14 integration tests covering both slices.
Adds first-class DeepSeek V4 Pro and Flash support, updates the default model to deepseek-v4-pro, aligns legacy aliases with the current V4 1M context behavior, and fixes thinking-mode request handling.
Key fixes:
- Send DeepSeek's raw Chat Completions `thinking` parameter at the top level instead of SDK-only `extra_body`.
- Preserve assistant `reasoning_content` for all prior thinking-mode tool-call turns so subsequent requests satisfy DeepSeek V4's replay requirement.
- Fix npm wrapper concurrent first-run downloads by using per-process temporary download paths.
- Add `.mailmap` so historical bot-attributed commits aggregate under Hunter Bown where mailmap is honored.
Verified with the full local Rust gate, live DeepSeek V4 smoke, npm wrapper temp-install smoke, and green PR CI across Linux, macOS, and Windows.
Keep legacy /normal and settings fallback behavior mapped to Agent, align docs around the three visible modes, and include the current TUI and onboarding refinements in this worktree.
- Move src/* into crates/tui/src/ to create a proper workspace structure
- Add .claude/ and .trimtab/ directories for Trimtab closed-loop workflow
- Add DEPENDENCY_GRAPH.md and update documentation
- Update Cargo.toml files to reflect new crate dependencies
- Update CI workflows and npm package scripts
- All tests pass, release build works
Major Features:
- Runtime API for external integrations and turn management
- Task manager with persistence and recovery
- Shell output streaming and improved tool execution
- Error taxonomy and audit logging
- Command palette and UI enhancements
Documentation:
- Runtime API documentation
- Operations runbook
- Architecture updates
Fixes:
- Auto-compaction threshold and triggering logic
- Doctor command API key validation
- Clippy and formatting compliance