Announce workflow run updates only after they are saved, and close helpers safely
A run card and its finish message were broadcast before they were saved, so a failed save left people watching a job finish that a reload then lost. The update is now built as a candidate outside shared chat state and saved first: the save stores the candidate transcript and run cards (read when the commit runs, so messages added meanwhile are kept), and only when it succeeds are the card and finish message added to the chat and announced together. While a save is pending, or after it fails, nothing else that broadcasts chat state or greets a joining client can show the unsaved card or message. Reports are handled one at a time, so a report that arrives while an earlier save is pending is computed from the state after that save settles. When the save of a report fails, that report is remembered. If the helper is released afterwards, the release first waits for pending reports and then publishes the remembered report instead of treating the runs as cut off, so a transient storage error no longer turns a finished run into a "was stopped" card; the finished card and its single finish message are saved and announced together. Other review fixes from Maggie Appleton on the same code: - A quick job that ended before the Planner's reply finished now keeps its last card and gets its finish message. - A run first reported as already ended gets one finish message, and a run already recorded as ended never gets another. - When the last job ends while a reply (or a queued reply) is still running, cleanup is checked again once it finishes and releases only the same helper, and only if it still has no active or paused runs. - A slow release of an old helper no longer clears the owner or overwrites the run cards of a newer helper. Every run card belongs to the Planner session that reported it, and that is the only rule for reports and releases. A report from a session replaces or updates only the cards that session owns and appends its new ones; live cards of another session still being released are carried through unchanged, so a newer helper reporting its own runs no longer drops an older helper's card. Releasing a session replays its own remembered unsaved report first (a failed final finished report still publishes as finished), then marks stopped exactly the unended cards it owns, saving and announcing one "was stopped" message each in a single save. Cards owned by another session, and the owner and agent of a newer helper, are never touched; the owner is cleared only when the released session is still the current agent and no turn is running. If the final save also fails, nothing is announced and the cards of the released session are stopped by the next report from any session. Assistant-model: Claude Sonnet 5.5 Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Assistant-verification: table test "releasing an old helper after %s stops exactly its own cards once" (four cases: none, run-less turn, own runs, run-less turn after a failed save) failed before the fix for the own-runs case (run-1 dropped from chat.runs when the newer helper reported run-2; 25 pass, 1 fail in invoke.test.ts) and passes for all four after (26 pass, 0 fail); earlier round-1 to round-5 tests stay green Assistant-verification: bun test passed: 1918 pass, 2 PostgreSQL skips, 0 fail (private TMPDIR) Assistant-verification: bun run types passed: all workspaces and e2e Assistant-verification: bun run ci passed: dprint, oxlint (0 errors, 6 warnings, none in touched files), tokens, design contract, design record, Impeccable Co-authored-by: Alex Lavaee <lavaman131@github.com>
Parentcf2058d
4 files changed+579-33
StackedSplit