feat(coordinator): frontend store wiring, IPC lifecycle, and session restore (PR 3/4)#124
feat(coordinator): frontend store wiring, IPC lifecycle, and session restore (PR 3/4)#124brooksc wants to merge 2 commits into
Conversation
…task model Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…restore Wires the coordinator MCP engine (PR 2) into the frontend store and Electron IPC layer. Feature is dark by default — coordinatorModeEnabled defaults to false; nothing is user-visible until PR 4 adds the Settings checkbox and NewTaskDialog entry point. Key additions: - Task model: coordinatorMode, coordinatedBy, controlledBy, stagedNotification, mcpStartupStatus, signalDone*, needsReview, and 3 global coordinator fields - initMCPListeners(): subscribes to 7 MCP push events (TaskCreated, TaskClosed, CleanupFailed, NotificationStaged/Cleared/Orphaned, TaskStateSync, TaskHydrated) - MCP startup state machine: markTaskMcpPending/Ready/Error, retryTaskMcpStartup - setTaskControl(): coordinator ↔ human hand-off; collapseTask guard for children - closeTask(): calls MCP_CoordinatedTaskClosed / MCP_CoordinatorDeregistered on close - Session restore: App.tsx calls StartMCPServer for each persisted coordinator task on startup, rewriting .mcp.json with the new session port/token; children start pending until MCP_HydrateCoordinatedTask returns - Persistence: all coordinator fields round-tripped through saveState/loadState; tasks with coordinatorMode/coordinatedBy load with mcpStartupStatus pending - coordinator-preamble.ts: system preamble injected into coordinator agent prompts - mcpStatus.ts: polls GetMCPStatus every 3 s; inert when no coordinator is active - Deregister fix: suppresses spurious needsReview notification for children that never received their prompt (assignedPromptDelivered false) - Tests: ~40 new cases across tasks, notifications, taskStatus, persistence Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Reviewed the stacked delta from #120 (
|
Overview
This is PR 3 of 4 in the coordinator series splitting #100 as requested in the round-4 review. It is stacked on PR 2 (#120) (
coordinator-2-mcp-backend) and must be merged after that one. The diff shown here includes PRs 1–2's content; the meaningful delta for this PR is the frontend store wiring and IPC lifecycle described below.PR sequence:
coordinator-1-securitycoordinator-2-mcp-backendcoordinator-3-store-ipccoordinator-4-uiNothing coordinator-related is user-visible until PR 4 adds the
NewTaskDialogcheckbox and Settings toggle (coordinatorModeEnableddefaults tofalse).What's in this PR (delta over PR 2)
Task model extensions (
src/store/types.ts)New fields on
Task/CollapsedTask:coordinatorMode,coordinatedBy,controlledBy('coordinator' | 'human'),propagateSkipPermissionsmcpConfigPath,mcpStartupStatus('pending' | 'ready' | 'error'),mcpStartupErrorsignalDoneReceived,signalDoneAt,signalDoneConsumed,needsReview,initialPromptstagedNotification: StagedNotification— pending coordinator batch notificationcoordinatorModeEnabled,coordinatorNotificationDelayMs,coordinatorControlHintDismissedMCPStatustype for frontend pollingMCP lifecycle (
src/store/tasks.ts)New store functions wired to IPC:
initMCPListeners()— subscribes to 7 MCP push events (MCP_TaskCreated,MCP_TaskClosed,MCP_TaskCleanupFailed,MCP_CoordinatorNotificationStaged/Cleared/Orphaned,MCP_TaskStateSync,MCP_TaskHydrated)markTaskMcpPending/Ready/Error()— MCP startup state transitionsretryTaskMcpStartup()— retry after error, with coordinator-dependency guardsetTaskControl()— togglescontrolledBybetween'coordinator'and'human'clearStagedNotification(),setStagedNotificationUserEdited()getCoordinatorCloseWarning()— warning text when closing a coordinator with active childrenreorderTaskVisually()— drag-reorder respecting coordinator group boundariescollapseTask()guard — no-op for coordinated children (prevents orphaning from coordinator group)closeTask()— callsMCP_CoordinatedTaskClosed/MCP_CoordinatorDeregisteredon close; errors swallowed so the task is always removedSession restore (
src/App.tsx)On startup, iterates all persisted tasks with
coordinatorMode === trueand callsStartMCPServerfor each, rewriting.mcp.jsonconfig with the new session's port and token. Child tasks start withmcpStartupStatus: 'pending'(terminal spawn deferred) untilMCP_HydrateCoordinatedTaskreturns.Persistence (
src/store/persistence.ts)saveState()/loadState()now round-trips all coordinator fields:coordinatorMode,coordinatedBy,controlledBy,propagateSkipPermissions,mcpConfigPath,signalDone*,needsReview. Global fieldscoordinatorModeEnabled,coordinatorNotificationDelayMs,coordinatorControlHintDismissedalso persisted. Tasks withcoordinatorModeorcoordinatedByload withmcpStartupStatus: 'pending'to defer terminal spawn until the session's MCP server is ready.Coordinator preamble (
src/store/coordinator-preamble.ts)System preamble prepended to the coordinator agent's initial prompt. Documents all 10 MCP tools and operating rules.
MCP status polling (
src/store/mcpStatus.ts)Polls
GetMCPStatusIPC every 3 s; stores result instore.mcpStatus. Returns{ running: false, ... }when no coordinator is active — no user-visible effect until a coordinator task exists.Staged notification store (
src/store/taskStatus.ts)getTaskStatus()/getTaskDotStatus()return'review'for tasks withneedsReviewset. Replace-on-arrival: new batch replaces the previous one before it fires;userEditedresets per-notification (not sticky).Backend fix (
electron/mcp/coordinator.ts)In
deregisterCoordinator, child tasks that never received their prompt (assignedPromptDeliveredfalse) havereviewNotificationQueuedset — suppresses a spurious "needs review" notification for tasks the user never saw.IPC preload (
electron/preload.cjs)New channels exposed:
set_coordinator_mode_enabled,mcp_hydrate_coordinated_task,mcp_task_hydrated,mcp_coordinated_task_closed,mcp_task_cleanup_failed.Tests
src/store/tasks.test.tscontrolledBystate machine,MCP_TaskCreatedhandler,collapseTaskchild guard,hasActiveCoordinator, MCP startup transitions (pending/ready/error/retry/child-failure),MCP_TaskCleanupFailed,closeTaskIPC orderingsrc/store/notifications.test.tsuserEditedper-notification reset,clearStagedNotificationsrc/store/taskStatus.test.tsgetTaskStatus/getTaskDotStatusreturn'review'forneedsReview; busy overrides reviewsrc/store/persistence.test.tselectron/mcp/docker.integration.test.tscoordinatorTaskIdchanged from'coord-1'to a valid UUID constantAssumptions and important notes
getCoordinatorChildren/isCoordinatedChildfromsidebar-order.tsbecause PR 2 provides them. Without PR 2, the app will not typecheck.mcpStatus.tspolls every 3 s as soon as this lands, but returns empty status until a coordinator task exists.StartMCPServercalls fire in parallel for all coordinator tasks on startup. Each child staysmcpStartupStatus: 'pending'until hydration completes. The UI for that state lives in PR 4.agentId(parallel-code-{agentId.slice(0,12)}). The container is likely dead after restart; wiring exists so the user can manually restart the coordinator agent.coordinatorModeEnableddefaults tofalse. No user-visible change until PR 4 adds the Settings checkbox.🤖 Generated with Claude Code