@yemi33/minions 0.1.1587 → 0.1.1589

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.js CHANGED
@@ -27,6 +27,7 @@ const shared = require('./engine/shared');
27
27
  const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS,
28
28
  WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS,
29
29
  FAILURE_CLASS } = shared;
30
+ const { resolveRuntime } = require('./engine/runtimes');
30
31
  const queries = require('./engine/queries');
31
32
 
32
33
  // ─── Paths ──────────────────────────────────────────────────────────────────
@@ -245,6 +246,54 @@ function _maxTurnsForType(type, engineConfig) {
245
246
  return globalOverride || _MAX_TURNS_BY_TYPE[type] || ENGINE_DEFAULTS.maxTurns;
246
247
  }
247
248
 
249
+ // ─── Runtime adapter integration (P-2a6d9c4f) ────────────────────────────────
250
+ //
251
+ // _buildAgentSpawnFlags translates a resolved opts bag into the named CLI
252
+ // flags consumed by `engine/spawn-agent.js`. spawn-agent.js parses these back
253
+ // into an opts object and calls `runtime.buildArgs(opts)` once — keeping the
254
+ // adapter as the single source of truth for CLI-level flag formatting and
255
+ // avoiding double-emission.
256
+ //
257
+ // Capability gating happens HERE (in engine.js), not in the adapter:
258
+ // - Runtimes that don't support a feature (Copilot has no budgetCap, no
259
+ // bareMode, no fallbackModel) never see the flag. The adapter doesn't
260
+ // have to silently drop opts it can't honor.
261
+ // - Truly cross-runtime opts (model, maxTurns, allowedTools, effort,
262
+ // sessionId) are emitted whenever set; capability flags filter the
263
+ // ones that are runtime-specific.
264
+ // - Copilot-specific opts (`stream`, `disableBuiltinMcps`,
265
+ // `suppressAgentsMd`, `reasoningSummaries`) are emitted unconditionally;
266
+ // the Claude adapter ignores them via the "tolerate unknown opts" rule.
267
+
268
+ function _buildAgentSpawnFlags(runtime, opts = {}) {
269
+ const caps = (runtime && runtime.capabilities) || {};
270
+ const flags = ['--runtime', String(runtime?.name || 'claude')];
271
+
272
+ // Always-applicable: every runtime understands these.
273
+ if (opts.maxTurns != null) flags.push('--max-turns', String(opts.maxTurns));
274
+ if (opts.model) flags.push('--model', String(opts.model));
275
+ if (opts.allowedTools) flags.push('--allowedTools', String(opts.allowedTools));
276
+
277
+ // Capability-gated. The first three (effort/resume) gate on whether the
278
+ // runtime exposes the feature at all; the next three gate on whether the
279
+ // runtime's CLI surface actually accepts the flag.
280
+ if (caps.effortLevels && opts.effort) flags.push('--effort', String(opts.effort));
281
+ if (caps.sessionResume && opts.sessionId) flags.push('--resume', String(opts.sessionId));
282
+ if (caps.budgetCap && opts.maxBudget != null) flags.push('--max-budget-usd', String(opts.maxBudget));
283
+ if (caps.bareMode && opts.bare === true) flags.push('--bare');
284
+ if (caps.fallbackModel && opts.fallbackModel) flags.push('--fallback-model', String(opts.fallbackModel));
285
+
286
+ // Copilot-specific opts. Always emitted when set; the Claude adapter
287
+ // silently ignores them (per the "tolerate unknown opts" rule from
288
+ // P-7e3a8b1c). Engine code never branches on `runtime.name`.
289
+ if (opts.stream != null && opts.stream !== '') flags.push('--stream', String(opts.stream));
290
+ if (opts.disableBuiltinMcps === true) flags.push('--disable-builtin-mcps');
291
+ if (opts.suppressAgentsMd === true) flags.push('--no-custom-instructions');
292
+ if (opts.reasoningSummaries === true) flags.push('--enable-reasoning-summaries');
293
+
294
+ return flags;
295
+ }
296
+
248
297
  // Resolve dependency plan item IDs to their PR branches
