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
This commit is contained in:
Hunter Bown
2026-04-30 20:53:10 -05:00
parent 3c92753a44
commit 3e8da4b99b
17 changed files with 100 additions and 58 deletions
+13 -1
View File
@@ -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
Generated
+14 -14
View File
@@ -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"
+1 -1
View File
@@ -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"
+32
View File
@@ -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`
+1 -1
View File
@@ -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
+9 -9
View File
@@ -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
+7 -7
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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"
+2 -7
View File
@@ -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);
+4 -1
View File
@@ -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(),
+2 -2
View File
@@ -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",