Commit Graph

324 Commits

Author SHA1 Message Date
Hunter Bown fb12e331ab feat(snapshot): #137 add /restore command and revert_turn tool
Two user-facing entry points to the snapshot side-repo:

- `/restore [N]` (slash command) — `/restore` with no arg lists the
  10 most recent snapshots so the user can see what's available.
  `/restore N` restores the N-th most recent snapshot. Outside YOLO
  or `/trust on`, the command refuses to mutate files and tells the
  user how to opt in (no in-flow modal-confirm path inside slash
  commands today; trust mode is the explicit gate).

- `revert_turn` (agent-callable tool) — `turn_offset` (default 1)
  counts in `pre-turn:*` snapshots, so the model can say "undo my
  last edit" without having to enumerate the history. Approval-gated
  (`ApprovalRequirement::Required`) since it mutates the workspace,
  and registered through `with_full_agent_surface` so children
  inherit it just like every other agent-mode tool.

Tests for both surfaces use the process-wide env mutex
(`crate::test_support::lock_test_env`) plus an RAII `HOME` guard so
tempdir-based snapshot resolution stays inside the per-test sandbox
even when the runner threads multiple tests in parallel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:31:47 -05:00
Hunter Bown 8ff4f66b95 feat(core): #137 add pre/post-turn snapshot hooks
Wire `pre_turn_snapshot` and `post_turn_snapshot` helpers into
`core::turn`, then call them from `Engine::handle_send_message` —
pre-turn fires right after `turn_counter` is incremented, post-turn
fires right after `Event::TurnComplete` is emitted.

Both hooks are dispatched via `tokio::task::spawn_blocking` so the
agent loop never waits on the side-git commit, and helper failures are
swallowed at WARN log level so a busted disk or missing `git` binary
can never derail a turn (per the snapshot module's documented
non-fatal contract).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:31:31 -05:00
Hunter Bown 3dc116b9fc feat(snapshot): #137 add workspace snapshot side-repo module
Introduce `crate::snapshot` — a per-workspace side-git repo that lives
under `~/.deepseek/snapshots/<project_hash>/<worktree_hash>/.git` and
captures the workspace into commits via `git add -A` + `git commit
--allow-empty`. The user's own `.git` is never touched: every git
invocation passes both `--git-dir` (side repo) and `--work-tree`
(workspace) together, which is the load-bearing safety invariant.

Module layout:
- `paths.rs` — resolves the side-repo dir; strips `.worktrees/<name>`
  so worktrees of the same checkout share a project_hash but get
  distinct worktree_hashes.
- `repo.rs` — `SnapshotRepo::open_or_init / snapshot / restore / list /
  prune_older_than`. Shells out to system `git` (avoids `git2` LGPL
  surface). Honors workspace `.gitignore` automatically.
- `prune.rs` — boot-time helper used by session_manager (next commit).
  Default retention is 7 days.

