feat(orchestration): Control Panel — live coordinator DAG in Mission Control (O1 #6 + O2 #7) - #22
Conversation
…enderer (O1, #6) Today only three aggregate ints reach the renderer via OrchestrationActivity. Extend the main→renderer graph sync to carry, per running coordinator and keyed by coordinator paneKey, the run's live task DAG: each task's status + deps + title + target_key, its active dispatch (assignee handle/agent, status, last heartbeat, computed stale flag), and the latest worker signal (most recent heartbeat phase or worker_done summary from the messages table). - listTasksWithDispatch now also surfaces the active dispatch status + last_heartbeat_at (aliased to avoid colliding with the task's own status); the taskList RPC strips them so its shape is unchanged. - New pure assembler (run-dag-snapshot.ts) maps DB rows → OrchestrationRunDag, bounds the payload (task cap + truncation count), and indexes the latest worker signal per dispatch. Run-scoped per #12 so concurrent runs don't bleed. - buildOrchestrationRunDagByPaneKey() wires DB + per-handle agent resolution into the assembler alongside the existing activity builder. - New orchestration-run-dag store slice reuses the equality-check pattern from orchestration-activity to avoid re-render churn on high-frequency title ticks. Behind experimentalOrchestrators; additive to the sync; mode-agnostic (LLM and future recipe directors both emit the same shape). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ol (O2, #7) Rewire OrchestratorMissionControl from pure-lineage to DAG-backed. When the director has a live coordinator run, render its task DAG using Orca's existing visual vocabulary — AgentStateDot for status, the shared agent glyphs for who is working — instead of inventing new status visuals: - Director dot now uses deriveOrcastratorDotState so a director that handed control back reads as supervising/stalled while its run is live. - Header shows the OrchestrationActivity counts (N tasks · M workers · K stalled) and a recipe line when present (absent for LLM directors). - TASKS section renders one row per task: dot from task status (pending/ready→ idle, dispatched→working, stale heartbeat→stalled, blocked→blocked, completed→done, failed→interrupted), icon from the dispatch's agent, message from the latest heartbeat phase / worker_done summary / "waiting on <dep>". - Pure mappers (orchestrator-task-row.ts) + per-run selectors keep the logic testable; row/section/summary split into focused components (line cap). Mode-agnostic: the same panel renders LLM and future recipe directors. When a director has no coordinator run yet, the existing lineage "Spawned work" + Shipped sections render unchanged, so nothing regresses. Behind experimentalOrchestrators; STYLEGUIDE tokens; new strings synced across locales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review panel — not merge-ready (top findings verified against source)3-lens panel; the load-bearing items re-verified against source by the coordinator. No blockers, but a real perf regression + a feature gap. 🟠 Must-fix
🟠 Should-fix
NitsDedup |
…er_done on completed rows (#22 round 2) Addresses the round-2 review must-fixes on the Control Panel DAG sync: PERF (#1): buildOrchestrationRunDagByPaneKey ran the heavy listTasksWithDispatch + getAllMessagesForHandle(500) per run on every 16ms-coalesced syncWindowGraph tick. Now a cheap per-run change token (getRunDagChangeToken: MAX(rowid) of the run's tasks + dispatch_contexts, and MAX(sequence) of the coordinator handle's messages) is computed each tick; the heavy work runs only when the token moves, otherwise the cached snapshot is reused. The cache is pruned to the running set so it can't grow unbounded. FLAG (#2): the builder is gated on experimentalOrchestrators (read from the runtime store), so un-opted-in users with a running coordinator no longer pay the cost for a panel they can't see. worker_done (#3): listTasksWithDispatch joins only pending/dispatched dispatches, so a completed task's row has dispatch_id = null and a dispatch-keyed signal never landed the worker_done summary. Worker signals are now keyed by task_id (present on both heartbeat and worker_done payloads), which reaches completed rows. New real-join test (in-memory DB → complete a task with a worker_done → assert the summary lands) drives the actual join and fails against the dispatch-keyed code. stale grace (#4): per-task stale now mirrors getStaleDispatches exactly — requires dispatched_at past the grace AND a stale/null heartbeat — so a freshly-dispatched worker no longer flashes amber while the aggregate count shows 0. listTasksWithDispatch surfaces dispatched_at for this. Tests (#6) + nits: main-side buildOrchestrationRunDagByPaneKey tests for cross-pane no-bleed (two runs → distinct panes, task + signal scoping) and the change-token cache (heavy queries run once while the token is stable, again when it moves), plus the flag-off omission. Dedup the running-runs + hung-threshold across the activity and DAG builders; wrap the per-run loop so a malformed run can't forfeit the whole sync tick; blank title → task id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unts line (#22 round 2) Round-2 review should-fixes on the renderer: i18n (#7): the task row status words (queued/working/stalled/blocked/done/ failed) and the "waiting on <dep>" hint were hardcoded English body text. The mappers now return stable tokens (TaskStatusLabel) / a structured TaskMessage, and the row component routes them through translate() with new locale keys synced across all catalogs. Worker-authored content (heartbeat phase / worker_done summary) still renders verbatim. counts label (#5): the supervision summary shows pendingTasks (outstanding, not-yet-terminal) but labeled it "N tasks", conflicting with the tasks-section header's total task count. Relabeled to "N outstanding". nit: a "waiting on <dep>" hint is omitted (rather than rendering a raw uuid) when the blocking dep isn't in the synced set — e.g. truncated past the 200-task cap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round 2 — must-fix #1–#3 + should-fix #4–#7 + nits landedPushed two commits ( Must-fix
Should-fix
NitsDeduped Verificationtypecheck (node/web/cli), electron-vite build, oxlint, and localization parity all green. Full vitest green for everything touched (orchestration DB/builder/runtime + renderer). The only full-suite failures were pre-existing flaky native-PTY / git-relay / subprocess / dev-startup suites that pass in isolation — unrelated to this change. Also fixed a pre-existing missing Not merging. |
Part of #5. Implements O1 (#6) and O2 (#7): make the coordinator's live task DAG visible in Mission Control. Behind
experimentalOrchestrators; additive; no regression to the existing aggregate-count path or the shipped-log section.O1 — sync the per-run task DAG + dispatch detail to the renderer
Previously only three aggregate ints crossed to the renderer (
OrchestrationActivity). Now the main→renderer graph sync also carries, per running coordinator, keyed by coordinator paneKey, run-scoped per F1 (#12):{ id, status, deps, title (display_name ?? task_title ?? first spec line), targetKey }fromlistTasksWithDispatch({ coordinatorRunId }).{ assigneeHandle, assigneeAgent, status, lastHeartbeatAt, stale }.assigneeAgentis resolved in main from the worker PTY's launch agent;staleis computed in main (it owns the clock + hung threshold) so the renderer stays pure.heartbeatphase orworker_donesummary from themessagestable (addressed to the run's coordinator handle, so it stays run-scoped without a run column on messages).listTasksWithDispatchnow also surfaces the active dispatch'sstatus+last_heartbeat_at(aliased to avoid colliding with the task's ownstatus); thetaskListRPC strips them so its shape is unchanged. A pure assembler (run-dag-snapshot.ts) maps DB rows →OrchestrationRunDag, caps the payload (200 tasks, reports the truncated count,console.warnon truncation), and indexes the latest signal per dispatch. A neworchestration-run-dagstore slice reuses the equality-check pattern fromorchestration-activityto avoid re-render churn on high-frequency title ticks.O2 — rewire Mission Control to DAG-backed + render the live Control Panel
OrchestratorMissionControlnow renders the coordinator's DAG using Orca's existing visual vocabulary —AgentStateDotfor status and the shared agent glyphs (AgentIcon) for who's working. No new status visuals invented.Field mapping:
deriveOrcastratorDotState(supervising/stalled)OrchestrationActivity(N tasks · M workers · K stalled)OrchestrationRunDag.recipe(absent for LLM directors)assigneeAgent→ agent glyph.orcastrate/log.jsonlparse (unchanged)Mode-agnostic + fallback
The same panel renders the current LLM director and a future recipe director — both emit the same
CoordinatorRun+ tasks + dispatches. When a director has no coordinator run yet (e.g. an LLM director that hasn't calledorchestration.run), the existing lineage "Spawned work" + Shipped view renders unchanged, so nothing regresses.Constraints
STYLEGUIDE tokens + shadcn +
AgentStateDot; cross-platform; SSH/remote (sync already crosses that boundary). AGENTS.md: nomax-linesdisable (row/section/summary split into focused components),.tsover.d.ts, comments document the why, concrete file names. New UI strings synced across all locale catalogs.Test evidence
run-dag-snapshot.test.ts(stale derivation, signal latest-wins for heartbeat phase / worker_done summary, deps parsing, task cap + truncation, recipe passthrough);db.test.ts(new dispatch columns surfaced;listTasksWithDispatch({ coordinatorRunId })scoping — two runs don't bleed);orchestration-run-dag.test.ts(store equality avoids churn, replaces on change);orchestrator-mission-control-data.test.ts(selector picks the director's run by paneKey tab id; concurrent runs don't bleed).orchestrator-task-row.test.ts(status→AgentStateDot mapping for each status incl. working vs stalled; message derivation);MissionControlTaskRow.test.tsx(correct dot/glyph/message per status);MissionControlTasksSection.test.tsx(row per task, empty state, truncation note);OrchestratorMissionControl.dag.test.tsx(renders DAG + counts when a run exists; falls back to lineage view when none; Shipped still renders).TuiAgenttypecheck errors inorca-runtime.test.ts— both unrelated to this change). typecheck (node/web/cli), electron-vite build, and oxlint all green; localization catalog parity verified.🤖 Generated with Claude Code