@yemi33/minions 0.1.2383 → 0.1.2384

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -175,7 +175,8 @@ const RENDER_VERSIONS = {
175
175
  // Bumped to 3 by W-mqk2s8q1 (Advanced → Diagnostics: relocated the diag button).
176
176
  // Bumped to 4 by W-mqrdavob (Agents table: per-agent charter editor column).
177
177
  // Bumped to 5 by W-mr0qs0vw (Worker Pool: CC session idle-timeout field).
178
- settings: 5,
178
+ // Bumped to 6 for the fleet ACP pool opt-in default and warning copy.
179
+ settings: 6,
179
180
  };
180
181
  const _sectionCache = {};
181
182
  const _lastValueByKey = {};
@@ -448,7 +448,7 @@ async function openSettings() {
448
448
  '<div class="settings-pane-sub">Persistent CC worker pool and git worktree provisioning. Tune these only if you see worktree-create timeouts or slow CC cold-spawns.</div>' +
449
449
  '<div class="settings-stack" style="margin-bottom:12px">' +
450
450
  settingsToggle('CC Worker Pool', 'set-ccUseWorkerPool', (e.ccUseWorkerPool === undefined ? ((e.ccCli || e.defaultCli) === 'copilot') : !!e.ccUseWorkerPool), 'Route Command Center / doc-chat through a persistent copilot --acp worker per tab instead of spawning a fresh CLI per turn. Copilot-only (Agent Client Protocol transport); Claude does not implement ACP, so this toggle has no effect when CC runtime is Claude. Default ON for copilot (cold-spawn ~20s on Windows); forced OFF for non-copilot CC runtimes regardless of this toggle.') +
451
- settingsToggle('Agent Worker Pool', 'set-agentUseWorkerPool', e.agentUseWorkerPool !== false, 'Route fleet Copilot dispatches through persistent copilot --acp workers. Default ON; runtimes without ACP support ignore this setting.') +
451
+ settingsToggle('Agent Worker Pool', 'set-agentUseWorkerPool', e.agentUseWorkerPool === true, 'Route fleet Copilot dispatches through persistent copilot --acp workers. Default OFF while the newer fleet-pool lifecycle path stabilizes; runtimes without ACP support ignore this setting.') +
452
452
  // W-mq6f2fe0000557fa — opt-in: auto-kill orphan spawn-agent processes
453
453
  // holding a stuck worktree's cwd. Gated by safe-reap criteria (cmdline
454
454
  // matches spawn-agent.js + basename + age > agentTimeout * 2). Default
package/dashboard.js CHANGED
@@ -13637,7 +13637,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13637
13637
  }
13638
13638
  }},
13639
13639
 
