fix(agent-ui): keep chat runs alive after New Task + show running indicator (#1580)#1656
Conversation
…icator (#1580) Clicking New Task (or switching sessions) while an agent was still generating used to abort the run: the SSE was torn down on the old view's unmount, the backend cancelled the turn, and the in-flight answer was discarded with no trace it had ever been running. The user lost both the work and any way to tell the original agent was still going. Now a chat turn is a first-class background run that outlives the SSE connection. Navigating away leaves it running server-side; it finishes and persists to the DB on its own. The sidebar shows a spinner on any session with a live run (backend-truth, so it survives refresh and revisit), and re-opening a running session re-attaches to its live stream so progress resumes in the view. - RunManager owns each run (producer thread + persistence) in a detached task; the SSE endpoint is now a thin subscriber over a replay buffer with multi-subscriber fan-out. - GET /api/chat/active surfaces running session ids; GET /api/chat/attach reconnects to an in-flight run. - Stop now calls the cancel endpoint explicitly (a client disconnect no longer cancels the run), and deleting a session cancels its run so it can't persist to a dead session.
SummarySolid, well-tested fix for the #1580 follow-up: a chat turn becomes a first-class No critical or security issues. The threading-atomicity reasoning in Issues🟡 ImportantDelete-while-running can persist to a dead session (
For the common path this is narrow, but data integrity on delete is worth hardening. Options: have the producer re-check session existence before its final persist, or gate 🟢 Minor
Strengths
VerdictApprove with suggestions. No blocking issues. Please confirm (or guard) the delete-while-running persistence race before merge — if the orphaned-row outcome is benign in your schema, a one-line note suffices; otherwise a re-check before the producer's final |
The Agent UI Vitest suite was silently broken on `main`: any PR touching a `webui/` path (e.g. amd#1694, an installer fix) fails CI with `TypeError: Cannot read properties of undefined (reading 'then')` in `ChatView.test.tsx`, even when the PR has nothing to do with the UI. This unblocks those PRs. Root cause: amd#1656 added an `api.getActiveRuns().then(...)` call to `ChatView`'s mount effect but never stubbed `getActiveRuns` in the test's auto-mocked api module, so it returned `undefined`. `main`'s own Vitest is path-filtered and hadn't re-run since the regression landed, so it stayed latent until a webui-touching PR triggered it. ## Test plan - [ ] `cd src/gaia/apps/webui && npx vitest run` — all 62 tests pass - [ ] `npx vitest run src/components/__tests__/ChatView.test.tsx` — the previously-failing test passes
Why this matters
Follow-up to #1584. Before: clicking New Task (or switching sessions) while an agent was still generating killed the run — the SSE was torn down on unmount, the backend cancelled the turn, and the in-flight answer was discarded with no sign it had ever been running. @sdevinenamd reported the visible half ("losing the status bar… hard to tell if the original agent is still running") while waiting on the email-triage agent. After: the run keeps going in the background, finishes and persists on its own, the sidebar shows a spinner on any session with a live run, and re-opening that session re-attaches to its live stream so progress resumes.
A chat turn is now a first-class background run (
RunManager) that outlives the SSE connection: the producer + persistence run in a detached task, and the SSE endpoint is a thin subscriber over a replay buffer with multi-subscriber fan-out.GET /api/chat/activeis the backend-truth source for the sidebar indicator (survives refresh/revisit);GET /api/chat/attachreconnects to an in-flight run. Because a disconnect no longer cancels, Stop now calls the cancel endpoint explicitly, and deleting a session cancels its run so it can't persist to a dead session.Test plan
pytest tests/unit/chat/ui/— 720 passed (1 pre-existing Windows cp1252 flake in an unrelatedagent.py-reading test, deselected). Includes newtest_run_manager.py(run survives subscriber disconnect & persists, late-subscriber replay, active list, overlap-409, cancel) andtest_chat_active_attach.py(active/attach endpoints, send-409-while-running, delete-cancels-run).tsc --noEmit— clean on all app code;vite build— clean.vitest run(addedchatStorerunning-sessions cases) — runs in CI; not executed locally (vitest absent from the local env).