249
298
  function resolveDependencyBranches(depIds, sourcePlan, project, config) {
250
299
  const results = []; // [{ branch, prId }]
@@ -813,27 +862,27 @@ async function spawnAgent(dispatchItem, config) {
813
862
  log('warn', `Agent ${agentId} running ${type} task in main repo (no worktree) for ${id} — changes may land on master directly`);
814
863
  }
815
864
 
816
- // Build claude CLI args
817
- const args = [
818
- '--output-format', claudeConfig.outputFormat || 'stream-json',
819
- '--max-turns', String(_maxTurnsForType(type, engineConfig)),
820
- '--verbose',
821
- '--permission-mode', claudeConfig.permissionMode || 'bypassPermissions'
822
- ];
823
-
824
- if (claudeConfig.allowedTools) {
825
- args.push('--allowedTools', claudeConfig.allowedTools);
826
- }
827
-
828
- // Effort level: use 'low' for fast work types unless configured otherwise
829
- const effort = engineConfig.agentEffort || (_FAST_WORK_TYPES.has(type) ? 'low' : null);
830
- if (effort) args.push('--effort', effort);
831
-
832
- // Session resume: reuse last session if same branch and recent enough (< 2 hours)
865
+ // ── Runtime + opts resolution (P-2a6d9c4f) ────────────────────────────────
866
+ // Every CLI-specific knob flows through the runtime adapter resolved from
867
+ // resolveAgentCli(agent, engine). Engine code MUST NOT branch on
868
+ // `runtime.name === ...`; capability flags gate runtime-specific features.
869
+ const agentConfig = config.agents?.[agentId] || null;
870
+ const runtime = resolveRuntime(shared.resolveAgentCli(agentConfig, engineConfig));
871
+ const runtimeName = runtime.name;
872
+ const resolvedModel = runtime.resolveModel(shared.resolveAgentModel(agentConfig, engineConfig));
873
+ const resolvedMaxBudget = shared.resolveAgentMaxBudget(agentConfig, engineConfig);
874
+ const resolvedBare = shared.resolveAgentBareMode(agentConfig, engineConfig);
875
+
876
+ // Effort: use 'low' for fast work types unless configured otherwise.
877
+ // The capability gate happens inside _buildAgentSpawnFlags runtimes
878
+ // without effortLevels never see the flag.
879
+ const requestedEffort = engineConfig.agentEffort || (_FAST_WORK_TYPES.has(type) ? 'low' : null);
880
+
881
+ // Session resume: gated on `runtime.capabilities.sessionResume`. Same branch
882
+ // means the agent is continuing work on the same PR/feature (e.g., author
883
+ // fixing their own build failure).
833
884
  let cachedSessionId = null;
834
- // Only resume when the context is relevant — same branch means the agent is
835
- // continuing work on the same PR/feature (e.g., author fixing their own build failure)
836
- if (!agentId.startsWith('temp-')) {
885
+ if (runtime.capabilities.sessionResume && !agentId.startsWith('temp-')) {
837
886
  try {
838
887
  const sessionFile = safeJson(path.join(AGENTS_DIR, agentId, 'session.json'));
839
888
  if (sessionFile?.sessionId && sessionFile.savedAt) {
@@ -841,13 +890,30 @@ async function spawnAgent(dispatchItem, config) {
841
890
  const sameBranch = branchName && sessionFile.branch && sessionFile.branch === branchName;
842
891
  if (sessionAge < 2 * 60 * 60 * 1000 && sameBranch) {
843
892
  cachedSessionId = sessionFile.sessionId;
844
- args.push('--resume', sessionFile.sessionId);
845
893
  log('info', `Resuming session ${sessionFile.sessionId} for ${agentId} on branch ${branchName} (age: ${Math.round(sessionAge / 60000)}min)`);
846
894
  }
847
895
  }
848
896
  } catch (e) { log('warn', 'session resume lookup: ' + e.message); }
849
897
  }
850
898
 
899
+ // Build the spawn-agent.js flag bag. spawn-agent parses the named flags
900
+ // back into an opts object and calls runtime.buildArgs(opts) — so the
901
+ // adapter is the single source of truth for the actual CLI args.
902
+ const args = _buildAgentSpawnFlags(runtime, {
903
+ model: resolvedModel,
904
+ maxTurns: _maxTurnsForType(type, engineConfig),
905
+ allowedTools: claudeConfig.allowedTools,
906
+ effort: requestedEffort,
907
+ sessionId: cachedSessionId,
908
+ maxBudget: resolvedMaxBudget,
909
+ bare: resolvedBare,
910
+ fallbackModel: engineConfig.claudeFallbackModel,
911
+ stream: engineConfig.copilotStreamMode,
912
+ disableBuiltinMcps: engineConfig.copilotDisableBuiltinMcps,
913
+ suppressAgentsMd: engineConfig.copilotSuppressAgentsMd,
914
+ reasoningSummaries: engineConfig.copilotReasoningSummaries,
915
+ });
916
+
851
917
  // MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
852
918
  // No --mcp-config needed — avoids redundant config and ensures agents always have latest servers.
853
919
 
@@ -1060,15 +1126,32 @@ async function spawnAgent(dispatchItem, config) {
1060
1126
  return;
1061
1127
  }
1062
1128
 
1063
- // Build resume args
1064
- const resumeArgs = [
1065
- '--output-format', claudeConfig?.outputFormat || 'stream-json',
1066
- '--max-turns', String(engineConfig?.maxTurns || ENGINE_DEFAULTS.maxTurns),
1067
- '--verbose',
1068
- '--permission-mode', claudeConfig?.permissionMode || 'bypassPermissions',
1069
- '--resume', steerSessionId,
1070
- ];
1071
- if (claudeConfig?.allowedTools) resumeArgs.push('--allowedTools', claudeConfig.allowedTools);
1129
+ // Steering resume — gated on `runtime.capabilities.sessionResume`. If the
1130
+ // runtime can't resume sessions, fail fast: nothing to spawn here.
1131
+ if (!runtime.capabilities.sessionResume) {
1132
+ log('warn', `Steering: runtime ${runtime.name} does not support session resume — skipping for ${agentId}`);
1133
+ try { fs.appendFileSync(liveOutputPath, `\n[steering-failed] Runtime ${runtime.name} does not support session resume. Message was: ${steerMsg}\n`); } catch {}
1134
+ try { fs.unlinkSync(steerPromptPath); } catch {}
1135
+ activeProcesses.delete(id);
1136
+ completeDispatch(id, DISPATCH_RESULT.SUCCESS, 'Steering not supported by runtime', '', { processWorkItemFailure: false });
1137
+ return;
1138
+ }
1139
+
1140
+ // Reuse the same flag-builder used by the main spawn so capability gates
1141
+ // and runtime-specific opts stay consistent across the two paths.
1142
+ const resumeArgs = _buildAgentSpawnFlags(runtime, {
1143
+ model: resolvedModel,
1144
+ maxTurns: engineConfig?.maxTurns || ENGINE_DEFAULTS.maxTurns,
1145
+ allowedTools: claudeConfig?.allowedTools,
1146
+ sessionId: steerSessionId,
1147
+ maxBudget: resolvedMaxBudget,
1148
+ bare: resolvedBare,
1149
+ fallbackModel: engineConfig?.claudeFallbackModel,
1150
+ stream: engineConfig?.copilotStreamMode,
1151
+ disableBuiltinMcps: engineConfig?.copilotDisableBuiltinMcps,
1152
+ suppressAgentsMd: engineConfig?.copilotSuppressAgentsMd,
1153
+ reasoningSummaries: engineConfig?.copilotReasoningSummaries,
1154
+ });
1072
1155
 
1073
1156
  const spawnScript = path.join(ENGINE_DIR, 'spawn-agent.js');
1074
1157
  const childEnv = shared.cleanChildEnv();
@@ -1354,6 +1437,10 @@ async function spawnAgent(dispatchItem, config) {
1354
1437
  if (idx < 0) return dispatch;
1355
1438
  const item = dispatch.pending.splice(idx, 1)[0];
1356
1439
  item.started_at = startedAt;
1440
+ // Persist the resolved runtime so post-completion hooks (lifecycle.js) can
1441
+ // route output parsing through the right adapter. Also surfaces the choice
1442
+ // in dispatch.json for debugging multi-runtime fleets.
1443
+ item.runtimeName = runtimeName;
1357
1444
  delete item.skipReason;
1358
1445
  delete item._agentBusySince;
1359
1446
  if (!dispatch.active.some(d => d.id === id)) {
@@ -3786,7 +3873,7 @@ module.exports = {
3786
3873
  reconcileItemsWithPrs, detectDependencyCycles,
3787
3874
  parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
3788
3875
  isWorktreeRetryableError, removeStaleIndexLock, // exported for testing
3789
- _maxTurnsForType, buildProjectContext, normalizeAc, // exported for testing
3876
+ _maxTurnsForType, buildProjectContext, normalizeAc, _buildAgentSpawnFlags, // exported for testing
3790
3877
 
3791
3878
  // Playbooks
3792
3879
  renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS, buildWorkItemDispatchVars,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1587",
3
+ "version": "0.1.1589",
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"