Commit Graph

70 Commits

Author SHA1 Message Date
Hunter Bown 5fa24733e9 chore(rebrand): update repository links for CodeWhale 2026-05-23 14:07:36 -05:00
Hunter Bown ddaabbfed2 chore(rebrand): finish codewhale release surfaces 2026-05-23 13:41:46 -05:00
Hunter Bown 2c642ec375 feat(session): fork conversations inside the TUI 2026-05-21 00:24:52 +08:00
Hunter Bown 2e022bda25 Merge remote-tracking branch 'origin/pr/1065' into work/v0.8.34
# Conflicts:
#	crates/tui/src/commands/mod.rs
#	crates/tui/src/tui/app.rs
#	crates/tui/src/tui/ui.rs
2026-05-13 00:06:56 -05:00
Hunter Bown ccfece77e3 Merge remote-tracking branch 'origin/pr/1534' into work/v0.8.34
# Conflicts:
#	crates/tui/src/commands/config.rs
#	crates/tui/src/commands/mod.rs
#	crates/tui/src/palette.rs
#	crates/tui/src/settings.rs
#	crates/tui/src/tui/app.rs
#	crates/tui/src/tui/color_compat.rs
#	crates/tui/src/tui/widgets/footer.rs
2026-05-12 23:58:39 -05:00
Hunter Bown 5aee539ff2 fix(commands): dispatch pinyin skills alias 2026-05-12 23:46:56 -05:00
Hunter Bown 4063c61ad6 Merge remote-tracking branch 'origin/pr/1306' into work/v0.8.34
# Conflicts:
#	crates/tui/src/commands/mod.rs
#	crates/tui/src/tui/ui/tests.rs
#	crates/tui/src/tui/widgets/mod.rs
2026-05-12 23:26:13 -05:00
Hunter Bown f4f1916221 Merge remote-tracking branch 'origin/pr/1548' into work/v0.8.34 2026-05-12 23:13:25 -05:00
Hunter Bown 485ba7bbd4 chore(release): finish v0.8.33 polish 2026-05-12 22:03:47 -05:00
Hunter Bown 99c6b22e83 chore(release): v0.8.33 — sub-agent and RLM renovation with persistent sessions
- Persistent RLM sessions (rlm_open/rlm_eval/rlm_close) with bounded REPL helpers
- Fork-aware sub-agent sessions (agent_open/agent_eval/agent_close) with handle_read
- Shared handle_read storage with slice/range/count/JSONPath projections
- Slash-command routing: /rlm, /agent, /relay (/接力) for handoff prompts
- Sidebar renamed to "Work" tab, consistent across Plan/Agent/YOLO modes
- Tool papercuts: file_search excludes, grep_files strings, fetch_url JSON,
  edit_file fuzz, exec_shell merged stdout/stderr, revert_turn no-op reject
