Summary:
- Keep default auto alternate-screen mode inside the TUI so transcript scrolling stays app-owned unless users explicitly opt out.
- Queue terminal resume events when the engine channel is full, avoiding stranded paused terminal state after interactive tool cancellation or bursts.
- Scope crash-checkpoint recovery to the resolved launch workspace instead of the shell cwd.
- Add runtime deepseek_version to the prompt environment block so agents can distinguish installed runtime identity from a stale checkout.
Test plan:
- cargo test -p deepseek-tui --locked on a simulated merge with current main
- cargo fmt --all -- --check
- git diff --check
- Existing PR CI was green for lint, version drift, Linux/macOS/Windows tests, npm wrapper smoke, and GitGuardian.
Summary:
- Use Reverse for job timestamp sorting to avoid negation overflow edge cases.
- Make secret redaction UTF-8 safe while preserving the previous short-secret threshold.
- Update remaining setup and doctor guidance to use the supported deepseek dispatcher name.
Test plan:
- cargo test -p deepseek-config list_values_redacts --locked
- cargo test -p deepseek-core --locked
- cargo test -p deepseek-tui doctor_endpoint_tests --locked
- cargo fmt --all -- --check
- git diff --check
Supersedes #957.
Summary:
- Stop treating -v as an npm wrapper version fallback.
- Keep wrapper fallback for --version and -V.
- Add a Node regression test for wrapper version flag detection.
Test plan:
- npm test from npm/deepseek-tui
- git diff --check origin/main...HEAD
Supersedes #959.
Summary:
- Add a mouse-only jump-to-latest affordance when the transcript is scrolled away from the live tail.
- Hide the control at the tail or when mouse capture is disabled.
- Add tests for visibility and click behavior.
Maintainer verification on current origin/main:
- cargo test -p deepseek-tui jump_to_latest --locked
- cargo fmt --all -- --check
- git diff --check origin/main...HEAD
Closes#971
Summary:
- Truncate oversized memory prompt content at the previous valid UTF-8 character boundary.
- Preserve the existing memory cap and truncation marker behavior.
- Add regression coverage for accented and emoji content crossing the byte cutoff.
Maintainer verification on current origin/main:
- cargo test -p deepseek-tui memory --locked
- cargo fmt --all -- --check
- git diff --check origin/main...HEAD
Summary:
- Treat Ctrl+H as Backspace in the help overlay filter.
- Document the terminal erase behavior inline.
- Add regression coverage for the help filter widening after Ctrl+H.
Maintainer verification on current origin/main:
- cargo test -p deepseek-tui ctrl_h_widens_match_set --locked
- cargo fmt --all -- --check
- git diff --check origin/main...HEAD
Fixes#958
Summary:
- Skip empty auto-created sessions when continuing a workspace.
- Recover a saved session ending in an unanswered user prompt as an editable draft instead of auto-resubmitting it.
- Preserved and repaired the local Qthresh poisoned session records by moving them to ~/.deepseek/sessions/recovery-backups/qthresh-2026-05-07-auto-poison.
Test plan:
- cargo test -p deepseek-tui latest_session_for_workspace_skips_empty_auto_created_session --locked
- cargo test -p deepseek-tui apply_loaded_session_restores_dangling_user_tail_as_retry_draft --locked
- cargo test -p deepseek-tui --locked
- cargo fmt --all -- --check
- git diff --check
- git diff --check origin/main...HEAD
- cargo clippy -p deepseek-tui --all-targets --all-features --locked -- -D warnings
Closes#988
Closes #944\n\n## Summary\n- mark Docker/GHCR publishing as experimental while the package is not publicly readable\n- align installer and release docs with the live npm/Scoop state\n- keep package-channel verification explicit for release triage\n\n## Test plan\n- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/release.yml"); puts "release.yml ok"'\n- cargo test -p deepseek-tui-cli update::tests::test_asset_matching_accepts_binary_assets_and_rejects_checksums --locked\n- cargo fmt --all -- --check\n- git diff --check origin/main...HEAD\n- CI: Version drift, Lint, Test (ubuntu-latest), Test (macos-latest), Test (windows-latest), npm wrapper smoke
Closes#899.
Detects light terminal profiles from COLORFGBG, keeps the existing dark theme as the fallback, and maps DeepSeek dark-palette cells to readable light surfaces in the existing ColorCompatBackend.
Local verification:
- cargo fmt --all -- --check
- cargo test -p deepseek-tui palette --all-features
- cargo test -p deepseek-tui color_compat --all-features
- cargo build
CI: all required checks passed on #931.
Integrates source PR #525 by @shentoumengxin.
Adds `/anchor` for critical user facts that should survive compaction via `.deepseek/anchors.md`. Keeps `/pin` unregistered so the resident-context `/pin` lane remains available, and renders anchors as structured bullets in compaction summaries instead of raw separator text.
Local verification:
- cargo fmt --all -- --check
- cargo test -p deepseek-tui anchor --all-features
- cargo build
CI: all required checks passed on #930.
Co-authored-by: ZZHAsus <3075047037@qq.com>
* feat(tui): add `notification_condition` override + assistant text in body (#820)
`[notifications]` already controls method (auto/osc9/bel/off), the
`threshold_secs` gate, and the `include_summary` body. Some users
want a simpler high-level switch — "always notify on every turn" or
"never notify" — without having to know the lower-level fields.
This adds a single optional `[tui].notification_condition` field:
- `"always"` — notify on every successful turn (no duration
threshold). The configured `[notifications].method` and
`include_summary` flag are still respected.
- `"never"` — suppress all turn-completion notifications.
- omitted — fall back to the existing `[notifications]` defaults
(the v0.8.15 behavior is unchanged).
The OSC 9 / BEL body now also carries the assistant's reply text,
sanitized and truncated to 360 characters, with a fallback to the
latest assistant message in `api_messages` when the streaming buffer
was empty (e.g. a tool-only turn). When `include_summary = true`,
the elapsed/cost line is appended on a new line.
Drive-by: drop the unused `Method::from_str` helper (the new code
match-arms over the typed `NotificationMethod` enum, so the parser
helper had no remaining callers).
Differences from upstream #820:
- Keeps the `[notifications]` section in `config.example.toml`
(with `notification_condition` documented as an opt-in override)
rather than deleting the existing block. This avoids breaking
configs that already set `[notifications].method` etc.
- Drops the unrelated `#[allow(dead_code)]` on
`schema_migration::registry`.
- Threads `Option<CostEstimate>` through the helper (the cost
surface changed from `Option<f64>` since #820 was authored).
Tests:
- `notification_settings_*` (3) — `always` keeps the configured
method, `never` returns `None`, missing override falls back.
- `completed_turn_notification_*` (4) — streaming text wins, falls
back to latest assistant message, default placeholder, and 360-char
truncation with `...`.
Integrates #820.
Co-authored-by: zero <1603852@qq.com>
Co-authored-by: zerx-lab <161401688+zerx-lab@users.noreply.github.com>
* style: fmt — collapse short test message helper calls
---------
Co-authored-by: zero <1603852@qq.com>
Co-authored-by: zerx-lab <161401688+zerx-lab@users.noreply.github.com>
Replace the model's first-message language detection with a
deterministic `## Environment` block at the top of the workspace-
static portion of the system prompt. The block lists:
- lang: resolved BCP-47 tag (`en`, `zh-Hans`, `ja`, `pt-BR`)
- platform: `std::env::consts::OS`
- shell: `$SHELL` (or `unknown`)
- pwd: workspace path
`base.md`'s `## Language` directive now points the model at the
`lang` field instead of asking it to guess from the user's first
turn. When `lang` is missing or ambiguous the existing detect-from-
writing fallback still applies.
The block is injected at "step 2.25" in the prompt builder — after
mode prompt + project context, before the configured `instructions`
files and the skills section — so it lives in the same workspace-
static cache layer as the surrounding blocks. All four inputs
(locale tag, platform, shell, pwd) are resolved once per session
and stay byte-stable across turns, preserving prefix-cache hits.
`render_environment_block` is intentionally I/O-free: callers pass
the resolved locale tag in `PromptSessionContext.locale_tag` /
`EngineConfig.locale_tag`. `resolve_locale(...)` is invoked once
when constructing the engine config in `tui::ui`, `runtime_threads`,
and `run_exec_agent`.
Tests:
- `render_environment_block_lists_supplied_locale_and_workspace`
- `environment_block_is_inserted_into_system_prompt`
Verified:
- `cargo test -p deepseek-tui --bin deepseek-tui` (2224 passed)
- `cargo test -p deepseek-tui-core --test snapshot --locked`
- `cargo clippy -p deepseek-tui --all-targets -- -D warnings`
- `cargo fmt --all -- --check`
Integrates #813.
Co-authored-by: lloydzhou <lloydzhou@qq.com>
After `deepseek fork` saves the forked session, surface the source
session title and the truncated source/new session ids so the user
sees what was created before the TUI takes over the screen.
Implementation differs slightly from the original PR:
- Reuse the existing `session_manager::truncate_id` helper instead of
defining a second copy in `main.rs`.
- Guard against an empty saved-title string so the line stays
readable for unnamed sessions.
This is a UX-only addition that does not address the broader
`/fork` request from #576; that one is asking for an in-TUI fork
picker, which is out of scope for v0.8.15.
Integrates #600.
Co-authored-by: macworkers <Mann_Juarezxgs@cash4u.com>
Null bytes embedded in command strings can be used to slip past
parsers that treat them as terminators while shells still see the
trailing payload. The existing analyzer already blocks `\n` / `\r`
multi-line input but lets `\0` through; add a matching dangerous
classification beside it.
This PR intentionally takes only the null-byte slice from #706. The
broader `command.contains("eval")` / `command.contains("exec ")`
guard from the same PR is *not* applied because it false-positives on
routine commands such as `cargo run -- eval` (the offline eval
harness) or any binary whose name contains `eval` (`evaluator.py`,
`primeval`). A regression test pins that behavior.
Tests:
- `test_null_byte_is_blocked` — `ls\0 -la` and `echo hello\0world`
classified as Dangerous.
- `test_eval_substring_is_not_misclassified` — `cargo run --bin
deepseek -- eval` and `python evaluator.py` are *not* Dangerous.
Integrates #706.
Co-authored-by: 浩淼的mac <haomiaodemac@haomiaodemacdeMacBook-Air.local>
Surface the existing `deepseek update` self-update command in the
English and Chinese README command listings so users can discover it
without reading source. The `deepseek update` subcommand has shipped
since the dispatcher gained `crates/cli/src/update.rs`.
This integration takes only the README slice from #838. The startup
GitHub-Releases polling check from the same PR is intentionally
deferred until it can be made opt-in / config-backed without adding
unconditional network calls or perceptible startup latency.
Integrates #838.
Co-authored-by: leo119 <leo.hou0924@gmail.com>
Integrates #856 as a focused runtime API security slice.
Default local behavior remains unchanged. `/v1/*` routes require a token only when `--auth-token` or `DEEPSEEK_RUNTIME_TOKEN` is set, and `/health` remains public for readiness checks.
Co-authored-by: Zhuoran Deng <dengzhuoran9@gmail.com>
Integrates #879 safe lockfile dependency updates.
Regenerated the dependency bumps on current main so workspace crates stay on v0.8.15. The Dockerfile edits from #879 were intentionally left out because they pin stale Debian package versions and add an invalid trailing `MD []` instruction.
Co-authored-by: RinZ27 <222222878+RinZ27@users.noreply.github.com>
Integrates the useful custom HTTP header support from #881 onto current main.
- support root, provider-specific, and DEEPSEEK_HTTP_HEADERS overrides
- apply validated extra headers to model API requests while preserving protected Authorization and Content-Type defaults
- document the config shape in README, config.example.toml, and docs/CONFIGURATION.md
Co-authored-by: Desheng <8596814+dst1213@users.noreply.github.com>
@imakid reported on Windows PowerShell that clicking the OS restore
button (maximize → windowed) during a long task turns the entire
terminal black, unrecoverable until Ctrl+C. The "refreshing context"
chip in the footer indicates the freeze coincides with a compaction
summary call (engine state `CoherenceState::RefreshingContext`).
Hypothesis (no Windows dev box, awaiting @imakid confirmation):
* Symptom 1 — "transcript stops refreshing" — is the expected
no-stream window during the side-channel compaction summary call.
Tokens don't stream until the summary returns and the next assistant
turn resumes. That's not a bug, but the UI doesn't currently surface
the distinction well.
* Symptom 2 — "black screen on restore-from-maximized" — most likely
comes from a Windows ConHost transient: `crossterm::terminal::size()`
briefly returns stale (maximized) dimensions during the
maximize→windowed transition, while the `Event::Resize(w, h)`
payload itself already carries the correct post-restore size. The
current handler does `terminal.clear() + terminal.draw()` and
relies on ratatui's internal autoresize, which calls
`crossterm::terminal::size()` — meaning we paint a frame sized to
the stale dimensions into the post-restore viewport, leaving most
of the visible area unpainted (the user-reported black screen).
The change forwards the event-reported `(w, h)` to ratatui via
`Terminal::resize(Rect)` before the clear+draw, so the viewport
commit always uses the OS-truthful new size regardless of whether
`crossterm::terminal::size()` has caught up. This is a defensive
change everywhere — the event payload is authoritative on every
platform — and it specifically addresses the Windows ConHost stale-
size race.
Also widens the resize tracing event to include `coherence_state`
and `use_alt_screen` so the next user bug report includes the
context we'd ask for in triage.
Tests
=====
`chat_widget_renders_cleanly_after_resize_during_refreshing_context`
pins the renderer-side invariant: a resize cycle that arrives while
`coherence_state == RefreshingContext` must produce non-empty frames
at every cycled width, and must not mutate the engine's
coherence_state. The actual fix lives in the event-handler size
forwarding; the test guards the renderer's no-empty-buffer contract
so a future regression that gates layout on coherence state would be
caught immediately.
What this PR does NOT change
============================
* No platform-specific code path. The fix is universal — passing the
event-reported size to ratatui is correct everywhere; Windows just
happens to be where the bug manifests today.
* No change to the freeze symptom directly. If symptom 1 is the
expected compaction-summary no-stream window, the right
follow-up is a UX cue ("compacting context, please hold") rather
than a bugfix.
@imakid — please test by installing this branch:
cargo install --git https://github.com/Hmbown/DeepSeek-TUI.git \\
--branch fix/582-powershell-resize
Then run a long task, click the OS restore button mid-task, and
confirm whether the black-screen symptom is gone. If it still
reproduces, please run with `RUST_LOG=deepseek_tui=debug` and post
the resize lines from the log so we can see the dimensions
crossterm/ratatui actually saw.
Verification
============
* `cargo fmt --all -- --check` clean.
* `cargo clippy -p deepseek-tui --bin deepseek-tui --all-features
--locked -- -D warnings` clean.
* `cargo test -p deepseek-tui --bin deepseek-tui --locked` →
2029 passed, 2 ignored.
Refs #582.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dispatcher's top-level error handler prints `"error: {err}"`,
which is anyhow's bare Display. anyhow's Display only renders the
top-level context message and drops every cause beneath it. Users hit
"failed to parse config at <path>" with zero hint about the actual
TOML error (line/column, expected token, missing quote, BOM, etc.).
This is the gap reported in #767: the OP got
`error: failed to parse config at C:\Users\y1547\.deepseek\config.toml`
with nothing else, while a separate code path that uses a different
formatter shows a rich `Caused by: TOML parse error at line 1, column
20 ...` chain. Maintainer was unable to triage without the underlying
parse error.
Print the full chain by iterating `err.chain().skip(1)` after the
top-level message. Output for the issue's case becomes:
error: failed to parse config at C:\Users\y1547\.deepseek\config.toml
caused by: TOML parse error at line N, column M
| (snippet from toml-rs)
Tests: regression test pins the chain semantics so a future refactor
of the print path can't silently drop causes again.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
JetBrains' JediTerm — the terminal embedded in PyCharm, IDEA, CLion,
WebStorm, GoLand, etc. — advertises mouse support but forwards SGR
mouse-event escape sequences as raw input characters rather than
interpreting them. Users see the composer auto-fill with garbled
characters when they move the mouse over the TUI window. The
workaround was already a one-flag fix (`--no-mouse-capture` or
`[tui] mouse_capture = false` in config) but discovering it required
finding a maintainer comment on a related issue.
Auto-detect via `TERMINAL_EMULATOR=JetBrains-JediTerm` (the env var
JediTerm sets) and default `mouse_capture` off for that environment,
mirroring the existing Windows handling. Explicit `--mouse-capture`
or `[tui] mouse_capture = true` still wins, so power users who don't
hit the issue can opt back in.
Implementation:
- `default_mouse_capture_enabled` now takes `terminal_emulator: Option<&str>`
so the function is pure and trivially testable. The CLI entry point
reads the env var once and passes it through.
- `should_use_mouse_capture` keeps the same public signature; tests
call `should_use_mouse_capture_with` which takes the env explicitly,
removing test sensitivity to the host's actual TERMINAL_EMULATOR.
- Match is `eq_ignore_ascii_case` because JetBrains has occasionally
varied the casing across releases.
Tests:
- 4 new tests covering JetBrains default-off, case-insensitive match,
CLI override, and config-file override.
- Existing 6 mouse-capture tests retained, all passing.
- `cargo test -p deepseek-tui --bin deepseek-tui --all-features
terminal_mode_tests --locked` → 10/10 pass.
- `cargo clippy -p deepseek-tui --bins --all-features --locked --
-D warnings` clean.
- `cargo fmt --all -- --check` clean.
Docs in `docs/MODES.md` and `docs/CONFIGURATION.md` updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When the onboarding flow writes the API key under `~/.deepseek/`, the
parent-dir and config-file chmod are propagated as hard errors if they
fail. On Docker-on-Windows where the container mounts `$USERPROFILE\
.deepseek` to `/home/deepseek/.deepseek`, the bind-mounted NTFS volume
can't accept Unix chmod, so `set_permissions` returns EPERM and the
user sees `Failed to save API key: Failed to set permissions on
/home/deepseek/.deepseek` — even though the directory and the secret
file were already created successfully.
The chmod is a hardening pass: the dir already lives under the user's
home and the file is created with `O_CREAT | mode(0o600)` via
OpenOptions. On filesystems where Unix permissions don't apply at all
(Docker bind-mount of NTFS, network shares, FAT, certain CI volumes),
the host's native ACL model is doing access control regardless. So
demote the chmod to best-effort on Unix: warn loudly via
`tracing::warn!` for security-sensitive operators who run with
`RUST_LOG=warn`, then continue.
Three sites:
- `config::ensure_parent_dir` — parent-dir 0o700 hardening
- `config::write_config_file_secure` — file 0o600 hardening
- `secrets::FileKeyringStore::store_unlocked` — file 0o600 hardening
(the parent-dir chmod here was already best-effort via `let _ =`)
Tests:
- `cargo test -p deepseek-secrets --all-features --locked` → 16/16 pass
(including `file_store_round_trips_with_secure_perms` which still
asserts mode 0o600 on a normal Linux test FS)
- `cargo test -p deepseek-tui --bin deepseek-tui --all-features
save_api_key --locked` → 5/5 pass
- `cargo clippy -p deepseek-tui -p deepseek-secrets --all-features
--locked -- -D warnings` clean
- `cargo fmt --all -- --check` clean
The GitBash paste failure mode reported in the same issue is a terminal
quirk (GitBash on Windows doesn't reliably forward Ctrl+V to TUI apps);
PowerShell + Shift+Insert work, as the reporter discovered. Not in
scope here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The skill registry already walks workspace-local `.claude/skills` for
Claude Code interop, plus global `~/.agents/skills` and
`~/.deepseek/skills`. Picking up the global `~/.claude/skills` brings
DeepSeek TUI in line with the broader Claude-ecosystem convention so
users can inherit skills installed for other Claude-compatible tools
without re-authoring them in DeepSeek's native layout.
Adds `claude_global_skills_dir()` mirroring `agents_global_skills_dir()`
and inserts it into `skills_directories()` between the agentskills.io
global and the DeepSeek-native global. Workspace candidates still win
on name conflicts; first-match-wins is preserved.
Tests:
- claude_global_skills_dir_returns_home_relative_path
- existing_skill_dirs_orders_globals_agents_then_claude_then_deepseek
- All 55 pre-existing skills tests still pass
Docs synced (README publishing-skills section, CONFIGURATION).
docs/COMPETITIVE_ANALYSIS.md already advertised this lookup; this
brings the implementation in line with the documented contract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>