@yemi33/minions 0.1.1698 → 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,5 +1,13 @@
1
1
  # Changelog
2
2
 
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
+
3
11
  ## 0.1.1698 (2026-05-04)
4
12
 
5
13
  ### Features
package/dashboard.js CHANGED
@@ -1825,6 +1825,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1825
1825
  if (onAbortReady) onAbortReady(p1.abort);
1826
1826
  result = await p1;
1827
1827
  llm.trackEngineUsage(label, result.usage);
1828
+ if (result.missingRuntime) return result;
1828
1829
 
1829
1830
  if (result.text) {
1830
1831
  updateSession(store, sessionKey, result.sessionId || sessionId, true);
@@ -1862,6 +1863,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1862
1863
  if (onAbortReady) onAbortReady(p2.abort);
1863
1864
  result = await p2;
1864
1865
  llm.trackEngineUsage(label, result.usage);
1866
+ if (result.missingRuntime) return result;
1865
1867
 
1866
1868
  if (result.text) {
1867
1869
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1879,6 +1881,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1879
1881
  if (onAbortReady) onAbortReady(p3.abort);
1880
1882
  result = await p3;
1881
1883
  llm.trackEngineUsage(label, result.usage);
1884
+ if (result.missingRuntime) return result;
1882
1885
 
1883
1886
  if (result.text) {
1884
1887
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1912,6 +1915,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1912
1915
  if (onAbortReady) onAbortReady(p1.abort);
1913
1916
  result = await p1;
1914
1917
  llm.trackEngineUsage(label, result.usage);
1918
+ if (result.missingRuntime) return result;
1915
1919
 
1916
1920
  if (result.text) {
1917
1921
  updateSession(store, sessionKey, result.sessionId || sessionId, true);
@@ -1948,6 +1952,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1948
1952
  if (onAbortReady) onAbortReady(p2.abort);
1949
1953
  result = await p2;
1950
1954
  llm.trackEngineUsage(label, result.usage);
1955
+ if (result.missingRuntime) return result;
1951
1956
 
1952
1957
  if (result.text) {
1953
1958
  updateSession(store, sessionKey, result.sessionId, false);
@@ -1966,6 +1971,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
1966
1971
  if (onAbortReady) onAbortReady(p3.abort);
1967
1972
  result = await p3;
1968
1973
  llm.trackEngineUsage(label, result.usage);
1974
+ if (result.missingRuntime) return result;
1969
1975
 
1970
1976
  if (result.text) {
1971
1977
  updateSession(store, sessionKey, result.sessionId, false);
@@ -2071,6 +2077,10 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
2071
2077
  if (session) session._docHash = docHash;
2072
2078
  }
2073
2079
 
2080
+ if (result.missingRuntime) {
2081
+ return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
2082
+ }
2083
+
2074
2084
  if (result.code !== 0 || !result.text) {
2075
2085
  console.error(`[doc-chat] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, stderr=${(result.stderr || '').slice(0, 200)}`);
2076
2086
  return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
@@ -2125,6 +2135,10 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
2125
2135
  if (session) session._docHash = docHash;
2126
2136
  }
2127
2137
 
2138
+ if (result.missingRuntime) {
2139
+ return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
2140
+ }
2141
+
2128
2142
  if (result.code !== 0 || !result.text) {
2129
2143
  console.error(`[doc-chat-stream] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, stderr=${(result.stderr || '').slice(0, 200)}`);
2130
2144
  return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
@@ -5054,6 +5068,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5054
5068
  _ccHeartbeatTimer = null;
5055
5069
  }
5056
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
+ };
5057
5078
  try {
5058
5079
  const body = await readBody(req);
5059
5080
  if (!body.message && !body.reconnect) { res.statusCode = 400; res.end('message required'); return; }
@@ -5176,6 +5197,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5176
5197
  const result = await llmPromise;
5177
5198
  trackUsage('command-center', result.usage);
5178
5199
 
5200
+ if (result.missingRuntime) {
5201
+ finishMissingRuntime(result, liveState);
5202
+ return;
5203
+ }
5204
+
5179
5205
  // Handle failure — non-zero exit with text = max_turns or partial success, still usable
5180
5206
  if (!result.text && wasResume && result.code !== 0 && !req.destroyed) {
5181
5207
  // Resume failed (stale/expired session) — auto-retry as fresh session (skip if client already disconnected)
@@ -5198,6 +5224,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5198
5224
  Object.assign(result, retryResult);
5199
5225
  }
5200
5226
  }
5227
+ if (result.missingRuntime) {
5228
+ finishMissingRuntime(result, liveState);
5229
+ return;
5230
+ }
5201
5231
  if (!result.text) {
5202
5232
  if (req.destroyed) { _ccStreamEnded = true; return; } // client already gone — nothing to send
5203
5233
  const debugInfo = result.code !== 0 ? `(exit code ${result.code})` : '(empty response)';
@@ -5381,6 +5411,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5381
5411
  model: 'haiku', maxTurns: 1, timeout: 30000, label: 'schedule-parse', direct: true,
5382
5412
  engineConfig: CONFIG.engine,
5383
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
+ }
5384
5417
  const parsed = JSON.parse(result.text.trim());
5385
5418
  if (!parsed.cron) return jsonReply(res, 422, { error: 'Could not parse schedule' });
5386
5419
  return jsonReply(res, 200, { cron: parsed.cron, description: parsed.description || '' });
@@ -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:31:58.762Z"
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1698",
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"