fix(agent-chat): stop labeling a live run as interrupted after a workspace switch#164
Open
Zeus-Deus wants to merge 1 commit into
Open
fix(agent-chat): stop labeling a live run as interrupted after a workspace switch#164Zeus-Deus wants to merge 1 commit into
Zeus-Deus wants to merge 1 commit into
Conversation
…space switch
Switching workspaces unmounts the inactive pane while its run keeps
going and keeps persisting rows. On switch-back the remount hydrate
recomputed interrupted purely from the persisted tail, and a healthy
mid-flight run has no turn_completed after its last user turn yet, so
lastTurnUnsettled flagged it and the pane showed a false "Run
interrupted" divider plus a Continue chip until the next live event
cleared them. Subagent-heavy runs hit this reliably because their
item_completed rows grow the disk transcript past the frozen slice,
which is exactly what lets the remount hydrate guard pass.
Give hydrate an authoritative signal instead of the heuristic alone:
- New AgentProvider::turn_active default trait method: true iff a live
(non-dead) session is bound to the thread and its active_turn is set.
Implemented for Claude and Codex as a cheap in-memory sessions map
check, and for OpenCode via the SSE event context turn_active flag
behind the same absent/dead guards. Never touches the subprocess.
- New agent_chat_turn_active command exposing it, read-only, no
auto-resume; registered alongside the other agent_chat commands.
- The remount hydrate effect probes it after fetching payloads (if the
turn settles between the two calls the turn_completed row is already
in the payloads, so a stale "alive" answer cannot mislabel a finished
run) and threads hydrateThread(threadId, payloads, { runLive })
through to replayPayloads. runLive true forces interrupted false,
suppressing both the unsettled-tail heuristic and a replay-observed
child_exited, and sets streaming true so the streaming marker shows
instead of the divider. A probe error degrades to false, keeping the
previous behavior.
- Dev mock implements the command from its in-flight turn set; docs
cover the new guard and command.
Verified with npm run verify (cargo check + cargo test, tsc, vitest:
171 files / 2522 tests) plus new coverage: Rust adapter tests for
unknown thread, in-flight, settled, and dead-session cases on Claude
and Codex; hydrate unit tests for the runLive override; pane tests for
probe-true, probe-rejects, and fallback routing. Exercised end to end
in the browser against the dev mock: the probe flips false/true/false
across a streamed turn, switching away and back mid-run no longer
surfaces the divider, and a genuine child_exited interrupt still
renders the divider and Continue chip.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Switching workspaces unmounts the inactive workspace's chat pane while its run keeps going in the backend. On switch-back, the remount hydrate recomputes the
interruptedflag purely from the persisted transcript: a healthy mid-flight run has noturn_completedafter its last user turn yet, solastTurnUnsettledflags it and the pane shows a false "Run interrupted" divider + Continue run chip over a run that is actually alive (it then "continues by itself" once the next live event clears the flag).Subagent-heavy runs hit this reliably: their
item_completedrows grow the disk transcript past the frozen in-memory slice, which is exactly the condition that lets the remount hydrate guard pass.Fix
Give hydrate an authoritative liveness signal instead of the heuristic alone:
AgentProvider::turn_active— new default trait method:trueiff a live (non-dead) session is bound to the thread and itsactive_turnis set. Cheap in-memory check, never touches the subprocess. Implemented for Claude and Codex via the sessions map, and for OpenCode via the SSE event-context flag behind the same absent/dead guards.agent_chat_turn_active— new read-only command exposing it (no auto-resume).turn_completedrow is already in the payloads — a stale "alive" answer can't mislabel a finished run) and threads{ runLive }throughhydrateThread→replayPayloads.runLive: trueforcesinterrupted: falseand setsstreaming: trueso the streaming marker shows instead of the divider. A probe error degrades tofalse(previous behavior).docs/features/agent-chat.mddocuments the new guard and command.The genuine cases keep working: dead-run detection (child-exit watchdog, app-died-mid-turn hydrate) still surfaces the divider and Continue chip.
Verification
npm run verifygreen: cargo check + cargo test, tsc, vitest (171 files / 2522 tests).runLiveoverride (incl. overriding a replay-observedchild_exited); pane tests for probe-true, probe-rejected fallback, and arg routing.false → true → falseacross a streamed turn; switching away and back mid-run no longer surfaces the divider; a genuinechild_exitedinterrupt still renders the divider + Continue chip.