13640
- { method: 'POST', path: '/api/pr-action/offer-create-pr', desc: 'Offer a "Create PR" follow-up chip after Command Center makes a LOCAL code edit itself in a configured project (P-cc-createpr). Validates the project has uncommitted working-tree changes (git status --porcelain in project.localPath) and returns a create-pr follow-up chip — same CC-mediated shape as the pr-action chips, so the existing dashboard chip renderer/click handler light up unchanged. Returns {ok, hasChanges, branch, changedFileCount, followups}; followups is [] when there is nothing to PR. READ-ONLY: never stages/commits/pushes — clicking the chip routes a fresh CC turn that does the commit/push/PR-create/link.', params: 'project (configured project name), branch (optional — defaults to the working-tree current branch), contextOnly (optional bool — link the new PR as context-only instead of auto-managed)', handler: async (req, res) => {
13640
+ { method: 'POST', path: '/api/pr-action/offer-create-pr', desc: 'Offer a "Create PR" follow-up chip for pre-existing operator changes in a configured project. Validates the project has uncommitted working-tree changes (git status --porcelain in project.localPath) and returns a create-pr follow-up chip. Returns {ok, hasChanges, branch, changedFileCount, followups}; followups is [] when there is nothing to PR. READ-ONLY: never stages/commits/pushes.', params: 'project (configured project name), branch (optional — defaults to the working-tree current branch), contextOnly (optional bool — link the new PR as context-only instead of auto-managed)', handler: async (req, res) => {
13641
13641
  const body = await readBody(req);
13642
13642
  try {
13643
13643
  reloadConfig();
@@ -13658,10 +13658,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13658
13658
  const hasChanges = changedFileCount > 0;
13659
13659
  const branch = (typeof body?.branch === 'string' && body.branch.trim()) || currentBranch || '';
13660
13660
  // The Create-PR action follows the SAME checkout pattern as a dispatch
13661
- // for this project: 'live' commits in the live checkout; 'worktree'
13662
- // routes the changes through an isolated worktree (so nothing is ever
13663
- // committed in the operator's live main). See engine/create-pr-worktree.js.
13664
- const checkoutMode = shared.resolveCheckoutMode(project);
13661
+ // for this project: 'live' uses the live-checkout flow; 'worktree'
13662
+ // copies changes into an isolated worktree and preserves the source.
13663
+ const checkoutMode = shared.resolveCheckoutMode(project, shared.WORK_TYPE.IMPLEMENT);
13665
13664
  // Resolve the project's actual main/base branch instead of trusting
13666
13665
  // whatever `git rev-parse --abbrev-ref HEAD` returned as a safe base —
13667
13666
  // the live checkout may be sitting on a stale/topic branch whose
@@ -13677,7 +13676,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13677
13676
  }
13678
13677
  }},
13679
13678
 
13680
- { method: 'POST', path: '/api/pr-action/prepare-create-pr-worktree', desc: 'Stage a worktree-mode project\'s uncommitted live-checkout changes into an ISOLATED git worktree and restore the live checkout clean (P-cc-createpr-checkout-mode). The Create-PR action calls this for projects whose checkoutMode is "worktree" so the subsequent commit/push/PR happen off the operator tree — never committing to the live main. Safe ordering: the worktree is populated and verified BEFORE the live checkout is touched; on apply failure the worktree is removed and the live checkout is left untouched. Returns {ok, worktreePath, branch, baseBranch, baseSha, trackedChanged, untrackedCount}; {ok:false, reason:"no-changes"} when the tree is clean.', params: 'project (configured project name), branch (optional preferred branch name)', handler: async (req, res) => {
13679
+ { method: 'POST', path: '/api/pr-action/prepare-create-pr-worktree', desc: 'Copy a worktree-mode project\'s pre-existing operator changes into an ISOLATED git worktree without modifying the source checkout. Subsequent commit/push/PR operations happen only in the returned worktree. Returns {ok, worktreePath, branch, baseBranch, baseSha, trackedChanged, untrackedCount, sourceCheckoutPreserved}; {ok:false, reason:"no-changes"} when the tree is clean.', params: 'project (configured project name), branch (optional preferred branch name)', handler: async (req, res) => {
13681
13680
  const body = await readBody(req);
13682
13681
  try {
13683
13682
  reloadConfig();
@@ -13688,6 +13687,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13688
13687
  if (!project.localPath) return jsonReply(res, 400, { error: `project ${projectName} has no localPath` }, req);
13689
13688
  const result = await createPrWorktree.prepareCreatePrWorktree({
13690
13689
  project,
13690
+ projects: shared.getProjects(CONFIG),
13691
13691
  branch: typeof body?.branch === 'string' ? body.branch : undefined,
13692
13692
  minionsDir: MINIONS_DIR,
13693
13693
  engine: CONFIG.engine,
@@ -13707,7 +13707,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13707
13707
  const project = projectName ? shared.getProjects(CONFIG).find(p => p && p.name === projectName) : null;
13708
13708
  const worktreePath = typeof body?.worktreePath === 'string' ? body.worktreePath.trim() : '';
13709
13709
  if (!worktreePath) return jsonReply(res, 400, { error: 'worktreePath is required' }, req);
13710
- const result = await createPrWorktree.cleanupCreatePrWorktree({ worktreePath, project });
13710
+ const result = await createPrWorktree.cleanupCreatePrWorktree({
13711
+ worktreePath,
13712
+ project,
13713
+ projects: shared.getProjects(CONFIG),
13714
+ });
13711
13715
  return jsonReply(res, 200, result, req);
13712
13716
  } catch (e) {
13713
13717
  return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
@@ -22,7 +22,7 @@ Set `meta.keep_processes: true` on the WI; agent writes `agents/<id>/keep-pids.j
22
22
  {
23
23
  "pids": [12345], // ≤5 entries
24
24
  "purpose": "gradle daemon for follow-up test WI",
25
- "cwd": "D:/repos/my-android-app", // MUST be a real git worktree
25
+ "cwd": "<MINIONS_AGENT_CWD>", // assigned dispatch root or a subdirectory
26
26
  "ports": [8080], // advisory, engine doesn't bind
27
27
  "expires_at": "2026-04-29T00:00:00Z", // TTL ≤1440 min; default 60
28
28
  "written_by": "ripley",
@@ -42,6 +42,11 @@ Set `meta.keep_processes: true` on the WI; agent writes `agents/<id>/keep-pids.j
42
42
 
43
43
  - **PIDs:** ≤5 per sidecar.
44
44
  - **TTL:** ≤1440 min (24h). Default 60.
45
- - **Cwd:** must exist and resolve to inside a git worktree (`requireGitWorkdir: true`). Monorepo subdirs walk up to `gitWorktreeMaxParentDepth` (default 6).
45
+ - **Cwd:** must exist, resolve inside a git worktree, and stay at or below the current dispatch root from `MINIONS_AGENT_CWD`. Monorepo subdirs are allowed. In worktree mode, pointing at `project.localPath` is rejected before the wrapper decides which descendants survive. `keep_processes` dispatches use the cold spawn wrapper rather than the ACP pool so descendant ownership remains enforceable.
46
+
47
+ After acceptance, the engine adds project/work-type/checkout-mode/dispatch-root
48
+ provenance to the sidecar. Boot sweep uses it to kill and unlink any retained
49
+ process whose cwd now overlaps a worktree-mode operator checkout (including
50
+ legacy sidecars from the old read-only-in-`localPath` behavior).
46
51
 
47
52
  See also: [`CLAUDE.md`](../CLAUDE.md) → **Agent Spawn**, [managed-spawn.md](managed-spawn.md).
@@ -78,7 +78,7 @@ This is **DESTRUCTIVE** — tracked WIP and untracked files can be permanently u
78
78
 
79
79
  #### 2c. Stale `.git/index.lock` self-heal (W-mr3tayu4)
80
80
 
81
- Before any preflight git command runs, `prepareLiveCheckout` calls the shared, age-gated `shared.removeStaleIndexLock(localPath)` to clear a leftover `.git/index.lock` from a crashed/killed git process. Live mode never resets/cleans the operator tree, so nothing else would clear it, and — since `LIVE_CHECKOUT_FAILED` is retryable — every automatic retry used to hit the same stale lock and fail identically forever until an operator deleted it by hand. The remover only touches locks older than 5 minutes, so a lock held by a currently-running git process is never removed out from under it. `engine.js`'s own worktree-mode lock cleanup now delegates to the same shared helper.
81
+ Before any preflight git command runs, `prepareLiveCheckout` calls the shared, age-gated `shared.removeStaleIndexLock(localPath)` to clear a leftover `.git/index.lock` from a crashed/killed git process. Live mode never resets/cleans the operator tree, so nothing else would clear it, and — since `LIVE_CHECKOUT_FAILED` is retryable — every automatic retry used to hit the same stale lock and fail identically forever until an operator deleted it by hand. The remover only touches locks older than 5 minutes, so a lock held by a currently-running git process is never removed out from under it. Worktree-mode setup never calls this helper against `project.localPath`.
82
82
 
83
83
  ### 3. No implicit remote sync or force checkout
84
84
 
@@ -214,7 +214,7 @@ Enable it fleet-wide via **Settings → "Live-checkout auto-base-repair (fleet-w
214
214
 
215
215
  ## Dispatch-end auto-restore
216
216
 
217
- Live-mode agents run **in-place** in the operator's checkout, so when a dispatch ends the engine switches the tree back to the ref it was on before the agent ran. This runs on **every** terminal result — success, failure, timeout, crash — and also on **every restart-recovery reaping path**: the engine-restart re-attach orphan-completion path (`engine/cli.js`) AND the `engine/timeout.js` paths that reap a dispatch the engine lost the process handle for (`completeFromOutput` + the orphan sweep). All three route through the shared `maybeRestoreLiveCheckoutFromRecord(item, …)` helper (which fires from the *persisted* dispatch record — `originalRef`'s presence is the live-mode signal), so a live agent that spans an engine restart and finishes afterward never strands the checkout on the agent's branch. (Previously the timeout.js reaping paths had **no** restore wiring, which is exactly how a restart-spanning live dispatch left the operator stuck on `work/W-…`.)
217
+ Live-mode agents run **in-place** in the operator's checkout, so when a dispatch ends the engine switches the tree back to the ref it was on before the agent ran. This runs on terminal results and restart-recovery reaping paths through `maybeRestoreLiveCheckoutFromRecord`. Restore now requires both persisted `checkoutMode: "live"` provenance (legacy records may use `originalRef`) and a current configured mode that still resolves live for the dispatch type. A worktree marker or current worktree mode fails closed before any Git probe.
218
218
 
219
219
  - **Original-ref capture.** `prepareLiveCheckout` records the operator's starting ref *before* the first checkout: `git symbolic-ref --short HEAD` → `{ originalRef:<branch>, originalRefType:'branch' }`, falling back to `git rev-parse HEAD` → `{ originalRef:<sha>, originalRefType:'detached' }`. `spawnAgent` persists `originalRef` / `originalRefType` onto the dispatch record via `mutateDispatch`, so the restore survives an engine restart, where the in-memory spawn closure is gone and only the persisted record remains.
220
220
  - **Self-healing dirty recovery (PL-live-checkout-reliability-hardening).** Before the plain checkout, if the tree is **dirty AND HEAD is on the agent branch** (and that branch differs from `originalRef`), the dirt is provably **agent-authored** — the engine created that branch and verified the tree clean before switching to it. The leftovers are committed onto the **agent branch** (`git add -A` + `git commit --no-verify -m "minions: auto-save agent WIP (dispatch …)"`), so the plain `git checkout <originalRef>` then succeeds and the operator tree returns clean. This is the fix for the *"no recovery from dirty checkout"* deadlock, where an agent's crash-leftover WIP refused the restore, stranded the tree dirty on the agent branch, and then made **every future WI** for that project fail `LIVE_CHECKOUT_DIRTY` until a human cleaned it. It only ever mutates the engine-created branch, never the operator's branch, never discards (the WIP lands as a visible, revertable commit / on its PR), and gitignored artifacts are never staged (so a tree dirty only with ignored build output never reaches here). Best-effort: a failed auto-commit falls through to the manual-recovery alert below.
@@ -229,7 +229,7 @@ The core invariant holds end-to-end through restore: **the engine only ever swit
229
229
 
230
230
  ## Create-PR chip safety (CC-initiated, separate from the dispatch path)
231
231
 
232
- The Command Center **"Create PR"** chip (`dashboard.js` `POST /api/pr-action/offer-create-pr` `engine/pr-action.js#buildCreatePrFollowups`) is a **separate, ad-hoc path** that opens a PR for a local edit Command Center just made in a live-checkout project. It does **not** go through `prepareLiveCheckout`, so the dirty-tree / orphaned-branch / mid-operation defenses above (Guarantees 2 & 5, dispatch-start self-heal) do **not** apply to it. This path needs its own equivalent safety contract, because it shares the same failure mode: the live checkout may be sitting on a stale/topic branch left over from a previous run, a manually-checked-out branch, or any other non-main state.
232
+ Command Center never creates edits inside a configured project's `localPath`. The **"Create PR"** chip is only for pre-existing operator changes. In worktree mode, `prepareCreatePrWorktree` creates a fresh branch from `origin/<mainBranch>` (falling back to local main when no remote exists), applies only the uncommitted diff/untracked files, and leaves the source checkout unchanged. Topic commits from the operator's current branch cannot leak into the PR. Live-mode Create-PR remains a deliberate in-place flow and retains the base-branch protections below.
233
233
 
234
234
  **The bug it fixes (W-mr3jbpru).** The old live-mode chip message keyed its branch instruction off `git rev-parse --abbrev-ref HEAD` (the *current* branch) and only told the agent to branch off cleanly **if** that current branch already happened to be the project's main branch. If the checkout was on any other branch, the message told the agent to commit the new changes **directly onto that branch** — silently carrying all of that branch's unrelated commits into the new PR. There was no engine-side verification that the current branch was clean or at parity with main; it was trusted blindly. A user hit exactly this: a Create-PR chip PR ended up built on top of a leftover topic branch and carried unrelated diffs.
235
235
 
@@ -240,7 +240,7 @@ The Command Center **"Create PR"** chip (`dashboard.js` → `POST /api/pr-action
240
240
  3. If `git stash pop` reports a merge conflict (the stashed diff doesn't cleanly apply onto the fresh main tip), the message instructs the agent to **stop and surface the conflict** to the user rather than force-resolving it silently.
241
241
  4. The message **names the resolved main branch explicitly** (e.g. `origin/android`) instead of leaving "the project main branch" vague, so the always-branch-off-resolved-main rule is stated unambiguously.
242
242
 
243
- This mirrors, in CC-instruction form, what `prepareLiveCheckout` enforces engine-side for the dispatch path: never reuse whatever branch the live checkout happened to be on as the base for new work. The **worktree-mode** branch of `buildCreatePrFollowups` is unaffected — that path already routes the changes through an isolated worktree (`engine/create-pr-worktree.js`) so nothing is ever committed on the operator's `main`.
243
+ This mirrors, in CC-instruction form, what `prepareLiveCheckout` enforces engine-side for the dispatch path: never reuse whatever branch the live checkout happened to be on as the base for new work. The **worktree-mode** branch copies existing changes to an isolated worktree without modifying the operator checkout.
244
244
 
245
245
  ## Operator workflow
246
246
 
@@ -362,11 +362,11 @@ Live-checkout mode is deliberately small. These are NOT supported and will not b
362
362
  |---|---|
363
363
  | `engine/shared.js` — `CHECKOUT_MODES`, `validateCheckoutMode`, `resolveCheckoutMode`, `isLiveCheckoutProject` | Enum + validator + back-compat resolver (P-a3f9b201; consolidated W-mqiaw974). |
364
364
  | `engine/shared.js` — `resolveLiveCheckoutAutoReset` + `ENGINE_DEFAULTS.liveCheckoutAutoReset` | Pure precedence resolver (per-project boolean > fleet-wide engine default > false) + the fleet-wide default (OFF as of W-mrawgw4q000a6bff — was ON as of W-mqzbbhn2). Gates the dirty-tree auto-reset in `prepareLiveCheckout` (W-mqvejug6000eeb20); in `engine.js spawnAgent` only fires as a fallback after auto-stash fails/is disabled (W-mrawgw4q000a6bff). |
365
- | `engine/shared.js` — `resolveSpawnPaths` | Returns `{ cwd: localPath, worktreeRootDir: null, liveMode: true }` for live projects (P-a3f9b202). |
365
+ | `engine/shared.js` — `resolveSpawnPaths` | Returns `{ cwd: localPath, worktreeRootDir: null, liveMode: true }` for live dispatches; project-bound worktree-mode read-only types request detached worktree placement. |
366
366
  | `engine/live-checkout.js` — `prepareLiveCheckout` | Pure helper by default: pre-mutation mid-operation / detached-HEAD preflight, original-ref capture, dirty check, **already-on-branch fast path**, `refs/heads/<branch>` existence check, branch resolution from HEAD (no fetch — issue #226), **no-half-switch + `blob-fetch`/`worktree-conflict` classification**, and opt-in destructive WIP cleanup (`reset --hard HEAD` + `clean -fd` + re-check + audit note) that preserves the branch and committed history. Git probes use a **50 MB maxBuffer**. |
367
367
  | `engine/live-checkout.js` — `restoreLiveCheckoutAtDispatchEnd` | Dispatch-end auto-restore (plain `git checkout <originalRef>`, never `--force`/reset/clean/stash, best-effort) + **self-healing dirty recovery** (auto-commit agent WIP onto the agent branch) + `live-checkout-failed-<dispatchId>` terminal-failure alert + `live-checkout-branch-<dispatchId>` fallback notify (now also on unexpected restore errors) (P-d9e6b2c4; self-heal PL-live-checkout-reliability-hardening). |
368
368
  | `engine/live-checkout.js` — `resolveLiveCheckoutAutoStash`, `performLiveCheckoutAutoStash`, `applyLiveCheckoutAutoStash` | Auto-stash resolver (per-project boolean, then fleet setting, then default true) + `git stash push --include-untracked` runner + stash/re-preflight orchestration. Engine never auto-pops (W-mqtvnnj1000357fa). |
369
- | `engine/live-checkout.js` — `maybeRestoreLiveCheckoutFromRecord` | Shared wrapper that fires the dispatch-end restore from a persisted dispatch record; used by `cli.js` + both `timeout.js` reaping paths so a restart-spanning live dispatch is never stranded (PL-live-checkout-reliability-hardening). |
369
+ | `engine/live-checkout.js` — `maybeRestoreLiveCheckoutFromRecord` | Shared persisted-record restore wrapper; requires explicit live provenance plus current live configuration and fails closed for worktree mode. |
370
370
  | `engine.js` — `spawnAgent` live-mode block | Calls `prepareLiveCheckout`, delegates opt-in auto-stash to `applyLiveCheckoutAutoStash` on a dirty tree (W-mqtvnnj1000357fa), handles dirty / throw branches, gates `git worktree add` on `!liveMode` (P-a3f9b204). |
371
371
  | `engine.js` — `spawnAgent` mid-op / detached-HEAD refusal block | Emits `LIVE_CHECKOUT_MID_OPERATION`, writes `live-checkout-blocked-<wi-id>` alert, stamps `_pendingReason: 'live_checkout_mid_operation'` / `'live_checkout_detached_head'` (P-c5a1f3b8). |
372
372
  | `engine.js` — `spawnAgent` originalRef persistence | Persists `originalRef` / `originalRefType` onto the dispatch record via `mutateDispatch` so restore survives an engine restart (P-c5a1f3b8). |
@@ -376,7 +376,7 @@ Live-checkout mode is deliberately small. These are NOT supported and will not b
376
376
  | `engine.js` — dispatcher `liveProjectsInUse` set | Per-project mutating-concurrency cap (P-a3f9b205). |
377
377
  | `engine.js` — worktree-pool / orphan-GC short-circuits | `worktreePath===null` no-ops in live mode (P-a3f9b206). |
378
378
  | `engine/cleanup.js` — `runPeriodicWorktreeSweep` live filter | Excludes live-checkout projects from the registry-derived periodic worktree GC so the operator's primary checkout never enters the GC decision surface (PL-live-checkout-reliability-hardening). |
379
- | `engine/create-pr-worktree.js` — `prepareCreatePrWorktree` step-4 restore | `reset --hard HEAD` (not the index-leaking `checkout -- .`) + retried untracked removal + `liveTreeDirty` surfaced + `shared.removeWorktree` teardown (PL-live-checkout-reliability-hardening). |
379
+ | `engine/create-pr-worktree.js` — `prepareCreatePrWorktree` | Copies pre-existing operator changes into an isolated worktree and returns `sourceCheckoutPreserved: true`; never resets, cleans, checks out, or deletes source files. |
380
380
  | `engine/pr-action.js` — `buildCreatePrFollowups({ …, mainBranch })` live-mode message | CC "Create PR" chip instruction. In live mode the PR is ALWAYS built on a fresh branch off `origin/<mainBranch>` (stash → fetch → `checkout -B` → pop → commit → push → PR → restore), never reusing the current branch as the base; stop-on-stash-pop-conflict. `dashboard.js` `POST /api/pr-action/offer-create-pr` resolves `mainBranch` via `shared.resolveMainBranch` and threads it in (W-mr3jbpru). |
381
381
  | `dashboard/js/settings.js` — checkoutMode dropdown + chip + `set-liveCheckoutAutoReset` fleet toggle | Operator-facing UI; the fleet-wide auto-reset toggle persists to `engine.liveCheckoutAutoReset` (per-project UI deferred — config.json only) (P-a3f9b207; auto-reset toggle W-mqvejug6000eeb20). |
382
382
  | `test/unit/{resolve-spawn-paths-live-mode,prepare-live-checkout,spawn-agent-live-mode-wiring,live-checkout-auto-stash,live-checkout-stash-before-reset}.test.js` | Wiring and contract tests (P-a3f9b208; auto-stash helpers W-mqtvnnj1000357fa; stash-before-reset ordering W-mrawgw4q000a6bff). |
@@ -35,7 +35,7 @@ The sidecar lives at `<MINIONS_DIR>/agents/<agentId>/managed-spawn.json` and is
35
35
  "name": "constellation-host", // kebab-case, ≤64 chars, unique within file
36
36
  "cmd": "bun", // must be on engine.managedSpawn.executableAllowlist
37
37
  "args": ["run", "dev"], // ≤64 entries
38
- "cwd": "D:/repos/constellation", // must be inside a real git worktree (requireGitWorkdir: true) — monorepo subdirs ok, ancestor walked up to gitWorktreeMaxParentDepth parents
38
+ "cwd": "<MINIONS_AGENT_CWD>", // assigned dispatch root or a subdirectory
39
39
  "env": { "CONSTELLATION_SERVER": "http://localhost:3000" }, // ≤32 keys; POSIX-shape + denylist enforced
40
40
  "ports": [3001], // 1024-65535; ≤20 per spec; advisory only (engine doesn't bind)
41
41
  "ttl_minutes": 240, // ≤1440 (24h hard cap); defaults to 720 (12h)
@@ -59,6 +59,19 @@ The sidecar lives at `<MINIONS_DIR>/agents/<agentId>/managed-spawn.json` and is
59
59
 
60
60
  The renderer in [`buildManagedSpawnHint`](../engine/managed-spawn.js) at `engine/managed-spawn.js:419` emits this exact shape (with allowlist + cap reminders) into the agent's prompt whenever the work item has `meta.managed_spawn: true`. Treat the rendered hint as the source of truth — if this doc and the hint drift, the hint wins.
61
61
 
62
+ The engine also confines every spec to the dispatch root: `cwd` must resolve at
63
+ or below `MINIONS_AGENT_CWD`. In worktree mode, `project.localPath` is rejected
64
+ even though it is a valid Git checkout. Project-specific env deny patterns are
65
+ resolved from the dispatch's configured project, not inferred from the sibling
66
+ worktree path.
67
+
68
+ Persisted managed-process rows carry the canonical dispatch root and originating
69
+ work type. Manual restart and boot reconciliation revalidate that provenance
70
+ against current project checkout mode; a legacy or live-to-worktree record that
71
+ would restart inside `project.localPath` is killed and removed instead. The
72
+ periodic sweep applies the same check, so mode drift is enforced immediately
73
+ rather than waiting for TTL or another engine restart.
74
+
62
75
  ### Smoke-test before writing the sidecar (mandatory, W-mpbpexrg00110661)
63
76
 
64
77
  The hint mandates that agents run each `cmd args` in the declared `cwd` for at least 5 seconds AND confirm the healthcheck endpoint returns the expected status BEFORE writing `managed-spawn.json`. Sidecars written from guessed-at commands are how dispatches silently lose specs: the engine spawns what the agent declared; if the command crashes on first launch, the engine kills the spec, removes it from state, and (for partial failures) marks the WI with `_managedSpawnPartial` rather than demoting it — the agent's primary work succeeded by validator standards. The cost of guessing wrong is the operator finding out hours later that half the stack is down.
@@ -159,12 +159,15 @@ handlers, PID-file writing, live-output.log, timeout/steering cancel-vs-kill,
159
159
  restart-reattach) runs unmodified regardless of whether `proc` is a real OS
160
160
  process or a pooled lease. Assignment strategy, in order:
161
161
 
162
- 1. A **free** worker whose MCP-server set already matches wins first — reused
163
- via a fresh `session/new`, no respawn.
162
+ 1. A **free** worker whose MCP-server set, process environment, and canonical
163
+ process cwd already match wins first — reused via a fresh `session/new`, no
164
+ respawn.
164
165
  2. Otherwise another free worker is **evicted** (closed) and a replacement is
165
166
  spawned fresh with the requested MCP servers/cwd (MCP servers are resolved
166
- at `copilot --acp` process boot, so a changed server set requires a full
167
- respawn, not just a new session).
167
+ at `copilot --acp` process boot, so a changed server set or process cwd
168
+ requires a full respawn, not just a new session). This prevents a worker OS
169
+ process rooted in a live operator checkout from carrying that cwd into an
170
+ isolated worktree dispatch.
168
171
  3. Otherwise, if the pool has room (fewer than `agentAcpPoolSize` workers
169
172
  allocated across free + busy + in-flight-spawn), a brand-new worker spawns.
170
173
  4. Otherwise the request queues FIFO until a `release()` or worker crash frees
@@ -11,6 +11,28 @@ Lifecycle keeps the cross-cutting invariants; the detail lives here.
11
11
  > `_killGitDescendantsForWorktree`, `pruneOrphanWorktrees*`, `gcDispatchWorktreeIfOrphan`),
12
12
  > `engine/cleanup.js`. Last verified: 2026-06-09.
13
13
 
14
+ ## Operator-checkout boundary
15
+
16
+ For `checkoutMode: "worktree"`, `project.localPath` is an operator-owned source
17
+ checkout, not an execution directory. Project-bound read-only and mutating
18
+ dispatches both run in engine-owned worktrees; Command Center delegates writes;
19
+ Create-PR staging copies existing operator changes without resetting or deleting
20
+ the source; restart restore requires explicit live dispatch provenance plus
21
+ current live configuration. Branchless project dispatches receive detached
22
+ worktrees, and path containment resolves symlinks/junctions before accepting a
23
+ worktree or `meta.workdir`. Placement and removal are checked against **every**
24
+ configured `localPath`, so a nested project's worktree cannot land inside a
25
+ parent project's operator checkout. Restart reattachment requires either a
26
+ validated persisted worktree path or current live-mode provenance; unsafe
27
+ legacy processes are terminated and retried. Worktree setup may update shared Git refs/metadata,
28
+ but it never checks out, resets, cleans, writes files, or removes
29
+ `.git/index.lock` in the operator checkout.
30
+
31
+ Worktrees that host live `managed_spawn` or `keep_processes` cwd records are
32
+ anchored across dispatch-end, periodic, boot, in-root, and out-of-root GC.
33
+ Anchor comparisons use canonical filesystem paths so junction/case aliases do
34
+ not make a live service's worktree look orphaned.
35
+
14
36
  ## Live-worktree guard (W-mq5rwwss000f30a7)
15
37
 
16
38
  **Invariant:** every code path that wipes, renames, or recycles a worktree