- CLI reasoning-effort honoured on non-auto exec routes (#1511 @h3c-hexin)
- Edit-file replacement boundaries clarified (#1516)
- Pandoc output validated before probing (#1523)
- Running turns steerable/repaintable (#1533, #1537)
- Tasks/Activity Detail calmer under load
- npm retry timeout hint (#1538 @reidliu41)
- Issue templates improved (#1525 @reidliu41)
- Shell: kill process group to prevent UI freeze (#828 @CrepuscularIRIS)
- TUI: ignore leaked SGR mouse reports in composer (#1421 @reidliu41)
- Footer: keep chips within available width (#1417 @Wenjunyun123)
- Session picker: scope Ctrl+R to current workspace (#1395 @LinQ)
- Removed stale competitive-analysis doc
- Prompts/docs teach only new tool names
2026-05-12 19:54:08 -05:00
zhuang biaowei 2346252a63 Fix changelog command version hints 2026-05-13 08:17:19 +08:00
laoye_tchs 7bf63ea654 Add Catppuccin/Tokyo Night/Dracula/Gruvbox themes + /theme picker
Wires the previously-dormant `theme` setting (#657 follow-up) into the
live Settings struct so the choice survives restart. `/theme` opens an
interactive picker with live preview; `/theme <name>` switches and
persists non-interactively.

Most render sites use bare `palette::TEXT_BODY` / `DEEPSEEK_INK` /
`BORDER_COLOR` constants rather than reading `app.ui_theme`, so simply
adding new UiTheme variants only repaints ~20% of surfaces. The fix is
a third stage in ColorCompatBackend (alongside the existing dark<->light
and truecolor<->256 stages) that rewrites every well-known dark-palette
constant to the corresponding UiTheme slot for the active preset. The
remap is a no-op for System / Whale / WhaleLight, so legacy dark/light
flows stay byte-identical.

Settings: theme = system | dark | light | catppuccin-mocha |
tokyo-night | dracula | gruvbox-dark. Unknown values normalise to
system. background_color still overlays on top.

Tests: new coverage in theme_picker and palette; pinned make_app() in
footer tests to ThemeId::System after App::new (matching the existing
default_model pin) since App::new now honors settings.theme.
2026-05-12 22:24:52 +08:00
Hunter Bown d62facafac feat(translate): opt-in /translate command localises model replies
Two-layer design for users whose UI locale is not English:

1. **System-prompt directive (primary)**: when the user enables
   translation via `/translate`, a `## Language Output Requirement`
   block is appended to the system prompt instructing the model to
   reply in the resolved session locale (Simplified Chinese,
   Traditional Chinese, Japanese, or Brazilian Portuguese). Code
   identifiers, technical terms without an established translation,
   and code blocks the user explicitly requests in English are
   exempt. The block is gated on `PromptSessionContext.translation_
   enabled`, so it adds zero tokens for installs that don't opt in.

2. **Post-hoc heuristic (fallback)**: a lightweight detector in
   `tui::translation` compares Latin-letter count against weighted
   CJK characters (CJK chars carry ~3× the information per glyph,
   so the ratio comparison stays fair across mixed code+prose).
   When a reply still surfaces English despite the directive, the
   detector flags it and a focused per-message `client.translate()`
   call renders the localised version before display. The dedicated
   translation request runs without conversation history, tool
   calls, or streaming — the only role is translate-and-return.

Adds the `/translate` slash command, locale strings for the new UI
states, the post-hoc fallback module, the per-message
`TranslationStatus`, and threading through `core::ops`,
`core::engine`, `runtime_threads`, and the TUI app/UI surface.

Trust-boundary check: opt-in only — `translation_enabled` defaults
to false everywhere, so English-locale installs see zero behaviour
change. The system prompt addition is conditional on the runtime
flag, not the contributor's earlier always-on form. Threaded the
new `Locale::ZhHant` arm through the v0.8.32 `/change` slash
command match to keep the pattern exhaustiveness check passing.

Harvested from PR #1462 by @YaYII

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:46:50 -05:00
Hunter Bown 1f0065ccee feat(commands): add /change slash command to display latest CHANGELOG entry
`/change` reads the most recent `## [version]` section from the
workspace `CHANGELOG.md` (or the bundled release-notes copy when
no workspace changelog is available) and renders it inline in the
TUI. On non-English locales the command also queues a model-side
translation request so localised users see the changelog text in
their UI language; with no API key configured, the offline path
returns the section verbatim with a brief explanatory header.

Lets users discover what changed in the version they just upgraded
into without leaving the chat — and keeps the v0.8.32 release-notes
flow consistent with `deepseek update`'s newly-fixed sibling-TUI
refresh: now both binaries match the version, and `/change` shows
what that version actually delivered.

Resolved a `clippy::needless_range_loop` warning in the section
extractor (idiomatic `iter().enumerate().skip(...).find(...)` instead
of an indexed range loop) so the harvest passes the workspace's
`-D warnings` clippy gate without touching the contributor's design.

Harvested from PR #1416 by @zhuangbiaowei

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:25:30 -05:00
Hunter Bown bfb9da3462 test: isolate config-mutating smoke tests 2026-05-11 13:28:45 -05:00
reidliu41 6d099d425c feat: add note management commands
Extend /note beyond append-only usage with list, show, edit, remove,
  clear, path, and explicit add subcommands.

  Keep existing /note <text> behavior compatible, preserve the existing
  --- separated file format, and number notes only at display time so the
  stored notes stay clean.

  Update command help, localization, docs, and tests.
2026-05-10 20:56:39 -05:00
Hunter Bown c87196835c feat(skills): /skills <prefix> filters the local list (#1318)
On top of v0.8.26's inter-row spacing for /skills (#1328 from
@reidliu41), the list now also accepts an optional name-prefix
argument so users with crowded skill folders can narrow the view
without scrolling.

  /skills           → full list (unchanged)
  /skills git       → only skills whose name starts with "git"
  /skills GIT       → same (case-insensitive)
  /skills nope      → "No skills match prefix `nope` (out of 12 …)"
  /skills --remote  → unchanged
  /skills sync      → unchanged
  /skills --bogus   → "Usage: …" error (rejected so future flags
                      don't silently turn into no-match prefixes)

The match-count header reflects both the matched count and the
registry total, so the user can see at a glance how aggressive
the filter is. Empty match sets explicitly say so and point back
at unfiltered `/skills`. Skill names that start with `-` aren't
allowed by the loader, so reserving the dash prefix for flags is
safe.

Plus the matching usage / description updates in the command
metadata + all four shipping locales (en / ja / zh-Hans / pt-BR)
so /help shows the new argument.

Closes #1318. Thanks @simuusang for the report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:51:57 -05:00
reidliu41 0ccd4e731c Add feedback command for GitHub links
Add a /feedback command for opening project feedback links.

  The command shows a picker when run without arguments and supports direct
  bug, feature, and security targets. Bug and feature options open the matching
  GitHub issue templates, while security opens the repository security policy.
2026-05-10 08:16:13 -05:00
reidliu41 24ec0839e1 Add runtime status command
Add a dedicated /status command that reports the current runtime session state.

  The new report shows provider, model, workspace, mode, permissions, session,
  context usage, token telemetry, cache telemetry, cost, transcript counts, and
  rate-limit availability. /statusline remains available for footer configuration.
2026-05-10 08:16:07 -05:00
reidliu41 4f8eff0c69 refactor: unify mode switching under /mode
Replace the separate /agent, /plan, and /yolo commands with a single
  /mode command that can either open a picker or switch directly by name
  or number.

  This keeps mode switching in one command surface and avoids duplicating
  similar commands for each mode.
2026-05-10 08:15:19 -05:00
sockerch e62e48a723 Merge branch 'main' into feature/chinese-pinyin-aliases 2026-05-09 22:23:31 +08:00
markchang 00bb1d3ff3 feat(commands): add pinyin aliases for all commands 2026-05-09 21:06:50 +08:00
Hunter Bown 54ca5718d2 feat(cache): cache-aware prompt diagnostics and wire payload optimization (#1196)
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.
2026-05-09 00:20:13 -05:00
Hunter Bown 8dcb467bf5 feat(commands): scan workspace-local .deepseek/.cursor/.claude commands
Extend load_user_commands() to scan workspace-local command directories
in addition to the global ~/.deepseek/commands/. Precedence model
mirrors skills_directories(): project-local shadows global by name.

Scanned directories (in precedence order):
  1. <workspace>/.deepseek/commands/
  2. <workspace>/.claude/commands/   (Claude Code interop)
  3. <workspace>/.cursor/commands/   (Cursor interop)
  4. ~/.deepseek/commands/           (user-global fallback)

Workspace context threaded through:
  - try_dispatch_user_command (has App reference)
  - user_commands_matching (new workspace parameter)
  - all_command_names_matching (new workspace parameter)
  - slash_completion_hints (new workspace parameter)

Closes #1259
2026-05-09 00:11:28 -05:00
Hunter Bown 4de726abc5 feat(tui): compact live thinking by default 2026-05-08 14:13:50 -05:00
吴梦知 379186d911 feat(tui): add /theme command to toggle dark/light theme (#1057)
Slash command toggles between the dark and light `UiTheme` presets without round-tripping through `/config`. Useful for quickly matching the editor or terminal theme.

Thanks to @MengZ-super — small, scoped, exactly the right surface for a quick toggle.
2026-05-07 12:43:56 -05:00
J3y0r f1d86901da feat(tui): add workspace switch command 2026-05-07 13:37:00 +00:00
Hunter Bown ebcffaadf9 feat(compaction): add /anchor compaction facts (#930)
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>
2026-05-06 20:45:22 -05:00
kitty 719594636c feat(commands): add /rename command to set a custom session title (#836)
* feat(commands): add /rename command to set a custom session title

Adds a `/rename <new title>` slash command that lets users set a
human-readable name for the current session. The new title is
persisted immediately to the session JSON file so it appears in
the session picker on the next open.

- Max title length capped at 100 characters (char-count aware, handles CJK)
- Errors on missing/empty arg or no active session
- Inner `rename_with_manager` helper keeps unit tests fully isolated
  from ~/.deepseek/sessions
- Localized descriptions in en, ja, zh-Hans, pt-BR

* fix(rename): sync App state before saving to prevent data loss

Use update_session() to merge current in-memory messages and tokens
into the session before writing the renamed title, preventing stale
disk data from overwriting unsaved App state.

* style: format rename command

---------

Co-authored-by: Hunter Bown <hmbown@gmail.com>
2026-05-06 04:13:23 -05:00
Hunter Bown a09a72572f fix(network): add allowlist slash command (#819) 2026-05-06 02:46:02 -05:00
Hunter Bown 50780a5289 fix: restore auto model routing (#772)
Keep auto as a local routing mode, resolve concrete model/thinking before API requests, and wire auto routing through CLI, TUI, runtime threads, and subagents.
2026-05-05 21:22:03 -05:00
Hunter Bown caf1ac2a89 feat(skills): remote registry sync with /skills sync command (#654) 2026-05-05 00:12:17 -05:00
wangfeng e577db47e4 feat(commands): unified slash-command namespace with template substitution (closes #435)
Three sources share the /foo namespace with clear precedence:
  1. Native built-ins (match block in mod.rs)
  2. User-config commands (~/.deepseek/commands/*.md) — checked first
  3. Skills (~/.deepseek/skills/) — new fallback in the _ arm

Template substitution: $1, $2, $ARGUMENTS are replaced in user-command
and skill content before the message is sent. Existing exact-match and
alias behavior is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 16:39:28 -07:00
wangfeng 5430e1a9c5 feat(skills): remote registry sync with /skills sync command (closes #433)
Add `sync_registry` to `skills/install.rs` that pulls `index.json` from the
configured `[skills] registry_url`, resolves each entry to a download URL,
checks ETag + SHA-256 for freshness, and writes SKILL.md (or the full
unpacked tarball) into `~/.deepseek/cache/skills/<name>/`.  A `.cache-meta.json`
sidecar records the ETag and hash so subsequent syncs skip unchanged skills in
one round-trip.

Wire the new `/skills sync` slash-command in `commands/skills.rs` (dispatched
from `list_skills`) and update the `COMMANDS` usage string in `mod.rs` to
`/skills [--remote|sync]`.  The per-skill failure model is non-fatal: the
command prints a per-entry `[+]`/`[=]`/`[!]` summary and returns a final
tally.  `default_cache_skills_dir` and the new outcome types are re-exported
from `skills/mod.rs` for downstream consumers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 16:27:26 -07:00
20bytes 8aed1bb674 memory: polish help and docs (#569)
- 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
2026-05-04 02:25:13 -05:00
Hunter Bown bda30b0fd6 Merge main into feat/v0.8.8-tui-polish + gemini-code-assist feedback
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.
2026-05-03 08:29:59 -05:00
Hunter Bown 8ed1cb4e68 feat(hooks): /hooks events subcommand for discovery (#460 polish)
The shipped `/hooks list` told users WHAT was configured but
not WHAT they could configure. Without this, the only way to
learn the supported `HookEvent` values is to grep source — not
ideal when most users just want to wire up a notification on
session_end.

Adds `/hooks events` (aliases `event` / `list-events`) which
prints every `HookEvent` variant alongside a short descriptive
blurb (when it fires, current observability-vs-mutation status).
Ordered lifecycle → per-tool → situational so the listing reads
naturally and stays stable across releases.

Updates `CommandInfo::usage` to `/hooks [list|events]` so the
fuzzy autocomplete shows the new subcommand.

Tests:
  1 new test (`events_subcommand_lists_every_event_variant_in_documented_order`)
  pins the order, the per-event descriptive blurb format, and
  exhaustive variant coverage. The existing 6 hooks tests pass
  unchanged.
2026-05-03 06:51:27 -05:00
Hunter Bown a368dc53b8 feat(commands): /hooks read-only lifecycle hook listing (#460 MVP)
Slash command enumerates configured lifecycle hooks from the
user's `[hooks]` table, grouped by event. The full picker /
persisted enable-disable surface in #460 is still M-sized work;
this MVP gives users a no-typing view of what's actually loaded
— the most-asked question once hooks start firing.

Implementation:

* `crates/tui/src/commands/hooks.rs` formats the hook list with
  per-event headings, hook name (or `(unnamed)`), background
  marker, timeout, condition summary, and a 60-char shell
  command preview.
* `condition_summary` covers every `HookCondition` variant
  (Always/ToolName/ToolCategory/Mode/ExitCode/All/Any) so the
  listing stays informative for compound conditions too.
* `event_label` maps each `HookEvent` to its config-file string
  so the listing matches what the user wrote in TOML.
* New `HookExecutor::config()` accessor exposes the underlying
  `HooksConfig` for read-only callers; doesn't open the door
  to mutation, which still belongs to the broader #460 work.
* Registered in `commands::COMMANDS` with `aliases: &["hook"]`,
  usage `/hooks [list]`, and `MessageId::CmdHooksDescription`
  localized in en, ja, zh-Hans, pt-BR.
* Wired into `command_palette::command_runs_directly` so
  pressing Enter from Ctrl+K runs `/hooks list` straight.

Tests:
  6 unit tests covering preview-cap truncation, newline
  stripping, condition-summary variants, event-label
  exhaustiveness, and BTreeMap-grouping ordering.
2026-05-03 06:36:37 -05:00
Hunter Bown 15127046e8 feat(stash): /stash clear subcommand to wipe the stash file (#440 polish)
Pairs with `/stash list` and `/stash pop` so the user can fully
manage the stash from inside the TUI without reaching for `rm`.

* New `composer_stash::clear_stash()` returns the number of
  entries dropped so the slash command can report it.
  Atomic-write replaces the file with empty content; missing /
  empty files return `Ok(0)` without erroring.
* `clear` / `wipe` / `drop` are accepted as the subcommand
  alias. The "unknown subcommand" hint now lists the three live
  subcommands explicitly.
* CommandInfo usage updated to `/stash [list|pop|clear]` so
  `/help` and the autocomplete reflect the new option.
* 3 new tests in `composer_stash`: returns-0 when file absent,
  returns-0 when file is empty, drops entries and reports count
  on a populated stash.

No new dependency; reuses `crate::utils::write_atomic` for the
truncate-and-rewrite.
2026-05-03 06:28:18 -05:00
Hunter Bown ba871c56f6 feat(cli): deepseek pr <N> — pre-seed TUI with PR context (#451)
`deepseek pr 1234` fetches the PR's title, body, base/head, URL,
and full diff via `gh`, then launches the interactive TUI with a
review prompt already typed in the composer. The user can edit
before sending or hit Enter to fire as-is. Falls back gracefully
with an actionable error when `gh` is not on PATH.

Implementation:

* `Commands::Pr { number, repo, checkout }` subcommand. Optional
  `--repo <owner/name>` mirrors `gh pr view`'s flag. Optional
  `--checkout` opt-in for `gh pr checkout`; default is to leave
  the working tree alone since `gh pr checkout` errors out on
  dirty trees.
* `run_pr` helper drives three best-effort gh shell-outs
  (`pr view --json`, `pr diff`, optional `pr checkout`) and
  formats a structured prompt: PR header → URL → branches →
  description → fenced ```diff block.
* `format_pr_prompt` caps the diff at 200 KiB with codepoint-
  safe truncation so a massive PR doesn't blow the model's
  context window before the user even hits Enter.
* New `TuiOptions::initial_input: Option<String>` plumbs the
  pre-typed text into `App::new` (which now branches its
  composer-state init around the option). Cursor lands at the
  end of the seed text. Future callers (welcome screens, share-
  link landing pages, etc.) can reuse the same channel.
* `run_interactive` gains an `initial_input: Option<String>`
  parameter; existing callers pass `None`.

Tests:
  3 new tests in `pr_prompt_tests` cover the happy path
  (title/url/branches/body/diff render correctly), empty-input
  fallbacks (placeholder for missing title/body/branches/url),
  and codepoint-safe truncation when the diff exceeds the
  200 KiB cap.

Bulk update: every other `TuiOptions { ... }` test-builder
across the workspace (~21 sites) gains `initial_input: None`
so the new field doesn't break the existing test suite.
2026-05-03 06:23:54 -05:00
Hunter Bown 2db48435e8 feat(stash): register /stash in /help and autocomplete (#440 polish)
The slash command landed in 6fb87 but only via the dispatch
match arm — `/help` and the fuzzy autocomplete consult
`COMMANDS: &[CommandInfo]` to enumerate available commands, and
without a `CommandInfo` entry the new `/stash` was effectively
hidden from discovery.

Adds a `CommandInfo` row with `aliases: &["park"]`, a
`/stash [list|pop]` usage hint, and a new
`MessageId::CmdStashDescription` localized in en, ja, zh-Hans,
pt-BR. The description reminds users that Ctrl+S is the
matching push entry point — both surfaces should reinforce each
other in the help overlay.

No behavior change on the dispatch path; this is pure
discoverability.
2026-05-03 06:13:06 -05:00
Hunter Bown 6fb8739feb feat(composer): prompt stash — Ctrl+S parks, /stash list+pop (#440)
A stash is a side-channel from history: it holds drafts the user
parked deliberately instead of submissions made in the past
(which live in `composer_history.rs`).

* `crates/tui/src/composer_stash.rs` — JSONL-backed store at
  `~/.deepseek/composer_stash.jsonl`. One JSON object per line
  with `ts` (RFC 3339) and `text`. Self-healing parser drops
  malformed lines instead of poisoning the file. Multi-line
  drafts round-trip intact via JSON's newline escaping. Capped
  at 200 entries; oldest pruned at push time. Empty /
  whitespace-only text is silently dropped.
* `crates/tui/src/commands/stash.rs` — `/stash list` renders the
  stash with one-line previews and timestamps; `/stash pop`
  restores the most recently parked draft into the composer
  (LIFO) and rewrites the file. `/park` aliases `/stash`.
* Composer Ctrl+S handler in `tui/ui.rs` — pushes the current
  draft onto the stash, clears the composer, and surfaces a
  toast confirming the action so the no-op-feel doesn't fool
  users into thinking nothing happened. Empty composers are a
  no-op so a stray Ctrl+S can't pollute the file.
* New `KbStashDraft` keybinding entry registered in the help
  overlay; localized in en, ja, zh-Hans, pt-BR.

Tests:
  7 unit tests in `composer_stash.rs` cover round-trip, LIFO pop,
  empty-on-pop, drop-empty-text, multi-line preservation,
  malformed-line resilience, and cap pruning. 4 unit tests in
  `commands/stash.rs` cover the preview helper's truncation,
  multi-line first-line behavior, and empty-input handling.
2026-05-03 06:09:35 -05:00
Hunter Bown 89500e4ebe feat(commands): /sessions prune <days> slash command (#406 phase-1.5)
The previous commit shipped \`SessionManager::prune_sessions_older_than\`
as a bare helper marked \`#[allow(dead_code)]\` pending phase-2 wiring.
This commit wires it into a user-callable slash command so users can
clean up stale sessions today, and removes the dead-code allow.

### Surface

\`\`\`
/sessions                      → open the picker (existing)
/sessions show | list | picker → alias for the picker
/sessions prune <days>         → drop sessions older than N days
\`\`\`

\`/sessions prune 30\` returns "pruned N sessions older than 30d" or
"no sessions older than 30d to prune". Errors:
- missing arg → usage hint
- non-positive / non-integer arg → typed error
- unknown subcommand → typed error with usage

The prune handler builds a fresh \`SessionManager\` from
\`default_location\` so it reads the same \`~/.deepseek/sessions/\`
directory the persistence layer writes; doesn't take a lock since
it's a one-shot CLI-style operation that runs to completion.

### What changed

- \`commands::session::sessions\` now takes \`arg: Option<&str>\`
  and dispatches \`show\` / \`prune\` / unknown.
- New \`prune\` private fn parses the days argument, opens
  \`SessionManager::default_location\`, calls
  \`prune_sessions_older_than\` with the corresponding \`Duration\`.
- \`commands::COMMANDS\` table updated: usage now reads
  \`/sessions [show|prune <days>]\`.
- \`commands::mod\` dispatch arm passes \`arg\` through.
- \`SessionManager::prune_sessions_older_than\` doc comment updated
  to reflect the live wiring; \`#[allow(dead_code)]\` removed.

### Tests

5 new tests in \`commands::session::tests\`:
- existing \`test_sessions_pushes_picker_view\` updated to the new
  signature
- \`test_sessions_show_subcommand_pushes_picker_view\` —
  \`/sessions show\` is an explicit alias for the picker
- \`test_sessions_prune_requires_days_argument\` — missing arg
  produces usage hint
- \`test_sessions_prune_rejects_non_positive_days\` — \`0\`,
  negative, non-numeric, and decimal inputs are all rejected
- \`test_sessions_unknown_subcommand_errors\` — typo path errors
  with subcommand list

### Verification

cargo fmt --all -- --check                                          ✓
cargo clippy --workspace --all-targets --all-features --locked --   -D warnings   ✓
cargo test --workspace --all-features --locked                      ✓ 1865 + supporting

Refines #406 — phase 1.5 (user-callable surface) shipped on top of
phase 1 (helper). Phase 2 (boot-prune + retention policy) stays open
for v0.8.9 once the policy is decided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 04:31:00 -05:00
Hunter Bown 7547d168a4 feat(memory): user-memory MVP — persistent notes, # quick-add, /memory, remember tool (#489–#493)
Adds a small, opt-in user-memory layer so the model has access to durable
preferences and conventions across sessions, and the user can dump quick
notes without leaving the TUI.

### What ships

- **Hierarchy loader** (#490): on every prompt assembly the engine reads
  `Config::memory_path()` (defaults to `~/.deepseek/memory.md`, override via
  `memory_path` in config or `DEEPSEEK_MEMORY_PATH`) and injects the file
  as a `<user_memory>` block alongside the existing `<project_instructions>`
  block. Goes above the volatile-content boundary so prefix-cache stays warm.
  Oversize files (>100 KiB) are truncated with a marker.
- **`# foo` composer quick-add** (#492): typing a single line that starts
  with `#` (but not `##` / `#!`) appends a timestamped bullet to the memory
  file and consumes the input — no turn fires. The composer status line
  surfaces the path that was written. Multi-`#` prefixes deliberately fall
  through so users can paste Markdown headings.
- **`/memory` slash command** (#491): `/memory` (or `/memory show`) prints
  the resolved path and contents inline; `/memory path`, `/memory clear`,
  and `/memory edit` (prints `${VISUAL:-${EDITOR:-vi}} <path>`) cover the
  rest of the manual-curation surface.
- **`remember` tool** (auto-update): model-callable tool that takes a
  `note` string and appends it as a bullet — the same persistence path as
  `# foo`. Auto-approved (writes only the user's own memory file). Only
  registered when memory is enabled, so it doesn't pollute the catalog when
  the feature is off.
- **Opt-in toggle** (#493): default behaviour is off. Enable with
  `[memory] enabled = true` in `config.toml` or `DEEPSEEK_MEMORY=on` in
  the environment.

### What's wired

- New `crates/tui/src/memory.rs` module (`load`, `as_system_block`,
  `compose_block`, `append_entry`).
- New `crates/tui/src/tools/remember.rs` (`RememberTool` + 3 tests).
- New `crates/tui/src/commands/memory.rs` (`memory(app, arg)` handler).
- `EngineConfig` gains `memory_enabled: bool` + `memory_path: PathBuf`.
- `ToolContext` gains `memory_path: Option<PathBuf>`.
- `App` exposes `memory_path` + `use_memory` from `AppOptions` (previously
  destructured-and-dropped); `main.rs` populates `use_memory` from
  `config.memory_enabled()`.
- `system_prompt_for_mode_with_context_and_skills` accepts an optional
  `user_memory_block` parameter; the engine computes it via
  `memory::compose_block(...)` and threads it through.
- Composer Enter handler intercepts `# foo` only when
  `config.memory_enabled()` is true; otherwise falls through to existing
  turn-submission path.
- `MemoryConfig` table (`[memory] enabled`) added to `Config`, surfaced
  in `config.example.toml`, plumbed through `merge_config`.

### Tests

- 8 unit tests in `memory::tests` covering `load` (missing / whitespace /
  real), `as_system_block` (xml shape, empty input, oversize truncation),
  and `append_entry` (creation, repeated append, empty-after-strip rejection).
- 3 unit tests in `tools::remember::tests` covering disabled-state error,
  successful append, and missing-`note`-field validation.

### Verification

cargo fmt --all -- --check                                          ✓
cargo clippy --workspace --all-targets --all-features --locked --   -D warnings   ✓
cargo test --workspace --all-features --locked                      ✓ (1821 + supporting; was 1809 on main)

Closes #490 #491 #492 #493
Refines #489 (EPIC parent — phase-1 MVP delivered; phase-2 items
494–497 stay on the v0.9.0 board)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 02:51:17 -05:00
Hunter Bown 5bfc1feb62 v0.8.6: survivability, UX polish, and release hardening
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.
2026-05-02 20:11:33 -05:00
Hunter Bown 2d61513a9e v0.8.5: config test fixes + default_model session-apply bugfix (#381)
* feat: add config UI support for TUI and web modes

- Introduced a new `config_ui.rs` module to handle configuration UI for TUI and web.
- Updated `TuiOptions` and `App` structures to include `config_path` and `config_profile`.
- Implemented functions to build and apply configuration documents.
- Added tests to ensure the new configuration UI behaves as expected.
- Integrated web configuration session handling into the event loop.
- Updated various modules to accommodate the new configuration options and UI.

* refactor(tui): remove local path reference for schemaui dependency

Remove the local file system path reference for schemaui in favor of
using the published crate from the registry. This change updates the
Cargo.toml to use only the version specification and adds the source
and checksum information to Cargo.lock.

* fix: add AGENTS.md guide and improve config error handling

- Add comprehensive AGENTS.md file with project instructions for AI
  assistants, including build commands, dependencies, and GitHub
  operations guidance
- Introduce is_error field to CommandResult struct for better error
  tracking
- Refactor config application logic to properly handle errors using
  the new is_error flag
- Add test utilities for WebConfigSession to support testing
- Optimize web config event polling by extracting drain logic into
  separate function
- Add unit tests for session-only config application and engine sync
  requirements

* fix(security): add SSRF protection to fetch_url (#261)

Block private, link-local, and cloud metadata IPs in fetch_url HTTP requests. Co-authored-by: JasonOA888

* test(portability): inject paths instead of mutating HOME (Windows fix)

CI's `Test (windows-latest)` job failed because both my new tests
(composer_history and the spawn_supervised crash-dump test) mutated
HOME to redirect `dirs::home_dir()`. That works on macOS / Linux but
not on Windows, where dirs::home_dir() reads USERPROFILE / queries
SHGetKnownFolderPath rather than HOME.

Fix: refactor both modules to expose path-injecting helpers so tests
never need to touch the env var:

- composer_history: split load_history / append_history into thin
  wrappers around load_history_from(&Path) / append_history_to(&Path).
  Tests use the *_to / *_from form with a tempdir path.
- utils::write_panic_dump: same pattern — write_panic_dump_to(&Path)
  takes the crash dir directly. The spawn_supervised end-to-end test
  splits into two: one verifies panic-doesn't-propagate (no on-disk
  side effect needed), one verifies write_panic_dump_to writes the
  expected log format.

Production callers continue to use the env-driven default (`HOME`/
`USERPROFILE` via `dirs::home_dir()`) so no behavior change. Tests
work identically on every platform now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tui): clear chat area each frame so stale cells don't bleed into sidebar

ChatWidget's render path was `Paragraph::new(lines).render(content_area, buf)`
with no Block and no Clear — ratatui's Paragraph only writes cells that
contain text, leaving any cell the current frame's paragraph doesn't
touch holding the *previous* frame's contents. With wide tool output
(`gh pr list`, `git log`) emitting ISO-8601 timestamps like
`2026-05-02T07:29:24Z`, then a subsequent shorter-paragraph frame, the
old timestamp tails (`:24Z`, `7:29:24Z`, etc.) persisted on the right
edge of the chat area, visually colliding with the section headers in
the sidebar (`Plan` rendering as `:24Zan`, `Agents` as `:24Zents`).

Fix: render `Clear` over the full content_area before drawing the
Paragraph. Cheap (one buffer-fill per frame) and guarantees stale cells
can never persist into the next frame's render.

Reported in v0.8.5 testing right after install. The other v0.8.5
bordered widgets (composer, sidebar sections, footer) already render
into a Block with a solid background style, so they were never
affected — only the chat area used a bare Paragraph.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(theme): vendor + theme schemaui to deepseek navy palette (config UI)

The schemaui-0.12.0 crate the contributor brought in via #365 ships
hardcoded Color::Gray / Color::DarkGray / Color::White / Color::Yellow
references across its rendering components. Visually it clashed with
the rest of deepseek-tui — the editor area read as gray-on-black on a
TUI that's otherwise navy ink + sky accents. Two ship-day options
weren't acceptable: defaulting back to the legacy modal lost the new
editor's UX, and living with gray was off-brand.

This commit forks schemaui at 0.12.0 into vendor/schemaui-0.12.0 and
themes the rendering layer to match deepseek-tui's palette. The patch
is wired in via a workspace-level [patch.crates-io] override so the
deepseek-tui Cargo.toml continues to depend on `schemaui = "0.12.0"`
and would automatically resolve back to crates.io if we ever drop the
override (e.g. once upstream lands a ColorTheme API).

Changes inside the vendored fork:

- New `src/deepseek_palette.rs` with the brand RGB values:
  SURFACE_INK / SURFACE_RAISED for backgrounds, BORDER_DIM /
  BORDER_ACTIVE for chrome, TEXT_PRIMARY / TEXT_MUTED / TEXT_DIM,
  ACCENT_SKY / ACCENT_BLUE / ACCENT_PURPLE, and STATUS_OK / WARN /
  ERROR. Values mirror crates/tui/src/palette.rs in the workspace.
- `src/lib.rs` exposes the palette module under `cfg(feature = "tui")`.
- `src/tui/view/frame.rs::draw` paints a navy backdrop across the
  full frame area before any child widget renders, so any cell that
  doesn't get explicitly written reads as ink instead of the terminal
  default.
- `tabstrip.rs`, `overlay.rs`, `popup.rs`, `body.rs`, `sections.rs`,
  `footer.rs`, `help.rs`, `fields.rs`: every Color::Gray / DarkGray /
  White / Yellow / Cyan / Blue / Magenta / Red / Green / LightBlue
  swapped out for a deepseek_palette token, plus explicit `bg(...)`
  fills on the top-level Block styles and Paragraph wrappers.
- `Cargo.toml` adds an empty `[workspace]` so the vendored crate
  builds standalone (its dev-deps don't drift into ours).

Workspace-level changes:

- `Cargo.toml` adds `[patch.crates-io] schemaui = { path =
  "vendor/schemaui-0.12.0" }`. Production deepseek-tui builds pick up
  the themed fork transparently.
- `.gitignore` excludes `vendor/.../web/ui/node_modules/` (15 MB of
  npm artefacts the Rust build doesn't need) and the vendored
  Cargo.lock (regenerated locally per build).

Verification:
- cargo build --workspace --all-features: clean
- cargo clippy --workspace --all-targets --all-features --locked: clean
- cargo test --workspace: 1777 passed, 0 failed
- /config inside `deepseek` now opens a navy-themed editor matching
  the rest of the TUI; tabs, body panel, footer, popup, and help
  overlay all read on brand.

Future work tracked separately: upstream a `with_theme(ColorTheme)`
builder API to schemaui so we can drop the fork. Until then, sync the
fork against new schemaui releases when we want their fixes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert "feat(theme): vendor + theme schemaui to deepseek navy palette"

This reverts ed597ccc — vendoring 28,913 lines of schemaui to recolor
a config editor was the wrong tradeoff. Maintenance cost for a
cosmetic match wasn't worth it, and the recolor wasn't even fully
working (terminal-default bg kept bleeding through Style::default()
calls in the form fields).

The simpler path: keep the schemaui-driven editor available as
`/config tui` for users who want the form-style UX, but make bare
`/config` open the legacy native modal that already matches the
deepseek-tui navy chrome by inheritance. No fork, no vendored copy,
no ongoing sync burden.

Changes:
- `git rm -r vendor/schemaui-0.12.0/` (28,913 lines gone)
- Drop `[patch.crates-io]` from workspace Cargo.toml — schemaui
  resolves back to crates.io v0.12.0 unmodified.
- Drop the corresponding `.gitignore` exclusions (no more vendor dir
  to filter).
- `config_ui::parse_mode` default mode flipped from `Tui` to `Native`.
  Bare `/config` → legacy navy modal. Explicit `/config tui` → the
  contributor's schemaui editor (still available, gray-on-default
  chrome, but opt-in). `/config web` and `/config <key>` /
  `/config <key> <value>` unchanged.
- Help text updated to list `[native|tui|web]` in that order.

Verified: cargo build / clippy --workspace --all-features --locked
with -D warnings: clean.

The contributor's work (#365) ships and gets credit; users discover
the alternate editor via the help text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tui): paint chat area with explicit navy ink instead of Clear

The Clear-instead-of-fill in 0ae2cead reset cells to the terminal's
default background, which read as a brown-gray on most user setups
even though the rest of the TUI chrome is navy. Replace the Clear
with an explicit Block fill at palette::DEEPSEEK_INK, and pass the
same bg through to the Paragraph itself so streamed text cells
inherit ink rather than bouncing back to terminal default.

Net effect: the chat area visually unifies with the sidebar /
composer / footer instead of showing as a contrasting brown-gray
panel in the middle of an otherwise navy frame.

Stale-cell guarantee from #372-followup is preserved — the Block
fills every cell in the area on each frame, so wide tool output
(`gh pr list` ISO timestamps, etc.) still can't bleed past the
current frame's actual text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(config): update tests for Native default + fix default_model override in session-only apply

- Update test_show_config_defaults_to_native and
  execute_config_opens_config_view_action to expect
  OpenConfigView (Native) instead of OpenConfigEditor(Tui),
  matching the parse_mode default change from ce98f054.

- Fix apply_document bug where default_model was processed
  in the main key-value loop after model, causing
  set_config_value('default_model') to overwrite the
  runtime model. default_model is now only applied when
  persist=true, preventing session-only edits from being
  silently reverted.

* style: cargo fmt

* chore: remove end-of-night report (session artifact)

---------

Co-authored-by: unic <yuniqueunic@gmail.com>
Co-authored-by: Jason <jason@aveoresearchlabs.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: YuniqueUnic <YuniqueUnic@users.noreply.github.com>
2026-05-02 16:25:03 -05:00
Hunter Bown 47bb91a9b7 fix(commands): wire /config <key> <value> to setter — args no longer silently ignored (#338)
Add config_command(app, arg) that dispatches three paths:
  /config (no args)       -> opens interactive editor (existing behavior)
  /config <key>           -> shows current value of a single setting
  /config <key> <value>   -> sets value via existing set_config_value

Keys like model, approval_mode, locale, auto_compact, calm_mode,
show_thinking, mode, max_history, sidebar_width, sidebar_focus,
composer_density, composer_border, transcript_spacing are all read
live from App state for the /config <key> display path.

Unknown keys show a helpful error referencing /help config.
2026-05-02 02:28:25 -05:00
Hunter Bown aa23182674 chore(tools): remove /swarm command + agent_swarm/spawn_agents_on_csv tool surface; park swarm.rs pending #357 cascade (#336)
Surface removed: /swarm slash command, agent_swarm, spawn_agents_on_csv, swarm_status, swarm_result, swarm_cancel tools, report_agent_job_result. Prompts/docs/tests updated. swarm.rs parked with #![allow(dead_code)] pending the full cascade in #357. RLM prompt audit tracked in #358.
2026-05-02 01:30:23 -05:00
Hunter Bown 87f42656a7 feat(swarm): add /swarm command with sequential|mixture|distill|deliberate modes (Phase A foundation, #303) 2026-05-01 23:48:24 -05:00
Hunter Bown 359c27437b feat(i18n): Phase 1c-extra — keybinding descriptions, /home, /settings, /help labels
Closes the gate the maintainer set for v0.8.5: every / command, /help,
and /settings should look perfect in both English and Chinese before
multi-agent work begins. v0.8.4 shipped Phase 1a/b/c (88 MessageIds)
but four mixed-language gaps remained:

1. **Keybinding descriptions** (41 entries) — the help overlay showed
   translated section labels (Phase 1c) over English description text.
   `KeybindingEntry` now carries `description_id: MessageId` instead
   of a raw `&'static str`; all 41 descriptions translated to
   en/ja/zh-Hans/pt-BR.

2. **Settings: header** — `Settings::display` now takes a `Locale`
   and resolves the title via `MessageId::SettingsTitle`. The
   field-name keys (auto_compact, calm_mode, etc.) intentionally stay
   English — they are the literal TOML keys users edit.

3. **/home dashboard** — entirely English before. ~25 lines of section
   headers, mode tips, and quick-action hints translated. Path
   interpolations route through `display_path` (privacy invariant).

4. **/help <topic>** text command — the inline labels `Usage:` and
   `Aliases:` plus the `Unknown command:` fallback all use tr().

Also adds three buffer-render tests confirming the help overlay /
settings / home dashboard render in zh-Hans without missing markers
or English bleed-through.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:38:27 -05:00