@yemi33/minions 0.1.2374 → 0.1.2375

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.
@@ -446,6 +446,7 @@ async function openSettings() {
446
446
  '<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>' +
447
447
  '<div class="settings-stack" style="margin-bottom:12px">' +
448
448
  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.') +
449
+ 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.') +
449
450
  // W-mq6f2fe0000557fa — opt-in: auto-kill orphan spawn-agent processes
450
451
  // holding a stuck worktree's cwd. Gated by safe-reap criteria (cmdline
451
452
  // matches spawn-agent.js + basename + age > agentTimeout * 2). Default
@@ -484,12 +485,6 @@ async function openSettings() {
484
485
  '</select>' +
485
486
  '</div>' +
486
487
  settingsField('Copilot fallback model', 'set-copilotFallbackModel', e.copilotFallbackModel || '', 'e.g. gpt-5.4', 'Copilot has no --fallback-model flag. On a MODEL_UNAVAILABLE (overloaded/503) retry, the engine OVERRIDES --model with this value (Copilot only).') +
487
- '</div>' +
488
- '<div class="settings-stack" style="margin-top:12px">' +
489
- settingsField('Copilot: disable agent MCP servers', 'set-copilotAgentDisabledMcpServers',
490
- Array.isArray(e.copilotAgentDisabledMcpServers) ? e.copilotAgentDisabledMcpServers.join(', ') : (e.copilotAgentDisabledMcpServers || ''),
491
- 'e.g. playwright, maestro, loop',
492
- 'Comma-separated MCP server names (from ~/.copilot/mcp-config.json) the engine disables for autonomous Copilot agent dispatches by filtering them out of the agent-only COPILOT_HOME. Copilot loads your user MCP config on every spawn regardless of --add-dir; list the noisy/auth-popping servers (e.g. playwright, maestro) to keep them out of agents. Empty = inherit all.') +
493
488
  '</div>';
494
489
 
495
490
  const paneClaude =
@@ -588,7 +583,7 @@ async function openSettings() {
588
583
  '</div>' +
589
584
  '<h4>Agent Memory</h4>' +
590
585
  '<div class="settings-stack">' +
591
- settingsToggle('Shadow mode', 'set-memoryRetrievalShadowMode', e.memoryRetrievalShadowMode !== false, 'Measure retrieval without changing prompts') +
586
+ settingsToggle('Shadow mode', 'set-memoryRetrievalShadowMode', !!e.memoryRetrievalShadowMode, 'Measure retrieval without changing prompts') +
592
587
  settingsToggle('Capture task episodes', 'set-memoryEpisodicCapture', !!e.memoryEpisodicCapture, 'Store compact outcomes, never transcripts') +
593
588
  '</div>' +
594
589
  '<div class="settings-grid-2">' +
@@ -1128,6 +1123,7 @@ async function saveSettings() {
1128
1123
  autoFixHumanComments: document.getElementById('set-autoFixHumanComments').checked,
1129
1124
  autoCompletePrs: document.getElementById('set-autoCompletePrs').checked,
1130
1125
  ccUseWorkerPool: !!document.getElementById('set-ccUseWorkerPool')?.checked,
1126
+ agentUseWorkerPool: !!document.getElementById('set-agentUseWorkerPool')?.checked,
1131
1127
  autoReapOrphanWorktreeHolders: !!document.getElementById('set-autoReapOrphanWorktreeHolders')?.checked,
1132
1128
  liveCheckoutAutoStash: !!document.getElementById('set-liveCheckoutAutoStash')?.checked,
1133
1129
  liveCheckoutAutoReset: !!document.getElementById('set-liveCheckoutAutoReset')?.checked,
@@ -1188,7 +1184,6 @@ async function saveSettings() {
1188
1184
  claudeBareMode: !!document.getElementById('set-claudeBareMode')?.checked,
1189
1185
  claudeFallbackModel: (document.getElementById('set-claudeFallbackModel')?.value ?? '').trim(),
1190
1186
  copilotFallbackModel: (document.getElementById('set-copilotFallbackModel')?.value ?? '').trim(),
1191
- copilotAgentDisabledMcpServers: (document.getElementById('set-copilotAgentDisabledMcpServers')?.value ?? '').trim(),
1192
1187
  copilotDisableBuiltinMcps: !!document.getElementById('set-copilotDisableBuiltinMcps')?.checked,
1193
1188
  copilotStreamMode: document.getElementById('set-copilotStreamMode')?.value || 'on',
1194
1189
  copilotReasoningSummaries: !!document.getElementById('set-copilotReasoningSummaries')?.checked,
package/dashboard.js CHANGED
@@ -11374,17 +11374,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
11374
11374
  else if (valid.includes(e.copilotStreamMode)) _setEngineConfig('copilotStreamMode', e.copilotStreamMode);
11375
11375
  else _clamped.push(`copilotStreamMode: "${e.copilotStreamMode}" not in [on, off] (kept previous value)`);
11376
11376
  }
11377
- // P-mcp-storm — MCP server names to disable for autonomous Copilot agents
11378
- // (--disable-mcp-server). Accept an array OR a comma/whitespace string;
11379
- // normalize to a deduped array of trimmed names. Empty clears (inherit all).
11380
- if (e.copilotAgentDisabledMcpServers !== undefined) {
11381
- const src = Array.isArray(e.copilotAgentDisabledMcpServers)
11382
- ? e.copilotAgentDisabledMcpServers
11383
- : String(e.copilotAgentDisabledMcpServers || '').split(/[\s,]+/);
11384
- const names = [...new Set(src.map(s => String(s == null ? '' : s).trim()).filter(Boolean))];
11385
- if (names.length) _setEngineConfig('copilotAgentDisabledMcpServers', names);
11386
- else _deleteEngineConfig('copilotAgentDisabledMcpServers');
11387
- }
11388
11377
  // W-mpmwxkrw000872ec — fontSize allowlist. Clamps invalid values
11389
11378
  // (rather than silently failing) so the dashboard bootstrap never
11390
11379
  // ends up with an unknown data-font-size attribute.
@@ -15099,18 +15088,6 @@ if (require.main === module) {
15099
15088
  // engine reads, port binding) is captured rather than dying silently.
15100
15089
  _installCrashHandlers();
15101
15090
 
15102
- // Deliberately do NOT set process.env.COPILOT_HOME here. Command Center,
15103
- // doc-chat, and the CC worker pool (`copilot --acp`) all spawn `copilot`
15104
- // as this process's children, so they should inherit the operator's real
15105
- // `~/.copilot` home (including their model preference in settings.json)
15106
- // exactly like an interactive `copilot` CLI session would — that's the
15107
- // documented CLI contract. An earlier revision forced these onto an
15108
- // isolated, MCP-filtered home to dodge MCP-server auth popups, but that
15109
- // broke CC's settings inheritance and reintroduced a staleness/reseed bug;
15110
- // reverted (W-mre75l6x00024f49). Per-dispatch agent isolation is unaffected
15111
- // — the engine process still seeds its own COPILOT_HOME at boot and
15112
- // re-stamps it per spawn via `_applyAgentCopilotHome` (engine.js).
15113
-
15114
15091
  // Kick off first npm check on startup, then re-check every 4 hours.
15115
15092
  // Guarded here so that requiring dashboard as a module (unit tests, _withDashServer)
15116
15093
  // does not spawn npm child processes that keep the event loop alive past test teardown
@@ -11,6 +11,7 @@ How the engine manages the lifecycle of a PR from creation through review, fix,
11
11
 
12
12
  - `discoverFromPrs()` (engine.js) runs each tick (~10s)
13
13
  - Gates: `status === 'active'` + `reviewStatus === 'pending'` + not reviewed since last push + not dispatched + not on cooldown
14
+ - "Not reviewed since last push" is `!isPrReviewCurrent(pr)` — compares `pr.reviewedHeadSha` (the SHA the completed review verdict actually evaluated) against the live head, falling back to the legacy `lastPushedAt <= lastReviewedAt` timestamp comparison for PR records that predate `reviewedHeadSha`. The SHA-based check replaces a pure timestamp comparison, which could false-positive as "already reviewed" when a commit landed mid-review (`lastReviewedAt` is stamped at completion, i.e. *after* the mid-review push) — root cause of github:opg-microsoft/minions#821. Re-review (`reviewStatus === 'waiting'`) uses the mirrored `isPrFixedAfterReview(pr)` gate for the same reason. Both dispatch sites capture `reviewHeadSha: getPrCauseHead(pr)` into dispatch `meta` so the eventual verdict can stamp the exact SHA it evaluated.
14
15
  - Pre-dispatch: `checkLiveReviewStatus()` hits GitHub/ADO API to catch stale cached status
15
16
  - Routes to reviewer via `resolveAgent('review')`, dispatches with `review.md` playbook
16
17
 
@@ -144,8 +145,9 @@ A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be con
144
145
  | `buildErrorLog` | Poller | Actual CI error output for fix agents |
145
146
  | `_buildFixPushedAt` | Discovery (on dispatch) | Grace period timestamp |
146
147
  | `_buildFailNotified` | Discovery | Dedup for inbox alert |
147
- | `lastPushedAt` | Poller (new commit) | Tracks latest push for re-review logic |
148
- | `lastReviewedAt` | `updatePrAfterReview()` | Prevents re-dispatch if reviewed |
148
+ | `lastPushedAt` | Poller (new commit) | Tracks latest push for re-review logic (legacy fallback signal) |
149
+ | `lastReviewedAt` | `updatePrAfterReview()` | Legacy fallback for freshness gating on PR records that predate `reviewedHeadSha` |
150
+ | `reviewedHeadSha` | `updatePrAfterReview()` | SHA the completed review verdict actually evaluated (captured at dispatch time via `meta.reviewHeadSha`, or falls back to the live head for legacy in-flight dispatches); primary review-freshness signal (`isPrReviewCurrent`/`isPrFixedAfterReview`) |
149
151
  | `minionsReview` | Post-completion hooks | `{ reviewer, reviewedAt, note, fixedAt }` |
150
152
  | `humanFeedback` | `pollPrHumanComments()` | `{ pendingFix, feedbackContent, lastProcessedCommentDate }` |
151
153
  | `_noOpFixes[cause]` | `recordPrNoOpFixAttempt()` | Per-cause record `{ count, paused, fingerprint, beforeHead, afterHead }` driving the issue #2969 pause loop |
@@ -321,8 +321,6 @@ means zero such files were found), **Proposed target module**.
321
321
  | `resolveCcModel` | 3935-3950 | Resolve the model for the Command Center / doc-chat. Priority: 1. `engine.ccModel` — CC-only override 2. `engine.defaultModel` — fleet default (CC inherits this when ccModel unset) 3. `undefined` — let the runtime adapte | yes | `engine/llm.js`, `engine/pre-dispatch-eval.js`, `test/unit/auto-recovery.test.js`, `test/unit.test.js`, `dashboard.js` | engine/shared.js (keep) |
322
322
  | `resolveAgentMaxBudget` | 3951-3973 | Resolve the per-spawn USD budget cap. Priority: 1. `agent.maxBudgetUsd` — per-agent override 2. `engine.maxBudgetUsd` — fleet default 3. `undefined` — no cap Uses nullish coalescing so a literal `0` is honored as a valid | yes | `test/unit.test.js`, `engine.js` | engine/shared.js (keep) |
323
323
  | `resolveAgentBareMode` | 3974-3991 | Resolve whether this agent should run in Claude `--bare` mode. Priority: 1. `agent.bareMode` — per-agent override (boolean) 2. `engine.claudeBareMode` — fleet default 3. `false` — hardcoded fallback Strict undefined/null | yes | `test/unit.test.js`, `engine.js` | engine/shared.js (keep) |
324
- | `_normalizeMcpServerList` | 3992-4005 | P-mcp-storm — Resolve the list of MCP server names to disable for spawned // Copilot agents. The engine FILTERS these out of the agent's seeded // COPILOT_HOME mcp-config (engine.js `_applyAgentCopilotHome`) — a server a | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
325
- | `resolveCopilotAgentDisabledMcpServers` | 4006-4030 | Resolve copilot agent disabled mcp servers. | yes | `test/unit/agent-copilot-home.test.js` | engine/shared.js (keep) |
326
324
  | `applyLegacyCcModelMigration` | 4063-4082 | If `config.engine.ccModel` is set but `config.engine.defaultModel` is unset, mutate the in-memory `config.engine` so `defaultModel` mirrors `ccModel` and log a one-time deprecation notice. Does NOT write to disk. Returns | yes | `engine/cli.js`, `test/unit.test.js` | engine/shared.js (keep) |
327
325
  | `_resetLegacyCcModelMigrationFlag` | 4083-4113 | Test helper: reset the dedup flag so repeated tests can re-trigger the log. | yes | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
328
326
  | `runtimeConfigWarnings` | 4114-4194 | Inspect runtime fleet config and return warning entries for misconfiguration. Warnings emitted: - Unknown CLI: any `cli` value (per-agent, ccCli, defaultCli) not in `registeredRuntimes`. Each unknown value produces one e | yes | `engine/preflight.js` | engine/process-utils.js (new) — process spawn/pid/tmp-dir lifecycle helpers |
@@ -12,7 +12,7 @@ Multiple agents work the same repository in parallel and across days. Without sh
12
12
  - Agents re-litigate decisions the team already made (drift, regressions).
13
13
  - Human-flagged warnings (e.g., "do not self-approve PRs") get ignored because they live outside the prompt.
14
14
 
15
- Team memory closes the loop. The engine always injects pinned context. By default it also injects recent team and personal notes; when structured retrieval is enabled outside shadow mode, it replaces those broad appendices with a bounded task-relevant memory pack. Playbooks still instruct agents to consult `knowledge/` and `notes/inbox/` on disk before going wider.
15
+ Team memory closes the loop. The engine always injects pinned context. By default, structured retrieval replaces broad team and personal notes with a bounded task-relevant memory pack. Playbooks still instruct agents to consult `knowledge/` and `notes/inbox/` on disk before going wider.
16
16
 
17
17
  ## Structured memory and retrieval
18
18
 
@@ -20,11 +20,11 @@ Migration `017-agent-memory.js` backfills the existing Markdown sources into `me
20
20
 
21
21
  `engine/memory-retrieval.js` searches active records in global, current-project, and assigned-agent scopes. It reranks lexical matches by scope, trust, path match, recency, importance, and confidence, then packs the best records into a strict byte budget. Agent- or external-authored procedural records are never recalled as instructions. Every injected pack remains inside an `<UNTRUSTED-INPUT>` fence.
22
22
 
23
- Rollout is controlled by the default-off `memoryRetrieval` feature flag:
23
+ Structured retrieval is controlled by the default-on `memoryRetrieval` feature flag:
24
24
 
25
25
  - **Flag off:** legacy prompt behavior.
26
- - **Flag on + `memoryRetrievalShadowMode: true` (default):** compute retrieval and telemetry, but keep legacy prompts.
27
- - **Flag on + shadow mode off:** inject `Relevant Memory` when retrieval finds evidence; otherwise fall back to legacy notes and personal memory.
26
+ - **Flag on + shadow mode off (default):** inject `Relevant Memory` when retrieval finds evidence; otherwise fall back to legacy notes and personal memory.
27
+ - **Flag on + `memoryRetrievalShadowMode: true`:** compute retrieval and telemetry, but keep legacy prompts.
28
28
 
29
29
  Settings expose top-k, candidate count, byte budget, shadow mode, and episodic capture. The Inbox page's **Memory search** control shows provenance and status and supports explicit pin, retract, and restore actions.
30
30
 
@@ -96,8 +96,8 @@ const FEATURES = {
96
96
  expires: '2026-12-01',
97
97
  },
98
98
  'memoryRetrieval': {
99
- description: 'Select bounded task-relevant long-term memory with SQLite FTS5 instead of injecting only recent team and personal notes. Starts in shadow mode by default.',
100
- default: false,
99
+ description: 'Select bounded task-relevant long-term memory with SQLite FTS5 instead of injecting broad team and personal notes.',
100
+ default: true,
101
101
  addedIn: '0.1.2233',
102
102
  expires: '2027-01-31',
103
103
  },
@@ -9,6 +9,17 @@
9
9
  const crypto = require('node:crypto');
10
10
  const { getDb, withTransaction } = require('./db');
11
11
 
12
+ // Per CLAUDE.md's "State storage" mutator contract, every mutator wrapper
13
+ // must call emitStateEvent() after a successful write so the dashboard's
14
+ // MAX(events.id) cache-version advances — otherwise the dashboard keeps
15
+ // serving stale cached inbox state until an unrelated event happens to
16
+ // bump the counter. Best-effort: never let event emission fail a write.
17
+ function _emitInboxEvent(payload) {
18
+ try {
19
+ require('./db-events').emitStateEvent('inbox', payload);
20
+ } catch { /* best-effort */ }
21
+ }
22
+
12
23
  // In-process monotonic counter. Millisecond timestamps are too coarse to
13
24
  // disambiguate bursts of createInboxEntry() calls for the same
14
25
  // recipient+sender pair (e.g. rapid consolidation/notification writes), and
@@ -126,6 +137,7 @@ function createInboxEntry({
126
137
  }
127
138
  }
128
139
 
140
+ _emitInboxEvent({ id, recipientSessionId, action: 'create' });
129
141
  return getInboxEntryById(id);
130
142
  });
131
143
  }
@@ -144,6 +156,7 @@ function markInboxEntriesAsRead(entryIds) {
144
156
  const query = `UPDATE inbox_entries SET unread = 0, read_at = ? WHERE id IN (${placeholders})`;
145
157
  const stmt = db.prepare(query);
146
158
  const result = stmt.run(now, ...entryIds);
159
+ if (result.changes > 0) _emitInboxEvent({ entryIds, action: 'mark_read' });
147
160
  return { wrote: result.changes > 0 };
148
161
  });
149
162
  }
@@ -161,6 +174,7 @@ function markInboxEntriesAsUnread(entryIds) {
161
174
  const query = `UPDATE inbox_entries SET unread = 1, read_at = NULL WHERE id IN (${placeholders})`;
162
175
  const stmt = db.prepare(query);
163
176
  const result = stmt.run(...entryIds);
177
+ if (result.changes > 0) _emitInboxEvent({ entryIds, action: 'mark_unread' });
164
178
  return { wrote: result.changes > 0 };
165
179
  });
166
180
  }
@@ -176,6 +190,7 @@ function markAllInboxAsRead(recipientSessionId) {
176
190
  const result = db.prepare(
177
191
  'UPDATE inbox_entries SET unread = 0, read_at = ? WHERE recipient_session_id = ? AND unread = 1'
178
192
  ).run(now, recipientSessionId);
193
+ if (result.changes > 0) _emitInboxEvent({ recipientSessionId, action: 'mark_all_read' });
179
194
  return { wrote: result.changes > 0 };
180
195
  });
181
196
  }
@@ -188,6 +203,7 @@ function deleteInboxEntry(id) {
188
203
 
189
204
  return withTransaction(db, () => {
190
205
  const result = db.prepare('DELETE FROM inbox_entries WHERE id = ?').run(id);
206
+ if (result.changes > 0) _emitInboxEvent({ id, action: 'delete' });
191
207
  return { wrote: result.changes > 0 };
192
208
  });
193
209
  }
@@ -204,6 +220,7 @@ function deleteInboxEntries(entryIds) {
204
220
  const placeholders = entryIds.map(() => '?').join(',');
205
221
  const query = `DELETE FROM inbox_entries WHERE id IN (${placeholders})`;
206
222
  const result = db.prepare(query).run(...entryIds);
223
+ if (result.changes > 0) _emitInboxEvent({ entryIds, action: 'delete_batch' });
207
224
  return { wrote: result.changes > 0 };
208
225
  });
209
226
  }
@@ -222,6 +239,7 @@ function deleteInboxEntriesOlderThan(timestampMs, { unreadOnly = false } = {}) {
222
239
  query += ' AND unread = 1';
223
240
  }
224
241
  const result = db.prepare(query).run(...params);
242
+ if (result.changes > 0) _emitInboxEvent({ timestampMs, unreadOnly, action: 'delete_older_than' });
225
243
  return { wrote: result.changes > 0, deletedCount: result.changes };
226
244
  });
227
245
  }
@@ -252,6 +270,7 @@ function markInboxEntryNotified(id) {
252
270
  return withTransaction(db, () => {
253
271
  const now = Date.now();
254
272
  const result = db.prepare('UPDATE inbox_entries SET notified_at = ? WHERE id = ?').run(now, id);
273
+ if (result.changes > 0) _emitInboxEvent({ id, action: 'mark_notified' });
255
274
  return { wrote: result.changes > 0 };
256
275
  });
257
276
  }
package/engine/shared.js CHANGED
@@ -3485,25 +3485,8 @@ const ENGINE_DEFAULTS = {
3485
3485
  claudePreApproveWorkspaceMcps: true,
3486
3486
  copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
3487
3487
  copilotReasoningSummaries: false, // Copilot --enable-reasoning-summaries (Anthropic-family models only)
3488
- ccUseWorkerPool: false, // Sub-task C of W-mp2w003600196c51 (CC perf): when true AND CC runtime is copilot, _invokeCcStream routes through engine/cc-worker-pool.js (persistent `copilot --acp` per CC tab) instead of spawning a fresh CLI per turn. Off by default — opt-in feature flag. **Structurally copilot-only**: the pool spawns `copilot --acp` (Agent Client Protocol); Claude Code does not implement ACP, so resolveCcUseWorkerPool returns false on non-copilot CC runtimes even with explicit-true (W-mphlriic00095f69 — prevents silent runtime switch). Engine/agent dispatch path stays per-process regardless.
3488
+ ccUseWorkerPool: true, // Sub-task C of W-mp2w003600196c51 (CC perf): when true AND CC runtime is copilot, _invokeCcStream routes through engine/cc-worker-pool.js (persistent `copilot --acp` per CC tab) instead of spawning a fresh CLI per turn. On by default (PR #2492 cold-spawn measurements; matches engine/features.js's `ccUseWorkerPool` registry default) — opt out via `engine.ccUseWorkerPool: false`. **Structurally copilot-only**: the pool spawns `copilot --acp` (Agent Client Protocol); Claude Code does not implement ACP, so resolveCcUseWorkerPool returns false on non-copilot CC runtimes even with explicit-true (W-mphlriic00095f69 — prevents silent runtime switch). Engine/agent dispatch path stays per-process regardless.
3489
3489
  agentUseWorkerPool: false, // P-9b3d5f61: mirrors ccUseWorkerPool's shape but for the fleet agent-dispatch path (engine/agent-worker-pool.js). Opt-in, default OFF — see resolveAgentUseWorkerPool in engine/shared.js for the full priority chain and rationale.
3490
- // PR #321 — "kill auth-popup storm". Copilot spawns and authenticates EVERY
3491
- // MCP server in `$COPILOT_HOME/mcp-config.json` on every process start, even
3492
- // when the corresponding tool is hidden from the model via
3493
- // `--disable-mcp-server`. If left unfiltered, every auth-requiring user MCP
3494
- // (azure-kusto/azmcp, DevBox, workiq, loop, teams, …) boots and pops a
3495
- // Microsoft sign-in window on EVERY agent spawn — at minions' continuous
3496
- // spawn cadence that is an infinite popup storm.
3497
- //
3498
- // The engine therefore points agents at a private COPILOT_HOME
3499
- // (`shared.resolveAgentCopilotHome`) whose mcp-config is a copy of the operator's
3500
- // `~/.copilot/mcp-config.json` MINUS the names in this list (see engine.js
3501
- // `_applyAgentCopilotHome`). Default [] = inherit ALL of the operator's MCP
3502
- // servers for agents (non-breaking). Add a name to disable it for agents only;
3503
- // the operator's interactive `~/.copilot` is never touched. Per-agent override
3504
- // `agent.copilotAgentDisabledMcpServers`. Resolved through
3505
- // `shared.resolveCopilotAgentDisabledMcpServers(agent, engine)`.
3506
- copilotAgentDisabledMcpServers: [],
3507
3490
  maxBudgetUsd: undefined, // fleet USD ceiling for --max-budget-usd (per-agent override: agents.<id>.maxBudgetUsd). Honors 0 via ?? so a literal cap of $0 works
3508
3491
  disableModelDiscovery: false, // skip runtime.listModels() REST calls fleet-wide (settings UI falls back to free-text)
3509
3492
  // Phase 8 (qa-runs.json + qa-sessions.json → SQL). When true, every QA
@@ -3605,7 +3588,7 @@ const ENGINE_DEFAULTS = {
3605
3588
  agentMemorySummaryEnabled: false, // opt-in: when true, eviction batches go through an LLM-compressed summary before being dropped. Default off to mirror the conservative gating on the existing reconcile pass (LLM cost + test stability). Operators flip via engine.agentMemorySummaryEnabled.
3606
3589
  agentMemorySummaryThreshold: 30, // batch window: when summary is enabled and a prune evicts entries, fold at least this many oldest sections into one summary. Means "summary every ~30 entries" in steady state (the original PRD intent).
3607
3590
  agentMemorySummaryDays: 30, // age trigger: when the oldest section is older than this and >= agentMemorySummaryThreshold entries exist, summarize the oldest window even if the file is under the entry cap.
3608
- memoryRetrievalShadowMode: true, // compute + measure FTS5 retrieval but preserve legacy prompt injection until explicitly disabled
3591
+ memoryRetrievalShadowMode: false, // inject bounded FTS5-selected memory by default; true computes telemetry while preserving legacy prompt injection
3609
3592
  memoryRetrievalTopK: 8, // maximum structured memory records injected into one dispatch
3610
3593
  memoryRetrievalMaxBytes: 8 * 1024, // hard byte budget for the Relevant Memory prompt appendix
3611
3594
  memoryRetrievalCandidateLimit: 50, // bounded FTS5 candidate pool before deterministic re-ranking
@@ -3863,7 +3846,7 @@ function resolveCcUseWorkerPool(engine) {
3863
3846
  if (engine && (engine.ccUseWorkerPool === true || engine.ccUseWorkerPool === false)) {
3864
3847
  return engine.ccUseWorkerPool;
3865
3848
  }
3866
- return true; // CC runtime is copilot — default ON (cold-start savings)
3849
+ return ENGINE_DEFAULTS.ccUseWorkerPool;
3867
3850
  }
3868
3851
 
3869
3852
  /**
@@ -3988,39 +3971,6 @@ function resolveAgentBareMode(agent, engine) {
3988
3971
  return false;
3989
3972
  }
3990
3973
 
3991
- // P-mcp-storm — Resolve the list of MCP server names to disable for spawned
3992
- // Copilot agents. The engine FILTERS these out of the agent's seeded
3993
- // COPILOT_HOME mcp-config (engine.js `_applyAgentCopilotHome`) — a server absent
3994
- // from the config can't be spawned, which is the only effective disable
3995
- // (`--disable-mcp-server` does not stop the process). Chain: per-agent
3996
- // `agent.copilotAgentDisabledMcpServers` → `engine.copilotAgentDisabledMcpServers`
3997
- // → `[]` (inherit all). Tolerant of either an array OR a comma/whitespace-
3998
- // separated string (Settings UI / `MINIONS_*` env deliver strings). Always
3999
- // returns a deduped array of trimmed, non-empty strings. See ENGINE_DEFAULTS
4000
- // for the full rationale.
4001
- function _normalizeMcpServerList(v) {
4002
- if (v == null) return null;
4003
- const raw = Array.isArray(v) ? v : String(v).split(/[\s,]+/);
4004
- const out = [];
4005
- const seen = new Set();
4006
- for (const item of raw) {
4007
- const name = String(item == null ? '' : item).trim();
4008
- if (!name || seen.has(name)) continue;
4009
- seen.add(name);
4010
- out.push(name);
4011
- }
4012
- return out;
4013
- }
4014
-
4015
- function resolveCopilotAgentDisabledMcpServers(agent, engine) {
4016
- const a = agent ? _normalizeMcpServerList(agent.copilotAgentDisabledMcpServers) : null;
4017
- if (a) return a;
4018
- const e = engine ? _normalizeMcpServerList(engine.copilotAgentDisabledMcpServers) : null;
4019
- if (e) return e;
4020
- return [];
4021
- }
4022
-
4023
-
4024
3974
  // ─── Legacy ccModel → defaultModel Migration ─────────────────────────────────
4025
3975
  //
4026
3976
  // Pre-P-3b8e5f1d, `engine.ccModel` was the single fleet-wide model knob (it
@@ -6051,91 +6001,6 @@ function resolveAgentTempBaseDir(project, engine, minionsDir) {
6051
6001
  return path.join(anchor, '.agent-temp');
6052
6002
  }
6053
6003
 
6054
- /**
6055
- * Resolve the stable, repo-external COPILOT_HOME used to isolate spawned
6056
- * Copilot agents from the operator's interactive `~/.copilot` MCP servers
6057
- * (W-copilot-auth-popup-storm / PR #321 — "give copilot agents an MCP-free
6058
- * COPILOT_HOME (kill auth-popup storm)"). Copilot spawns and authenticates
6059
- * EVERY server in `$COPILOT_HOME/mcp-config.json` on every process start;
6060
- * `--disable-mcp-server <name>` only hides that server's tools from the model,
6061
- * it does NOT stop the server process from launching (verified live: agents
6062
- * spawned azmcp.exe/workiq.exe despite those servers being in
6063
- * `copilotAgentDisabledMcpServers`). The only effective lever is to point
6064
- * agents at a COPILOT_HOME whose mcp-config has NO disabled-list servers — see
6065
- * `ensureAgentCopilotHome` below, which the engine seeds at boot and re-stamps
6066
- * per dispatch (`_applyAgentCopilotHome` in engine.js).
6067
- *
6068
- * `COPILOT_HOME` overrides `$HOME/.copilot`; copilot auth is unaffected (it
6069
- * uses GH_TOKEN / the OS credential store, not files under the home), and the
6070
- * operator's real `~/.copilot` — i.e. their interactive copilot — is
6071
- * completely untouched.
6072
- *
6073
- * Placed OUTSIDE the repo so concurrent git branch switches in the working
6074
- * tree can never delete or dirty it, on the MINIONS_DIR volume (copilot's
6075
- * session-store can grow to GBs, so keep it off a possibly-full system
6076
- * drive). Stable path so copilot session resume (`--resume`) stays
6077
- * consistent across spawns. Falls back to the user home when no minionsDir
6078
- * is given.
6079
- *
6080
- * Base selection is cross-platform (W-copilot-home-posix-root): on Windows
6081
- * the MINIONS_DIR drive root (`C:\`, `D:\`, or a UNC share root) is
6082
- * user-writable, so we anchor there. On POSIX the filesystem root (`/`) is
6083
- * NOT user-writable — a home placed there can never be created (`mkdir`
6084
- * EACCES), `ensureAgentCopilotHome` fail-opens and returns the uncreatable
6085
- * path anyway, and every spawned `copilot --acp` / copilot agent then
6086
- * inherits a `COPILOT_HOME` it can't use and exits code 1 silently. So when
6087
- * the drive root is the bare POSIX root we fall back to the user home —
6088
- * repo-external, writable, and on the user's own volume.
6089
- */
6090
- function resolveAgentCopilotHome(minionsDir) {
6091
- let base;
6092
- if (minionsDir) {
6093
- const root = path.parse(path.resolve(String(minionsDir))).root;
6094
- // The POSIX filesystem root ('/') is not user-writable; Windows drive/UNC
6095
- // roots are. Anchor at the user home in the unwritable-root case.
6096
- base = (root === '/' || root === path.sep) ? os.homedir() : root;
6097
- } else {
6098
- base = os.homedir();
6099
- }
6100
- return path.join(base, '.minions-agent-copilot-home');
6101
- }
6102
-
6103
- // Seed the agent COPILOT_HOME's mcp-config (copy of `~/.copilot/mcp-config.json`
6104
- // MINUS `engine.copilotAgentDisabledMcpServers`) and return the home path. This
6105
- // is the source of truth for "what MCP servers may an agent-dispatch copilot
6106
- // load." It is applied to the engine-process copilot spawn paths ONLY — agent
6107
- // dispatches (`_applyAgentCopilotHome`) and the engine process's own internal
6108
- // `llm.callLLM` calls — by setting `process.env.COPILOT_HOME` at engine boot
6109
- // and re-stamping it per dispatch, so those copilot CHILDREN inherit the
6110
- // filtered config. copilot SPAWNS every server in its config and
6111
- // authenticates it (`--disable-mcp-server` only hides tools), so any
6112
- // auth-requiring server pops a Microsoft window per spawn — filtering the
6113
- // config is the only effective control. Idempotent: writes only when the
6114
- // content changes (safe under concurrent callers). The operator's real
6115
- // `~/.copilot` (interactive copilot, and now also Command Center / doc-chat /
6116
- // the CC worker pool, per W-mre75l6x00024f49) is never touched. Fail-open.
6117
- function ensureAgentCopilotHome(minionsDir, engine) {
6118
- const home = resolveAgentCopilotHome(minionsDir);
6119
- try {
6120
- fs.mkdirSync(home, { recursive: true });
6121
- const disabled = new Set(resolveCopilotAgentDisabledMcpServers(null, engine));
6122
- let servers = {};
6123
- try {
6124
- const userCfgPath = path.join(os.homedir(), '.copilot', 'mcp-config.json');
6125
- const userCfg = JSON.parse(fs.readFileSync(userCfgPath, 'utf8').replace(/^/, ''));
6126
- for (const [name, def] of Object.entries(userCfg.mcpServers || {})) {
6127
- if (!disabled.has(name)) servers[name] = def;
6128
- }
6129
- } catch { servers = {}; /* no/unreadable user config → no MCPs */ }
6130
- const desired = JSON.stringify({ mcpServers: servers }, null, 2);
6131
- const cfgFile = path.join(home, 'mcp-config.json');
6132
- let current = null;
6133
- try { current = fs.readFileSync(cfgFile, 'utf8'); } catch {}
6134
- if (current !== desired) fs.writeFileSync(cfgFile, desired);
6135
- } catch { /* fail-open: leave whatever home/config exists */ }
6136
- return home;
6137
- }
6138
-
6139
6004
  // ── Spawn cwd vs worktree placement (W-mp73x32w000l143d) ──────────────────────
6140
6005
  // Work types that don't need a git worktree — they read repo state but don't
6141
6006
  // produce code changes. Centralized here so engine.js spawnAgent and any
@@ -9056,7 +8921,6 @@ module.exports = {
9056
8921
  resolvePollFlag, // P-c4d8e1a3 — granular per-poller flag resolution
9057
8922
  resolveAgentCli, resolveCcCli, resolveCcUseWorkerPool, resolveAgentUseWorkerPool, resolveAgentAcpPoolSize, resolveAgentModel, resolveCcModel,
9058
8923
  resolveAgentMaxBudget, resolveAgentBareMode,
9059
- resolveCopilotAgentDisabledMcpServers,
9060
8924
  applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
9061
8925
  runtimeConfigWarnings,
9062
8926
  projectWorkSourceWarnings,
@@ -9162,9 +9026,7 @@ module.exports = {
9162
9026
  parseWorktreePorcelain,
9163
9027
  assertWorktreeOutsideProject,
9164
9028
  resolveProjectRootDir,
9165
- resolveAgentCopilotHome,
9166
9029
  resolveAgentTempBaseDir,
9167
- ensureAgentCopilotHome,
9168
9030
  resolveSpawnPaths,
9169
9031
  validateWorkItemWorkdir,
9170
9032
  applyWorkdir,
package/engine.js CHANGED
@@ -2100,32 +2100,6 @@ async function recoverPartialWorktree(rootDir, worktreePath, branchName, gitOpts
2100
2100
  }
2101
2101
  }
2102
2102
 
2103
- // Seed a spawned agent's COPILOT_HOME mcp-config as a copy of the operator's
2104
- // `~/.copilot/mcp-config.json` MINUS the servers in
2105
- // `engine.copilotAgentDisabledMcpServers`, then point COPILOT_HOME at it.
2106
- //
2107
- // Why config-filtering and not `--disable-mcp-server`: copilot SPAWNS every
2108
- // server in its config on startup and authenticates it; `--disable-mcp-server
2109
- // <name>` only hides that server's TOOLS from the model, it does NOT stop the
2110
- // server process (PR #321 — verified live: agents spawned azmcp.exe/workiq.exe
2111
- // despite those servers being in `copilotAgentDisabledMcpServers`).
2112
- //
2113
- // Default disabled list `[]` => agents inherit ALL of the operator's MCP servers
2114
- // (non-breaking; matches interactive copilot). Add a name to disable it for
2115
- // agents. COPILOT_HOME is copilot-specific; claude/codex ignore it, so this is
2116
- // set unconditionally (no runtime.name branching, per the adapter contract).
2117
- // Copilot auth is unaffected (GH_TOKEN / OS credential store); the operator's
2118
- // real ~/.copilot is untouched. Best-effort/fail-open: any failure leaves the
2119
- // inherited COPILOT_HOME so a hiccup never blocks a dispatch.
2120
- function _applyAgentCopilotHome(childEnv) {
2121
- // Re-seed (picks up Settings changes to the disabled list) + stamp the env.
2122
- // process.env.COPILOT_HOME is also set at engine boot (see below) so the
2123
- // child would inherit it anyway; this is belt-and-suspenders + keeps the
2124
- // seed fresh per dispatch. See shared.ensureAgentCopilotHome.
2125
- try { childEnv.COPILOT_HOME = shared.ensureAgentCopilotHome(MINIONS_DIR, getConfig()?.engine); }
2126
- catch { /* leave inherited COPILOT_HOME untouched */ }
2127
- }
2128
-
2129
2103
  // Point a spawned agent's TEMP/TMP/TMPDIR at a scratch dir on the worktree
2130
2104
  // volume instead of the (often full) system drive — see
2131
2105
  // shared.resolveAgentTempBaseDir. copilot/node/git + JVM-based MCP servers write
@@ -4598,16 +4572,6 @@ async function spawnAgent(dispatchItem, config) {
4598
4572
  // random localhost port (blank window). Belt-and-suspenders with dashboard.js's
4599
4573
  // stdout.isTTY gate.
4600
4574
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
4601
- // MCP-free copilot config so agents never pop a per-spawn Microsoft auth
4602
- // window — see _applyAgentCopilotHome / shared.resolveAgentCopilotHome.
4603
- // P-1d8f0b93 — SKIPPED for pooled dispatches: the pooled ACP worker is a
4604
- // long-lived process shared across many dispatches, so it always uses the
4605
- // operator's REAL COPILOT_HOME (this is the isolation-removal half of the
4606
- // pooling feature — MCP-auth-popup risk becomes bounded by pool size
4607
- // instead of eliminated per-dispatch; documented tradeoff in the plan).
4608
- if (!useAgentPool) {
4609
- _applyAgentCopilotHome(childEnv);
4610
- }
4611
4575
  // Keep agent scratch off a (possibly full) system drive — anchors TEMP/TMP to
4612
4576
  // the worktree volume. See _applyAgentTempEnv / shared.resolveAgentTempBaseDir.
4613
4577
  _applyAgentTempEnv(childEnv, project);
@@ -5059,8 +5023,6 @@ async function spawnAgent(dispatchItem, config) {
5059
5023
  // W-mqef-dashboard-tty — same browser-popup suppression on steering resume
5060
5024
  // (see the initial spawn site). Agents must never auto-open a browser.
5061
5025
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
5062
- // Same MCP-free copilot home on steering resume (see the initial spawn site).
5063
- _applyAgentCopilotHome(childEnv);
5064
5026
  // Same agent-scratch redirect on steering resume.
5065
5027
  _applyAgentTempEnv(childEnv, project);
5066
5028
  if (getRepoHost(project) === 'ado') {
@@ -11685,17 +11647,6 @@ function ensureGitHooksInstalled() {
11685
11647
 
11686
11648
  if (require.main === module) {
11687
11649
  try { ensureGitHooksInstalled(); } catch { /* best-effort */ }
11688
- // Every copilot this process spawns — agent dispatches AND internal
11689
- // `llm.callLLM` calls (consolidation, classification, meetings, …) — inherits
11690
- // this process's COPILOT_HOME, so point it at the seeded, MCP-filtered home.
11691
- // Without this, those `direct:true` copilot calls load the operator's full
11692
- // `~/.copilot` stack and pop a Microsoft auth window per call. This isolation
11693
- // is engine-process-only: the dashboard process deliberately does NOT set
11694
- // COPILOT_HOME at its own boot, so Command Center / doc-chat / the CC worker
11695
- // pool inherit the operator's real `~/.copilot` home instead (reverted in
11696
- // W-mre75l6x00024f49; see dashboard.js boot for rationale).
11697
- // See shared.ensureAgentCopilotHome. Fail-open.
11698
- try { process.env.COPILOT_HOME = shared.ensureAgentCopilotHome(MINIONS_DIR, getConfig()?.engine); } catch {}
11699
11650
  const { handleCommand } = require('./engine/cli');
11700
11651
  const [cmd, ...args] = process.argv.slice(2);
11701
11652
  handleCommand(cmd, args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2374",
3
+ "version": "0.1.2375",
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"