Deletes crates/tui/src/responses_api_proxy/ (443 LOC), client/responses.rs
(406 LOC), and removes the ResponsesApiProxy CLI command, the
EXPERIMENTAL_RESPONSES_API_ENV env var plumbing, chat_fallback_counter,
use_chat_completions, RESPONSES_RECOVERY_INTERVAL, and the
RequestPayloadMode::ResponsesApi variant. The experimental Responses API
path was never instantiated and had no documented users; removing it
simplifies the client surface for the upcoming --anthropic-wire flag.
Closes#723
V4 occasionally emits non-canonical tool names (Read_file, readFile,
read-file, read_file_tool). The new resolver runs a deterministic
ladder — lowercase exact, kebab/space → snake, CamelCase → snake,
strip _tool suffix twice, fuzzy prefix match — before falling through
to "Unknown tool". Recovers ~80% of these for free.
Closes#713
The new schema_sanitize module collapses Pydantic-style nullable
anyOf/oneOf unions to {type, nullable: true}, injects empty properties
on bare objects, prunes dangling required entries, and collapses
single-element unions. Run on every tool schema before
build_api_tools() returns the catalog.
Closes a class of silent 400s on /beta/chat/completions strict tool
mode for MCP-derived schemas.
Closes#715
`normalize_model_name` now passes v-series snapshots through unchanged
(deepseek-v4-flash-20260423 stays pinned, future v5-* matches via
regex). Removes ~245 LOC of legacy alias machinery: deepseek_legacy_aliases,
the chat/reasoner/r1/v3/v3.2 fold-arm, is_current_deepseek_v4_alias,
v4 fallback branch, alias capacity test seeds, alias config test block.
The migration from V3 → V4 is over; users on legacy names route their
own request to DeepSeek and see the server actual response (404 if
deprecated, success if still served). No more silent renaming.
Closes#717
PR #646 imported `MessageBeep` from `windows::Win32::UI::WindowsAndMessaging`,
but in `windows` crate 0.60 the function lives at
`windows::Win32::System::Diagnostics::Debug::MessageBeep` and now takes a
typed `MESSAGEBOX_STYLE` returning `Result<()>`. The wrong import broke
every Windows build (Test, npm wrapper smoke, and the windows-msvc release
matrix entry). Fix the import path, enable the `Win32_System_Diagnostics_Debug`
feature, pass `MESSAGEBOX_STYLE(0)` for MB_OK, and discard the Result.
The v0.8.12 release also tripped on a transient `apt-get update` mirror
sync error on the ubuntu-24.04-arm runner, cascading via fail-fast. Wrap
every apt-get update in CI/release with a 5-attempt retry so flaky
ports.ubuntu.com mirrors no longer take down the binary matrix.
Verified: cargo check --target x86_64-pc-windows-gnu compiles cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported by @Hmbown as the recurring "scrolling uses the parent
terminal scrollbar instead of the TUI's" / "TUI rendered only at the
bottom of the viewport" symptom. Same family as 5c72e5f4 and 899c703d,
but neither covered the cancellation case — they fixed the per-turn
scroll lock and panic-time cleanup respectively.
The bug
=======
`execute_tool_with_lock` in tool_execution.rs sent
`Event::PauseEvents` before an interactive tool ran (which makes the
TUI leave alt-screen, disable raw mode, release mouse capture so the
child sees a normal terminal) and `Event::ResumeEvents` after it
returned. Both sends were `let _ = tx_event.send(...).await`.
If the future was dropped between the two awaits — Ctrl+C, agent
cancel, parent task aborted, sub-agent terminated mid-tool — the
second `await` never reached and `ResumeEvents` was never sent. The
TUI sat in the paused state until the next pause/resume cycle, with
the symptoms above.
The fix
=======
Replace the two `send` calls with a `InteractiveTerminalGuard` RAII
type. `engage()` sends `PauseEvents` (only when `interactive` is
true) and arms the guard. `Drop` synchronously sends `ResumeEvents`
via `try_send` (Drop can't await). The guard fires on every exit
path: Ok return, Err return, panic, future cancellation.
`try_send` can fail on a full bounded channel; the guard logs a
`tracing::warn!` rather than swallowing silently so the rare
cancel-with-saturated-event-channel case is visible in traces. The
TUI's own teardown path remains a final backstop.
Verified locally
================
* `cargo build` clean
* `cargo fmt --all -- --check` clean
* `cargo clippy --workspace --all-targets --all-features --locked
-- -D warnings` clean
* `cargo test --workspace --all-features --locked` — 2062 passed,
0 failed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported by @Hmbown as the recurring "scrolling uses the parent
terminal scrollbar instead of the TUI's" / "TUI rendered only at the
bottom of the viewport" symptom. Same family as 5c72e5f4 and 899c703d,
but neither covered the cancellation case — they fixed the per-turn
scroll lock and panic-time cleanup respectively.
The bug
=======
`execute_tool_with_lock` in tool_execution.rs sent
`Event::PauseEvents` before an interactive tool ran (which makes the
TUI leave alt-screen, disable raw mode, release mouse capture so the
child sees a normal terminal) and `Event::ResumeEvents` after it
returned. Both sends were `let _ = tx_event.send(...).await`.
If the future was dropped between the two awaits — Ctrl+C, agent
cancel, parent task aborted, sub-agent terminated mid-tool — the
second `await` never reached and `ResumeEvents` was never sent. The
TUI sat in the paused state until the next pause/resume cycle, with
the symptoms above.
The fix
=======
Replace the two `send` calls with a `InteractiveTerminalGuard` RAII
type. `engage()` sends `PauseEvents` (only when `interactive` is
true) and arms the guard. `Drop` synchronously sends `ResumeEvents`
via `try_send` (Drop can't await). The guard fires on every exit
path: Ok return, Err return, panic, future cancellation.
`try_send` can fail on a full bounded channel; the guard logs a
`tracing::warn!` rather than swallowing silently so the rare
cancel-with-saturated-event-channel case is visible in traces. The
TUI's own teardown path remains a final backstop.
Verified locally
================
* `cargo build` clean
* `cargo fmt --all -- --check` clean
* `cargo clippy --workspace --all-targets --all-features --locked
-- -D warnings` clean
* `cargo test --workspace --all-features --locked` — 2062 passed,
0 failed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the security/privacy fix that was hotfixed to main earlier
(commit f779c7de6) so the v0.8.12 CHANGELOG reflects it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported by @Hmbown: launching `deepseek` from any directory silently
auto-recovered the most recent interrupted session, even if that
session originated in a completely different workspace. Tools then
operated on file paths from the prior workspace while the status bar
showed the *current* workspace name — a confusing trust-boundary
violation that also leaks api_messages, working_set entries, and any
secrets the prior session had accumulated into a new terminal that
was never meant to see them.
`try_recover_checkpoint()` now compares the saved session's workspace
to `std::env::current_dir()` (canonicalised, with a strict-equality
fallback when canonicalisation fails — e.g. the original workspace
was deleted) and only auto-recovers on a match.
On a mismatch the checkpoint is still persisted as a regular session
and cleared, so the user can recover it explicitly via
`deepseek resume <ID>` after `cd`-ing back to the original workspace —
no data is lost. A one-line stderr notice points at the resume command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported by @Hmbown: launching `deepseek` from any directory silently
auto-recovered the most recent interrupted session, even if that
session originated in a completely different workspace. Tools then
operated on file paths from the prior workspace while the status bar
showed the *current* workspace name — a confusing trust-boundary
violation that also leaks api_messages, working_set entries, and any
secrets the prior session had accumulated into a new terminal that
was never meant to see them.
`try_recover_checkpoint()` now compares the saved session's workspace
to `std::env::current_dir()` (canonicalised, with a strict-equality
fallback when canonicalisation fails — e.g. the original workspace
was deleted) and only auto-recovers on a match.
On a mismatch the checkpoint is still persisted as a regular session
and cleared, so the user can recover it explicitly via
`deepseek resume <ID>` after `cd`-ing back to the original workspace —
no data is lost. A one-line stderr notice points at the resume command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README Usage block now documents `deepseek resume <SESSION_ID>` and
`deepseek fork <SESSION_ID>`. Both commands have existed since v0.7
but were undiscoverable; #682 reported "no way to resume."
- New GitHub Actions for issue triage (#688):
* triage.yml — keyword-driven auto-labeller (bug / feat / docs /
question, area:* by file-path mention, os:*,
lang:zh on CJK titles). Only adds labels that
already exist on the repo so it can't create
noise unilaterally.
* stale.yml — 14 d stale → 7 d close on `needs-info` issues
only; PRs untouched; respects pinned/keep-open/
bug/security exemptions.
* spam-lockdown.yml — auto-closes promotional issues from accounts
<30 days old. Pure github-script (no
third-party action) so the matching rules
stay readable.
- CHANGELOG (v0.8.12) updated: README install rewrite (#672), Scoop
(#696), pricing extension (#692), Resume docs surface, and the
cargo-install-on-stable fix from the previous commit. Lease
"pending" caveat removed since it's now actually fixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resident-file leases were stamped as "pending" at spawn time because the
agent id is only assigned by the manager later. The release function
introduced in 2ee926924 matches by agent id, so it could never find
those entries and leases would persist for the lifetime of the process.
After spawn returns the real agent id, replace any "pending" entry with
it so the existing release-on-terminal-state path actually fires.
Resolves the documented v0.8.12 caveat noted in the CHANGELOG. Closes
the loop with PR #694, which proposed a release-by-file-path API but
did not address the placeholder problem itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47 fmt drifts had accumulated from the squash-merged community PRs on
this branch (#653, #654, #655, #645, #658, #668, #659, #661, #660,
#667, #656). Pure formatting — no behavioural changes — applied via
`cargo fmt --all` to satisfy CI's `cargo fmt --all -- --check` gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The match guard at tui/ui.rs:1603 used `&& let Some(...) = ...` inside an
`if` guard, which requires the `if_let_guard` nightly feature on Rust
< 1.94. Reported by an external user attempting `cargo install
deepseek-tui` on stable rustc — it failed with E0658.
Rewrite as a plain match guard with a nested `if let` inside the arm
body so the language-picker hotkeys compile on every supported rustc.
Workspace also now declares `rust-version = "1.88"` to match the
codebase's actual reliance on `let_chains` in if/while conditions, so
users on too-old toolchains see a clear cargo error instead of a
confusing rustc one.
`AGENTS.md` and `CLAUDE.md` gain a "stable Rust only" section
documenting the trap and how to rewrite around it.
Also annotate the deferred `TuiPrefs` (#657) and `handoff::THRESHOLDS`
(#667) APIs with `#[allow(dead_code)]` so CI's `-D warnings` flag stays
green while the call sites are staged for v0.8.13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the prerequisite block that told readers to install Node before
anything else (which contradicted the lede claim that the binary needs
no Node runtime) with a multi-path install section. npm, cargo, and
direct download are presented side-by-side; the npm path is documented
as a thin installer that downloads the prebuilt binary, not as a
runtime dependency.
Same change applied to README.zh-CN.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved RESIDENT_LEASES from block-scoped static to module level so the
release function can access it. Added release_resident_leases_for()
called at all three terminal transitions:
- Cancelled (cancel_agent)
- Failed (update_failed)
- Completed (run_subagent_turn result construction)
Previously leases were acquired but never released, causing false
conflict warnings on subsequent spawns targeting the same file.
The sandbox backend infrastructure was complete but the engine never
called create_backend(), leaving the feature dead. Now:
- Engine::new() creates the backend from api_config (non-fatal on error)
- build_tool_context() attaches it to the ToolContext
- exec_shell checks context.sandbox_backend and routes accordingly
Neither field had any code path that read it. Shipping config knobs
that do nothing trains users to mistrust config. Remove until the
implementation exists.
#651: fix test assertion — section_bg now Color::Reset (was DEEPSEEK_INK)
#645: replace expect() with Result in OpenSandboxBackend::new()
#653: correct resolve_prefixes docstring to describe deny-always-wins