@yemi33/minions 0.1.2260 → 0.1.2262

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.
@@ -70,6 +70,25 @@ Any violation rejects the **whole turn** with a typed error envelope (`code: 'in
70
70
 
71
71
  **No new `engine.*` flag.** The limits above are module-level constants in `dashboard.js` (`CC_IMAGE_MAX_COUNT`, `CC_IMAGE_MAX_DECODED_BYTES`, `CC_IMAGE_MIME_ALLOWLIST`), not config keys; `engine/shared.js` `ENGINE_DEFAULTS` was not touched. Per CLAUDE.md Best Practice #9 (Settings parity), no Settings toggle is added because no new `engine.*` flag was introduced. Runtime selection that determines whether images are accepted is the existing `engine.ccCli` / `ccModel` override, which already has a Settings control.
72
72
 
73
+ ## Create-PR flow — checkout-mode-aware (PR #387)
74
+
75
+ The "Create PR" chip offered by CC after a local edit follows the **same checkout pattern** as a normal dispatch for the project (`shared.resolveCheckoutMode`).
76
+
77
+ - **`checkoutMode: 'live'`** (default for live-mode projects): unchanged behavior — the agent commits and pushes directly in the operator's live checkout as the CC system prompt instructs.
78
+ - **`checkoutMode: 'worktree'`** (the default for most projects): the engine routes the changes through an isolated worktree deterministically and server-side so nothing depends on the LLM running git correctly.
79
+
80
+ For worktree-mode projects the flow uses **`engine/create-pr-worktree.js`**:
81
+
82
+ 1. `prepareCreatePrWorktree` captures the live checkout's uncommitted changes (tracked diff via `git diff --binary HEAD` + untracked files) and applies them into a fresh worktree on a `cc-pr/<project>-<uid>` branch. 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 (operator never loses work).
83
+ 2. The agent then commits, pushes, and opens the PR from inside the worktree.
84
+ 3. `cleanupCreatePrWorktree` removes the worktree, refusing any path without the `.minions-worktree` ownership marker.
85
+
86
+ Dashboard endpoints added by this feature:
87
+ - `POST /api/pr-action/prepare-create-pr-worktree` — called before the agent's git operations
88
+ - `POST /api/pr-action/cleanup-create-pr-worktree` — called after the PR is open (or on failure)
89
+
90
+ The `buildCreatePrFollowups` helper in `engine/pr-action.js` emits worktree-aware instructions for worktree mode and the legacy live-checkout instructions for live mode; `dashboard.js /api/pr-action/offer-create-pr` resolves the checkout mode and passes it through.
91
+
73
92
  ## Per-turn surfacing pipeline
74
93
 
75
94
  CC handler generates `ccTurnId = 'cct-' + shared.uid()` per request; injected into sysprompt AND prompt body via `_ccTurnHeaderPart(turnId)` (load-bearing: on resumed sessions `engine/llm.js` skips re-sending the sysprompt, so without body injection CC keeps the stale turn ID). Handler reads via `_readCcTurnIdHeader(req)` and calls `_recordCcTurnCreation(turnId, ...)` on success. End-of-turn: `_buildSyntheticActionResultsForTurn` produces synthetic `{action, result}` pairs (`_serverExecuted: true`). Client renders as standalone `role='action'` messages outside the assistant bubble. TTL: 5 min. Endpoints wired: `/api/work-items`, `/api/notes`, `/api/plan`, `/api/knowledge`, `/api/watches`.
@@ -174,6 +174,40 @@ EPERM/EBUSY stragglers that the dispatch-end GC couldn't reap and sweeps
174
174
  the `git worktree list` registry for OUT-of-root entries the in-root
175
175
  scanner is blind to.
176
176
 
177
+ ### Scratch-dir skip and bounded reaper (PR #388)
178
+
179
+ The worktree root (`<localPath>/../worktrees/`) is a **shared namespace**: it
180
+ holds both engine-managed worktrees AND `.agent-temp` — the scratch base where
181
+ dispatched agents and agent-run test suites write throwaway git repos. Before
182
+ this fix, `cleanup.js` enumerated the worktree root one level deep on every
183
+ cleanup cycle and called `removeWorktree` on each entry (which correctly
184
+ refused — scratch repos are not worktrees). With 26k+ leaked scratch dirs that
185
+ readdir + stat walk stalled the tick loop for minutes, surfacing as "engine
186
+ stale" (process alive but `lastTickAt` minutes in the past).
187
+
188
+ **Architectural invariant:** any dot-prefixed entry in the worktree root is
189
+ infra / scratch, never a managed worktree. Engine-created worktree dir names
190
+ (`buildWorktreeDirName`) are always `W-…` or `<project>-<branch>-<hash>` and
191
+ never start with `.`. The predicate `shared.isWorktreeRootInfraEntry(name)`
192
+ (returns `true` when `name` starts with `.`) is now applied at **both**
193
+ on-disk enumeration sites — `cleanup.js` worktree scan and
194
+ `worktree-gc.js#pruneOrphanWorktrees` — so the sweep is O(1) on scratch size
195
+ regardless of backlog.
196
+
197
+ **Bounded TTL reaper (`cleanup.js#reapAgentScratch`).** Skipping scratch
198
+ prevents stalls but doesn't shrink the pile. `reapAgentScratch` deletes
199
+ entries under `<worktreeRoot>/.agent-temp`
200
+ (`shared.WORKTREE_SCRATCH_DIR_NAME`) whose `mtime` is older than **6 h**
201
+ (comfortably above the 5 h `agentTimeout`, so only dead-dispatch scratch is
202
+ touched). Scan is capped at **2,000 entries per cleanup cycle** so the reaper
203
+ itself can never stall the tick on a huge backlog — it drains across cycles.
204
+ Deletion is plain `fs.rmSync` (not `removeWorktree`) because scratch repos are
205
+ not registered worktrees.
206
+
207
+ When adding new code that enumerates the worktree root directory, apply
208
+ `shared.isWorktreeRootInfraEntry(name)` to skip dot-prefixed entries before
209
+ calling any `removeWorktree` path.
210
+
177
211
  ### Ownership marker — out-of-root GC only touches engine worktrees (W-mqecdoot)
178
212
 
179
213
  A project repo's `git worktree list` includes EVERY worktree registered
package/engine/cli.js CHANGED
@@ -609,28 +609,6 @@ const commands = {
609
609
  try { shared.applyLegacyCcModelMigration(config, { logger: e.log }); }
610
610
  catch (err) { e.log('warn', `legacy ccModel migration failed: ${err.message}`); }
611
611
 
612
- // One-time force-on of the CC worker pool. The pool has been the resolved
613
- // default for copilot CC since PR #2492, but configs still carrying an
614
- // explicit `ccUseWorkerPool: false` (set before the default flipped) stay
615
- // opted out forever. Flip those to ON once, persisting the
616
- // `engine._ccPoolForcedOnV1` marker so a deliberate later opt-out sticks.
617
- // Disk-side re-derives from the on-disk copy so a concurrent dashboard
618
- // write isn't clobbered; skipWriteIfUnchanged makes it a no-op once marked.
619
- try {
620
- const forced = shared.applyCcWorkerPoolForceOnMigration(config);
621
- if (forced.changed) {
622
- const configPath = path.join(shared.MINIONS_DIR, 'config.json');
623
- shared.mutateJsonFileLocked(configPath, (onDisk) => {
624
- shared.applyCcWorkerPoolForceOnMigration(onDisk);
625
- return onDisk;
626
- }, { defaultValue: {}, skipWriteIfUnchanged: true });
627
- if (forced.flipped.length) {
628
- e.log('info', `Forced CC worker pool ON — flipped explicit opt-out in: ${forced.flipped.join(', ')}`);
629
- console.log(` Forced CC worker pool ON (was explicitly off in: ${forced.flipped.join(', ')}).`);
630
- }
631
- }
632
- } catch (err) { e.log('warn', `cc worker pool force-on migration failed: ${err.message}`); }
633
-
634
612
  // Repair work-item note links that broke before the rewriter covered
635
613
  // completion-report artifact links (W-mq1j85cj00055a8f). Notes the engine
636
614
  // auto-archived back then left the WI pill pointing at the gone
@@ -118,9 +118,17 @@ async function prepareCreatePrWorktree({
118
118
  }
119
119
 
120
120
  // 4. The worktree now holds the changes — restore the live checkout clean.
121
- await git(['-C', localPath, 'checkout', '--', '.']);
122
- for (const rel of untracked) {
123
- try { fsm.rmSync(path.join(localPath, rel.replace(/\/$/, '')), { recursive: true, force: true }); } catch { /* ignore */ }
121
+ try {
122
+ await git(['-C', localPath, 'checkout', '--', '.']);
123
+ for (const rel of untracked) {
124
+ try { fsm.rmSync(path.join(localPath, rel.replace(/\/$/, '')), { recursive: true, force: true }); } catch { /* ignore */ }
125
+ }
126
+ } catch (e) {
127
+ try { await git(['-C', localPath, 'worktree', 'remove', '--force', wtPath]); } catch { /* leak rather than double-throw */ }
128
+ throw new Error(
129
+ `prepareCreatePrWorktree: failed to restore live checkout — ${e.message}. ` +
130
+ `Worktree at ${wtPath} may need manual cleanup.`,
131
+ );
124
132
  }
125
133
 
126
134
  log('info', `[cc-create-pr] staged ${project.name} changes into isolated worktree ${wtPath} on branch ${branchName} (live checkout restored)`);
package/engine/shared.js CHANGED
@@ -3523,51 +3523,6 @@ function _resetLegacyCcModelMigrationFlag() {
3523
3523
  _legacyCcModelMigrationLogged = false;
3524
3524
  }
3525
3525
 
3526
- /**
3527
- * One-time force-on for the CC worker pool.
3528
- *
3529
- * The pool has been the resolved default for copilot CC since PR #2492
3530
- * (`resolveCcUseWorkerPool` returns true when the config has no explicit
3531
- * value). But configs that carried an explicit `ccUseWorkerPool: false` —
3532
- * set before the default flipped, or copied from an old template — stay
3533
- * opted out forever. This migration flips those explicit-false opt-outs to
3534
- * `true` ONCE, on both surfaces the value lives in (`engine.ccUseWorkerPool`
3535
- * which the resolver reads, and `features.ccUseWorkerPool` which drives the
3536
- * Settings toggle / `isFeatureOn`), so the two stay consistent.
3537
- *
3538
- * Idempotency: records `engine._ccPoolForcedOnV1` after running. Once that
3539
- * marker is set the migration never touches the value again — so an operator
3540
- * who *deliberately* turns the pool off afterward is never re-forced. The
3541
- * marker is persisted even when nothing needed flipping, which is what lets a
3542
- * later opt-out be distinguished from a never-migrated config.
3543
- *
3544
- * Pure + idempotent: safe to call in-memory then re-apply to the on-disk copy
3545
- * under a lock (mirrors backfillProjectWorkSourceDefaults). Returns
3546
- * `{ changed, flipped }` — `changed` true means the config was mutated (marker
3547
- * set and/or values flipped) and should be persisted; `flipped` lists which
3548
- * surfaces ('engine'/'features') were actually turned on.
3549
- */
3550
- function applyCcWorkerPoolForceOnMigration(config) {
3551
- const result = { changed: false, flipped: [] };
3552
- if (!config || typeof config !== 'object') return result;
3553
- const engine = (config.engine && typeof config.engine === 'object') ? config.engine : null;
3554
- if (!engine) return result; // no engine section — retry next start, harmless
3555
- if (engine._ccPoolForcedOnV1) return result; // already forced once; respect later opt-out
3556
-
3557
- if (engine.ccUseWorkerPool === false) {
3558
- engine.ccUseWorkerPool = true;
3559
- result.flipped.push('engine');
3560
- }
3561
- const features = (config.features && typeof config.features === 'object') ? config.features : null;
3562
- if (features && features.ccUseWorkerPool === false) {
3563
- features.ccUseWorkerPool = true;
3564
- result.flipped.push('features');
3565
- }
3566
- engine._ccPoolForcedOnV1 = true; // marker — never force again
3567
- result.changed = true; // marker always needs persisting on first run
3568
- return result;
3569
- }
3570
-
3571
3526
  // ─── Runtime Config Preflight Warnings ──────────────────────────────────────
3572
3527
  //
3573
3528
  // Emit non-fatal warnings about runtime/CLI configuration drift. Consumed by
@@ -4362,13 +4317,6 @@ const FAILURE_CLASS = {
4362
4317
  VERIFY_MISSING_PR: 'verify-missing-pr', // W-mqsk1ip00006cbae: a verify WI exited done but no PR was attached (neither _prUrl/_pr on the item nor a matching pull-requests.json entry for the plan). Flipped to failed so the plan doesn't silently advance without an E2E PR. Retryable — agent may have phantom-crashed before pushing the branch.
4363
4318
  UNKNOWN: 'unknown', // Unclassified failure
4364
4319
  };
4365
- const ESCALATION_POLICY = {
4366
- NO_RETRY: 'no-retry', // CONFIG_ERROR, PERMISSION_BLOCKED — never retry
4367
- RETRY_SAME: 'retry-same', // MERGE_CONFLICT, BUILD_FAILURE, MAX_TURNS — retry same agent
4368
- RETRY_FRESH: 'retry-fresh', // TIMEOUT, SPAWN_ERROR — retry with fresh session
4369
- HUMAN_REVIEW: 'human-review', // EMPTY_OUTPUT, OUT_OF_CONTEXT — flag for human
4370
- AUTO: 'auto', // UNKNOWN, NETWORK_ERROR — use default retry logic
4371
- };
4372
4320
 
4373
4321
  // Structured completion protocol — fields agents must produce in ```completion blocks
4374
4322
  const COMPLETION_FIELDS = ['status', 'summary', 'files_changed', 'tests', 'pr', 'not_changed', 'failure_class', 'retryable', 'needs_rerun', 'verdict', 'artifacts', 'nonce', 'securityFlags'];
@@ -4724,51 +4672,6 @@ function agentCanUseRepo(agent, repoTarget) {
4724
4672
  return false;
4725
4673
  }
4726
4674
 
4727
- /**
4728
- * Check whether `agent` may invoke `toolName`. Case-sensitive comparison —
4729
- * tool names are canonical (e.g. `Edit`, `Read`, `Bash`).
4730
- *
4731
- * Null/missing `allowed_tools` → permissive.
4732
- * Empty array → denies everything.
4733
- */
4734
- function agentCanUseTool(agent, toolName) {
4735
- const manifest = resolveAgentManifest(agent);
4736
- if (manifest.allowed_tools == null) return true;
4737
- if (typeof toolName !== 'string' || toolName.length === 0) return false;
4738
- return manifest.allowed_tools.includes(toolName);
4739
- }
4740
-
4741
- /**
4742
- * Check whether `agent` may fetch `urlString`. Wildcard entries of the form
4743
- * `*.example.com` match any subdomain AND the apex (`example.com`). Bare
4744
- * `example.com` matches only the exact host.
4745
- *
4746
- * Null/missing `allowed_external_urls` → permissive.
4747
- * Empty array → denies everything.
4748
- * Malformed URL → denied (cannot extract host to compare).
4749
- */
4750
- function agentCanFetchUrl(agent, urlString) {
4751
- const manifest = resolveAgentManifest(agent);
4752
- if (manifest.allowed_external_urls == null) return true;
4753
- let host = '';
4754
- try {
4755
- host = new URL(String(urlString)).hostname.toLowerCase();
4756
- } catch { return false; }
4757
- if (!host) return false;
4758
- if (manifest.allowed_external_urls.length === 0) return false;
4759
- for (const raw of manifest.allowed_external_urls) {
4760
- const entry = String(raw).trim().toLowerCase();
4761
- if (!entry) continue;
4762
- if (entry.startsWith('*.')) {
4763
- const suffix = entry.slice(2);
4764
- if (host === suffix || host.endsWith('.' + suffix)) return true;
4765
- } else if (host === entry) {
4766
- return true;
4767
- }
4768
- }
4769
- return false;
4770
- }
4771
-
4772
4675
  /** Return the agent's effective memory scope. Unknown values fall back to 'shared'. */
4773
4676
  function agentMemoryScope(agent) {
4774
4677
  const manifest = resolveAgentManifest(agent);
@@ -8854,7 +8757,6 @@ module.exports = {
8854
8757
  resolveCopilotAgentDisabledMcpServers,
8855
8758
  resolveAgentHermeticHarness,
8856
8759
  applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
8857
- applyCcWorkerPoolForceOnMigration,
8858
8760
  runtimeConfigWarnings,
8859
8761
  projectWorkSourceWarnings,
8860
8762
  backfillProjectWorkSourceDefaults,
@@ -8862,7 +8764,7 @@ module.exports = {
8862
8764
  WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS, WATCH_ACTION_TYPE,
8863
8765
  WATCH_STALLED_DEFAULT_TICKS, WATCH_STUCK_STAGE_DEFAULT_TICKS,
8864
8766
  PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,
8865
- FAILURE_CLASS, ESCALATION_POLICY, COMPLETION_FIELDS,
8767
+ FAILURE_CLASS, COMPLETION_FIELDS,
8866
8768
  HARNESS_USED_KINDS, HARNESS_USED_MAX_ENTRIES, HARNESS_USED_MAX_FIELD_LEN, normalizeHarnessUsed, groundHarnessUsed,
8867
8769
  DEFAULT_AGENT_METRICS,
8868
8770
  DEFAULT_AGENTS,
@@ -8874,8 +8776,6 @@ module.exports = {
8874
8776
  validateWorkspaceManifest,
8875
8777
  resolveAgentManifest,
8876
8778
  agentCanUseRepo,
8877
- agentCanUseTool,
8878
- agentCanFetchUrl,
8879
8779
  agentMemoryScope,
8880
8780
  mergeManifestAllowedTools,
8881
8781
  formatManifestRejection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2260",
3
+ "version": "0.1.2262",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"