@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 +14 -2
- package/bin/minions.js +11 -6
- package/dashboard.js +70 -1
- package/engine/consolidation.js +4 -0
- package/engine/copilot-models.json +1 -1
- package/engine/kb-sweep.js +8 -0
- package/engine/llm.js +87 -4
- package/engine/pipeline.js +3 -1
- package/engine.js +28 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 +
|
|
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
|
});
|
package/engine/consolidation.js
CHANGED
|
@@ -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 || '';
|
package/engine/kb-sweep.js
CHANGED
|
@@ -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
|
-
|
|
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({
|
package/engine/pipeline.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
1251
|
-
|
|
1252
|
-
|
|
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.
|
|
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"
|