From 3e8da4b99b2aac5f7ee845998f8fcf9c7bcf4c8e Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Thu, 30 Apr 2026 20:53:10 -0500 Subject: [PATCH] chore: bump version to 0.7.9 Includes: - Post-turn freeze fix (reorder maybe_advance_cycle before TurnComplete) - Enter/steering fix (QueueFollowUp when model is streaming) - Esc fanout hardening (idempotent finalize methods) - cargo fmt pass on new code - CHANGELOG, README, and version bump across workspace + npm --- CHANGELOG.md | 14 +++++++++++++- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- README.md | 32 ++++++++++++++++++++++++++++++++ crates/agent/Cargo.toml | 2 +- crates/app-server/Cargo.toml | 18 +++++++++--------- crates/cli/Cargo.toml | 14 +++++++------- crates/config/Cargo.toml | 2 +- crates/core/Cargo.toml | 16 ++++++++-------- crates/execpolicy/Cargo.toml | 2 +- crates/hooks/Cargo.toml | 2 +- crates/mcp/Cargo.toml | 2 +- crates/tools/Cargo.toml | 2 +- crates/tui/Cargo.toml | 4 ++-- crates/tui/src/tui/ui.rs | 9 ++------- crates/tui/src/tui/ui/tests.rs | 5 ++++- npm/deepseek-tui/package.json | 4 ++-- 17 files changed, 100 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c8898c6..13e5f263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.9] - 2026-05-02 + +### Fixed +- **Post-turn freeze** — the checkpoint-restart cycle boundary (`maybe_advance_cycle`) now runs *before* `TurnComplete` emission instead of after, so the terminal is immediately responsive when the UI receives the completion event. The status chip ("↻ context refreshing…") remains visible during the cycle wait. (#234) +- **Enter during streaming no longer corrupts the turn** — a new `QueueFollowUp` submit disposition parks the draft on `queued_messages` when the model is actively streaming text. Previously, pressing Enter during streaming would forward the message as a mid-turn steer, which could interfere with the in-flight response. The message now dispatches as a normal user message after `TurnComplete`. (#234) +- **Idempotent Esc during fanout** — `finalize_active_cell_as_interrupted` and `finalize_streaming_assistant_as_interrupted` are now guarded by `Option::take()`. When Esc cancels a turn and the engine later delivers `TurnComplete(Interrupted)`, the second call is a no-op — no double `[interrupted]` prefix, no corrupted cell state. Regression test locks in the contract. (#243) + +### Tests +- 2 new tests: `submit_disposition_queue_follow_up_when_streaming` (Enter/steering fix), `turn_complete_after_esc_is_idempotent` (Esc fanout double-call hardening) +- 1 expanded test: `submit_disposition_queue_when_offline_and_busy` now covers streaming state + ## [0.7.8] - 2026-05-01 ### Added @@ -715,7 +726,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hooks system and config profiles - Example skills and launch assets -[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.8...HEAD +[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.9...HEAD +[0.7.9]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.8...v0.7.9 [0.7.8]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.7...v0.7.8 [0.7.7]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.6...v0.7.7 [0.7.6]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.7.5...v0.7.6 diff --git a/Cargo.lock b/Cargo.lock index 55fac355..5d8bdf56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,7 +1011,7 @@ dependencies = [ [[package]] name = "deepseek-agent" -version = "0.7.8" +version = "0.7.9" dependencies = [ "deepseek-config", "serde", @@ -1019,7 +1019,7 @@ dependencies = [ [[package]] name = "deepseek-app-server" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "axum", @@ -1042,7 +1042,7 @@ dependencies = [ [[package]] name = "deepseek-config" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "deepseek-secrets", @@ -1055,7 +1055,7 @@ dependencies = [ [[package]] name = "deepseek-core" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "chrono", @@ -1074,7 +1074,7 @@ dependencies = [ [[package]] name = "deepseek-execpolicy" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "deepseek-protocol", @@ -1083,7 +1083,7 @@ dependencies = [ [[package]] name = "deepseek-hooks" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "async-trait", @@ -1097,7 +1097,7 @@ dependencies = [ [[package]] name = "deepseek-mcp" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "deepseek-protocol", @@ -1107,7 +1107,7 @@ dependencies = [ [[package]] name = "deepseek-protocol" -version = "0.7.8" +version = "0.7.9" dependencies = [ "serde", "serde_json", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "deepseek-secrets" -version = "0.7.8" +version = "0.7.9" dependencies = [ "dirs", "keyring", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "deepseek-state" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "chrono", @@ -1140,7 +1140,7 @@ dependencies = [ [[package]] name = "deepseek-tools" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "async-trait", @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "deepseek-tui" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "arboard", @@ -1213,7 +1213,7 @@ dependencies = [ [[package]] name = "deepseek-tui-cli" -version = "0.7.8" +version = "0.7.9" dependencies = [ "anyhow", "chrono", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "deepseek-tui-core" -version = "0.7.8" +version = "0.7.9" [[package]] name = "deranged" diff --git a/Cargo.toml b/Cargo.toml index 91446056..75eba577 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ default-members = ["crates/cli", "crates/app-server", "crates/tui"] resolver = "2" [workspace.package] -version = "0.7.8" +version = "0.7.9" edition = "2024" license = "MIT" repository = "https://github.com/Hmbown/DeepSeek-TUI" diff --git a/README.md b/README.md index 0aae3865..cba6fab7 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,38 @@ cargo install --path crates/cli --bin deepseek --locked --- +## What's new in v0.7.9 + +### ⚡ Post-turn responsiveness + +The checkpoint-restart cycle boundary now runs *before* `TurnComplete` +is emitted to the UI, so the terminal is immediately responsive after +every completed turn. The "↻ context refreshing…" status chip stays +visible during the cycle wait instead of blocking input. + +### ⌨️ Enter during streaming now queues + +When the model is actively streaming text and you press Enter, your +message is now parked on the queue for dispatch after the turn finishes +— no more corrupted mid-turn steering. A status line shows "Queued +follow-up: … (N queued)" so you know what's waiting. + +### 🛡️ Esc during fanout is robust + +Canceling a fanout (swarm/sub-agent spawn) with Esc no longer leaves +the transcript in a broken state when the engine sends a delayed +`TurnComplete`. Both finalization paths are now idempotent — no +double `[interrupted]` prefixes, no stale tool cards. + +### 🧪 Test hardening + +New regression tests lock in the Esc-during-fanout idempotency +contract and the streaming vs. steer dispatch decision. + +Full changelog: [CHANGELOG.md](CHANGELOG.md). + +--- + ## What's new in v0.7.8 ### ⚡ Shell controls: foreground-to-background detach + `exec_shell_cancel` diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml index 68ed79bf..e3d94214 100644 --- a/crates/agent/Cargo.toml +++ b/crates/agent/Cargo.toml @@ -7,5 +7,5 @@ repository.workspace = true description = "Model/provider registry and fallback strategy for DeepSeek workspace architecture" [dependencies] -deepseek-config = { path = "../config", version = "0.7.8" } +deepseek-config = { path = "../config", version = "0.7.9" } serde.workspace = true diff --git a/crates/app-server/Cargo.toml b/crates/app-server/Cargo.toml index 67d37ae9..f7f189b5 100644 --- a/crates/app-server/Cargo.toml +++ b/crates/app-server/Cargo.toml @@ -10,15 +10,15 @@ description = "Codex-style app-server transport for DeepSeek workspace architect anyhow.workspace = true axum.workspace = true clap.workspace = true -deepseek-agent = { path = "../agent", version = "0.7.8" } -deepseek-config = { path = "../config", version = "0.7.8" } -deepseek-core = { path = "../core", version = "0.7.8" } -deepseek-execpolicy = { path = "../execpolicy", version = "0.7.8" } -deepseek-hooks = { path = "../hooks", version = "0.7.8" } -deepseek-mcp = { path = "../mcp", version = "0.7.8" } -deepseek-protocol = { path = "../protocol", version = "0.7.8" } -deepseek-state = { path = "../state", version = "0.7.8" } -deepseek-tools = { path = "../tools", version = "0.7.8" } +deepseek-agent = { path = "../agent", version = "0.7.9" } +deepseek-config = { path = "../config", version = "0.7.9" } +deepseek-core = { path = "../core", version = "0.7.9" } +deepseek-execpolicy = { path = "../execpolicy", version = "0.7.9" } +deepseek-hooks = { path = "../hooks", version = "0.7.9" } +deepseek-mcp = { path = "../mcp", version = "0.7.9" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } +deepseek-state = { path = "../state", version = "0.7.9" } +deepseek-tools = { path = "../tools", version = "0.7.9" } serde.workspace = true serde_json.workspace = true tokio.workspace = true diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 62a9b253..bc552784 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,13 +14,13 @@ path = "src/main.rs" anyhow.workspace = true clap.workspace = true clap_complete.workspace = true -deepseek-agent = { path = "../agent", version = "0.7.8" } -deepseek-app-server = { path = "../app-server", version = "0.7.8" } -deepseek-config = { path = "../config", version = "0.7.8" } -deepseek-execpolicy = { path = "../execpolicy", version = "0.7.8" } -deepseek-mcp = { path = "../mcp", version = "0.7.8" } -deepseek-secrets = { path = "../secrets", version = "0.7.8" } -deepseek-state = { path = "../state", version = "0.7.8" } +deepseek-agent = { path = "../agent", version = "0.7.9" } +deepseek-app-server = { path = "../app-server", version = "0.7.9" } +deepseek-config = { path = "../config", version = "0.7.9" } +deepseek-execpolicy = { path = "../execpolicy", version = "0.7.9" } +deepseek-mcp = { path = "../mcp", version = "0.7.9" } +deepseek-secrets = { path = "../secrets", version = "0.7.9" } +deepseek-state = { path = "../state", version = "0.7.9" } chrono.workspace = true dirs.workspace = true serde.workspace = true diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index a4071fb0..4e5aa9d1 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -8,7 +8,7 @@ description = "Config schema and precedence model for DeepSeek workspace archite [dependencies] anyhow.workspace = true -deepseek-secrets = { path = "../secrets", version = "0.7.8" } +deepseek-secrets = { path = "../secrets", version = "0.7.9" } dirs.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index a5a613ff..8719e4a1 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -9,14 +9,14 @@ description = "Core runtime boundaries for DeepSeek workspace architecture" [dependencies] anyhow.workspace = true chrono.workspace = true -deepseek-agent = { path = "../agent", version = "0.7.8" } -deepseek-config = { path = "../config", version = "0.7.8" } -deepseek-execpolicy = { path = "../execpolicy", version = "0.7.8" } -deepseek-hooks = { path = "../hooks", version = "0.7.8" } -deepseek-mcp = { path = "../mcp", version = "0.7.8" } -deepseek-protocol = { path = "../protocol", version = "0.7.8" } -deepseek-state = { path = "../state", version = "0.7.8" } -deepseek-tools = { path = "../tools", version = "0.7.8" } +deepseek-agent = { path = "../agent", version = "0.7.9" } +deepseek-config = { path = "../config", version = "0.7.9" } +deepseek-execpolicy = { path = "../execpolicy", version = "0.7.9" } +deepseek-hooks = { path = "../hooks", version = "0.7.9" } +deepseek-mcp = { path = "../mcp", version = "0.7.9" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } +deepseek-state = { path = "../state", version = "0.7.9" } +deepseek-tools = { path = "../tools", version = "0.7.9" } serde_json.workspace = true tokio.workspace = true uuid.workspace = true diff --git a/crates/execpolicy/Cargo.toml b/crates/execpolicy/Cargo.toml index d0a1e3ef..510ff00f 100644 --- a/crates/execpolicy/Cargo.toml +++ b/crates/execpolicy/Cargo.toml @@ -8,5 +8,5 @@ description = "Execution policy and approval model parity for DeepSeek workspace [dependencies] anyhow.workspace = true -deepseek-protocol = { path = "../protocol", version = "0.7.8" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } serde.workspace = true diff --git a/crates/hooks/Cargo.toml b/crates/hooks/Cargo.toml index 507ee9bf..e21c16a9 100644 --- a/crates/hooks/Cargo.toml +++ b/crates/hooks/Cargo.toml @@ -10,7 +10,7 @@ description = "Hook dispatch and notifications parity for DeepSeek workspace arc anyhow.workspace = true async-trait.workspace = true chrono.workspace = true -deepseek-protocol = { path = "../protocol", version = "0.7.8" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } reqwest.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/mcp/Cargo.toml b/crates/mcp/Cargo.toml index ddb641e5..b27640d0 100644 --- a/crates/mcp/Cargo.toml +++ b/crates/mcp/Cargo.toml @@ -8,6 +8,6 @@ description = "MCP server lifecycle and tool proxy compatibility for DeepSeek wo [dependencies] anyhow.workspace = true -deepseek-protocol = { path = "../protocol", version = "0.7.8" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } serde.workspace = true serde_json.workspace = true diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index 68befd90..1c752474 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -9,7 +9,7 @@ description = "Tool invocation lifecycle, schema validation, and scheduler paral [dependencies] anyhow.workspace = true async-trait.workspace = true -deepseek-protocol = { path = "../protocol", version = "0.7.8" } +deepseek-protocol = { path = "../protocol", version = "0.7.9" } serde.workspace = true serde_json.workspace = true tokio.workspace = true diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 88944250..4637e14b 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -13,8 +13,8 @@ path = "src/main.rs" [dependencies] anyhow = "1.0.100" arboard = "3.4" -deepseek-secrets = { path = "../secrets", version = "0.7.8" } -deepseek-tools = { path = "../tools", version = "0.7.8" } +deepseek-secrets = { path = "../secrets", version = "0.7.9" } +deepseek-tools = { path = "../tools", version = "0.7.9" } async-stream = "0.3.6" async-trait = "0.1" bytes = "1.11.0" diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index ba65fa4d..f81a2efc 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -3424,10 +3424,7 @@ async fn steer_user_message( /// Park a draft on the queued-messages bucket for dispatch after TurnComplete. /// Unlike a steer, the message is NOT forwarded immediately — it waits for /// the current turn to finish, then dispatches as a normal user message. -async fn queue_follow_up( - app: &mut App, - message: QueuedMessage, -) -> Result<()> { +async fn queue_follow_up(app: &mut App, message: QueuedMessage) -> Result<()> { let display = message.display.clone(); app.queue_message(message); app.status_message = Some(format!( @@ -3453,9 +3450,7 @@ async fn submit_or_steer_message( )); Ok(()) } - SubmitDisposition::QueueFollowUp => { - queue_follow_up(app, message).await - } + SubmitDisposition::QueueFollowUp => queue_follow_up(app, message).await, SubmitDisposition::Steer => { if let Err(err) = steer_user_message(app, engine_handle, message.clone()).await { app.queue_message(message); diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 6a65caf1..2a2631e3 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -3412,7 +3412,10 @@ fn turn_complete_after_esc_is_idempotent() { // State remains consistent — active_cell still None, streaming still // stopped, no double-interruption prefix. - assert!(app.active_cell.is_none(), "active_cell still cleared after 2nd call"); + assert!( + app.active_cell.is_none(), + "active_cell still cleared after 2nd call" + ); assert!(!app.is_loading, "is_loading still false after 2nd call"); assert_eq!( app.runtime_turn_status.as_deref(), diff --git a/npm/deepseek-tui/package.json b/npm/deepseek-tui/package.json index a1a58329..74b8abbe 100644 --- a/npm/deepseek-tui/package.json +++ b/npm/deepseek-tui/package.json @@ -1,7 +1,7 @@ { "name": "deepseek-tui", - "version": "0.7.8", - "deepseekBinaryVersion": "0.7.8", + "version": "0.7.9", + "deepseekBinaryVersion": "0.7.9", "description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.", "author": "Hmbown", "license": "MIT",