mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(tui): show the interrupted-session hint inside the TUI (Refs #7095)
A plain `codewhale` launch deliberately starts fresh. When it found an in-flight session snapshot (a session whose turn never finished, which exists only under ~/.codewhale/sessions/checkpoints/), the only pointer to `codewhale --continue` was an eprintln! printed just before the TUI took the alternate screen, so a user who closed the terminal mid-task reopened to an empty session and no visible way back (#7095). `preserve_interrupted_checkpoint_for_explicit_resume` now returns that message and the launch passes it as the TUI startup notice; an auto-resume receipt still takes precedence. Its doc comment no longer claims checkpoints are persisted as sessions (untrue since #4479). Also applies rustfmt to the `mod usage_persistence` line98104b9737left out of order, the only `cargo fmt --all -- --check` drift on origin/main at a19f2d8885. Not changed here (follow-ups): the SIGHUP/SIGTERM path exits without flushing the persistence actor; the resume picker and `codewhale sessions` do not list checkpoint-only sessions; the 24h checkpoint prune can remove them. Evidence (on an export of #7108's merged headcee2d8ab7cplus these lib.rs hunks, since origin/main codewhale-tui did not compile at the time): - scripts/dev-cargo.sh check -p codewhale-tui --lib --locked: rc=0, no errors or warnings. - scripts/dev-cargo.sh test -p codewhale-tui --lib --locked -- checkpoint: test result: ok. 79 passed; 0 failed (includes plain_launch_preserves_checkpoint_but_starts_fresh, plain_launch_consumes_legacy_checkpoint_after_preserving_it, continue_without_interactive_terminal_leaves_checkpoint_for_a_real_launch). - cargo fmt --all -- --check on origin/main + this commit: clean. Not run: a live TUI launch with a real interrupted session. CW-7525. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b32caa6a17
commit
22e341107e
3 files changed
+29
-29
No files matched your search
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **Interrupted session hint stays visible (#7095):** when a plain `codewhale` launch finds an in-flight session snapshot, the "Run `codewhale --continue` to resume it" hint now appears in the TUI's status line instead of being printed to the terminal just before the TUI covered it.
|
||||
- **Crate split S24a (internal):** the workshop byte-budget slot moves to `codewhale-runtime`, `AutoRouteDecisionEvidence` to `codewhale-models`, and cost-status session persistence to a tui `usage_persistence` module, so billing no longer reaches the session manager or model routing. No behavior change. (CW-6129)
|
||||
- **Nested and deletable threads (Runtime API):** a fork (including a backtrack fork) records `parent_thread_id`, `POST /v1/threads` accepts one, and `GET /v1/threads/summary` returns it so clients can nest child conversations. `DELETE /v1/threads/{id}` removes an idle thread with its turns, items, events and goal, and its saved session unless another thread shares it or a terminal holds it open; a running thread answers 409.
|
||||
- **Hunk actions in Review (Runtime API):** `POST /v1/git/stage`, `/v1/git/unstage` and `/v1/git/discard` take `hunks` (the `@@ … @@` headers from `GET /v1/diff`) with one path and apply only those hunks through `git apply`, honouring the same `expect` preconditions as whole-path writes. A hunk that no longer exists answers `409 git_state_changed` with `stale_hunks`, and nothing is written unless git accepts the whole patch.
|
||||
|
||||
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **Interrupted session hint stays visible (#7095):** when a plain `codewhale` launch finds an in-flight session snapshot, the "Run `codewhale --continue` to resume it" hint now appears in the TUI's status line instead of being printed to the terminal just before the TUI covered it.
|
||||
- **Crate split S24a (internal):** the workshop byte-budget slot moves to `codewhale-runtime`, `AutoRouteDecisionEvidence` to `codewhale-models`, and cost-status session persistence to a tui `usage_persistence` module, so billing no longer reaches the session manager or model routing. No behavior change. (CW-6129)
|
||||
- **Nested and deletable threads (Runtime API):** a fork (including a backtrack fork) records `parent_thread_id`, `POST /v1/threads` accepts one, and `GET /v1/threads/summary` returns it so clients can nest child conversations. `DELETE /v1/threads/{id}` removes an idle thread with its turns, items, events and goal, and its saved session unless another thread shares it or a terminal holds it open; a running thread answers 409.
|
||||
- **Hunk actions in Review (Runtime API):** `POST /v1/git/stage`, `/v1/git/unstage` and `/v1/git/discard` take `hunks` (the `@@ … @@` headers from `GET /v1/diff`) with one path and apply only those hunks through `git apply`, honouring the same `expect` preconditions as whole-path writes. A hunk that no longer exists answers `409 git_state_changed` with `stale_hunks`, and nothing is written unless git accepts the whole patch.
|
||||
|
||||
+27
-29
@@ -35,7 +35,6 @@ mod conformance;
|
||||
mod context_report;
|
||||
mod core;
|
||||
mod cost_status;
|
||||
mod usage_persistence;
|
||||
/// Guarded installation I/O reusing Engine's confined file primitives.
|
||||
pub mod delivery_files;
|
||||
mod doctor;
|
||||
@@ -46,6 +45,7 @@ mod extension_host;
|
||||
mod fleet;
|
||||
mod git_status;
|
||||
mod keybinding_table;
|
||||
mod usage_persistence;
|
||||
use crate::fleet::executor::exec_stream_final_answer_excerpt;
|
||||
mod hooks;
|
||||
mod image_attach;
|
||||
@@ -3104,14 +3104,14 @@ async fn run_async_main_dispatch(
|
||||
Some(id)
|
||||
} else if !cli.fresh {
|
||||
let workspace = resolve_workspace(&cli);
|
||||
preserve_interrupted_checkpoint_for_explicit_resume(&workspace);
|
||||
let checkpoint_notice = preserve_interrupted_checkpoint_for_explicit_resume(&workspace);
|
||||
// Opt-in auto-resume (#2934). Off by default, so the historical
|
||||
// "plain `codewhale` starts fresh" behaviour is unchanged unless the
|
||||
// user asked for something else. The decision never resumes an
|
||||
// archived, unreadable, or foreign-workspace session; every fallback
|
||||
// carries a receipt rather than silently starting blank.
|
||||
let (session_id, notice) = resolve_auto_resume(&workspace);
|
||||
startup_notice = notice;
|
||||
startup_notice = notice.or(checkpoint_notice);
|
||||
session_id
|
||||
} else {
|
||||
None
|
||||
@@ -13115,18 +13115,14 @@ fn saved_session_is_newer(
|
||||
/// the same folder" from re-entering the previous in-flight session while still
|
||||
/// leaving an explicit resume path.
|
||||
///
|
||||
/// Only the newest recent checkpoint drives the notice. The legacy
|
||||
/// single-slot file is persisted as a regular session and consumed (today's
|
||||
/// behavior for that slot); per-session checkpoint files are persisted but
|
||||
/// left in place — they may belong to a live session in another terminal,
|
||||
/// and `--continue` reads them directly.
|
||||
fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path) {
|
||||
let Some(manager) = session_manager::SessionManager::default_location().ok() else {
|
||||
return;
|
||||
};
|
||||
let Some(newest) = load_recent_checkpoints(&manager).into_iter().next() else {
|
||||
return;
|
||||
};
|
||||
/// Only the newest recent checkpoint drives the notice, which is returned for
|
||||
/// the TUI to show. The legacy single-slot file is migrated to a per-session
|
||||
/// checkpoint and consumed; per-session checkpoint files are left in place —
|
||||
/// they may belong to a live session in another terminal, and `--continue`
|
||||
/// reads them directly.
|
||||
fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path) -> Option<String> {
|
||||
let manager = session_manager::SessionManager::default_location().ok()?;
|
||||
let newest = load_recent_checkpoints(&manager).into_iter().next()?;
|
||||
|
||||
let session_workspace = newest.session.metadata.workspace.clone();
|
||||
// #4479: removed save_session call — checkpoint should not be auto-promoted to session
|
||||
@@ -13139,20 +13135,22 @@ fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path)
|
||||
}
|
||||
|
||||
let age_str = checkpoint_age_label(newest.age);
|
||||
if session_manager::workspace_scope_matches(&session_workspace, launch_workspace) {
|
||||
eprintln!(
|
||||
"Found an in-flight session snapshot ({age_str}). Starting a new \
|
||||
session. Run `codewhale --continue` to resume it."
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Note: an interrupted session from another workspace ({}) is \
|
||||
available. Run `codewhale sessions` to list saved sessions. Starting \
|
||||
fresh in {}.",
|
||||
session_workspace.display(),
|
||||
launch_workspace.display(),
|
||||
);
|
||||
}
|
||||
Some(
|
||||
if session_manager::workspace_scope_matches(&session_workspace, launch_workspace) {
|
||||
format!(
|
||||
"Found an in-flight session snapshot ({age_str}). Starting a new \
|
||||
session. Run `codewhale --continue` to resume it."
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Note: an interrupted session from another workspace ({}) is \
|
||||
available. Run `codewhale sessions` to list saved sessions. Starting \
|
||||
fresh in {}.",
|
||||
session_workspace.display(),
|
||||
launch_workspace.display(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Load project-level config from `$WORKSPACE/.codewhale/config.toml`, with
|
||||
|
||||
Reference in new issue
Block a user