@yemi33/minions 0.1.2239 → 0.1.2240

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.
package/engine/shared.js CHANGED
@@ -2958,19 +2958,20 @@ const ENGINE_DEFAULTS = {
2958
2958
  copilotSuppressAgentsMd: true, // Copilot --no-custom-instructions: stop AGENTS.md auto-load from fighting Minions playbook prompts
2959
2959
  copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
2960
2960
  copilotReasoningSummaries: false, // Copilot --enable-reasoning-summaries (Anthropic-family models only)
2961
- // P-mcp-storm — Copilot loads `~/.copilot/mcp-config.json` (the operator's
2962
- // user-scope MCP servers) UNCONDITIONALLY on every process start: it is NOT
2963
- // gated by `--add-dir`, cwd, or `hermeticHarness` (proven empirically a
2964
- // copilot spawned from a neutral dir with no `--add-dir ~/.copilot` still
2965
- // connects every server). So every spawned Copilot agent boots the operator's
2966
- // full interactive MCP stack; on Windows each `type:local`/`stdio` server
2967
- // (e.g. playwright, maestro) launches its own visible console window and
2968
- // playwright opens browser tabs. The only lever Copilot exposes is
2969
- // `--disable-mcp-server <name>` (repeatable). This list names the servers the
2970
- // engine disables for autonomous agent dispatches. Default [] = inherit ALL
2971
- // (no behavior change). Per-agent override `agent.copilotAgentDisabledMcpServers`.
2972
- // Resolved through `shared.resolveCopilotAgentDisabledMcpServers(agent, engine)`.
2973
- // NOTE: `hermeticHarness` does NOT suppress these (it only strips `--add-dir`).
2961
+ // P-mcp-storm — Copilot SPAWNS every server in its `mcp-config.json` on every
2962
+ // process start and authenticates it. `--disable-mcp-server <name>` only hides
2963
+ // a server's TOOLS from the model; it does NOT stop the server process (proven
2964
+ // empirically agents spawned azmcp/workiq even with the flag set), so any
2965
+ // auth-requiring server pops a Microsoft sign-in window on EVERY agent spawn.
2966
+ // The ONLY real lever is the CONFIG: a server absent from the config can't be
2967
+ // spawned. The engine therefore points agents at a private COPILOT_HOME
2968
+ // (`shared.resolveAgentCopilotHome`) whose mcp-config is a copy of the operator's
2969
+ // `~/.copilot/mcp-config.json` MINUS the names in this list (see engine.js
2970
+ // `_applyAgentCopilotHome`). Default [] = inherit ALL of the operator's MCP
2971
+ // servers for agents (non-breaking). Add a name to disable it for agents only;
2972
+ // the operator's interactive `~/.copilot` is never touched. Per-agent override
2973
+ // `agent.copilotAgentDisabledMcpServers`. Resolved through
2974
+ // `shared.resolveCopilotAgentDisabledMcpServers(agent, engine)`.
2974
2975
  copilotAgentDisabledMcpServers: [],
2975
2976
  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.
2976
2977
  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
@@ -3375,13 +3376,15 @@ function resolveAgentBareMode(agent, engine) {
3375
3376
  }
3376
3377
 
3377
3378
  // P-mcp-storm — Resolve the list of MCP server names to disable for spawned
3378
- // Copilot agents (emitted as `--disable-mcp-server <name>`). Chain: per-agent
3379
+ // Copilot agents. The engine FILTERS these out of the agent's seeded
3380
+ // COPILOT_HOME mcp-config (engine.js `_applyAgentCopilotHome`) — a server absent
3381
+ // from the config can't be spawned, which is the only effective disable
3382
+ // (`--disable-mcp-server` does not stop the process). Chain: per-agent
3379
3383
  // `agent.copilotAgentDisabledMcpServers` → `engine.copilotAgentDisabledMcpServers`
3380
3384
  // → `[]` (inherit all). Tolerant of either an array OR a comma/whitespace-
3381
3385
  // separated string (Settings UI / `MINIONS_*` env deliver strings). Always
3382
3386
  // returns a deduped array of trimmed, non-empty strings. See ENGINE_DEFAULTS
3383
- // for why this exists (Copilot loads ~/.copilot MCP config unconditionally;
3384
- // `hermeticHarness` does NOT suppress it).
3387
+ // for the full rationale.
3385
3388
  function _normalizeMcpServerList(v) {
3386
3389
  if (v == null) return null;
3387
3390
  const raw = Array.isArray(v) ? v : String(v).split(/[\s,]+/);
@@ -5637,6 +5640,32 @@ function resolveProjectRootDir(localPath, minionsDir) {
5637
5640
  return fallback;
5638
5641
  }
5639
5642
 
5643
+ /**
5644
+ * Resolve a stable, repo-EXTERNAL COPILOT_HOME for spawned copilot agents.
5645
+ *
5646
+ * Copilot loads `$COPILOT_HOME/mcp-config.json` and SPAWNS every server in it on
5647
+ * every process start. `--disable-mcp-server <name>` only hides a server's tools
5648
+ * from the model — it does NOT stop the server PROCESS from launching, so any
5649
+ * server that does interactive auth (azure-kusto/azmcp, DevBox, workiq, loop, …)
5650
+ * still pops a Microsoft sign-in window on EVERY agent spawn. With minions
5651
+ * spawning agents continuously that is an unbounded popup storm.
5652
+ *
5653
+ * The only effective lever is to give agents a COPILOT_HOME whose mcp-config has
5654
+ * NO user servers (the engine seeds `{"mcpServers":{}}` there). Copilot auth is
5655
+ * unaffected — it uses GH_TOKEN / the OS credential store, not files under the
5656
+ * home — and the operator's real `~/.copilot` (interactive copilot) is untouched.
5657
+ *
5658
+ * Placed on the MINIONS_DIR volume (copilot's session-store can grow to GBs, so
5659
+ * keep it off a possibly-full system drive) but OUTSIDE the repo, at the drive
5660
+ * root, so concurrent git branch switches in the working tree can never delete or
5661
+ * dirty it. Stable path so copilot session resume (`--resume`) stays consistent
5662
+ * across spawns. Falls back to the user home when no minionsDir is given.
5663
+ */
5664
+ function resolveAgentCopilotHome(minionsDir) {
5665
+ const base = minionsDir ? path.parse(path.resolve(String(minionsDir))).root : os.homedir();
5666
+ return path.join(base, '.minions-agent-copilot-home');
5667
+ }
5668
+
5640
5669
  // ── Spawn cwd vs worktree placement (W-mp73x32w000l143d) ──────────────────────
5641
5670
  // Work types that don't need a git worktree — they read repo state but don't
5642
5671
  // produce code changes. Centralized here so engine.js spawnAgent and any
@@ -8931,6 +8960,7 @@ module.exports = {
8931
8960
  parseWorktreePorcelain,
8932
8961
  assertWorktreeOutsideProject,
8933
8962
  resolveProjectRootDir,
8963
+ resolveAgentCopilotHome,
8934
8964
  resolveSpawnPaths,
8935
8965
  validateWorkItemWorkdir,
8936
8966
  applyWorkdir,
package/engine.js CHANGED
@@ -1886,6 +1886,50 @@ async function recoverPartialWorktree(rootDir, worktreePath, branchName, gitOpts
1886
1886
  }
1887
1887
  }
1888
1888
 
1889
+ // Seed a spawned agent's COPILOT_HOME mcp-config as a copy of the operator's
1890
+ // `~/.copilot/mcp-config.json` MINUS the servers in
1891
+ // `engine.copilotAgentDisabledMcpServers`, then point COPILOT_HOME at it.
1892
+ //
1893
+ // Why config-filtering and not `--disable-mcp-server`: copilot SPAWNS every
1894
+ // server in its config on startup and authenticates it; `--disable-mcp-server
1895
+ // <name>` only hides that server's TOOLS from the model, it does NOT stop the
1896
+ // server process. So any auth-requiring server (azure-kusto/azmcp, DevBox,
1897
+ // workiq, loop, teams…) pops a Microsoft sign-in window on EVERY agent spawn —
1898
+ // an unbounded popup storm at minions' spawn cadence. A server simply ABSENT
1899
+ // from the config can't be spawned, so filtering the config is the only real
1900
+ // disable lever.
1901
+ //
1902
+ // Default disabled list `[]` => agents inherit ALL of the operator's MCP servers
1903
+ // (non-breaking; matches interactive copilot). Add a name to disable it for
1904
+ // agents. COPILOT_HOME is copilot-specific; claude/codex ignore it, so this is
1905
+ // set unconditionally (no runtime.name branching, per the adapter contract).
1906
+ // Copilot auth is unaffected (GH_TOKEN / OS credential store); the operator's
1907
+ // real ~/.copilot is untouched. Best-effort/fail-open: any failure leaves the
1908
+ // inherited COPILOT_HOME so a hiccup never blocks a dispatch.
1909
+ function _applyAgentCopilotHome(childEnv) {
1910
+ try {
1911
+ const home = shared.resolveAgentCopilotHome(MINIONS_DIR);
1912
+ fs.mkdirSync(home, { recursive: true });
1913
+ const disabled = new Set(shared.resolveCopilotAgentDisabledMcpServers(null, getConfig()?.engine));
1914
+ let servers = {};
1915
+ try {
1916
+ const userCfgPath = path.join(os.homedir(), '.copilot', 'mcp-config.json');
1917
+ const userCfg = JSON.parse(fs.readFileSync(userCfgPath, 'utf8').replace(/^/, ''));
1918
+ for (const [name, def] of Object.entries(userCfg.mcpServers || {})) {
1919
+ if (!disabled.has(name)) servers[name] = def;
1920
+ }
1921
+ } catch { servers = {}; /* no/unreadable user config → agents get no MCPs */ }
1922
+ // Write only when the content changes — keeps concurrent spawns from racing
1923
+ // on the shared file (the engine-level list is identical across agents).
1924
+ const desired = JSON.stringify({ mcpServers: servers }, null, 2);
1925
+ const cfgFile = path.join(home, 'mcp-config.json');
1926
+ let current = null;
1927
+ try { current = fs.readFileSync(cfgFile, 'utf8'); } catch {}
1928
+ if (current !== desired) fs.writeFileSync(cfgFile, desired);
1929
+ childEnv.COPILOT_HOME = home;
1930
+ } catch { /* leave inherited COPILOT_HOME untouched */ }
1931
+ }
1932
+
1889
1933
  async function spawnAgent(dispatchItem, config) {
1890
1934
  const { id, agent: agentId, type, meta } = dispatchItem;
1891
1935
  // Resolve prompt — prefers sidecar file when dispatchItem._promptFile is set
@@ -3579,12 +3623,6 @@ async function spawnAgent(dispatchItem, config) {
3579
3623
  const resolvedModel = runtime.resolveModel(shared.resolveAgentModel(agentConfig, engineConfig));
3580
3624
  const resolvedMaxBudget = shared.resolveAgentMaxBudget(agentConfig, engineConfig);
3581
3625
  const resolvedBare = shared.resolveAgentBareMode(agentConfig, engineConfig);
3582
- // P-mcp-storm (opg#134 backport) — user-scope MCP servers to keep out of
3583
- // this agent dispatch. Copilot loads ~/.copilot/mcp-config.json
3584
- // unconditionally; the adapter re-emits these as --disable-mcp-server <name>.
3585
- // Other runtimes ignore the opt (their buildSpawnFlags don't read it), so no
3586
- // runtime.name branch here.
3587
- const resolvedDisabledMcpServers = shared.resolveCopilotAgentDisabledMcpServers(agentConfig, engineConfig);
3588
3626
  // P-49e1c8b7 — hermetic harness opt-out (per-agent override allowed).
3589
3627
  // When true, this dispatch:
3590
3628
  // - skips Claude workspace .mcp.json pre-approval (preApproveWorkspaceMcps),
@@ -3648,7 +3686,6 @@ async function spawnAgent(dispatchItem, config) {
3648
3686
  disableBuiltinMcps: engineConfig.copilotDisableBuiltinMcps,
3649
3687
  suppressAgentsMd: engineConfig.copilotSuppressAgentsMd,
3650
3688
  reasoningSummaries: engineConfig.copilotReasoningSummaries,
3651
- disabledMcpServers: resolvedDisabledMcpServers,
3652
3689
  });
3653
3690
 
3654
3691
  // MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
@@ -3725,6 +3762,9 @@ async function spawnAgent(dispatchItem, config) {
3725
3762
  // random localhost port (blank window). Belt-and-suspenders with dashboard.js's
3726
3763
  // stdout.isTTY gate.
3727
3764
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
3765
+ // MCP-free copilot config so agents never pop a per-spawn Microsoft auth
3766
+ // window — see _applyAgentCopilotHome / shared.resolveAgentCopilotHome.
3767
+ _applyAgentCopilotHome(childEnv);
3728
3768
 
3729
3769
  if (getRepoHost(project) === 'ado') {
3730
3770
  // Inject cached ADO token so ADO agents skip re-authentication (#998).
@@ -4205,7 +4245,6 @@ async function spawnAgent(dispatchItem, config) {
4205
4245
  disableBuiltinMcps: engineConfig?.copilotDisableBuiltinMcps,
4206
4246
  suppressAgentsMd: engineConfig?.copilotSuppressAgentsMd,
4207
4247
  reasoningSummaries: engineConfig?.copilotReasoningSummaries,
4208
- disabledMcpServers: resolvedDisabledMcpServers,
4209
4248
  });
4210
4249
  if (!resumeArgs.includes('--resume')) {
4211
4250
  log('warn', `Steering: runtime ${runtime.name} did not accept session resume — skipping for ${agentId}`);
@@ -4236,6 +4275,8 @@ async function spawnAgent(dispatchItem, config) {
4236
4275
  // W-mqef-dashboard-tty — same browser-popup suppression on steering resume
4237
4276
  // (see the initial spawn site). Agents must never auto-open a browser.
4238
4277
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
4278
+ // Same MCP-free copilot home on steering resume (see the initial spawn site).
4279
+ _applyAgentCopilotHome(childEnv);
4239
4280
  if (getRepoHost(project) === 'ado') {
4240
4281
  // Inject cached ADO token for steering session too (#998)
4241
4282
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2239",
3
+ "version": "0.1.2240",
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"