mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 10:36:05 -10:00
fix(tui): accept MCP status from a replacement engine (CW-7430)
A replaced engine (provider or workspace switch, /resume, /load, consent recovery) starts its MCP event generation at 0. The App kept the previous engine's high-water mark in mcp_snapshot_generation, and its guard drops any McpSessionBoot or supervisor update below that mark. After a respawn, the MCP panel and /mcp status therefore stayed stale until the new engine's counter overtook the old one, or until /mcp itself reset the mark. Every TUI engine replacement goes through spawn_tui_engine. It now takes the App, builds the engine config itself (all nine call sites already passed build_engine_config(app, config)), and resets the App's MCP generation mark and its invalidated flag for the new engine. The old engine's channel is dropped with its handle, so none of its events can arrive after the reset. Verification on this tree: - cargo fmt --all -- --check: clean. - clippy -p codewhale-tui --all-targets --locked with CI's flags: clean. - nextest, focused on mcp, respawn, provider switch, workspace switch, session state and engine replacement: 641 run, 641 passed, 14952 skipped. - npm test: 547 Node tests (540 passed, 7 skipped, 0 failed) and 772 Vitest passed. npm run check:web: exit 0. - Not run: an interactive provider switch in a real terminal. Refs CW-7430 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
1 parent
53918dd21b
commit
b5e270b690
6 files changed
+14
-18
No files matched your search
@@ -489,8 +489,10 @@ mod memory_quick_add_tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_tui_engine(config: EngineConfig, api_config: &Config) -> EngineHandle {
|
||||
let handle = spawn_engine(config, api_config);
|
||||
fn spawn_tui_engine(app: &mut App, api_config: &Config) -> EngineHandle {
|
||||
let handle = spawn_engine(build_engine_config(app, api_config), api_config);
|
||||
app.mcp_snapshot_generation = 0;
|
||||
app.mcp_snapshot_generation_invalidated = false;
|
||||
// Prime durable agent + coordination state through the same engine event
|
||||
// used by later refreshes. All TUI engine replacements use this wrapper,
|
||||
// so workspace switches and provider recovery cannot retain stale Work.
|
||||
@@ -502,7 +504,7 @@ fn spawn_tui_engine(config: EngineConfig, api_config: &Config) -> EngineHandle {
|
||||
/// before admitting any pending input. The existing engine remains the sole
|
||||
/// owner of model-facing history and the frozen system prefix.
|
||||
async fn spawn_tui_engine_with_session(app: &mut App, config: &Config) -> Result<EngineHandle> {
|
||||
let handle = spawn_tui_engine(build_engine_config(app, config), config);
|
||||
let handle = spawn_tui_engine(app, config);
|
||||
let restored = async {
|
||||
if !app.api_messages.is_empty() {
|
||||
handle
|
||||
|
||||
@@ -1283,8 +1283,7 @@ pub(crate) async fn apply_provider_fallback_switch(
|
||||
}
|
||||
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
let engine_config = build_engine_config(app, config);
|
||||
*engine_handle = spawn_tui_engine(engine_config, config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
|
||||
if !app.api_messages.is_empty() {
|
||||
let _ = engine_handle
|
||||
@@ -1593,7 +1592,7 @@ async fn apply_command_result_inner(
|
||||
sync_runtime_workspace_state(task_manager, app.workspace.clone()).await;
|
||||
if respawn {
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
*engine_handle = spawn_tui_engine(build_engine_config(app, config), config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
} else {
|
||||
let _ = engine_handle
|
||||
.send(Op::SetModel {
|
||||
@@ -1703,7 +1702,7 @@ async fn apply_command_result_inner(
|
||||
app.update_model_compaction_budget();
|
||||
if provider_changed || workspace_changed {
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
*engine_handle = spawn_tui_engine(build_engine_config(app, config), config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
}
|
||||
// SyncSession carries the conversation but not resolved route
|
||||
// limits. Refresh the engine's model first so a loaded,
|
||||
@@ -1830,7 +1829,7 @@ async fn apply_command_result_inner(
|
||||
);
|
||||
}
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
*engine_handle = spawn_tui_engine(build_engine_config(app, config), config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
if !app.api_messages.is_empty() {
|
||||
let _ = engine_handle
|
||||
.send(Op::SyncSession {
|
||||
@@ -2929,8 +2928,7 @@ async fn apply_command_result_inner(
|
||||
crate::config::initialize_cloud_facts(config);
|
||||
// Rebuild the engine with the new config so API key/model/base URL take effect.
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
let engine_config = build_engine_config(app, config);
|
||||
*engine_handle = spawn_tui_engine(engine_config, config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
if !app.api_messages.is_empty() {
|
||||
let _ = engine_handle
|
||||
.send(Op::SyncSession {
|
||||
|
||||
@@ -4542,8 +4542,7 @@ pub(crate) async fn run_event_loop(
|
||||
}
|
||||
if let Some(rollback_warning) = respawn_after_provider_rollback {
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
let engine_config = build_engine_config(app, config);
|
||||
engine_handle = spawn_tui_engine(engine_config, config);
|
||||
engine_handle = spawn_tui_engine(app, config);
|
||||
if !app.api_messages.is_empty() {
|
||||
let _ = engine_handle
|
||||
.send(Op::SyncSession {
|
||||
|
||||
@@ -2030,8 +2030,7 @@ pub(crate) async fn handle_view_events(
|
||||
// only while a saturated engine finishes its turn.
|
||||
if respawn {
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
*engine_handle =
|
||||
spawn_tui_engine(build_engine_config(app, config), config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
} else {
|
||||
let _ = engine_handle
|
||||
.send(Op::SetModel {
|
||||
|
||||
@@ -787,8 +787,7 @@ pub(crate) async fn switch_provider(
|
||||
}
|
||||
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
let engine_config = build_engine_config(app, config);
|
||||
*engine_handle = spawn_tui_engine(engine_config, config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
// A successful in-session switch must refresh the same key-scoped live
|
||||
// catalog as startup. TelecomJS is currently the only provider using this
|
||||
// seam; failures preserve the existing/static rows.
|
||||
|
||||
@@ -915,8 +915,7 @@ pub(crate) async fn switch_workspace(
|
||||
sync_runtime_workspace_state(task_manager, workspace.clone()).await;
|
||||
|
||||
let _ = engine_handle.send(Op::Shutdown).await;
|
||||
let engine_config = build_engine_config(app, config);
|
||||
*engine_handle = spawn_tui_engine(engine_config, config);
|
||||
*engine_handle = spawn_tui_engine(app, config);
|
||||
if !app.api_messages.is_empty() {
|
||||
let _ = engine_handle
|
||||
.send(Op::SyncSession {
|
||||
|
||||
Reference in new issue
Block a user