@yemi33/minions 0.1.1697 → 0.1.1699

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/CHANGELOG.md CHANGED
@@ -1,9 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1697 (2026-05-04)
3
+ ## 0.1.1699 (2026-05-04)
4
+
5
+ ### Features
6
+ - Gracefully handle missing runtime across Command Center and LLM call sites
7
+
8
+ ### Fixes
9
+ - yemi33/minions#2022
10
+
11
+ ## 0.1.1698 (2026-05-04)
12
+
13
+ ### Features
14
+ - fix update restart and Copilot steering (#2025)
15
+
16
+ ## 0.1.1696 (2026-05-04)
4
17
 
5
18
  ### Features
6
- - guard engine shutdown ownership (#2021)
7
19
  - preserve doc-chat final payload (#2019)
8
20
 
9
21
  ## 0.1.1695 (2026-05-04)
package/bin/minions.js CHANGED
@@ -203,6 +203,7 @@ function resolveMinionsHome(forInit = false) {
203
203
  const [cmd, ...rest] = process.argv.slice(2);
204
204
  let force = rest.includes('--force');
205
205
  const skipScan = rest.includes('--skip-scan');
206
+ const skipStart = rest.includes('--skip-start') || rest.includes('--no-start');
206
207
  const MINIONS_HOME = resolveMinionsHome(cmd === 'init');
207
208
 
208
209
  function isSubpath(parent, child) {
@@ -339,7 +340,12 @@ function init() {
339
340
  printPreflight(results, { label: 'Preflight checks' });
340
341
  } catch {}
341
342
 
342
- // Auto-start on fresh install; force-upgrade restarts automatically.
343
+ if (isUpgrade && skipStart) {
344
+ console.log(`\n Upgrade complete (${pkgVersion}). Restart skipped by caller.\n`);
345
+ return;
346
+ }
347
+
348
+ // Auto-start on fresh install; direct force-upgrade restarts automatically.
343
349
  if (isUpgrade) {
344
350
  try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME }); } catch {}
345
351
  }
@@ -436,7 +442,7 @@ function showVersion() {
436
442
  if (installed) {
437
443
  console.log(` Installed version: ${installed}`);
438
444
  if (installed !== pkg) {
439
- console.log('\n Update available! Run: minions init --force');
445
+ console.log('\n Update available! Run: minions update');
440
446
  } else {
441
447
  console.log(' Up to date.');
442
448
  }
@@ -449,7 +455,7 @@ function showVersion() {
449
455
  const latest = execSync('npm view @yemi33/minions version', { encoding: 'utf8', timeout: 5000, windowsHide: true }).trim();
450
456
  if (latest && latest !== pkg) {
451
457
  console.log(`\n Latest on npm: ${latest}`);
452
- console.log(' To update: npm update -g @yemi33/minions && minions init --force');
458
+ console.log(' To update: minions update');
453
459
  }
454
460
  } catch {} // offline or npm not available — skip silently
455
461
 
@@ -489,7 +495,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
489
495
 
490
496
  Setup:
491
497
  minions init Bootstrap ~/.minions/ (first time)
492
- minions update Update to latest version (npm update + init --force)
498
+ minions update Update to latest version (npm update + one restart)
493
499
  minions version Show installed vs package version
494
500
  minions doctor Check prerequisites and runtime health
495
501
  minions add <project-dir> Link a project (interactive)
@@ -538,7 +544,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
538
544
  console.error(' npm update failed:', e.message);
539
545
  process.exit(1);
540
546
  }
541
- execSync('minions init --force', { stdio: 'inherit', timeout: 120000 });
547
+ execSync('minions init --force --skip-start', { stdio: 'inherit', timeout: 120000 });
542
548
  }
543
549
  // Restart engine + dashboard so they pick up the new code
544
550
  console.log('\n Restarting engine and dashboard...\n');
@@ -761,4 +767,3 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
761
767
  console.log(' Run "minions help" for usage.\n');
762
768
  process.exit(1);
763
769
  }
764
-
package/dashboard.js CHANGED
@@ -226,6 +226,40 @@ function _agentSessionIsDraining(agentId) {
226
226
  return terminalIdx >= 0 && terminalIdx > lastSteer;
227
227
  }
228
228
 
229
+ function _dispatchBranch(item) {
230
+ const branch = item?.meta?.branch || item?.branch || item?.meta?.item?.branch || item?.meta?.item?.featureBranch;
231
+ return branch ? String(branch).replace(/^refs\/heads\//, '') : null;
232
+ }
233
+
234
+ function _hasCachedResumeSession(agentId, activeDispatch, maxAgeMs = 2 * 60 * 60 * 1000) {
235
+ const sessionFile = safeJson(path.join(AGENTS_DIR, agentId, 'session.json'));
236
+ if (!sessionFile?.sessionId || !sessionFile.savedAt) return false;
237
+ const savedAtMs = new Date(sessionFile.savedAt).getTime();
238
+ if (!Number.isFinite(savedAtMs) || Date.now() - savedAtMs >= maxAgeMs) return false;
239
+ const dispatchBranch = _dispatchBranch(activeDispatch);
240
+ const sessionBranch = sessionFile.branch ? String(sessionFile.branch).replace(/^refs\/heads\//, '') : null;
241
+ return !!dispatchBranch && !!sessionBranch && dispatchBranch === sessionBranch;
242
+ }
243
+
244
+ function _steeringDeliveryState(agentId) {
245
+ const activeDispatch = (getDispatchQueue().active || []).find(d => d.agent === agentId);
246
+ if (!activeDispatch) return { deliveryStatus: 'queued', pendingDelivery: true };
247
+
248
+ const runtimeName = shared.resolveAgentCli(CONFIG.agents?.[agentId], CONFIG.engine);
249
+ try {
250
+ const runtime = require('./engine/runtimes').resolveRuntime(runtimeName);
251
+ if (runtime?.capabilities?.midRunSessionId === false && !_hasCachedResumeSession(agentId, activeDispatch)) {
252
+ return {
253
+ deliveryStatus: 'pending_checkpoint',
254
+ pendingDelivery: true,
255
+ detail: 'Runtime has not emitted a resumable session yet; delivery is pending until the next resumable checkpoint.',
256
+ };
257
+ }
258
+ } catch { /* unknown runtime: checkSteering will surface retry state */ }
259
+
260
+ return { deliveryStatus: 'queued', pendingDelivery: false };
261
+ }
262
+
229
263
  const PLANS_DIR = path.join(MINIONS_DIR, 'plans');
230
264
  const TEAMS_INBOX_PATH = path.join(ENGINE_DIR, 'teams-inbox.json');
231
265
 
@@ -1791,6 +1825,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1791
1825
  if (onAbortReady) onAbortReady(p1.abort);
1792
1826
  result = await p1;
1793
1827
  llm.trackEngineUsage(label, result.usage);
1828
+ if (result.missingRuntime) return result;
1794
1829
 
1795
1830
  if (result.text) {
1796
1831
  updateSession(store, sessionKey, result.sessionId || sessionId, true);
@@ -1828,6 +1863,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1828
1863
  if (onAbortReady) onAbortReady(p2.abort);
1829
1864
  result = await p2;
1830
1865
  llm.trackEngineUsage(label, result.usage);
1866
+ if (result.missingRuntime) return result;
1831
1867
 
1832
1868
  if (result.text) {
1833
1869
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1845,6 +1881,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1845
1881
  if (onAbortReady) onAbortReady(p3.abort);
1846
1882
  result = await p3;
1847
1883
  llm.trackEngineUsage(label, result.usage);
1884
+ if (result.missingRuntime) return result;
1848
1885
 
1849
1886
  if (result.text) {
1850
1887
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1878,6 +1915,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1878
1915
  if (onAbortReady) onAbortReady(p1.abort);
1879
1916
  result = await p1;
1880
1917
  llm.trackEngineUsage(label, result.usage);
1918
+ if (result.missingRuntime) return result;
1881
1919
 
1882
1920
  if (result.text) {
1883
1921
  updateSession(store, sessionKey, result.sessionId || sessionId, true);
@@ -1914,6 +1952,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1914
1952
  if (onAbortReady) onAbortReady(p2.abort);
1915
1953
  result = await p2;
1916
1954
  llm.trackEngineUsage(label, result.usage);
1955
+ if (result.missingRuntime) return result;
1917
1956
 
1918
1957
  if (result.text) {
1919
1958
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1932,6 +1971,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1932
1971
  if (onAbortReady) onAbortReady(p3.abort);
1933
1972
  result = await p3;
1934
1973
  llm.trackEngineUsage(label, result.usage);
1974
+ if (result.missingRuntime) return result;
1935
1975
 
1936
1976
  if (result.text) {
1937
1977
  updateSession(store, sessionKey, result.sessionId, false);
@@ -2037,6 +2077,10 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
2037
2077
  if (session) session._docHash = docHash;
2038
2078
  }
2039
2079
 
2080
+ if (result.missingRuntime) {
2081
+ return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
2082
+ }
2083
+
2040
2084
  if (result.code !== 0 || !result.text) {
2041
2085
  console.error(`[doc-chat] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, stderr=${(result.stderr || '').slice(0, 200)}`);
2042
2086
  return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
@@ -2091,6 +2135,10 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
2091
2135
  if (session) session._docHash = docHash;
2092
2136
  }
2093
2137
 
2138
+ if (result.missingRuntime) {
2139
+ return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
2140
+ }
2141
+
2094
2142
  if (result.code !== 0 || !result.text) {
2095
2143
  console.error(`[doc-chat-stream] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, stderr=${(result.stderr || '').slice(0, 200)}`);
2096
2144
  return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
@@ -5020,6 +5068,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5020
5068
  _ccHeartbeatTimer = null;
5021
5069
  }
5022
5070
  };
5071
+ const finishMissingRuntime = (result, liveState) => {
5072
+ const text = result.text || result.stderr || 'Minions runtime is not installed or configured.';
5073
+ liveState.donePayload = { type: 'done', text, actions: [], sessionId: null, missingRuntime: true };
5074
+ if (liveState.writer) liveState.writer(liveState.donePayload);
5075
+ if (liveState.endResponse) liveState.endResponse();
5076
+ _scheduleCcLiveCleanup(tabId);
5077
+ };
5023
5078
  try {
5024
5079
  const body = await readBody(req);
5025
5080
  if (!body.message && !body.reconnect) { res.statusCode = 400; res.end('message required'); return; }
@@ -5142,6 +5197,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5142
5197
  const result = await llmPromise;
5143
5198
  trackUsage('command-center', result.usage);
5144
5199
 
5200
+ if (result.missingRuntime) {
5201
+ finishMissingRuntime(result, liveState);
5202
+ return;
5203
+ }
5204
+
5145
5205
  // Handle failure — non-zero exit with text = max_turns or partial success, still usable
5146
5206
  if (!result.text && wasResume && result.code !== 0 && !req.destroyed) {
5147
5207
  // Resume failed (stale/expired session) — auto-retry as fresh session (skip if client already disconnected)
@@ -5164,6 +5224,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5164
5224
  Object.assign(result, retryResult);
5165
5225
  }
5166
5226
  }
5227
+ if (result.missingRuntime) {
5228
+ finishMissingRuntime(result, liveState);
5229
+ return;
5230
+ }
5167
5231
  if (!result.text) {
5168
5232
  if (req.destroyed) { _ccStreamEnded = true; return; } // client already gone — nothing to send
5169
5233
  const debugInfo = result.code !== 0 ? `(exit code ${result.code})` : '(empty response)';
@@ -5347,6 +5411,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5347
5411
  model: 'haiku', maxTurns: 1, timeout: 30000, label: 'schedule-parse', direct: true,
5348
5412
  engineConfig: CONFIG.engine,
5349
5413
  });
5414
+ if (result.missingRuntime) {
5415
+ return jsonReply(res, 503, { error: result.text || result.stderr || 'Minions runtime is not installed or configured.', missingRuntime: true });
5416
+ }
5350
5417
  const parsed = JSON.parse(result.text.trim());
5351
5418
  if (!parsed.cron) return jsonReply(res, 422, { error: 'Could not parse schedule' });
5352
5419
  return jsonReply(res, 200, { cron: parsed.cron, description: parsed.description || '' });
@@ -6219,6 +6286,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6219
6286
  }
6220
6287
 
6221
6288
  const entry = steering.writeSteeringMessage(agentId, text);
6289
+ const delivery = _steeringDeliveryState(agentId);
6222
6290
 
6223
6291
  // Also append to live-output.log so it shows in the chat view
6224
6292
  const liveLogPath = path.join(agentDir, 'live-output.log');
@@ -6226,7 +6294,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6226
6294
 
6227
6295
  return jsonReply(res, 200, {
6228
6296
  ok: true,
6229
- message: 'Steering message queued',
6297
+ message: delivery.pendingDelivery ? 'Steering message pending delivery' : 'Steering message queued',
6298
+ ...delivery,
6230
6299
  file: entry?.file || null,
6231
6300
  inboxCount: steering.listUnreadSteeringMessages(agentId).length,
6232
6301
  });
@@ -201,6 +201,10 @@ function consolidateWithLLM(items, existingNotes, files, config) {
201
201
  if (_cleared) return;
202
202
  clearTimeout(timeoutHandle);
203
203
  trackEngineUsage('consolidation', result.usage);
204
+ if (result.missingRuntime) {
205
+ _fallback(`LLM consolidation skipped: ${result.text || result.stderr || 'runtime unavailable'} — falling back to regex`);
206
+ return;
207
+ }
204
208
 
205
209
  const extractedText = result.text || '';
206
210
  const rawText = result.raw || '';
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-04T06:13:46.106Z"
4
+ "cachedAt": "2026-05-04T06:56:12.183Z"
5
5
  }
@@ -135,6 +135,10 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
135
135
  engineConfig: opts.engineConfig,
136
136
  });
137
137
  trackEngineUsage('kb-sweep', result.usage);
138
+ if (result.missingRuntime) {
139
+ log('warn', `[kb-sweep] batch ${b + 1} LLM skipped: ${result.text || result.stderr || 'runtime unavailable'}`);
140
+ continue;
141
+ }
138
142
  } catch (e) { log('warn', `[kb-sweep] batch ${b + 1} LLM error: ${e.message}`); continue; }
139
143
 
140
144
  let batchPlan;
@@ -212,6 +216,10 @@ ${body}`;
212
216
  engineConfig: opts.engineConfig,
213
217
  });
214
218
  trackEngineUsage('kb-sweep', result.usage);
219
+ if (result.missingRuntime) {
220
+ log('warn', `[kb-sweep] rewrite ${c.entry.category}/${c.entry.file} skipped: ${result.text || result.stderr || 'runtime unavailable'}`);
221
+ continue;
222
+ }
215
223
  let newBody = (result.text || '').trim();
216
224
  // Strip accidental code fence
217
225
  const fence = newBody.match(/^```(?:markdown|md)?\s*([\s\S]*?)```$/);
package/engine/llm.js CHANGED
@@ -23,6 +23,7 @@ const { resolveRuntime } = require('./runtimes');
23
23
  const MINIONS_DIR = shared.MINIONS_DIR;
24
24
  const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
25
25
  const COPILOT_TASK_COMPLETE_GRACE_MS = 3000;
26
+ const MISSING_RUNTIME_EXIT_CODE = 78;
26
27
 
27
28
  // ─── Engine-Usage Metrics ────────────────────────────────────────────────────
28
29
 
@@ -96,6 +97,85 @@ function _resolveBin(runtime) {
96
97
 
97
98
  function _resetBinCache() { _binCache.clear(); }
98
99
 
100
+ function _runtimeInstallHint(runtimeName, runtime) {
101
+ if (runtime && typeof runtime.installHint === 'string' && runtime.installHint) return runtime.installHint;
102
+ return `install the ${runtimeName || 'selected'} CLI and make sure it is on PATH`;
103
+ }
104
+
105
+ function _missingRuntimeMessage(runtimeName, runtime, reason) {
106
+ const selected = runtime?.name || runtimeName || 'configured';
107
+ if (!runtime) {
108
+ return [
109
+ `Minions can't run the configured runtime "${selected}".`,
110
+ '',
111
+ reason || 'The configured runtime is not registered.',
112
+ '',
113
+ 'Choose a supported runtime in Settings -> Engine -> defaultCli/ccCli, then install its CLI:',
114
+ '- Claude Code: npm install -g @anthropic-ai/claude-code or download from https://claude.ai/download',
115
+ '- GitHub Copilot CLI: install via GitHub CLI/gh-copilot or download from https://github.com/github/copilot-cli/releases',
116
+ '',
117
+ 'After installing, restart Minions so the dashboard and engine inherit the updated PATH.',
118
+ ].join('\n');
119
+ }
120
+ const lines = [
121
+ `Minions can't run the "${selected}" runtime yet.`,
122
+ '',
123
+ reason || `The ${selected} CLI binary was not found on PATH.`,
124
+ '',
125
+ `Install steps for "${selected}": ${_runtimeInstallHint(selected, runtime)}.`,
126
+ '',
127
+ 'After installing, restart Minions so the dashboard and engine inherit the updated PATH.',
128
+ ];
129
+ if (selected !== 'claude') {
130
+ lines.push('Or switch Settings -> Engine -> defaultCli/ccCli back to "claude" after installing Claude Code.');
131
+ } else {
132
+ lines.push('Or switch Settings -> Engine -> defaultCli/ccCli to "copilot" after installing GitHub Copilot CLI.');
133
+ }
134
+ return lines.join('\n');
135
+ }
136
+
137
+ function _missingRuntimeResult(runtimeName, runtime, reason) {
138
+ const message = _missingRuntimeMessage(runtimeName, runtime, reason);
139
+ return {
140
+ text: message,
141
+ usage: null,
142
+ sessionId: null,
143
+ code: MISSING_RUNTIME_EXIT_CODE,
144
+ stderr: message,
145
+ raw: '',
146
+ toolUses: [],
147
+ runtime: runtime?.name || runtimeName || null,
148
+ errorClass: shared.FAILURE_CLASS.CONFIG_ERROR,
149
+ missingRuntime: true,
150
+ };
151
+ }
152
+
153
+ function _resolvedCallResult(result) {
154
+ const promise = Promise.resolve(result);
155
+ promise.abort = () => {};
156
+ return promise;
157
+ }
158
+
159
+ function _resolveRuntimeNameFor(callOpts = {}) {
160
+ let runtimeName = callOpts.cli;
161
+ if (!runtimeName && callOpts.engineConfig) runtimeName = resolveCcCli(callOpts.engineConfig);
162
+ return runtimeName || 'claude';
163
+ }
164
+
165
+ function _runtimeUnavailableResult(callOpts = {}) {
166
+ const runtimeName = _resolveRuntimeNameFor(callOpts);
167
+ let runtime = null;
168
+ try {
169
+ runtime = resolveRuntime(runtimeName);
170
+ } catch (err) {
171
+ return _missingRuntimeResult(runtimeName, null, err.message);
172
+ }
173
+ if (!_resolveBin(runtime)) {
174
+ return _missingRuntimeResult(runtimeName, runtime, `The ${runtime.name} CLI binary was not found on PATH or in the runtime cache.`);
175
+ }
176
+ return null;
177
+ }
178
+
99
179
  // ─── Spawn Helpers ───────────────────────────────────────────────────────────
