@yemi33/minions 0.1.2239 → 0.1.2241

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/dashboard.js CHANGED
@@ -14178,6 +14178,14 @@ if (require.main === module) {
14178
14178
  // engine reads, port binding) is captured rather than dying silently.
14179
14179
  _installCrashHandlers();
14180
14180
 
14181
+ // Every copilot the dashboard spawns — Command Center, doc-chat, and the CC
14182
+ // worker pool (`copilot --acp`) — inherits this process's COPILOT_HOME. Point
14183
+ // it at the seeded, MCP-filtered home so those `direct:true` copilot calls
14184
+ // load the filtered config instead of the operator's full `~/.copilot` stack
14185
+ // (which would pop a Microsoft auth window per call). Mirrors the engine's
14186
+ // boot-time set. See shared.ensureAgentCopilotHome. Fail-open.
14187
+ try { process.env.COPILOT_HOME = shared.ensureAgentCopilotHome(MINIONS_DIR, CONFIG?.engine); } catch {}
14188
+
14181
14189
  // Pre-warm the per-project git-status cache before accepting requests so
14182
14190
  // the first /api/status after restart already returns gitState='ok' with a
14183
14191
  // real branch instead of the ~8s pending gap that hides the projects-bar
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,66 @@ 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
+
5669
+ // Seed the agent COPILOT_HOME's mcp-config (copy of `~/.copilot/mcp-config.json`
5670
+ // MINUS `engine.copilotAgentDisabledMcpServers`) and return the home path. This
5671
+ // is the single source of truth for "what MCP servers may a minions-spawned
5672
+ // copilot load." It is applied to EVERY copilot spawn path — agent dispatches
5673
+ // (engine), internal `llm.callLLM` calls (engine), and CC / doc-chat / the CC
5674
+ // worker pool (dashboard) — by setting `process.env.COPILOT_HOME` at the boot of
5675
+ // both processes, so every copilot CHILD inherits the filtered config. copilot
5676
+ // SPAWNS every server in its config and authenticates it (`--disable-mcp-server`
5677
+ // only hides tools), so any auth-requiring server pops a Microsoft window per
5678
+ // spawn — filtering the config is the only effective control. Idempotent: writes
5679
+ // only when the content changes (safe under concurrent callers). The operator's
5680
+ // real `~/.copilot` (interactive copilot) is never touched. Fail-open.
5681
+ function ensureAgentCopilotHome(minionsDir, engine) {
5682
+ const home = resolveAgentCopilotHome(minionsDir);
5683
+ try {
5684
+ fs.mkdirSync(home, { recursive: true });
5685
+ const disabled = new Set(resolveCopilotAgentDisabledMcpServers(null, engine));
5686
+ let servers = {};
5687
+ try {
5688
+ const userCfgPath = path.join(os.homedir(), '.copilot', 'mcp-config.json');
5689
+ const userCfg = JSON.parse(fs.readFileSync(userCfgPath, 'utf8').replace(/^/, ''));
5690
+ for (const [name, def] of Object.entries(userCfg.mcpServers || {})) {
5691
+ if (!disabled.has(name)) servers[name] = def;
5692
+ }
5693
+ } catch { servers = {}; /* no/unreadable user config → no MCPs */ }
5694
+ const desired = JSON.stringify({ mcpServers: servers }, null, 2);
5695
+ const cfgFile = path.join(home, 'mcp-config.json');
5696
+ let current = null;
5697
+ try { current = fs.readFileSync(cfgFile, 'utf8'); } catch {}
5698
+ if (current !== desired) fs.writeFileSync(cfgFile, desired);
5699
+ } catch { /* fail-open: leave whatever home/config exists */ }
5700
+ return home;
5701
+ }
5702
+
5640
5703
  // ── Spawn cwd vs worktree placement (W-mp73x32w000l143d) ──────────────────────
5641
5704
  // Work types that don't need a git worktree — they read repo state but don't
5642
5705
  // produce code changes. Centralized here so engine.js spawnAgent and any
@@ -8931,6 +8994,8 @@ module.exports = {
8931
8994
  parseWorktreePorcelain,
8932
8995
  assertWorktreeOutsideProject,
8933
8996
  resolveProjectRootDir,
8997
+ resolveAgentCopilotHome,
8998
+ ensureAgentCopilotHome,
8934
8999
  resolveSpawnPaths,
8935
9000
  validateWorkItemWorkdir,
8936
9001
  applyWorkdir,
package/engine.js CHANGED
@@ -1886,6 +1886,35 @@ 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
+ // Re-seed (picks up Settings changes to the disabled list) + stamp the env.
1911
+ // process.env.COPILOT_HOME is also set at engine boot (see below) so the
1912
+ // child would inherit it anyway; this is belt-and-suspenders + keeps the
1913
+ // seed fresh per dispatch. See shared.ensureAgentCopilotHome.
1914
+ try { childEnv.COPILOT_HOME = shared.ensureAgentCopilotHome(MINIONS_DIR, getConfig()?.engine); }
1915
+ catch { /* leave inherited COPILOT_HOME untouched */ }
1916
+ }
1917
+
1889
1918
  async function spawnAgent(dispatchItem, config) {
1890
1919
  const { id, agent: agentId, type, meta } = dispatchItem;
1891
1920
  // Resolve prompt — prefers sidecar file when dispatchItem._promptFile is set
@@ -3579,12 +3608,6 @@ async function spawnAgent(dispatchItem, config) {
3579
3608
  const resolvedModel = runtime.resolveModel(shared.resolveAgentModel(agentConfig, engineConfig));
3580
3609
  const resolvedMaxBudget = shared.resolveAgentMaxBudget(agentConfig, engineConfig);
3581
3610
  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
3611
  // P-49e1c8b7 — hermetic harness opt-out (per-agent override allowed).
3589
3612
  // When true, this dispatch:
3590
3613
  // - skips Claude workspace .mcp.json pre-approval (preApproveWorkspaceMcps),
@@ -3648,7 +3671,6 @@ async function spawnAgent(dispatchItem, config) {
3648
3671
  disableBuiltinMcps: engineConfig.copilotDisableBuiltinMcps,
3649
3672
  suppressAgentsMd: engineConfig.copilotSuppressAgentsMd,
3650
3673
  reasoningSummaries: engineConfig.copilotReasoningSummaries,
3651
- disabledMcpServers: resolvedDisabledMcpServers,
3652
3674
  });
3653
3675
 
3654
3676
  // MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
@@ -3725,6 +3747,9 @@ async function spawnAgent(dispatchItem, config) {
3725
3747
  // random localhost port (blank window). Belt-and-suspenders with dashboard.js's
3726
3748
  // stdout.isTTY gate.
3727
3749
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
3750
+ // MCP-free copilot config so agents never pop a per-spawn Microsoft auth
3751
+ // window — see _applyAgentCopilotHome / shared.resolveAgentCopilotHome.
3752
+ _applyAgentCopilotHome(childEnv);
3728
3753
 
3729
3754
  if (getRepoHost(project) === 'ado') {
3730
3755
  // Inject cached ADO token so ADO agents skip re-authentication (#998).
@@ -4205,7 +4230,6 @@ async function spawnAgent(dispatchItem, config) {
4205
4230
  disableBuiltinMcps: engineConfig?.copilotDisableBuiltinMcps,
4206
4231
  suppressAgentsMd: engineConfig?.copilotSuppressAgentsMd,
4207
4232
  reasoningSummaries: engineConfig?.copilotReasoningSummaries,
4208
- disabledMcpServers: resolvedDisabledMcpServers,
4209
4233
  });
4210
4234
  if (!resumeArgs.includes('--resume')) {
4211
4235
  log('warn', `Steering: runtime ${runtime.name} did not accept session resume — skipping for ${agentId}`);
@@ -4236,6 +4260,8 @@ async function spawnAgent(dispatchItem, config) {
4236
4260
  // W-mqef-dashboard-tty — same browser-popup suppression on steering resume
4237
4261
  // (see the initial spawn site). Agents must never auto-open a browser.
4238
4262
  childEnv.MINIONS_NO_AUTO_OPEN = '1';
4263
+ // Same MCP-free copilot home on steering resume (see the initial spawn site).
4264
+ _applyAgentCopilotHome(childEnv);
4239
4265
  if (getRepoHost(project) === 'ado') {
4240
4266
  // Inject cached ADO token for steering session too (#998)
4241
4267
  try {
@@ -10232,6 +10258,14 @@ module.exports = {
10232
10258
  // ─── Entrypoint ─────────────────────────────────────────────────────────────
10233
10259
 
10234
10260
  if (require.main === module) {
10261
+ // Every copilot this process spawns — agent dispatches AND internal
10262
+ // `llm.callLLM` calls (consolidation, classification, meetings, …) — inherits
10263
+ // this process's COPILOT_HOME, so point it at the seeded, MCP-filtered home.
10264
+ // Without this, those `direct:true` copilot calls load the operator's full
10265
+ // `~/.copilot` stack and pop a Microsoft auth window per call. The dashboard
10266
+ // sets the same at its own boot for CC / doc-chat / the CC worker pool.
10267
+ // See shared.ensureAgentCopilotHome. Fail-open.
10268
+ try { process.env.COPILOT_HOME = shared.ensureAgentCopilotHome(MINIONS_DIR, getConfig()?.engine); } catch {}
10235
10269
  const { handleCommand } = require('./engine/cli');
10236
10270
  const [cmd, ...args] = process.argv.slice(2);
10237
10271
  handleCommand(cmd, args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2239",
3
+ "version": "0.1.2241",
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"