Tests (real `git` invocations on tempdirs, env-mutating tests serialised
through the existing `crate::test_support::lock_test_env` mutex) cover:
snapshot creates a commit in the side repo only, restore reverts files,
list respects limit, prune drops aged commits, gitignore is honored,
and re-init is idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:31:21 -05:00
Hunter Bown 87ac2e9bdc Merge branch 'feat/v070-network' (#135 egress network policy + audit)
- crates/tui/src/network_policy.rs (NEW, ~300 LOC) — NetworkPolicy/NetworkPolicyDecider/NetworkAuditor
- crates/tui/src/tools/{fetch_url,web_search,spec}.rs + mcp.rs — gate before egress
- crates/tui/src/core/engine.rs + runtime_threads.rs + tui/ui.rs — surface NetworkDenied
- crates/config/src/lib.rs + tui/src/config.rs + config.example.toml — [network] schema
- Subdomain-prefix matching with deny-wins precedence
- Audit format: <RFC3339> network <host> <tool> <decision>

# Conflicts:
#	crates/tui/src/config.rs
2026-04-28 00:07:42 -05:00
Hunter Bown a054789f79 Merge branch 'feat/v070-osc9' (#132 OSC 9 desktop notification on long-turn completion)
- crates/tui/src/tui/notifications.rs (NEW) — Method enum {Auto/Osc9/Bel/Off}, notify_done()
- crates/tui/src/tui/mod.rs + ui.rs — register module + hook EngineEvent::TurnComplete
- crates/tui/src/config.rs — NotificationsConfig (method/threshold_secs/include_summary)
- config.example.toml — [notifications] section
- 9 unit tests including tmux DCS passthrough wrapping
2026-04-28 00:06:26 -05:00
Hunter Bown ff21d2268d Merge branch 'feat/v070-arity' (#131 bash-command arity for safer auto_allow)
- crates/tui/src/command_safety.rs — port opencode arity dictionary (160+ entries) + classify_command()
- crates/tui/src/tools/approval_cache.rs — cache key uses canonical prefix
- config.example.toml — auto_allow prefix-semantics docblock
- 41 colocated unit tests
2026-04-28 00:06:21 -05:00
Hunter Bown 7b6993fecb Merge branch 'feat/v070-altv' (#138 Alt+V chord for tool-details pager)
- crates/tui/src/tui/ui.rs — new Alt+V/Alt+Shift+V arm next to Alt+A/Y/P family, no empty-input gate
- crates/tui/src/tui/history.rs — 5 hint strings + 4 test assertions updated to "Alt+V for details"
- crates/tui/src/tui/keybindings.rs — entry under Submission so ? overlay lists it
- Bare 'v' handler unchanged (legacy muscle memory)
2026-04-28 00:06:16 -05:00
Hunter Bown d3dd8a590f Merge branch 'feat/v070-keyring' (#134 OS keyring credential store)
- crates/secrets/ (NEW crate) — KeyringStore trait + Default/InMemory/File backends
- crates/config/src/lib.rs — api_key resolution via Secrets::auto_detect (CLI → keyring → env → config-file)
- crates/cli/src/main.rs — auth set/get/clear/migrate/list subcommands
- crates/tui/src/config.rs + main.rs — wire keyring resolver, doctor reports backend
- Lockfile updated for keyring 3.6 (apple/windows/linux native features)
- One-time deprecation warning when api_key is read from config.toml
2026-04-28 00:06:09 -05:00
Hunter Bown f36e891117 Merge branch 'feat/v070-skill-creator' (#139 bundled skill-creator system skill)
- crates/tui/assets/skills/skill-creator/SKILL.md (NEW) — MIT-attributed port from codex
- crates/tui/src/skills.rs → skills/mod.rs + skills/system.rs (NEW) — install_system_skills() with version-marker idempotence
- crates/tui/src/main.rs — startup auto-install hook (non-fatal on error)
- crates/tui/src/commands/skills.rs — /skill new alias for /skill skill-creator
- 7 unit tests covering fresh install, idempotence, user-deleted-dir respect, version-bump reinstall
2026-04-28 00:06:03 -05:00
Hunter Bown 3522040855 Merge branch 'feat/v070-mock-llm' (#69 mock LLM client + integration tests)
- crates/tui/src/llm_client.rs → llm_client/mod.rs (directory module)
- crates/tui/src/llm_client/mock.rs (NEW) — MockLlmClient at trait boundary
- crates/tui/tests/integration_mock_llm.rs (NEW) — 7 trait-level scenarios
- crates/tui/src/eval.rs + main.rs — `--record` fixture flag
- 4 engine-level tests #[ignore]'d pending engine LlmClient trait-object refactor
2026-04-28 00:05:55 -05:00
Hunter Bown 9db841fc62 test(tui): #69 integration tests for mock LLM client + record fixtures
Adds `integration_mock_llm.rs` covering the LlmClient trait surface:

- streaming turn loop (text deltas + finish reason)
- reasoning-content replay across tool-call rounds (V4 §5.1.1, the
  HTTP 400 path that broke v0.4.9-v0.5.1)
- tool-call round-trip with chunked input JSON
- multiple tool calls in one turn preserve event ordering
- compaction-style non-streaming `create_message`
- sub-agent style independent parent/child mocks
- capacity-gate observation of a captured request

Four full-engine tests are `#[ignore]`-marked as BLOCKED on the engine
refactor from concrete `Option<DeepSeekClient>` to `Arc<dyn LlmClient>`.
Once that wiring lands the ignored tests light up with no mock changes.

Adds:
- `tests/support/llm_client.rs` mirrors the trait so the mock can be
  brought into the integration test via `#[path]` without dragging in
  the rest of the binary's module tree
- `tests/fixtures/.gitkeep` so the `eval --record` output directory
  rides the repo
- `tests/README.md` documents both the trait-level mocking strategy
  and the `--record` fixture flow
- `record_flag_writes_one_jsonl_line_per_step` in `eval_harness.rs`
  exercises the new `--record` flag end-to-end

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:03:18 -05:00
Hunter Bown 0626bc80c0 feat(test): #69 mock LLM client + eval --record fixture flag
Adds a queue-driven `MockLlmClient` that implements the `LlmClient` trait
by replaying canned per-turn `StreamEvent` vectors and capturing every
outgoing `MessageRequest`. The mock lives at the trait boundary so it
stays decoupled from the concrete reqwest plumbing inside `DeepSeekClient`,
and surfaces builders (`canned::*`) for the common event shapes (text
delta, thinking delta, tool_use start, input JSON delta, message delta).

Wires a new `--record <DIR>` flag into `deepseek eval` that appends one
JSON Lines fixture line per step to `<DIR>/<scenario>.jsonl`. The format
is documented at the top of `eval.rs` and is the storage shape the mock
will replay from.

`crates/tui/src/llm_client.rs` becomes `crates/tui/src/llm_client/mod.rs`
to host the new submodule cleanly. The trait shape is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:03:00 -05:00
Hunter Bown f82f162e7f feat(network): #135 gate fetch_url, web_search, MCP HTTP via policy
Threads the optional `NetworkPolicyDecider` from `EngineConfig` through to
`ToolContext.network_policy` and `McpPool::with_network_policy`. Each gate
point follows the same pattern: extract the host, call `decider.evaluate`,
then `Allow` proceeds, `Deny` returns a structured permission-denied error,
and `Prompt` falls through to the same denial with a hint pointing to
`/network allow <host>` (full modal flow lands in a follow-up).

* `fetch_url` — gates on the parsed URL host.
* `web_search` — gates DuckDuckGo (`html.duckduckgo.com`) and the Bing
  fallback (`www.bing.com`) independently so a deny on one engine doesn't
  silently let the other through.
* MCP — only the HTTP/SSE transport is gated; STDIO MCP servers are
  unaffected. `McpConnection::connect_with_policy` replaces the old
  `connect` (no external callers existed).

The session cache short-circuits `evaluate` once a host is approved, so
the existing `approve_session` hook is enough to wire the prompt-once
flow when the approval modal lands.

`NetworkPolicyDecider::with_default_audit` materializes the auditor at
`~/.deepseek/audit.log` when the config has `audit = true`.

Includes one tool-level test asserting `fetch_url` denies a blocked host
through the policy gate.
2026-04-28 00:02:56 -05:00
Hunter Bown abbb86cdd2 feat(network): #135 add [network] config schema for policy
Adds the `[network]` table to both the workspace config crate (`ConfigToml`)
and the live tui config (`Config`), plus a documented example block in
`config.example.toml`. Schema:

```toml
[network]
default = "prompt"      # allow | deny | prompt
allow = ["api.deepseek.com", "github.com"]
deny = []
audit = true
```

`NetworkPolicyToml::into_runtime()` builds a runtime `NetworkPolicy` so the
engine can construct a `NetworkPolicyDecider` without reaching across crate
boundaries. Defaults preserve pre-v0.7.0 behavior: when the section is
absent, no policy is enforced.
2026-04-28 00:02:34 -05:00
Hunter Bown 45727a09f7 feat(network): #135 add per-domain network policy module
Introduces `network_policy::{Decision, NetworkPolicy, NetworkPolicyDecider,
NetworkAuditor, NetworkSessionCache, NetworkDenied}` for gating outbound
network calls.

Deny-wins precedence: a host listed in both `allow` and `deny` is denied.
Subdomain wildcard via leading-dot entries (`.example.com` matches
`api.example.com` but not the apex). Audit log writes one plaintext line
per terminal decision to `~/.deepseek/audit.log` in the format
`<RFC3339> network <host> <tool> <Allow|Deny|Prompt-Approved|Prompt-Denied>`.

Approve-once-for-session caching is implemented in `NetworkSessionCache`;
`approve_persistent` mutates the policy's allow list so callers can write
back to config later.

19 unit tests cover deny-wins precedence, subdomain matching, audit
logging, session-cache short-circuit, and `NetworkDenied` shape.
2026-04-28 00:02:27 -05:00
Hunter Bown 30d7650bae feat(cli): #134 add deepseek auth keyring subcommands and surface backend in doctor
Adds first-class keyring management on the dispatcher CLI and wires
the TUI to read its DeepSeek key through the same Secrets façade.

Subcommands:
* `auth set --provider <name>`   writes to the OS keyring; prompts
  on stdin without echo, never prints the key, never touches
  `config.toml`. Supports `--api-key` and `--api-key-stdin`.
* `auth get --provider <name>`   reports `set` / `not set` plus the
  resolving layer (keyring / env / config-file). Never prints the
  value.
* `auth clear --provider <name>` deletes from keyring and from any
  legacy plaintext slot in `config.toml` for parity.
* `auth list`                    table of all known providers and
  whether each layer holds a key. Non-revealing.
* `auth migrate [--dry-run]`     reads `api_key` (root + per-provider
  blocks) from `config.toml`, writes them to the keyring, then
  strips the entries from disk. Idempotent.
* `auth status`                  expanded to also report the active
  keyring backend and per-provider keyring state.

`doctor` now prints `keyring backend: ...` plus per-provider
`keyring=yes/no, env=yes/no` lines and points users at
`deepseek auth set` when no key resolves.

`Config::deepseek_api_key()` in the TUI is rewritten to consult
`Secrets::auto_detect()` first (keyring -> env), then fall back to
the existing TOML slots with a deprecation warning. Error messages
now lead with `deepseek auth set --provider <name>`.

5 new unit tests cover argument parsing for the new subcommands and
end-to-end auth set/clear/migrate behaviour against an
`InMemoryKeyringStore`, verifying that no plaintext key ever lands
in `config.toml`.

Verified manually on macOS:
  $ deepseek auth set --provider deepseek --api-key-stdin
  $ security find-generic-password -s deepseek -a deepseek
  # entry present
  $ deepseek auth migrate
  # api_key lines stripped from ~/.deepseek/config.toml

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:01:43 -05:00
Hunter Bown a5cc9d5852 feat(config): #134 resolve api_key through OS keyring with env fallback
Routes `ConfigToml::resolve_runtime_options` through the new
`deepseek_secrets::Secrets` façade so API keys are read from the OS
keyring before any environment variable, with the existing
plaintext-config layer kept as a deprecated last resort. The
precedence is now:

  CLI flag -> keyring -> env -> config-file

Reads of an `api_key` value from `~/.deepseek/config.toml` now emit
a one-time `tracing::warn!` directing users to
`deepseek auth set` / `deepseek auth migrate`.

`resolve_runtime_options_with_secrets` is exposed for tests and
process-level injection (the `cfg(test)` default uses an in-memory
store so unit tests never touch the real OS keychain). The
nvidia-nim provider keeps its `DEEPSEEK_API_KEY` env fallback for
back-compat. New tests cover keyring > env > config-file precedence
end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:01:23 -05:00
Hunter Bown f3ada0be88 chore(secrets): #134 scaffold deepseek-secrets crate
Adds the `deepseek-secrets` crate with the OS keyring backend,
in-memory store for tests, and a JSON-on-disk fallback for
headless environments. The Secrets façade collapses keyring -> env
into a single resolver; callers layer on CLI flags above and TOML
config below to preserve the keyring -> env -> config-file precedence.

* `KeyringStore` trait + `DefaultKeyringStore` (keyring 3.6 with
  per-platform native features).
* `InMemoryKeyringStore` for unit tests.
* `FileKeyringStore` writes ~/.deepseek/secrets/secrets.json with
  mode 0600 on unix; rejects world-readable files at read time.
* `Secrets::auto_detect` probes the OS keyring and falls back to
  the file store on headless Linux.
* 9 unit tests covering round-trips, precedence, and 0600 perms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:01:10 -05:00
Hunter Bown 6432d47c53 feat: #132 emit OSC 9 / BEL desktop notification on long turn completion
Adds crates/tui/src/tui/notifications.rs with Method enum (Auto/Osc9/Bel/Off),
notify_done / notify_done_to helpers, tmux DCS passthrough, and 9 unit tests.
Wires the hook at the TurnComplete event in tui/ui.rs so turns >= threshold_secs
(default 30 s) emit an escape to stdout; method auto-detects iTerm.app/Ghostty/
WezTerm for OSC 9 and falls back to BEL. Config exposed under [notifications] in
config.toml and documented in config.example.toml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:52:58 -05:00
Hunter Bown c31fbe9de2 feat(skills): #139 bundle skill-creator and auto-install on first launch
- Add crates/tui/assets/skills/skill-creator/SKILL.md (ported from
  OpenAI codex repo, MIT-licensed; all Codex→deepseek / ~/.codex→~/.deepseek
  replacements applied; MIT attribution comment at top).
- Convert crates/tui/src/skills.rs → skills/mod.rs + new skills/system.rs.
  install_system_skills() writes the bundled SKILL.md on fresh install or
  version bump; respects user-deleted directories; idempotent by design.
  Version marker at ~/.deepseek/skills/.system-installed-version.
- Wire install_system_skills() into run_interactive() (main.rs) before the
  TUI mounts; errors are non-fatal (logged as warnings).
- Add /skill new as an alias for /skill skill-creator in commands/skills.rs.
- 7 unit tests covering fresh install, idempotence, user-deleted dir, version
  bump, uninstall (all pass).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:52:58 -05:00
Hunter Bown ce3d1a73b9 feat(approval): #131 port opencode arity dict for prefix-based auto_allow matching
Add COMMAND_ARITY dictionary (160+ entries: git, npm, yarn, pnpm, cargo,
docker, kubectl, go, pip, gh, rustup, deno, bun) and classify_command()
so auto_allow = ["git status"] matches git status -s / --porcelain but
not git push.  Wire classify_command() into approval_cache command_prefix
key construction.  50 colocated unit tests including an explicit
auto_allow semantics test.  Document prefix semantics in config.example.toml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:49:56 -05:00
Hunter Bown eab853e92b feat(tui): #138 add Alt+V chord for tool-details pager
Add Alt+V keybinding that opens the tool-details pager regardless of
composer state, fixing the broken "press v for details" hint. Update
all 5 transcript hint strings to show "Alt+V for details". Bare v with
empty composer is preserved for legacy muscle memory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:46:49 -05:00
Hunter Bown 693fbca4ea fix(test): #101 nested-path test uses file_name() for Windows portability
Windows preserves the user-typed `/` when Path::join() ingests a multi-
component string with forward slashes, producing a mixed-separator path
in the rendered <file> block (e.g. `C:\...\.tmpKxj0Pk\nested/deep/file.md`).
The test compared full paths via display(), which mismatched.

Switch to a basename comparison per CLAUDE.md's portability rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:00:56 -05:00
Hunter Bown 65f5daa9f9 chore(release): v0.6.7
Bumps workspace + npm wrapper to 0.6.7. Auto-tag.yml will create the
v0.6.7 tag, which fires release.yml to build the binary matrix and draft
the GitHub Release.

v0.6.7 highlights (all 12 open issues for this release landed):

- #130 sub-agent mailbox abstraction with monotonic seq + watch-based
  backpressure; close-as-cancel propagates through nested children
- #128 in-transcript DelegateCard + FanoutCard consume the mailbox stream;
  sidebar demoted to a navigator
- #129 approval modal Codex-style takeover with benign / destructive
  variant routing — destructive ops require explicit second-key confirm
- #103 stream-error diagnostics + transparent retry on early decode
  failure; HTTP/2 keepalive defaults; DEEPSEEK_FORCE_HTTP1 escape hatch
- #101 @-file mention BLOCKER fix — two-pass workspace→cwd→fuzzy
  resolution; .deepseekignore honored; Workspace::resolve extracted
- #52 OpenRouter + Novita as first-class providers; /provider picker
  modal with inline API-key prompt; OPENROUTER_API_KEY / NOVITA_API_KEY
- #93 help overlay (?) with searchable command + keybinding catalog
- #95 /statusline picker for configurable footer items, persisted to
  config.toml under tui.status_items
- #94 live transcript overlay (Ctrl+T) with sticky-bottom auto-scroll
  and (cell, width, revision) wrap cache
- #85 pending input preview widget — three semantic buckets (pending
  steers / rejected steers / queued follow-ups); Alt+Up edits last queued
- #66 error taxonomy wired through engine + capacity controller + audit
  log + TUI severity rendering; ad-hoc string matching gone
- #68 sub-agent prompts tightened — explicit
  SUMMARY/EVIDENCE/CHANGES/RISKS/BLOCKERS contract, mode-specific
  guidance, tool-calling conventions in one canonical file

1358 deepseek-tui binary tests passing (1217 baseline → +141), all
parity gates green, clippy -D warnings clean, rustdoc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:46:38 -05:00
Hunter Bown 1184662875 Merge branch 'feat/v067-transcript-overlay' (#94 live transcript overlay Ctrl+T) 2026-04-27 22:44:33 -05:00
Hunter Bown 900825c411 Merge branch 'feat/v067-pending-input' (#85 pending input preview widget) 2026-04-27 22:44:29 -05:00
Hunter Bown 07be2f655e Merge branch 'feat/v067-error-taxonomy' (#66 wire error taxonomy) 2026-04-27 22:44:25 -05:00
Hunter Bown 3c4eb1ed55 feat(errors): #66 wire error taxonomy through engine + capacity + audit
Removed dead_code allow from error_taxonomy.rs. Event::error now carries
an ErrorEnvelope with category + severity instead of (String, bool); all
~13 engine callsites migrated through a small helper API (ErrorEnvelope::transient
/ ::fatal / ::fatal_auth / ::context_overflow / ::network / ::tool / ::classify).

Capacity controller branches on ErrorCategory instead of substring matches.
TUI renders severity with distinct palette tokens via a new HistoryCell::Error
variant. Audit log carries category + severity fields so downstream tooling
can categorize.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:43:08 -05:00
Hunter Bown f88f810b1a feat(tui): #94 live transcript overlay (Ctrl+T) with sticky-bottom auto-scroll
Pager view gains a sticky_to_bottom mode: scroll-up pauses auto-tail,
scrolling back to bottom resumes it. Wrapped lines cached by
(cell_id, width, revision); revisions bumped on live-cell mutation so
resize doesn't reflow the world.

Ctrl+T toggles; Esc returns. Engine continues streaming while overlay open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:36:54 -05:00
Hunter Bown 5f96990739 feat(tui): #85 pending input preview — three-bucket queued message UI
Port of codex-main's PendingInputPreview pattern. Three semantic buckets
render in the composer area: pending steers (Esc submits), rejected
steers (re-fires at end of turn), queued follow-ups (Alt+Up edits last).
Empty state renders zero rows.

Engine populates the new App.pending_steers / rejected_steers fields
through the steer-submission path; existing queued_messages plumbing
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:29:17 -05:00
Hunter Bown f6c9bf13db Merge branch 'feat/v067-statusline' (#95 /statusline picker)
# Conflicts:
#	crates/tui/src/tui/app.rs
#	crates/tui/src/tui/ui.rs
#	crates/tui/src/tui/views/mod.rs
2026-04-27 22:18:53 -05:00
Hunter Bown 5e7dbcd32b Merge branch 'feat/v067-help' (#93 help overlay) 2026-04-27 22:17:30 -05:00
Hunter Bown 48c30473da Merge branch 'feat/v067-providers' (#52 OpenRouter + Novita providers) 2026-04-27 22:17:27 -05:00
Hunter Bown 363f064fce Merge branch 'fix/v067-file-mention' (#101 @-file mention BLOCKER fix) 2026-04-27 22:17:22 -05:00
Hunter Bown 0f7252198d Merge branch 'fix/v067-stream-retry' (#103 stream-error retry + diagnostics) 2026-04-27 22:17:19 -05:00
Hunter Bown a3d0134173 Merge branch 'feat/v067-approval' (#129 approval modal Codex-style takeover) 2026-04-27 22:17:16 -05:00
Hunter Bown 7819fcc18b Merge branch 'feat/v067-cards' (#128 sub-agent in-transcript cards) 2026-04-27 22:17:11 -05:00
Hunter Bown 176a2ba4f4 Merge branch 'feat/v067-mailbox' (#130 sub-agent mailbox port) 2026-04-27 22:17:00 -05:00
Hunter Bown 34cba09e22 Merge branch 'feat/v067-prompts' (#68 sub-agent prompts tightening) 2026-04-27 22:16:57 -05:00
Hunter Bown 63cb06637b feat(tui): #128 in-transcript DelegateCard + FanoutCard
Cards consume the #130 mailbox stream and render live in the transcript:
- DelegateCard: last-3-actions tree for active agent_spawn
- FanoutCard: dot-grid + aggregate stats for agent_swarm / rlm fanout
Sidebar demoted to a navigator (count + role); detail lives in the card.

Engine wires SubAgentRuntime::with_mailbox so the primitive actually flows.
Cards re-bind on session resume via runtime_threads agent_ids.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:15:26 -05:00
Hunter Bown d6a7a0e84a feat(tui): #95 /statusline picker for configurable footer items
StatusItem enum covers all current + new (rate-limit, ctx %, git branch,
last-tool elapsed) items. /statusline opens a multi-select picker with
live preview; selections persist to config.toml under `tui.status_items`.

Default selection mirrors today's footer so upgraders see no change.
2026-04-27 22:00:44 -05:00
Hunter Bown f118db8201 feat(providers): #52 OpenRouter + Novita as first-class providers
ProviderKind gains Openrouter + Novita variants; ModelRegistry registers
deepseek/deepseek-v4-{pro,flash} against both. /provider opens a picker
modal with inline API-key prompt for un-configured providers. Env
fallbacks: OPENROUTER_API_KEY, NOVITA_API_KEY.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:58:51 -05:00
Hunter Bown 9d4c1c1966 feat(tui): #129 approval modal Codex-style takeover
Reflowed approval.rs to a full-screen modal with two stakes-based variants:
benign (single-key approve / always) and destructive (explicit confirm).
Variant routing classifies from tool kind + command-safety so destructive
ops never get a muscle-memory accept.

Existing approval tests still pass; new tests cover variant routing + keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:57:16 -05:00
Hunter Bown 36320c5bea fix(client): #103 stream-error diagnostics + transparent retry on early decode failure
Phase 1: log full reqwest error chain + headers + bytes-received at decode site
Phase 2: HTTP/2 keepalive settings + tcp keepalive on the reqwest builder
Phase 3: engine transparently retries when stream errors before any content;
         surface error on mid-stream failure (no double-bill); stream_errors
         threshold relaxed 3 -> 5 with the new keepalive
Phase 4: unit tests for the four classes of stream failure

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:57:13 -05:00
Hunter Bown b759e3f74c feat(tui): #93 help overlay — ? opens searchable command + keybinding reference
New HelpView modal lists all slash commands with descriptions and all
keybindings, with a live substring filter. Bound to `?` when focus is
outside the composer; Esc / `?` toggles.

Slash commands pull from the existing slash_menu registry; keybindings
pull from a new KeybindingCatalog single-source-of-truth so docs can't
drift from the wired handlers.
2026-04-27 21:54:25 -05:00
Hunter Bown fd13dffd60 fix(tui): #101 @-file mention resolves CWD before workspace fallback
Two-pass resolution in file_mention::resolve_mention_path -- try
workspace.join first, then std::env::current_dir().join, then a basename
fuzzy fallback. Extracted the shared resolver into working_set.rs so the
future Ctrl+P fuzzy picker (#97) uses the same logic.

Tests cover the workspace/CWD divergence repro that was masquerading as a
"@ doesn't work" report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:52:25 -05:00
Hunter Bown 8d8c1ad2d4 feat(prompts): #68 tighten sub-agent output format + stop conditions
Each sub-agent type now has an explicit SUMMARY / EVIDENCE / CHANGES /
RISKS / BLOCKERS output contract, mode-specific guidance (explorer /
planner / reviewer / general), and tool-calling conventions that prefer
the typed tool surface over exec_shell shellouts.

The output format is defined once and referenced from each per-type
prompt, so future tweaks live in one place.
2026-04-27 21:50:18 -05:00
Hunter Bown 32750cb52d feat(subagent): #130 mailbox abstraction with seq + backpressure
Internal upgrade — public tool surface (agent_spawn, agent_swarm, …) unchanged.
The mailbox primitive replaces ad-hoc mpsc plumbing in the runtime so:
- progress events have monotonic ordering
- subscribers get watch-based backpressure
- close-as-cancel propagates through nested children

Pairs with #128 (in-transcript cards consume the mailbox stream).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:50:14 -05:00
Hunter Bown 4ac7219d77 fix(test): cycle archive tests are Windows-portable
The Windows CI test for archive_cycle_writes_jsonl_with_header_and_messages
was failing because:

1. The path-suffix assertion used "/1.jsonl" as a literal — Windows uses
   backslashes, so the assertion never matched. Replace with file_name()
   comparison which is platform-agnostic.
2. set_var("HOME", ...) doesn't redirect dirs::home_dir() on Windows;
   that function reads USERPROFILE on Windows. Set both env vars so the
   test redirects portably.

Same HomeGuard pattern in tools/recall_archive.rs tests was passing on
Windows by luck (each test uses a unique session_id, so writes to the
real ~/.deepseek/sessions/ didn't clash) but was polluting the user's
home directory in CI. Mirror the dual-env-var fix there too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:25:33 -05:00
Hunter Bown 0db918f9e8 Merge branch 'feat/v066-ui-redesign'
#121 — UI redesign (4 of 6 sub-areas + foundation):
- Color-depth + brightness palette helpers
- Speaker glyphs (▎ / ●) with 2s pulse
- Reasoning treatment (dashed rail, italic, warm tint)
- Tool-card verb glyphs + family vocabulary module
- Footer crest waves + cost-on-left + 150ms cadence

Deferred (separate follow-up issues): sub-agent in-transcript cards,
approval modal Codex-style takeover.
2026-04-27 21:10:50 -05:00