fix(ui): reconcile runtime status so stale chips converge without a refresh (#11)
Live status deltas are one-shot best-effort broadcasts: a delta lost to a
disconnect window, a project-scope drop, or a not-ready store left the UI
stuck on a stale status ("thinking" vs stopped) until a hard refresh, which
rebuilds from the always-correct snapshot. Close the class, not the sites:
- Add a low-frequency runtime_status_sync reconciliation broadcast: every
12s (lazily started, cancelled on shutdown) re-broadcast the persisted
status + in-memory tracker state of every task with a live runtime, plus
one final tick for tasks that just ended. Candidates come purely from
in-memory registries (no table scans); idle system pays nothing.
- Frontend consumes it diff-before-dispatch: a tick where nothing drifted
triggers zero store updates and zero re-renders; clearing mirrors the
mergeLiveRuntimeField semantics already used by collab_sync.
- Fix the EventAdapter tracker state machine: tool_completed returns to
REFLECTING (the turn is still running), and turn_completed/turn_failed
now transition to IDLE and emit an authoritative idle runtime update.
- Guarantee the terminal board_task_status_changed in _run_session_task's
finally: a cancelled run previously skipped it, leaving the board on
"running". The fallback mirrors persisted state read-only.
Verified: 7 new tests in test_runtime_status_sync.py; 236 backend tests
pass with zero new failures; tsc clean; frontend structural tests pass;
dist rebuilt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -207,6 +207,8 @@ class EventAdapter:
|
||||
results.append(_ve(agent_id, runtime_type, dict(p)))
|
||||
if runtime_type in {
|
||||
"turn_started",
|
||||
"turn_completed",
|
||||
"turn_failed",
|
||||
"tool_started",
|
||||
"tool_progress",
|
||||
"tool_completed",
|
||||
@@ -233,6 +235,11 @@ class EventAdapter:
|
||||
elif runtime_type in {"turn_started", "permission_requested", "permission_resolved", "subagent_started", "subagent_updated", "verification_started"}:
|
||||
tracker.state = AgentAnimState.REFLECTING
|
||||
elif runtime_type in {"tool_completed", "subagent_completed", "verification_completed"}:
|
||||
# The turn is still running between tools — the agent is
|
||||
# back to reasoning, not idle. IDLE is owned by turn end.
|
||||
tracker.current_tool = None
|
||||
tracker.state = AgentAnimState.REFLECTING
|
||||
elif runtime_type in {"turn_completed", "turn_failed"}:
|
||||
tracker.current_tool = None
|
||||
tracker.state = AgentAnimState.IDLE
|
||||
extras: dict[str, Any] = {}
|
||||
|
||||
Reference in New Issue
Block a user