feat: Board task DAG + Agent Profiles (dispatcher on Cue tick, worktree isolation, plugin-bus events, CLI parity) - #1272
Conversation
Introduce lightweight Profiles: a named override bundle layered on an existing base agent that overrides model, reasoning effort, role system-prompt, and optionally extra CLI args - same binary, cwd, and SSH config as the base agent. Profiles are the "assignee" primitive the Board (later phases) wires cards to. - src/shared/profiles/types.ts: AgentProfile type, pure resolveProfileSpawnOverrides (profile -> base agent -> undefined) mapping onto existing SpawnAgentOptions fields (no new spawn params), and a reusable validateAgentProfile guard. - src/main/profiles/profile-storage.ts: per-project .maestro/profiles.yaml load/save/list/upsert/delete using the shared js-yaml dep; skips malformed/duplicate entries with a logged warning. - IPC: profiles:list/upsert/delete handler + preload bridge + window.maestro.profiles namespace, following the Cue handler pattern. - UI: minimal ProfilesModal (list + create by base agent/model/effort/role), registered with useModalLayer and select-none. - Tests: resolution-order + validation unit tests; storage round-trip and malformed-skip tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce the persistent Board: a per-project task DAG stored in .maestro/board.yaml (human-diffable, git-trackable, mirroring the Cue and Phase 1 Profiles conventions). This phase is the data foundation only - no dispatcher (Phase 3) and no rich UI (Phase 4). - src/shared/board/types.ts: pure BoardCard / CardStatus / CardRun / WorktreeRef / Board contracts with per-status meaning comments, plus validateBoardCard/validateBoard guards mirroring Phase 1's profile validator. - src/shared/board/graph.ts: pure DAG helpers consumed by the Phase 3 dispatcher - getEligibleCards (todo cards with all parents done), getBlockers (not-done parents), hasCycle (iterative 3-color DFS; self-parent = cycle, dangling parent id ignored). Fully unit-tested (22 cases). - src/main/board/board-storage.ts: single owner of .maestro/board.yaml using the shared js-yaml dep (no new dependency). load/save/list/get + card mutations (addCard, updateCard, updateCardStatus, deleteCard); save rejects a cyclic graph via hasCycle, load skips malformed cards/boards with a logged warning. Adds BOARD_CONFIG_PATH to shared/maestro-paths.ts. 13 tests. - IPC: board:list/get/addCard/updateCard/setCardStatus/deleteCard handler + preload bridge + typed window.maestro.board namespace, following the Cue and Profiles handler pattern; registered in the main process. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the Board actually run: a board-dispatch pass on the existing Cue engine heartbeat tick drains each project's task DAG - promote eligible cards, spawn the assignee profile through the existing executeCuePrompt path (SSH/custom config honored), enforce a WIP cap, and move cards to done/blocked from completion markers (marker wins, else exit status). - src/shared/board/cardMarkers.ts: parseCardMarkers (card-complete / card-block), modeled on goalMarkers. - src/main/board/board-dispatcher.ts: pure-ish orchestrator with all side effects injected (testable without Electron); promote / WIP-cap claim / marker+exit completion / circuit breaker (2 failures) / stale-running reclaim. - src/main/board/board-spawn.ts: resolve card profile -> base agent and run via executeCuePrompt. - src/main/cue/cue-engine.ts: boardDispatchTick on the shared heartbeat tick (no second timer), one dispatcher per board, gated on BOTH maestroCue && board (Board requires Cue) read fresh each tick. - src/shared/plugins/first-party.ts: BOARD_FIRST_PARTY_PLUGIN (mirrors Cue; dispatch/spawn stay host-owned). - renderer: board Encore flag + default + Extensions beta tile. - tests: cardMarkers + dispatcher lifecycle (A->B->C under cap 1, block marker, circuit breaker) with fakes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visual kanban surface over the persistent task DAG (Phase 2 IPC), dispatched on the Cue tick (Phase 3). Six status columns, a card create/edit editor, parent-dependency badges, and manual drag-to-move. - src/renderer/components/BoardModal.tsx: six-column kanban, live-polled board state, inline card editor (title/body/assignee-profile/status/ optional worktree/parents), client-side hasCycle guard with inline error, HTML5 drag-to-move calling setCardStatus (coarse-pointer haptic), "waiting on N" blocker badges via getBlockers. - board:create IPC: createBoard in board-storage + handler + preload + global.d.ts, so the UI can stand up the first board on demand. - modalStore/AppStandaloneModals/App.tsx: boardModal state, lazy render gated on encoreFeatures.board, force-close when board OR maestroCue turns off (Board depends on Cue), entry point gated on both flags. - Entry points: Quick Actions "Board" command (threaded onOpenBoard through the AppModals chain) + "Open Board" button in the Extensions Board tile (mirrors Pianola). - Extensions dependency gate: BUILTIN_DEPENDENCIES (board -> maestroCue, first dependent flag) + isDependencyMet; ExtensionDetails disables the Board Enable toggle with a "Requires Maestro Cue" tooltip when Cue is off. - Fixed the Phase 1 ProfilesModal captureException signature bug. - Tests: BoardModal card-create + cycle-rejection interaction, createBoard storage, extensionModel dependency gate; updated stale BUILTIN_FEATURES enumeration to include board. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Phase 5) Round out the Board with headless CLI control, an OPTIONAL off-by-default auto-decompose layer, structured completion-handoff metadata, and docs. The manual Board (Phases 1-4) stays fully useful without any of the new layers. - CLI: new `maestro-cli board` (list / show / add-card / set-status / tick) and `maestro-cli profile` (list / create / delete) commands, registered in src/cli/index.ts. They delegate to the SAME Electron-free board/profile storage the IPC uses (no drift). `board tick` reuses the Phase 3 pure dispatcher helpers (promote/claim/apply/reclaim) and the existing CLI spawnAgent path - honoring SSH + profile model/effort/role/args overrides - so no second spawn implementation. Repo try/catch-at-boundary convention. - Auto-decompose (off by default): board-level `autoDecompose?: boolean` flag + new framework-free src/main/board/board-decompose.ts. Gated strictly on the flag; takes up to DEFAULT_AUTO_DECOMPOSE_PER_TICK (3) triage cards per pass, runs one LLM pass each, parses fenced/bare JSON, clamps dependsOn to earlier/in-range/non-self indices (always acyclic), wires children to the triage umbrella + siblings, and retires the triage card to done so it is never re-expanded. Editable prompt src/prompts/board-decompose.md registered as PROMPT_IDS.BOARD_DECOMPOSE. Wired into CLI `board tick` and the desktop engine (cue-engine maybeAutoDecompose, guarded fire-and-forget; index.ts wiring). - Handoff: shared CARD_HANDOFF_REMINDER (one copy for CLI + desktop) prepended to every card prompt, asking for a `card-complete | summary` marker covering the four handoff questions. Summary capture already lands via applyCardResult; BoardModal now shows an expandable "Last run summary". Optional metadata. - Docs: new docs/agent-guides/BOARD.md + user-facing docs/board.md (registered in docs.json under Encore Features), plus two CLAUDE.md key-files rows. No em/en-dashes; docs/releases.md untouched. - Tests: board-decompose (flag-off never expands + cap/wiring/retirement), board + profile CLI round-trips, autoDecompose YAML round-trip. tsc clean, eslint clean, 108 board/profile/CLI/UI tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Let any opted-in agent in a board's project directory pick up cards, instead of pinning every card to one fixed agent. The board stays per-project and git-tracked; the "who runs it" binding changes from one agent to a free pool. Model: - Pool = agents with `boardWorker: true` whose cwd is inside the board's project dir (or a sub-folder). Opt-in via a toggle on EditAgentModal (default OFF, so interactive agents are never hijacked). - A card names a role (`assigneeProfileId`) and/or pins an agent (`assigneeAgentId`); at least one is required. Role-only cards float to any free worker; pinned cards run on that agent with its own settings. - `AgentProfile.baseAgentId` is now optional: absent = a pure pool role. - One card per worker (tracked via `CardRun.workerAgentId`, recomputed from the persisted board each tick so it survives restarts). All workers busy -> the card stays `ready` and retries next tick (no waiting/reservation). Dispatcher: additive `assign` dep + `claimReadyCardsPooled` + pooled tick path; the legacy single-agent path is untouched when `assign` is absent. Pool selection lives in one shared, pure module (`shared/board/pool.ts`) used by the desktop engine, the CLI (`board tick`), and BoardModal. UI: BoardModal card editor gains Role + "Pin to agent" selects and a role/pin/pool badge; ProfilesModal base agent gains a "None (pool role)" option; the board-worker toggle on EditAgentModal. CLI: `board add-card --assignee <role> --assignee-agent <pin>`, and `profile create --pool` for base-agent-less roles. Also fixes auto-decompose to inherit a pinned agent (agent-only triage cards would otherwise fan into children that validation drops), and a latent getBlockers() arg-order bug in `board show`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # src/__tests__/renderer/components/Settings/Extensions/extensionModel.test.ts # src/renderer/components/Settings/Extensions/extensionModel.ts # src/renderer/stores/settingsStore.ts # src/renderer/types/index.ts # src/shared/plugins/first-party.ts
…erywhere The toolbar pill displayed "Full Access" for a tab whose permissionMode was never set (defaulting the DISPLAY to 'full'), but the spawn path passed the raw undefined through to buildAgentArgs, which only grants the bypass on a literal 'full'. So Claude Code (whose --dangerously-skip-permissions lives only in fullAccessArgs) spawned in the default permission model and had every tool call denied - the box said Full Access while the agent was actually denied, and goal-driven / document Auto Run deadlocked. Introduce resolveTabPermissionMode() as the single source of truth for what an unset permissionMode means and call it from both the toolbar (display) and the interactive spawn path so they can no longer drift. Auto Run, which has no active tab, now spawns with permissionMode: 'full' explicitly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fullaccess # Conflicts: # src/__tests__/renderer/hooks/useAgentExecution.test.ts # src/__tests__/shared/agentMetadata.test.ts # src/main/preload/index.ts # src/renderer/App.tsx # src/renderer/components/AppModals/AppSessionModals.tsx # src/renderer/components/NewInstanceModal/EditAgentModal.tsx # src/renderer/components/NewInstanceModal/types.ts # src/renderer/hooks/session/useSessionLifecycle.ts
…eload, breaker fairness (Board 10x Phase 1) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… priority (Board 10x Phase 2)
- board:changed push event replaces the 2.5s kanban poll. board-storage
exposes setBoardSavedListener; index.ts broadcasts { projectRoot } to
every window plus the web-desktop bridge. Storage stays host-agnostic
so the CLI (no webContents) is unaffected.
- Cancellation: board-spawn remembers the Cue runId per card and kills it
via stopCueRun; the dispatcher gains cancelSpawn + cancelCard and a
cancel tombstone so the killed run's late resolution cannot re-finalize
the card as a failure. New 'canceled' run outcome returns the card to
todo and is excluded from the circuit breaker. board:cancelCard IPC
routes through the live dispatcher, with a storage-only finalize
fallback for cards left running by a previous app run. UI: stop button
on running tiles, drag-out of the running column now refuses with an
explanatory toast.
- Terminal transitions notify through the existing remote:notifyToast
relay (green done / red sticky blocked, sourceAgent 'Board').
- Safe destructive actions: two-click tile delete that names how many
dependents get re-parented, a ConfirmModal discard gate for unsaved
editor changes on Escape/backdrop/Back, and a delete button visible on
focus-within and always visible on coarse pointers.
- Card priority (high/normal/low, normal never serialized) drives one
shared ready-card dispatch order for both claim paths; surfaced in the
card editor, as a tile badge, and via `board add-card --priority`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Board 10x Phase 3) Close the headless-parity gap so a CI box or a headless host can drive the Board end to end without ever opening the desktop app. New CLI surface: board create | rename | delete (delete refuses non-done cards without --force) board update-card | remove-card (running cards are refused / need --force) board watch (board tick on a timer, SIGINT to stop) profile update | show (update keeps the id, so cards keep resolving) Storage gains renameBoard / deleteBoard and an options bag on createBoard (maxInProgress, autoDecompose). The non-done guard rail lives in storage, not the CLI, so the desktop path inherits it; board:rename / board:delete IPC, preload, and renderer types are plumbed for the Phase 6 UI. board tick and board watch now share one tickBoardsOnce pass. The CLI has no handle on an in-flight card process, so remove-card --force says plainly that the run was not canceled rather than implying a kill it cannot perform. docs/cli-reference.md regenerated (npm run gen:cli-reference); BOARD.md and board.md refreshed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- extract the local git worktree provisioning out of the git:worktreeSetup IPC handler into src/main/utils/git-worktree.ts so Auto Run and the Board share one implementation - add pure branch/path naming (src/shared/board/worktree.ts) and the Board provisioning wrapper (src/main/board/board-worktree.ts, Electron-free so the CLI uses the identical path) - spawn worktree cards with the checkout as cwd (desktop + CLI), record worktreePath/worktreeBranch on the CardRun, refuse isolation on SSH remotes - add the 'Run in isolated worktree' toggle, a branch badge on card tiles, the branch in the completion toast and last run summary, and a 'Worktree cards' section in docs/board.md with the merge-back commands Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…count, dispatch-grant RFC (Board 10x Phase 5) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ibility (Board 10x Phase 6) Modal priorities: PROFILES_MODAL moved 458 -> 462, which had collided with SSH_REMOTE (458). Profiles has to outrank BOARD_MODAL (457) because the Board's empty state now opens it, and 458-461 were taken (SSH_REMOTE, Pianola, Cue), so 462 is the first free value above Board. A uniqueness sweep of every value in modalPriorities.ts confirms the Board/Profiles collision is gone; three PRE-EXISTING collisions remain and are deliberately untouched (out of scope): 725 DOCUMENT_SELECTOR/PROMPT_COMPOSER, 710 TAB_SWITCHER/AUTORUN_SETUP/SYMPHONY, 650 NEW_GROUP_CHAT/SHORTCUTS_HELP. Accessibility and keyboard: - role="dialog" + aria-modal + aria-labelledby on Board and Profiles modals. - Columns are role="group" labelled with name and card count. - Tiles are role="button" tabIndex=0 with a themed accent focus ring. Arrows walk the grid, Enter/Space opens the card, Delete twice removes it (same arm-then-confirm path as the trash button), and `m` opens the editor focused on its "Move to" picker. Native HTML5 drag-and-drop is untouched for pointer users; no custom ARIA drag surface. - The editor's "Move to" select persists immediately via setCardStatus with the same guards a drop has, and updates the dirty baseline so a move is not read as an unsaved edit. New cards keep the plain Status select. - Initial dialog focus and the move-picker focus both go through useFocusAfterRender, one-shot via a mount ref. Running-card visibility: running tiles show attempt number, a ticking elapsed reading (formatElapsedTime, isolated in a RunElapsed child so one label re-renders per second), and the pooled worker. The worker name resolves through the session store, not getAgentDisplayName(), because CardRun.workerAgentId is a Left Bar agent id and getAgentDisplayName maps agent *type* ids. The run-details disclosure is now available while running, not only after. Multi-board: header switcher across all project boards plus New / inline Rename / Delete (confirm repeats the CLI's non-done-cards warning). The last selected board per project is remembered in localStorage (maestro.board.lastBoardId:<projectRoot>), the same lightweight mechanism GitDiffViewer and the History filters use, rather than a synced setting. The "no profiles yet" hint is now a button that opens the Profiles modal. Profiles parity: setProfilesSavedListener mirrors the board's post-save hook, index.ts broadcasts profiles:changed, preload exposes onProfilesChanged, and ProfilesModal refetches on it instead of loading once and going stale. Tests: BoardModal.test.tsx 21 passed (keyboard nav, Enter-to-open, Delete twice, move-to IPC, running-card metadata, board switching + persistence, board delete confirm, empty-state profiles button); new ProfilesModal.test.tsx 3 passed; main/profiles/storage.test.ts 16 passed. tsc, eslint, prettier clean.
…Board 10x Phase 7) Docs - BOARD.md: fail-closed loads, atomic + serialized writes and the shared write-chain helper, reclaimed/canceled run outcomes, status-change diff hook, a new plugin-bus board events section, SSH-worktree refusal and the board/profiles changed-event emit sites in Gotchas, per-board busy-set caveat. - board.md: priority / assignee-agent / worktree / runs in the YAML shape, a run outcome table, dispatch order, cancellation, notifications, multi-board. - CLAUDE.md: board worktree module in the Board key-files row. - CLAUDE-PLUGINS.md + PLUGIN-DEVELOPMENT.md: the four board.* topics and the 1.15.0 host-API line. plugin-sdk - Phase 5 bumped HOST_API_VERSION and added the board topics in src/shared/plugins but not in the vendored SDK copy, so the drift guard was red (it only runs on the sdk publish workflow, which is why CI stayed green). Vendor the topics + payloads, move HOST_API_VERSION to 1.15.0, bump the package to 0.9.0.
…(Board 10x Phase 7) The "//" comment in packages/plugin-sdk/package.json still pointed at HOST_API_VERSION 1.13.0 after the bump to 1.15.0. Comment-only; the drift guard does not read it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds persistent Board and Agent Profiles, including storage, dispatching, worktree isolation, CLI and renderer interfaces, plugin contracts, host API 1.15.0 updates, documentation, and automated coverage. ChangesBoard and Agent Profiles
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds persistent Board workflows and reusable Agent Profiles. The main changes are:
Confidence Score: 4/5CLI Board execution can lose persisted transitions, exclude valid workers, and bypass requested worktree isolation.
src/cli/commands/board.ts and the cross-process Board storage boundary Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as CLI board tick
participant Disk as board.yaml
participant Desktop as Desktop dispatcher
CLI->>Disk: Load snapshot A
Desktop->>Disk: Load current board
Desktop->>Disk: Persist transition B
CLI->>CLI: Apply result to snapshot A
CLI->>Disk: Replace file with snapshot A
Note over Disk: Transition B is lost
Reviews (1): Last reviewed commit: "MAESTRO: fix stale host-API version in p..." | Re-trigger Greptile |
Upstream rc shipped host API 1.14.0 (tool.executed topic + ui.panelPost, Agent Flow PR RunMaestro#1254) after this branch had already claimed 1.15.0 for the four board.* topics. Resolution keeps 1.15.0 as the merged version with 1.14.0 folded into the history text, unions the topic catalogs and payload maps in both src/shared/plugins and the vendored plugin-sdk copy (tool.executed first, board.* after, identically ordered so the drift guard passes), pins the drift test to 1.15.0, and bumps the SDK package to 0.10.0 since upstream published its own 0.9.0 for 1.14.0. Docs that described 1.14.0 as skipped now describe it as merged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI (4 red shards, 4 distinct causes): - MainPanelHeader.test.tsx: give the settings-store mock an encoreFeatures object; BoardStatusIndicator's selector crashed on undefined and took all 30 header tests down with it. - EditAgentModal.test.tsx: expect the new 19th onSave argument (boardWorker, false when the checkbox is untouched) in the five exact-args assertions. - pianola-first-party-plugin.test.ts: add ['board', 'com.maestro.board'] to the expected first-party registry (upstream test, written before the merge brought the Board definition in). - board-worktree.test.ts (Windows): realpathSync.native for the temp root; the plain realpath keeps 8.3 short names (RUNNER~1) which git expands, failing setupWorktreeLocal's same-repo comparison on worktree reuse. Review (greptile P1s on src/cli/commands/board.ts): - Pool parity bug FIXED: CLI worker selection now falls back projectRoot || cwd like the desktop wiring, so a worker stored with only a cwd is not silently dropped (role-only cards no longer wedge on no-free-worker). Covered by a new tick test that fails without the fix. - Cross-process save race DOCUMENTED: the write chain is process-local by design; BOARD.md now states it and docs/board.md no longer calls overlapping dispatchers 'safe' - one dispatcher per board. - Decompose-in-projectRoot DOCUMENTED as deliberate desktop parity: it is a read-only planning pass; the worktree belongs to the card's own run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (3)
src/renderer/components/ProfilesModal/ProfilesModal.tsx (1)
142-153: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider confirming profile deletion (and flagging in-use profiles).
handleDeleteremoves a profile immediately with no confirmation. Board cards that reference it viaassigneeProfileIdare then orphaned (they fall back to rendering the raw id and can no longer resolve the role at dispatch). This is inconsistent with the destructive-action guards elsewhere in this PR (board deletion confirm dialog, the tile's arm-then-delete). A confirm step, or at least a warning when the profile is still assigned to cards, would prevent silent breakage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/ProfilesModal/ProfilesModal.tsx` around lines 142 - 153, Update handleDelete in ProfilesModal to require confirmation before deleting a profile, and warn or block when the profile is referenced by board cards through assigneeProfileId. Reuse the existing destructive-action confirmation and profile/card data sources where available, preserving the current delete, error logging, exception capture, and toast behavior after confirmation.src/renderer/components/Settings/Extensions/extensionModel.ts (1)
82-106: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy liftShare the dependency rule across renderer and main.
This mapping is explicitly a renderer-side mirror of the main-process gate. Keeping separate definitions can let a future dependency be enabled in one layer but rejected in the other. Move the mapping to a shared module imported by both layers, or add a parity test that exercises both paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/Settings/Extensions/extensionModel.ts` around lines 82 - 106, Centralize BUILTIN_DEPENDENCIES in a shared module consumed by both the renderer dependency check isDependencyMet and the main-process dispatcher gate. Remove the duplicate layer-specific mapping and update both consumers to use the shared definition, preserving the existing board-to-maestroCue dependency behavior.src/cli/commands/board.ts (1)
1005-1013: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winPass
customArgsthrough tospawnAgent()in the decompose spawn.This CLI spawn site forwards
customModel,customEffort,customEnvVars, andsshRemoteConfigbut dropscustomArgs, so a role's extra CLI args are silently lost on the auto-decompose pass (the siblingspawnCarddoes pass it).overrideshere can carrycustomArgsfromresolveCliAssignment.As per coding guidelines: "For new CLI agent spawn sites, pass
sessionSshRemoteConfig,customArgs,customEnvVars,customModel, andcustomEffortthrough tospawnAgent()".♻️ Proposed fix
const result = await spawnAgent(base.toolType as ToolType, projectRoot, prompt, undefined, { customModel: overrides?.customModel, customEffort: overrides?.customEffort, + customArgs: overrides?.customArgs, customEnvVars: base.customEnvVars, sshRemoteConfig: base.sessionSshRemoteConfig, enableMaestroP: base.enableMaestroP, maestroPMode: base.maestroPMode, maestroPPath: base.maestroPPath, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli/commands/board.ts` around lines 1005 - 1013, Update the decompose spawn call to spawnAgent in the surrounding command flow to forward overrides?.customArgs alongside the existing customModel and customEffort options, preserving the resolved role-specific CLI arguments during auto-decompose.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/agent-guides/BOARD.md`:
- Line 177: Update the wording in the profile editing guidance to use “drivable”
instead of “driveable”, without changing the surrounding behavior or technical
details.
- Line 180: The cross-process dispatcher documentation incorrectly describes
desktop and CLI overlap as safe; update the guidance to warn that concurrent
read-modify-write cycles can overwrite persisted card transitions. In
docs/agent-guides/BOARD.md line 180, replace “safe” with the explicit
lost-update warning; in docs/board.md line 215, make overwrite risk the primary
behavior rather than a secondary caveat; and in docs/cli-reference.md line 613,
describe overlap as unsupported.
In `@docs/board.md`:
- Line 207: Update the board tick documentation to define completion as all
cards reaching terminal states, specifically done or blocked, rather than
stopping when nothing remains to promote. Revise the repeated-tick guidance in
the board workflow example to continue while cards are running, ready, or
awaiting retry, and mention a status check only if an existing documented
command supports automation.
In `@docs/cli-reference.md`:
- Around line 591-599: Update the set-status documentation to list only the
accepted manual transitions: triage, todo, blocked, and done; remove ready and
running from the status values, and do not imply that users can set
dispatcher-managed states through this command.
In `@Plans/rfc-runtime-dispatch-grants.md`:
- Around line 77-80: Clarify the project-root grant definition to require
canonical path resolution for both the configured project root and candidate
paths, separator-aware containment that excludes similarly prefixed paths such
as “repo-evil,” and explicit symlink and traversal handling that prevents
escaping the canonical project-root boundary.
In `@src/__tests__/renderer/components/BoardModal.test.tsx`:
- Around line 489-509: Widen the elapsed-time assertion in the “shows attempt,
elapsed time and the pooled worker on a running tile” test to match any seconds
value within the minute, rather than only single-digit seconds. Keep the
assertion anchored to the existing formatted “1m …s” output so it remains
tolerant of slow rendering.
In `@src/main/utils/atomic-json-store.ts`:
- Around line 116-129: Update atomicWriteFileSync to retry renameSync on
transient EPERM or EBUSY errors using the same bounded retry limit and
synchronous backoff as atomicWriteFile. Preserve cleanup of the temporary file
and rethrow non-retryable or ultimately failed errors.
In `@src/prompts/board-decompose.md`:
- Around line 12-18: Update the child-card count instruction in the
decomposition prompt so the two-to-six requirement applies normally, while
explicitly allowing a single child card when the source card is atomic. Preserve
the existing guidance for actionable scope, dependency ordering, imperative
titles, and avoiding invented work.
In `@src/renderer/components/AppStandaloneModals.tsx`:
- Around line 427-432: The Board modal must remain unavailable unless Maestro
Cue is enabled. In src/renderer/components/AppStandaloneModals.tsx lines
427-432, update the BoardModal render condition to also require
encoreFeatures.maestroCue; in
src/renderer/components/Settings/Extensions/ExtensionDetails.tsx lines 603-624,
update the active Open Board action to require dependencyMet and otherwise
display the existing dependency hint.
In `@src/renderer/components/BoardStatusIndicator.tsx`:
- Around line 47-51: Update the catch block in BoardStatusIndicator’s
card-counting flow to distinguish known recoverable board-list errors from
unexpected IPC failures. Keep the zero-count fallback and warning for recognized
recoverable errors, but report unexpected exceptions through the project’s
Sentry exception-reporting utility or rethrow them instead of silently
converting them to zero counts.
- Around line 39-53: The refresh callback in BoardStatusIndicator must prevent
an outdated board.list result from updating counts after projectRoot changes.
Add a request-generation or cancellation guard around the asynchronous work in
refresh, and only commit setCounts results or fallback values for the currently
active request; invalidate the prior request when dependencies change or the
callback is superseded.
In `@src/renderer/hooks/session/useSessionLifecycle.ts`:
- Around line 213-215: Update the agent-save payload containing boardWorker so
it only applies that field when the optional boardWorker argument is defined,
preserving an existing true opt-in when callers omit it. Keep the current value
unchanged for undefined arguments while retaining explicit true or false values.
In `@src/shared/board/worktree.ts`:
- Around line 35-60: Update boardCardBranchName to avoid truncating boardId and
cardId to only ID_SEGMENT_LENGTH characters. Use full validated IDs or append
collision-resistant suffixes derived from the complete IDs, while preserving
sanitization, fallback values, and the existing branch/path construction used by
boardWorktreePath and buildCardWorktreeRef.
In `@src/shared/profiles/types.ts`:
- Around line 109-137: Update validateAgentProfile so every present optional
field (baseAgentId, model, effort, appendSystemPrompt, and customArgs) causes
the whole profile to return null when its value is not a string. Refactor
optionalString or its callers to distinguish absent fields from malformed
values, while preserving normalization of blank strings to absent and trimming
retained values consistently.
---
Nitpick comments:
In `@src/cli/commands/board.ts`:
- Around line 1005-1013: Update the decompose spawn call to spawnAgent in the
surrounding command flow to forward overrides?.customArgs alongside the existing
customModel and customEffort options, preserving the resolved role-specific CLI
arguments during auto-decompose.
In `@src/renderer/components/ProfilesModal/ProfilesModal.tsx`:
- Around line 142-153: Update handleDelete in ProfilesModal to require
confirmation before deleting a profile, and warn or block when the profile is
referenced by board cards through assigneeProfileId. Reuse the existing
destructive-action confirmation and profile/card data sources where available,
preserving the current delete, error logging, exception capture, and toast
behavior after confirmation.
In `@src/renderer/components/Settings/Extensions/extensionModel.ts`:
- Around line 82-106: Centralize BUILTIN_DEPENDENCIES in a shared module
consumed by both the renderer dependency check isDependencyMet and the
main-process dispatcher gate. Remove the duplicate layer-specific mapping and
update both consumers to use the shared definition, preserving the existing
board-to-maestroCue dependency behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f7782068-4d68-4210-a0e7-1c741dabb069
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (92)
CLAUDE-PLUGINS.mdCLAUDE.mdPlans/board-10x-summary.mdPlans/rfc-runtime-dispatch-grants.mddocs/agent-guides/BOARD.mddocs/agent-guides/PLUGIN-DEVELOPMENT.mddocs/board.mddocs/cli-reference.mddocs/docs.jsonpackages/plugin-sdk/package.jsonpackages/plugin-sdk/src/__tests__/drift.test.tspackages/plugin-sdk/src/index.tssrc/__tests__/cli/commands/board.test.tssrc/__tests__/cli/commands/profile.test.tssrc/__tests__/main/board/board-decompose.test.tssrc/__tests__/main/board/board-dispatcher.test.tssrc/__tests__/main/board/board-spawn.test.tssrc/__tests__/main/board/board-worktree.test.tssrc/__tests__/main/board/storage.test.tssrc/__tests__/main/profiles/storage.test.tssrc/__tests__/renderer/components/BoardModal.test.tsxsrc/__tests__/renderer/components/MainPanel/MainPanelHeader.test.tsxsrc/__tests__/renderer/components/NewInstanceModal/EditAgentModal.test.tsxsrc/__tests__/renderer/components/ProfilesModal.test.tsxsrc/__tests__/renderer/components/QuickActionsModal/commands/commandBuilders.test.tssrc/__tests__/renderer/components/Settings/Extensions/extensionModel.test.tssrc/__tests__/shared/board/cardMarkers.test.tssrc/__tests__/shared/board/graph.test.tssrc/__tests__/shared/board/pool.test.tssrc/__tests__/shared/board/types.test.tssrc/__tests__/shared/pianola/pianola-first-party-plugin.test.tssrc/__tests__/shared/plugins/board-first-party.test.tssrc/__tests__/shared/plugins/events.test.tssrc/__tests__/shared/profiles/types.test.tssrc/cli/commands/board.tssrc/cli/commands/profile.tssrc/cli/index.tssrc/main/board/board-decompose.tssrc/main/board/board-dispatcher.tssrc/main/board/board-spawn.tssrc/main/board/board-storage.tssrc/main/board/board-worktree.tssrc/main/cue/cue-engine.tssrc/main/cue/cue-yaml-write.tssrc/main/index.tssrc/main/ipc/handlers/board.tssrc/main/ipc/handlers/git.tssrc/main/ipc/handlers/index.tssrc/main/ipc/handlers/profiles.tssrc/main/preload/board.tssrc/main/preload/index.tssrc/main/preload/profiles.tssrc/main/profiles/profile-storage.tssrc/main/utils/atomic-json-store.tssrc/main/utils/git-worktree.tssrc/prompts/board-decompose.mdsrc/renderer/App.tsxsrc/renderer/components/AppModals/AppModals.tsxsrc/renderer/components/AppModals/AppSessionModals.tsxsrc/renderer/components/AppModals/AppUtilityModals.tsxsrc/renderer/components/AppStandaloneModals.tsxsrc/renderer/components/BoardModal.tsxsrc/renderer/components/BoardStatusIndicator.tsxsrc/renderer/components/MainPanel/MainPanelHeader.tsxsrc/renderer/components/NewInstanceModal/EditAgentModal.tsxsrc/renderer/components/NewInstanceModal/types.tssrc/renderer/components/ProfilesModal/ProfilesModal.tsxsrc/renderer/components/QuickActionsModal/QuickActionsModal.tsxsrc/renderer/components/QuickActionsModal/commands/featureCommands.tssrc/renderer/components/QuickActionsModal/types.tssrc/renderer/components/Settings/Extensions/ExtensionDetails.tsxsrc/renderer/components/Settings/Extensions/ExtensionsGrid.tsxsrc/renderer/components/Settings/Extensions/ExtensionsView.tsxsrc/renderer/components/Settings/Extensions/extensionModel.tssrc/renderer/constants/modalPriorities.tssrc/renderer/global.d.tssrc/renderer/hooks/session/useSessionLifecycle.tssrc/renderer/stores/modalStore.tssrc/renderer/stores/settingsStore.tssrc/renderer/types/index.tssrc/shared/board/cardMarkers.tssrc/shared/board/graph.tssrc/shared/board/pool.tssrc/shared/board/types.tssrc/shared/board/worktree.tssrc/shared/maestro-paths.tssrc/shared/plugins/events.tssrc/shared/plugins/first-party.tssrc/shared/plugins/host-api.tssrc/shared/profiles/types.tssrc/shared/promptDefinitions.tssrc/shared/types.ts
Code:
- worktree naming uses FULL sanitized ids (board/<boardId>/<cardId>), not a
first-8 truncation: board.yaml ids are hand-editable, so 'feature-auth' and
'feature-api' collided on the same checkout and silently defeated isolation.
Slashes inside an id are flattened so an id can never add branch hierarchy.
Naming tests updated + collision/slash regression tests added; docs examples
now show full-id branches.
- validateAgentProfile fails closed on malformed optional fields: a present
non-string (model: 42, baseAgentId: {}) rejects the whole profile instead of
silently dropping the override and running the card with the wrong
model/effort/role. YAML null (empty 'model:' line) still means absent.
- handleSaveEditAgent only applies boardWorker when the caller passed it, so
an omitted optional arg can no longer clear an existing opt-in during an
unrelated save (+ regression test).
- atomicWriteFileSync retries renameSync on EPERM/EBUSY with the same bounded
backoff as its async twin (Atomics.wait for the dependency-free sync sleep).
- BoardStatusIndicator: request-generation guard so a stale board.list cannot
overwrite the next project's counts, and unexpected list failures are
reported to Sentry once per project instead of silently zeroed.
- Board modal render + Extensions 'Open Board' both require the Maestro Cue
dependency, matching every other open path; the disabled hint names the
missing dependency.
- board-decompose prompt (+ fallback template) makes the 2-6 child rule
explicitly conditional on the single-card atomic exception.
Docs:
- The 'overlapping dispatchers is safe' claim is corrected everywhere it
appeared (BOARD.md CLI section, docs/board.md, docs/cli-reference.md):
atomic writes prevent torn files, but cross-process read-modify-write can
lose transitions; overlap is unsupported.
- board tick completion condition is now 'every card terminal (done or
blocked)' with board show --json as the status check, not 'nothing left to
promote'.
- cli-reference set-status lists only the manual statuses (triage|todo|
blocked|done) and explains that ready/running are dispatcher-owned.
- dispatch-grants RFC specifies containment semantics for project-root
grants: canonicalization, separator-aware comparison, symlink handling.
- 'driveable' -> 'drivable'.
Tests: BoardModal elapsed-time regex widened to /1m \d+s/ (single-digit
seconds flaked under slow CI renders).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Header subtitle now reads 'card DAG · dispatched on the Cue tick' and the card editor body placeholder drops the leading unit word, reading 'Instructions handed to the assignee agent.' (the field label above already says 'Body / instructions'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Board hard-gated card creation on a pre-existing Agent Profile, but since Board Phase 6 a card is valid with either assigneeProfileId OR assigneeAgentId. Drop the profiles.length gate so 'New card' renders on board && !draft, and delete the 'Create an Agent Profile first' branch. canSaveDraft still guards assignee-less saves. Removed the now-unused getModalActions import (only caller was the deleted branch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
notifyCard's remote:notifyToast payload now carries sessionId and a jump-session clickAction when the run recorded a workerAgentId, mirroring the Cue precedent in cue-notify-bridge.ts. Applies to both the done and blocked branches (shared safeSend). Legacy profile-based runs have no workerAgentId and keep the non-clickable toast. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Forwards projectRoot and an onProfilesUpdated(updatedList, newProfileId) callback down to CardEditor so the upcoming inline '+ New role' quick-create can upsert a profile and select it on the open draft. The handler syncs the Board's own profiles state from the upsert return value and points the draft's assigneeProfileId at the new role. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # src/__tests__/renderer/components/Settings/Extensions/extensionModel.test.ts # src/renderer/hooks/session/useSessionSwitchCallbacks.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/index.ts (2)
1599-1604: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winKeep agent output out of plugin events.
The blocked-card event spreads
event.outcomeinto a contract described as metadata-only, even though outcomes may contain agent summaries or output. Remove it or emit only explicitly allow-listed metadata.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/index.ts` around lines 1599 - 1604, Update the blocked-card event emission in the blocked branch to stop spreading event.outcome into payload; preserve only the common metadata, or include solely explicitly allow-listed metadata fields that comply with the event contract.
1565-1568: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftSerialize all Board read-modify-write operations.
Cue dispatch and auto-decomposition bypass the queue used by Board IPC mutations, allowing stale snapshots to overwrite concurrent edits.
src/main/index.ts#L1565-L1568: serialize dispatcher reads and saves through the per-project Board queue.src/main/index.ts#L1659-L1662: serialize decomposition reload, merge, and save through the same queue.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/index.ts` around lines 1565 - 1568, Serialize both Board read-modify-write flows through the per-project Board queue: in src/main/index.ts lines 1565-1568, update the dispatcher’s listBoards/saveBoard operations; in src/main/index.ts lines 1659-1662, update decomposition reload, merge, and save operations. Ensure each flow reads the current board and persists its changes within the same queue serialization used by Board IPC mutations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@node_modules`:
- Line 1: Remove the host-specific node_modules symbolic link from version
control and add node_modules/ to the repository’s ignore configuration. Do not
replace it with another link unless the project explicitly requires a
repository-relative target.
---
Outside diff comments:
In `@src/main/index.ts`:
- Around line 1599-1604: Update the blocked-card event emission in the blocked
branch to stop spreading event.outcome into payload; preserve only the common
metadata, or include solely explicitly allow-listed metadata fields that comply
with the event contract.
- Around line 1565-1568: Serialize both Board read-modify-write flows through
the per-project Board queue: in src/main/index.ts lines 1565-1568, update the
dispatcher’s listBoards/saveBoard operations; in src/main/index.ts lines
1659-1662, update decomposition reload, merge, and save operations. Ensure each
flow reads the current board and persists its changes within the same queue
serialization used by Board IPC mutations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 31144c2d-2871-4d95-a8ab-a01ecf2c0c60
📒 Files selected for processing (18)
CLAUDE.mddocs/agent-guides/PLUGIN-DEVELOPMENT.mdnode_modulespackages/plugin-sdk/src/__tests__/drift.test.tspackages/plugin-sdk/src/index.tssrc/__tests__/renderer/components/Settings/Extensions/extensionModel.test.tssrc/__tests__/renderer/hooks/useSessionLifecycle.test.tssrc/main/cue/cue-engine.tssrc/main/index.tssrc/renderer/App.tsxsrc/renderer/components/Settings/Extensions/ExtensionsView.tsxsrc/renderer/components/Settings/Extensions/extensionModel.tssrc/renderer/global.d.tssrc/renderer/hooks/session/useSessionLifecycle.tssrc/renderer/hooks/session/useSessionSwitchCallbacks.tssrc/renderer/stores/settingsStore.tssrc/renderer/types/index.tssrc/shared/types.ts
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/plugin-sdk/src/tests/drift.test.ts
- src/renderer/stores/settingsStore.ts
- src/renderer/types/index.ts
- CLAUDE.md
- src/renderer/components/Settings/Extensions/ExtensionsView.tsx
- docs/agent-guides/PLUGIN-DEVELOPMENT.md
- src/renderer/hooks/session/useSessionLifecycle.ts
- src/shared/types.ts
- packages/plugin-sdk/src/index.ts
- src/renderer/global.d.ts
- src/renderer/App.tsx
- src/main/cue/cue-engine.ts
Lift the push + gh pr create body out of the git:createPR IPC handler into a new Electron-free src/main/utils/pr-creator.ts so the Board dispatcher (main process, no renderer) can open PRs through the same code path. The helper owns target-branch resolution (explicit -> repo default -> main), and git:getDefaultBranch now delegates to the exported resolveDefaultBranch so that logic exists once as well. Logging and warning are injected sinks, mirroring git-worktree.ts. The IPC reply shape is unchanged.
F2: 'review' is a distinct card status for work that finished but needs a human to approve or verify it, separate from 'blocked' (agent got stuck). Adds it to CardStatus/CARD_STATUSES (between running and blocked, so the Review column lands between Running and Blocked) and to CardRunOutcome so the run audit trail is honest. cardMarkers.ts gains CARD_REVIEW_RE for <!-- maestro:card-review: reason --> (reason optional, last match wins) plus review/reviewReason on CardMarkers, and CARD_HANDOFF_REMINDER now teaches all three markers: fix what you safely can and card-complete, card-review when human judgment is needed, card-block only when genuinely stuck. The dispatcher resolves precedence. BoardModal gets the minimal STATUS_META entry required to keep tsc green; the Review column styling and CardEditor work follow in their own commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the F3 data model: BoardCard.prOnDone ({ targetBranch? }) as a per-card
opt-in to open a PR when the card reaches done, and CardRun.prUrl to record
the PR that was opened. Both are structurally validated in types.ts and
dropped when malformed, so a bad value disarms the opt-in instead of silently
arming a PR. Absent prOnDone is never serialized, leaving existing board.yaml
files byte-identical.
board-storage.ts needs no change: it validates through validateBoardCard and
whole-object dumps the result.
Add the review branch to applyCardResult (block > review > complete/clean exit), reset the trailing-failure breaker on a review outcome, widen CardNotification.kind, and notify on the review transition. board-toast gains a yellow sticky review payload. notifyCard no longer emits board.cardCompleted for review cards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds src/main/board/board-pr.ts: a pure gate (done + prOnDone + a worktree branch on the latest run + no PR already opened), a pure stamp for run.prUrl, an injectable orchestrator, and the fire-and-forget startCardPr entry point that binds both to board storage and the shared gh-CLI createPullRequest. Both transitions into done route through it: the dispatcher finalize (new optional createCardPr dep, fired after saveAndAnnounce and never awaited) and the manual board:setCardStatus approval move. A PR failure is warn-logged and red-toasted and never changes the card status; success green-toasts the URL and records it on the run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… controls Renderer half of Board F2/F3: the Review column already renders from the CARD_STATUSES order and the STATUS_META entry added with the types, and moveCard already permits manual -> review and the review -> done approval, so this adds the CardEditor "Open PR when done" toggle plus its optional target-branch input, gated on worktree isolation and persisted to BoardCard.prOnDone only when both toggles are on.
Extend the shared board test suites for the new `review` card status and the per-card PR-on-done opt-in: - types.test.ts: deep-equal locks on CARD_STATUSES (review between running and blocked, which drives BoardModal column order) and CARD_RUN_OUTCOMES, review card/run round-trip, prOnDone round-trip plus disarm-when-malformed, and CardRun.prUrl round-trip plus drop-when-blank. - cardMarkers.test.ts: card-review with and without a reason, whitespace tolerance, last-match-wins, faithful reporting alongside complete/block (precedence stays the dispatcher's call), and handoff reminder coverage. - graph.test.ts: a review parent does not unblock its children and does not count toward the active-card cap. Tests only, no source changes. 69 pass in src/__tests__/shared/board/.
…main-process tests
User docs (docs/board.md): review in the run-outcome and card-status tables, a Review section covering the approval flow and why review does not unblock children, the card-review marker and the block > review > complete precedence, the yellow sticky review toast, and a new Open a PR when the card is done section under worktree cards. Agent guide (docs/agent-guides/BOARD.md): board-pr.ts / pr-creator.ts in the layout, prOnDone / CardRun.prUrl in the data model, the updated applyCardResult precedence and trailing-failure reset, and a PR on done section recording the extraction, the gate, the enqueueBoardWrite stamp, and the in-flight set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- useSessionSwitchCallbacks.test.tsx: import RefObject as a type instead of relying on a global React namespace (tsconfig has no allowUmdGlobalAccess), per CodeRabbit. - Remove an accidentally committed node_modules symlink (mode 120000 pointing to an absolute host path) from version control, and extend .gitignore so a bare node_modules symlink is ignored (the existing node_modules/ rule only matches directories), per CodeRabbit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…the card `updateCardStatus` guarded only against the dispatcher-derived statuses (`ready`/`running`). The `CardStatus` parameter annotation is erased at runtime, so any other string passed over the `board:setCardStatus` IPC channel was written straight onto the card and persisted. The load-side validator then rejected that card as malformed and dropped it, so the call resolved successfully while the user's card disappeared from both memory and board.yaml. Found by driving the packaged build over CDP: setting a bogus status on one of two cards left the board with one card and raised no error. Not reachable from the desktop UI, which only sends typed values, and the CLI already validates in `board set-status`. The IPC surface did not, and the web-desktop bridge relays it too. Adds an allowlist check against CARD_STATUSES ahead of the existing derived-status denylist, plus a regression test covering off-enum, empty, wrong-case, trailing-space, and `__proto__` inputs asserting the card survives with its previous status. Also extends the author-owned status case list to include `review`, which was missing.
Board worktrees carry no node_modules, so this repo's .husky/pre-push (which hard-requires node_modules/.bin/prettier and otherwise runs the full validation suite) fails every PR-on-done card at the push step. Adds an opt-in skipHooks flag to createPullRequest that switches the push argv to 'push --no-verify -u origin HEAD'. The Board's defaultCreatePr is the only caller that sets it; the manual git:createPR path keeps its byte-identical default argv and still runs the repo's gates (Decision 2).
A failed PR-on-done attempt only warn-logged and toasted, so the reason was gone the moment the toast cleared. Record it on the card's latest run instead: - CardRun.prError, parsed by validateCardRun like prUrl so it round-trips. - stampCardPrError() mirrors stampCardPrUrl (same vanished-card guard, capped at 500 chars), wired through the same enqueueBoardWrite queue in startCardPr. - stampCardPrUrl clears a stale prError, so a later success wipes the reason. - The gate is unchanged: it keys only on prUrl, so a failed attempt stays retryable. A failure still never changes the card's status.
Render the latest run's PR state on the card tile's existing badge row: a link chip when prUrl is set (routed through the canonical openUrl helper) and a compact error chip carrying the persisted prError in its tooltip when the last attempt failed. Display only - the Task 4 gate keys on prUrl alone, so a status round-trip already re-fires the flow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…head An agent that marks a card done without committing would otherwise push an empty board/<uuid>/<uuid> branch and then fail at gh pr create, littering the remote. createPullRequest now counts commits ahead of the resolved base (local ref, then origin/<base>) before pushing and returns a no-commits error without touching the remote. Scoped to skipHooks callers so the human git:createPR path is unchanged.
…set-status help from CARD_STATUSES
…one flags board set-status <id> done now awaits maybeCreateCardPr with the same storage wiring startCardPr binds in the desktop host, so a headless board opens the same pull request the IPC path opens. Awaited rather than fire-and-forget: the CLI process exits when the command returns and would kill the push mid-flight. Adds --pr-on-done [targetBranch] to add-card and update-card plus --no-pr-on-done to update-card, reusing the shared CardPrOnDone shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes AB1: clicking Stop killed the agent, sent the card back to `todo`, and the very next dispatcher tick re-promoted and re-spawned it, so Stop did not stop. Per the recorded decision (option A-lite, not the playbook's option B), the cancelled card stays `todo` and gains a persisted `heldByUser` flag that `getEligibleCards` honours, cleared when the user resumes. No new `CardStatus` and no reuse of `triage`, which `board-decompose` consumes on boards with `autoDecompose: true`. - BoardCard.heldByUser plus the matching copy in validateBoardCard, so the hold survives that validator's whitelist rebuild on save/load - getEligibleCards skips held cards - applyCardCancel stamps the flag; its docstring drops the old self-contradicting "a cancel is a pause, not a demotion" claim - next-tick regression test that fails when the flag is removed, plus a YAML round-trip test for the flag trailingFailureCount, the tombstone-before-kill ordering in cancelCard, and finalize are byte-identical.
… the graph tests Task 4 of FIX-BOARD-CANCEL-HOLD-01, adapted to the Decision 1 override (heldByUser flag on a todo card, not a triage move). - CardRunOutcome doc: canceled no longer claims the card returns to todo unheld; it is held via BoardCard.heldByUser until a person resumes it. - CardStatus doc: the todo line names its two populations (never-run and user-held). triage is left untouched. - graph.test.ts: pin Decision 3 - a held card is not eligible (with and without done parents) and a held parent still blocks its children; add the missing triage-parent and held-parent getBlockers cases. Comments and tests only, no runtime change.
Task 5 of FIX-BOARD-CANCEL-HOLD-01, implemented against the recorded Decision 1 (hold flag on a todo card) and Decision 2 (a real Resume button) rather than the playbook's rejected option B. - BoardModal: the cancel toast says the card is held until the user resumes it, a Resume button mirrors Stop on held cards, and a held badge explains why the card is sitting still. - Resume clears heldByUser through the existing updateCard IPC; the validator only serializes the flag when true, so false drops it. - board:cancelCard's handler comment describes the hold. - updateCardStatus clears the hold on a manual move, so a drag or maestro-cli board set-status is also a valid resume path.
Brings the Board + Agent Profiles branch up to date with upstream/rc after
upstream decomposed several monoliths into focused modules.
Conflicts resolved (12 files, 22 hunks), classified before touching:
- (a) keep-both unions x10: CLAUDE-PLUGINS.md version history, the vendored
plugin-sdk surface, src/main/index.ts module imports and boardSpawnContext,
git.ts imports, and src/shared/plugins/events.ts.
- (b) relocations x5: upstream moved setupIpcHandlers() to
src/main/ipc/bootstrap/index.ts and setupProcessListeners() to
src/main/process-listeners-wiring/. Our registerProfileHandlers() and
registerBoardHandlers({...}) pair moved into the bootstrap module, rewired to
deps.getCueEngine / deps.safeSend / the module logger.
- (c) judgement calls x7: HOST_API_VERSION reconciled to 1.16.0 in host-api.ts,
the vendored SDK index.ts, the drift.test.ts pin, and the CLAUDE-PLUGINS.md
doc line (upstream reserved 1.15.0 for this fork's Board work and took 1.16.0
for session.activated / sessions.focus / summonable panels, so both feature
sets survive at 1.16.0). plugin-sdk package version bumped to 0.11.0.
.gitignore takes upstream's bare node_modules with our comment reworded.
MainPanelHeader.tsx keeps BoardStatusIndicator plus upstream's GitPillMenu and
drops the now-unreferenced BranchSwitcherDropdown import.
Also repaired src/main/ipc/handlers/git.ts, which auto-merged without a conflict
but was left broken: restored the getShellPath import upstream's streaming-git
path needs, and deleted the resurrected local findLocalWorktreeForBranch copy
that our side had moved to src/main/utils/git-worktree.ts.
Validation: all three tsc configs clean, prettier and ESLint clean on the
touched files, 1268 scoped tests passing across board, profiles, plugin-sdk
drift, the git handler, BoardModal, ProfilesModal, MainPanelHeader, and the
shared/main plugin suites.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One conflict: a modify/delete on src/main/ipc/handlers/git.ts. Upstream (0027d69) split that file into eight modules under handlers/git/; this branch had rewritten it to delegate to the shared utils it introduced, deleting 273 lines of inline implementation so the Board's per-card worktree provisioning and PR-on-done could not drift from the IPC handlers. Taking either whole side loses something: upstream's deletion drops the delegation that PR RunMaestro#1272 was reviewed with, ours drops the modular split. So the delegation was RE-APPLIED onto upstream's new layout, at the three call sites it originally had: - handlers/git/worktree.ts -> setupWorktreeLocal (169 inline lines removed) - handlers/git/github.ts -> createPullRequest, reply shape unwrapped so renderer callers see the same object as before - handlers/git/branch.ts -> resolveDefaultBranch Both shared utils (utils/git-worktree.ts, utils/pr-creator.ts) are new on this branch and merged without conflict; their extracted logic was verified against the inline code being replaced before swapping (same guard strings, same return shape, byte-identical default-branch resolution). Dead code the delegation orphaned, surfaced by tsc rather than guessed at: findLocalWorktreeForBranch and its gitUtils import in worktree.ts, getShellPath in github.ts, execFileNoThrow in branch.ts. Scoped tests: 2916 passing across main/board, main/ipc and main/utils. Three tsc configs, ESLint and prettier clean.
Five conflicts, all the same shape: RunMaestro#1362 (finding AD1) landed in rc and appended `contextWindowSource` to the edit-agent save chain, while this branch had appended `boardWorker` to the same signatures. Pure addition on both sides. Both kept, with `contextWindowSource` first so rc's argument positions stay where RunMaestro#1362 put them and `boardWorker` extends past them. Applied consistently across all four signature sites (types.ts, AppSessionModals, EditAgentModal, useSessionLifecycle x2) and the five `onSave` assertions in the modal test, since a positional mismatch between any of them would typecheck and then read the wrong argument at runtime. Scoped tests: 182 across NewInstanceModal / useContextWindow / AppModals and 225 across main/board and the session hooks. Three tsc configs, ESLint and prettier clean.
The merge kept both `contextWindowSource` (from RunMaestro#1362, now in rc) and `boardWorker` on the edit-agent save chain, with contextWindowSource first. This call site in useSessionLifecycle.test.ts passes every argument positionally but was not itself a conflict, so git left it untouched and `false` landed in contextWindowSource instead of boardWorker. Exactly the hazard the merge commit warned about; I checked the four signature sites and the five modal assertions and missed this one because it lives in a different test file. Adds the missing `undefined, // contextWindowSource`. Swept for other positional callers of this chain: this was the only one, the rest pass named properties. Full renderer/hooks suite now passes (5028).
|
@chr1syy first off, thank you for this - it is a genuinely impressive contribution. The Board DAG plus Agent Profiles is a large, coherent feature, and the care shows: the decision records at the top of I have gone through both AI reviews and the code. Status from my side:
Two CodeRabbit findings landed as "outside diff range" notes, so they never became threads and could look unaddressed. For the record, I checked both and neither blocks:
The one thing blocking merge: conflictsThe branch is 129 commits behind Two of these need more than a mechanical resolution, so flagging them specifically:
Once it merges cleanly and both CI legs are green again, ping me and I will take another pass. Nothing in the code itself is holding this up. |
Board and Agent Profiles
Adds the Board: a persistent, git-trackable task DAG in
.maestro/board.yamlwhose cards are dispatched to Agent Profiles on the existing Maestro Cue engine tick (no second timer). Encore-gated, and gated again on Cue.Core
.maestro/profiles.yaml): named model / effort / role-prompt / args override bundles, resolved as profile, then base agent, then unset onto the existing spawn options. No new spawn parameters.done), then claim up to a WIP cap, then spawn, then apply. The board is persisted before any spawn resolves, so a restart or a re-entrant tick can never double-dispatch. Retries are bounded by a circuit breaker; every attempt is recorded as an auditableCardRun.boardWorker: truewhose working dir is inside the project can pick up role-only cards. Interactive agents are never hijacked.board/<board>/<card>, so cards dispatched in the same tick cannot collide. Provisioning goes through the single sharedsetupWorktreeLocal; nothing is auto-merged or auto-deleted. SSH remotes are refused rather than silently downgraded to a local checkout.maestro-cli board/profile): board and card lifecycle, profile lifecycle,tick, andwatch, all through the same storage modules and the same spawn path as the desktop app, so SSH remotes and profile overrides behave identically.board.cardStatusChanged,board.cardCompleted,board.cardBlocked,board.decomposed) at host API 1.15.0, on top of rc's 1.14.0 (tool.executed+ui.panelPost). Titles ship; summaries, prompts, outputs, and block reasons never do. The vendored plugin-sdk contracts are bumped in lockstep (0.10.0) with the drift guard green.triagecard into a wired child graph, capped per tick, using the editableboard-decomposecore prompt.Safety
Board and profile YAML loads fail closed (an unreadable file blocks writes instead of being silently replaced by an empty board, which used to destroy data), writes are atomic temp-file+rename, and async read-modify-write callers serialize through a shared per-file write chain. Card deletion keeps the DAG referentially intact through grandparent adoption. Deleting a board with unfinished cards requires an explicit force.
UI
Kanban modal with keyboard parity (a "Move to" picker rather than ARIA drag-and-drop), multi-board switching, per-card stop, done/blocked toasts, an expandable last-run summary, and a running/ready pill in the Main Window header. Both the modal and the pill update from a
board:changedpush after every write; nothing polls.Docs:
docs/board.md(users),docs/agent-guides/BOARD.md(contributors),docs/cli-reference.md(flags).Tests: shared DAG/markers/pool/types, storage, dispatcher, spawn, worktree, decompose, both CLI command suites, BoardModal, ProfilesModal, extension model, plugin events, and the SDK drift guard.
Known gaps (documented, deliberate):
agents:dispatch/process:spawnfor the Board; the runtime dispatch-grant seam is specified inPlans/rfc-runtime-dispatch-grants.mdand dispatch stays host-owned until it exists.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
maestro-cli board/maestro-cli profileheadless management (includingboard tick/watch).Documentation
Plugin Updates