100
180
 
101
181
  /**
@@ -386,10 +466,7 @@ function _createStreamAccumulator({
386
466
  function _resolveRuntimeFor(callOpts) {
387
467
  // Explicit `cli` opt wins; otherwise fall to `engineConfig` resolution;
388
468
  // otherwise default to claude (the historical behavior).
389
- let runtimeName = callOpts.cli;
390
- if (!runtimeName && callOpts.engineConfig) runtimeName = resolveCcCli(callOpts.engineConfig);
391
- if (!runtimeName) runtimeName = 'claude';
392
- return resolveRuntime(runtimeName);
469
+ return resolveRuntime(_resolveRuntimeNameFor(callOpts));
393
470
  }
394
471
 
395
472
  function _resolveModelFor(callOpts) {
@@ -434,6 +511,9 @@ function callLLM(promptText, sysPromptText, opts = {}) {
434
511
  stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
435
512
  } = opts;
436
513
 
514
+ const unavailable = _runtimeUnavailableResult({ cli: cliOverride, engineConfig });
515
+ if (unavailable) return _resolvedCallResult(unavailable);
516
+
437
517
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
438
518
  const model = _resolveModelForRuntime(runtime, { model: modelOverride, engineConfig });
439
519
  const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
@@ -531,6 +611,9 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
531
611
  stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
532
612
  } = opts;
533
613
 
614
+ const unavailable = _runtimeUnavailableResult({ cli: cliOverride, engineConfig });
615
+ if (unavailable) return _resolvedCallResult(unavailable);
616
+
534
617
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
535
618
  const model = _resolveModelForRuntime(runtime, { model: modelOverride, engineConfig });
536
619
  const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
@@ -488,7 +488,9 @@ async function executePlanStage(stage, stageState, run, config) {
488
488
  timeout: 120000, label: 'pipeline-plan', model: 'sonnet', maxTurns: 1,
489
489
  engineConfig: config.engine,
490
490
  });
491
- if (result.text) {
491
+ if (result.missingRuntime) {
492
+ log('warn', `Pipeline plan LLM skipped: ${result.text || result.stderr || 'runtime unavailable'} — falling back to raw meeting context`);
493
+ } else if (result.text) {
492
494
  content = result.text;
493
495
  log('info', `Pipeline plan: LLM generated ${content.length} chars from meeting context`);
494
496
  }
package/engine.js CHANGED
@@ -1129,14 +1129,35 @@ async function spawnAgent(dispatchItem, config) {
1129
1129
  const procInfo = activeProcesses.get(id);
1130
1130
  ackPendingSteeringFiles(agentId, procInfo, stdout);
1131
1131
 
1132
+ if (procInfo?._deferredSteeringFiles?.length && procInfo.sessionId) {
1133
+ const deferredPaths = new Set(procInfo._deferredSteeringFiles);
1134
+ const pendingDeferred = steering.listUnreadSteeringMessages(agentId)
1135
+ .filter(entry => deferredPaths.has(entry.path) && entry.message.trim());
1136
+ if (pendingDeferred.length > 0) {
1137
+ log('info', `Steering: delivering ${pendingDeferred.length} deferred message(s) for ${agentId} at resumable checkpoint`);
1138
+ procInfo._steeringMessage = pendingDeferred.map(entry => entry.message.trim()).join('\n\n');
1139
+ procInfo._steeringSessionId = procInfo.sessionId;
1140
+ procInfo._steeringEntry = pendingDeferred;
1141
+ procInfo._steeringDeferredCheckpoint = true;
1142
+ delete procInfo._deferredSteeringFiles;
1143
+ } else {
1144
+ delete procInfo._deferredSteeringFiles;
1145
+ }
1146
+ } else if (procInfo?._deferredSteeringFiles?.length) {
1147
+ log('warn', `Steering: ${agentId} exited before a resumable sessionId was available — message remains pending`);
1148
+ try { fs.appendFileSync(liveOutputPath, `\n[steering-pending] Agent exited before a resumable session was available. Your message remains unread and will be retried on the next dispatch.\n`); } catch {}
1149
+ }
1150
+
1132
1151
  // Check if this was a steering kill — re-spawn with resume
1133
1152
  if (procInfo?._steeringMessage) {
1134
1153
  const steerMsg = procInfo._steeringMessage;
1135
1154
  const steerSessionId = procInfo._steeringSessionId;
1136
1155
  const steerEntry = procInfo._steeringEntry;
1156
+ const steeringDeferredCheckpoint = procInfo._steeringDeferredCheckpoint === true;
1137
1157
  delete procInfo._steeringMessage;
1138
1158
  delete procInfo._steeringSessionId;
1139
1159
  delete procInfo._steeringEntry;
1160
+ delete procInfo._steeringDeferredCheckpoint;
1140
1161
 
1141
1162
  // Guard: can't resume without a session
1142
1163
  if (!steerSessionId) {
@@ -1247,9 +1268,13 @@ async function spawnAgent(dispatchItem, config) {
1247
1268
  ),
1248
1269
  });
1249
1270
 
1250
- // Reset output buffers so post-completion parsing only sees the resumed session
1251
- stdout = '';
1252
- stderr = '';
1271
+ // Live steering kills discard partial old output. Deferred checkpoint
1272
+ // steering keeps the completed turn output so completion parsing still
1273
+ // sees the original work if the follow-up only acknowledges steering.
1274
+ if (!steeringDeferredCheckpoint) {
1275
+ stdout = '';
1276
+ stderr = '';
1277
+ }
1253
1278
  sessionCaptureState.sessionLineBuffer = '';
1254
1279
  // Re-wire stdout/stderr handlers (same as original)
1255
1280
  resumeProc.stdout.on('data', (data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1697",
3
+ "version": "0.1.1699",
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"