@yemi33/minions 0.1.1753 → 0.1.1754

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1754 (2026-05-06)
4
+
5
+ ### Fixes
6
+ - canonical .minions home wins over nearest copied tree
7
+ - recover partial responses instead of always returning "Failed"
8
+ - surface adapter-supplied error messages instead of hardcoding Claude
9
+
10
+ ### Other
11
+ - refactor(doc-chat): dedupe error envelope shape into _buildDocChatErrorEnvelope
12
+
3
13
  ## 0.1.1753 (2026-05-06)
4
14
 
5
15
  ### Other
package/bin/minions.js CHANGED
@@ -191,12 +191,14 @@ function resolveMinionsHome(forInit = false) {
191
191
 
192
192
  if (forInit) return path.join(process.cwd(), '.minions');
193
193
 
194
- const localRoot = findNearestLocalMinionsRoot(process.cwd());
195
- if (localRoot) return localRoot;
196
-
197
194
  const pointerRoot = readRootPointer();
198
195
  if (isInstalledRoot(pointerRoot)) return pointerRoot;
199
196
 
197
+ if (isInstalledRoot(DEFAULT_MINIONS_HOME)) return DEFAULT_MINIONS_HOME;
198
+
199
+ const localRoot = findNearestLocalMinionsRoot(process.cwd());
200
+ if (localRoot) return localRoot;
201
+
200
202
  return DEFAULT_MINIONS_HOME;
201
203
  }
202
204
 
@@ -205,6 +207,7 @@ let force = rest.includes('--force');
205
207
  const skipScan = rest.includes('--skip-scan');
206
208
  const skipStart = rest.includes('--skip-start') || rest.includes('--no-start');
207
209
  const MINIONS_HOME = resolveMinionsHome(cmd === 'init');
210
+ process.env.MINIONS_HOME = MINIONS_HOME;
208
211
  const POST_UPDATE_INIT_TIMEOUT_MS = 120000;
209
212
  const POST_UPDATE_RESTART_TIMEOUT_MS = 60000;
210
213
 
@@ -542,6 +545,7 @@ function delegate(script, args) {
542
545
  const child = spawn(process.execPath, [path.join(MINIONS_HOME, script), ...args], {
543
546
  stdio: 'inherit',
544
547
  cwd: MINIONS_HOME,
548
+ env: { ...process.env, MINIONS_HOME },
545
549
  });
546
550
  child.on('exit', code => process.exit(code || 0));
547
551
  }
@@ -184,6 +184,31 @@ function _qaBuildLoadingHtml(loadingId, queueCount) {
184
184
  qaQueueBadge + '</div>';
185
185
  }
186
186
 
187
+ // Render the raw error envelope (stderr + metadata) returned by ccDocCallStreaming
188
+ // when the runtime fails. Always shown — collapsed by default — so the user can
189
+ // inspect the actual CLI output instead of relying on the friendly summary.
190
+ function _qaBuildRawErrorHtml(err) {
191
+ if (!err) return '';
192
+ const meta = [];
193
+ if (err.runtime) meta.push('runtime: ' + escHtml(String(err.runtime)));
194
+ if (err.errorClass) meta.push('class: ' + escHtml(String(err.errorClass)));
195
+ if (err.code !== null && err.code !== undefined) meta.push('exit: ' + escHtml(String(err.code)));
196
+ const metaLine = meta.length
197
+ ? '<div style="font-size:10px;color:var(--muted);margin-bottom:4px">' + meta.join(' · ') + '</div>'
198
+ : '';
199
+ const adapterMessage = err.errorMessage
200
+ ? '<div style="font-size:11px;color:var(--text);margin-bottom:6px;white-space:pre-wrap">' + escHtml(String(err.errorMessage)) + '</div>'
201
+ : '';
202
+ const stderrText = err.stderr ? String(err.stderr) : '';
203
+ const stderrBlock = stderrText
204
+ ? '<pre style="margin:0;padding:6px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;font-size:11px;white-space:pre-wrap;word-break:break-word;max-height:240px;overflow:auto">' + escHtml(stderrText) + '</pre>'
205
+ : '<div style="font-size:11px;color:var(--muted)">(no stderr captured)</div>';
206
+ return '<details class="modal-qa-raw-error" style="margin:6px 0 12px;padding:6px 8px;border:1px solid var(--red);border-radius:4px;background:var(--surface)">' +
207
+ '<summary style="cursor:pointer;font-size:11px;color:var(--red)">Raw error output</summary>' +
208
+ '<div style="margin-top:6px">' + metaLine + adapterMessage + stderrBlock + '</div>' +
209
+ '</details>';
210
+ }
211
+
187
212
  function _qaBuildAssistantHtml(text, opts) {
188
213
  const body = opts?.isError ? escHtml(text) : renderMd(text);
189
214
  const style = opts?.isError
@@ -231,12 +256,14 @@ function _qaMutateThreadHtml(key, mutate) {
231
256
  mutate(tmp);
232
257
  const html = tmp.innerHTML;
233
258
  if (_qaIsActiveSession(key)) {
259
+ const wasCollapsed = _qaIsThreadCollapsed();
234
260
  const thread = _qaThreadEl();
235
261
  if (thread) {
236
262
  thread.innerHTML = html;
237
263
  thread.scrollTop = thread.scrollHeight;
238
264
  }
239
- _showThreadWrap();
265
+ if (wasCollapsed) _setQaThreadCollapsed(true);
266
+ else _showThreadWrap();
240
267
  }
241
268
  return html;
242
269
  }
@@ -544,17 +571,47 @@ async function _processQaMessage(message, selection, opts) {
544
571
  clearInterval(qaTimer);
545
572
  _clearQaStreamWatchdog();
546
573
  const qaElapsed = Math.round((Date.now() - qaStartTime) / 1000);
547
- const borderColor = evt.edited ? 'var(--green)' : 'var(--blue)';
574
+ // Pick the border that best reflects what actually happened:
575
+ // - partial recovery (work landed despite a non-zero exit) \u2192 orange
576
+ // (checked first because partial responses now also carry an error
577
+ // envelope so the raw stderr can be surfaced)
578
+ // - hard error (no recoverable answer) \u2192 red
579
+ // - successful edit \u2192 green
580
+ // - normal answer \u2192 blue
581
+ const borderColor = evt.partial
582
+ ? 'var(--orange)'
583
+ : evt.error
584
+ ? 'var(--red)'
585
+ : evt.edited ? 'var(--green)' : 'var(--blue)';
548
586
  const suffix = evt.edited ? '\n\n\u2713 Document saved.' : '';
549
- const answerHtml = _qaBuildAssistantHtml((evt.text || '') + suffix, { borderColor, elapsed: qaElapsed });
587
+ // Fall back to the live-streamed text when the backend produced no final
588
+ // answer \u2014 covers the "stream had visible chunks then returned empty" case.
589
+ const finalText = (evt.text && evt.text.trim()) ? evt.text : (streamedText || '');
590
+ let bodyText = finalText + suffix;
591
+ if (evt.partial && evt.warning) {
592
+ bodyText += '\n\n_' + evt.warning + '_';
593
+ }
594
+ // On hard failures, surface tool side-effects so the user knows what
595
+ // landed before the runtime gave up \u2014 silent destruction was the worst
596
+ // class of "Failed to process request" UX.
597
+ if (evt.error && Array.isArray(evt.toolUses) && evt.toolUses.length > 0) {
598
+ const names = evt.toolUses.slice(0, 8).map(t => t.name).join(', ');
599
+ const more = evt.toolUses.length > 8 ? '\u2026' : '';
600
+ bodyText += '\n\n_Tools that ran before the failure: ' + names + more + ' \u2014 files or state may have been modified._';
601
+ }
602
+ const answerHtml = _qaBuildAssistantHtml(bodyText, { borderColor, elapsed: qaElapsed });
603
+ // On any runtime failure, surface the raw error so users can debug it
604
+ // directly instead of guessing what the friendly summary was hiding.
605
+ const rawErrorHtml = evt.error ? _qaBuildRawErrorHtml(evt.error) : '';
550
606
  let updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
551
607
  const loadingEl = tmp.querySelector('#' + loadingId);
552
608
  if (loadingEl) loadingEl.remove();
553
609
  tmp.insertAdjacentHTML('beforeend', answerHtml);
610
+ if (rawErrorHtml) tmp.insertAdjacentHTML('beforeend', rawErrorHtml);
554
611
  });
555
612
 
556
613
  runtime.history.push({ role: 'user', text: message });
557
- runtime.history.push({ role: 'assistant', text: evt.text || '' });
614
+ runtime.history.push({ role: 'assistant', text: finalText || '' });
558
615
  if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
559
616
 
560
617
  _qaNotifySidebar(capturedFilePath);
@@ -710,18 +767,26 @@ function qaAbort() {
710
767
 
711
768
  function toggleDocChat() {
712
769
  var wrap = document.getElementById('modal-qa-thread-wrap');
713
- var expandBar = document.getElementById('qa-expand-bar');
714
770
  if (!wrap) return;
715
771
  var visible = wrap.style.display !== 'none';
716
- wrap.style.display = visible ? 'none' : '';
717
- if (expandBar) expandBar.style.display = visible ? '' : 'none';
772
+ _setQaThreadCollapsed(visible);
718
773
  }
719
774
 
720
- function _showThreadWrap() {
775
+ function _qaIsThreadCollapsed() {
721
776
  var wrap = document.getElementById('modal-qa-thread-wrap');
722
777
  var expandBar = document.getElementById('qa-expand-bar');
723
- if (wrap) wrap.style.display = '';
724
- if (expandBar) expandBar.style.display = 'none';
778
+ return !!(wrap && wrap.style.display === 'none' && expandBar && expandBar.style.display !== 'none');
779
+ }
780
+
781
+ function _setQaThreadCollapsed(collapsed) {
782
+ var wrap = document.getElementById('modal-qa-thread-wrap');
783
+ var expandBar = document.getElementById('qa-expand-bar');
784
+ if (wrap) wrap.style.display = collapsed ? 'none' : '';
785
+ if (expandBar) expandBar.style.display = collapsed ? '' : 'none';
786
+ }
787
+
788
+ function _showThreadWrap() {
789
+ _setQaThreadCollapsed(false);
725
790
  }
726
791
 
727
792
  // ── Drag-to-resize doc chat thread ──────────────────────────────────────────
package/dashboard.js CHANGED
@@ -2538,15 +2538,36 @@ function _formatDocChatContext({ document, title, filePath, selection, canEdit,
2538
2538
 
2539
2539
  // Map errorClass codes from the runtime adapter to actionable user-facing messages.
2540
2540
  // sessionPreserved=true means ccCall preserved the session — user can retry immediately.
2541
- function _docChatErrorMessage(errorClass, sessionPreserved = false) {
2542
- if (errorClass === 'auth-failure') return 'Claude authentication failed run `claude auth` or check your API key, then try again.';
2543
- if (errorClass === 'context-limit') return 'Session context is too long. Click "Clear" to start a fresh conversation.';
2544
- if (errorClass === 'budget-exceeded') return 'API budget exceeded check your Claude account spending limit.';
2545
- if (errorClass === 'crash') return 'Claude runtime crashed unexpectedly. Try again.';
2546
- if (sessionPreserved) return 'Temporary connection issue — your conversation is intact, send your message again.';
2541
+ // toolUses=[] from result.toolUses lets the message warn that tools may have already
2542
+ // modified files/state before the failurethe user shouldn't assume nothing happened.
2543
+ // errorMessage is the runtime adapter's own remediation string (from parseError)
2544
+ // authoritative because it knows whether the runtime is Claude, Copilot, or another;
2545
+ // dashboard falls back to generic copy only when the adapter didn't supply one.
2546
+ function _docChatErrorMessage(errorClass, sessionPreserved = false, toolUses = [], errorMessage = null) {
2547
+ const tools = Array.isArray(toolUses) ? toolUses : [];
2548
+ const toolHint = tools.length > 0
2549
+ ? ` (${tools.length} tool${tools.length === 1 ? '' : 's'} ran before the failure: ${tools.slice(0, 5).map(t => t.name).join(', ')}${tools.length > 5 ? '…' : ''} — files or state may have been modified.)`
2550
+ : '';
2551
+ if (errorClass === 'auth-failure') return (errorMessage || 'Runtime authentication failed — check your CLI auth or API key, then try again.') + toolHint;
2552
+ if (errorClass === 'context-limit') return 'Session context is too long. Click "Clear" to start a fresh conversation.' + toolHint;
2553
+ if (errorClass === 'budget-exceeded') return (errorMessage || 'Runtime budget exceeded — check your account or quota.') + toolHint;
2554
+ if (errorClass === 'crash') return (errorMessage || 'Runtime crashed unexpectedly. Try again.') + toolHint;
2555
+ if (sessionPreserved) return 'Temporary connection issue — your conversation is intact, send your message again.' + toolHint;
2556
+ if (tools.length > 0) return 'The agent stopped responding before producing a final answer.' + toolHint;
2547
2557
  return 'Failed to process request. Try again.';
2548
2558
  }
2549
2559
 
2560
+ // Secondary note rendered alongside a recovered partial answer — distinct from the
2561
+ // hard-failure message because the answer/actions/document-edit DID land. The user
2562
+ // just needs to know the run wasn't clean.
2563
+ function _docChatPartialWarning(errorClass) {
2564
+ if (errorClass === 'auth-failure') return 'Note: auth failed — answer recovered from partial output, but follow-up turns may not work.';
2565
+ if (errorClass === 'context-limit') return 'Note: session context was too long — answer recovered. Click "Clear" before continuing.';
2566
+ if (errorClass === 'budget-exceeded') return 'Note: runtime budget exceeded — answer recovered, but further calls may fail.';
2567
+ if (errorClass === 'crash') return 'Note: runtime crashed before clean exit, but a complete response was recovered.';
2568
+ return 'Note: the agent exited unexpectedly. A complete response was recovered — verify any saved files or dispatched actions.';
2569
+ }
2570
+
2550
2571
  // Build the doc-chat extraContext for a single ccCall pass — refreshed on retry
2551
2572
  // so a fresh-session retry includes the full document instead of relying on the
2552
2573
  // dead session's prior turn for context.
@@ -2580,20 +2601,54 @@ async function _retryDocChatAfterResumeFailure({ result, initialPass, freshSessi
2580
2601
  // Build the {error} envelope returned to the dashboard when doc-chat ultimately
2581
2602
  // fails. Surfaces an actionable user-facing message (via _docChatErrorMessage)
2582
2603
  // plus the runtime's real stderr / exit code / errorClass so the UI can render
2583
- // the cause and so future failures are debuggable from logs.
2604
+ // the cause and so future failures are debuggable from logs. toolUses lets the
2605
+ // client surface what side-effects already landed despite the failure.
2606
+ // Shape the per-failure debug envelope (raw stderr + classification metadata)
2607
+ // shared by hard failures and partial recoveries — keeps the wire shape in lockstep.
2608
+ function _buildDocChatErrorEnvelope(result) {
2609
+ return {
2610
+ code: result.code ?? null,
2611
+ stderr: (result.stderr || '').slice(-2048),
2612
+ errorClass: result.errorClass || null,
2613
+ errorMessage: result.errorMessage || null,
2614
+ runtime: result.runtime || null,
2615
+ };
2616
+ }
2617
+
2584
2618
  function _docChatFailureResponse(label, filePath, result, sessionPreserved = false) {
2585
- const stderrTail = (result.stderr || '').slice(-2048);
2586
- console.error(`[${label}] Failed: code=${result.code}, errorClass=${result.errorClass || 'null'}, sessionPreserved=${sessionPreserved}, empty=${!result.text}, filePath=${filePath}, stderr=${stderrTail.slice(0, 200)}`);
2619
+ const envelope = _buildDocChatErrorEnvelope(result);
2620
+ const toolUses = Array.isArray(result.toolUses) ? result.toolUses : [];
2621
+ console.error(`[${label}] Failed: code=${result.code}, errorClass=${result.errorClass || 'null'}, sessionPreserved=${sessionPreserved}, empty=${!result.text}, tools=${toolUses.length}, filePath=${filePath}, stderr=${envelope.stderr.slice(0, 200)}`);
2587
2622
  return {
2588
- answer: _docChatErrorMessage(result.errorClass, sessionPreserved),
2623
+ answer: _docChatErrorMessage(result.errorClass, sessionPreserved, toolUses, result.errorMessage || null),
2589
2624
  content: null,
2590
2625
  actions: [],
2591
- error: {
2592
- code: result.code ?? null,
2593
- stderr: stderrTail,
2594
- errorClass: result.errorClass || null,
2595
- runtime: result.runtime || null,
2596
- },
2626
+ toolUses,
2627
+ error: envelope,
2628
+ };
2629
+ }
2630
+
2631
+ // Try to salvage useful work from a non-zero / empty-text result before falling
2632
+ // through to the failure response. Even when the runtime exits non-zero, the
2633
+ // model often produced a complete answer / action JSON / document edit in
2634
+ // result.text — discarding it just to show "Failed to process request" wastes
2635
+ // successful work and confuses users who watched tools run during streaming.
2636
+ // Returns null when there's nothing parseable; caller falls through to failure.
2637
+ function _recoverPartialDocChatResponse(result, sessionKey) {
2638
+ if (!result || !result.text || !result.text.trim()) return null;
2639
+ const parsed = _parseDocChatResultText(result.text);
2640
+ const hasActions = Array.isArray(parsed.actions) && parsed.actions.length > 0;
2641
+ const hasAnswer = typeof parsed.answer === 'string' && !!parsed.answer.trim();
2642
+ const hasContent = typeof parsed.content === 'string' && !!parsed.content.trim();
2643
+ if (!hasAnswer && !hasContent && !hasActions) return null;
2644
+ return {
2645
+ ...parsed,
2646
+ partial: true,
2647
+ warning: _docChatPartialWarning(result.errorClass),
2648
+ toolUses: Array.isArray(result.toolUses) ? result.toolUses : [],
2649
+ // Recovery path still attaches the raw runtime failure — the answer landed
2650
+ // despite a non-zero exit; users still benefit from seeing why.
2651
+ error: _buildDocChatErrorEnvelope(result),
2597
2652
  };
2598
2653
  }
2599
2654
 
@@ -2653,6 +2708,9 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
2653
2708
  }
2654
2709
 
2655
2710
  if (result.code !== 0 || !result.text) {
2711
+ // Try to salvage a parseable answer / action / document edit before failing.
2712
+ const recovered = _recoverPartialDocChatResponse(result, sessionKey);
2713
+ if (recovered) return recovered;
2656
2714
  const sessionPreserved = !!(resolveSession('doc', sessionKey)?.sessionId);
2657
2715
  return _docChatFailureResponse('doc-chat', filePath, result, sessionPreserved);
2658
2716
  }
@@ -2709,6 +2767,8 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
2709
2767
  }
2710
2768
 
2711
2769
  if (result.code !== 0 || !result.text) {
2770
+ const recovered = _recoverPartialDocChatResponse(result, sessionKey);
2771
+ if (recovered) return recovered;
2712
2772
  const sessionPreserved = !!(resolveSession('doc', sessionKey)?.sessionId);
2713
2773
  return _docChatFailureResponse('doc-chat-stream', filePath, result, sessionPreserved);
2714
2774
  }
@@ -4822,7 +4882,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4822
4882
  }
4823
4883
  }
4824
4884
 
4825
- const { answer, content, actions, actionParseError, error: ccError } = await ccDocCall({
4885
+ const { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCall({
4826
4886
  message: body.message, document: currentContent, title: body.title,
4827
4887
  filePath: body.filePath, selection: body.selection, canEdit, isJson,
4828
4888
  model: body.model || undefined,
@@ -4837,6 +4897,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4837
4897
  ...(actionResults ? { actionResults } : {}),
4838
4898
  ...(actionParseError ? { actionParseError } : {}),
4839
4899
  ...(ccError ? { error: ccError } : {}),
4900
+ ...(partial ? { partial: true, warning } : {}),
4901
+ ...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
4840
4902
  ...extra,
4841
4903
  });
4842
4904
 
@@ -4938,7 +5000,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4938
5000
 
4939
5001
  try {
4940
5002
 
4941
- const { answer, content, actions, actionParseError, error: ccError } = await ccDocCallStreaming({
5003
+ const { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCallStreaming({
4942
5004
  message: body.message, document: currentContent, title: body.title,
4943
5005
  filePath: body.filePath, selection: body.selection, canEdit, isJson,
4944
5006
  model: body.model || undefined,
@@ -4956,6 +5018,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4956
5018
  ...(actionResults ? { actionResults } : {}),
4957
5019
  ...(actionParseError ? { actionParseError } : {}),
4958
5020
  ...(ccError ? { error: ccError } : {}),
5021
+ ...(partial ? { partial: true, warning } : {}),
5022
+ ...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
4959
5023
  ...extra,
4960
5024
  });
4961
5025
 
@@ -7400,6 +7464,10 @@ module.exports = {
7400
7464
  parsePinnedEntries,
7401
7465
  _parseDocChatResultText,
7402
7466
  _formatDocChatContext,
7467
+ _docChatErrorMessage,
7468
+ _docChatPartialWarning,
7469
+ _docChatFailureResponse,
7470
+ _recoverPartialDocChatResponse,
7403
7471
  _linkPullRequestForTracking: linkPullRequestForTracking,
7404
7472
  _resolveSkillReadPath,
7405
7473
  DOC_CHAT_DOCUMENT_DELIMITER,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-06T21:07:27.809Z"
4
+ "cachedAt": "2026-05-06T21:43:07.445Z"
5
5
  }
package/engine/llm.js CHANGED
@@ -232,6 +232,7 @@ function _missingRuntimeResult(runtimeName, runtime, reason) {
232
232
  toolUses: [],
233
233
  runtime: runtime?.name || runtimeName || null,
234
234
  errorClass: shared.FAILURE_CLASS.CONFIG_ERROR,
235
+ errorMessage: message,
235
236
  missingRuntime: true,
236
237
  };
237
238
  }
@@ -689,6 +690,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
689
690
  toolUses: parsed.toolUses,
690
691
  runtime: runtime.name,
691
692
  errorClass: errInfo.code,
693
+ errorMessage: errInfo.message || null,
692
694
  });
693
695
  };
694
696
 
@@ -711,7 +713,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
711
713
  resolve({
712
714
  text: '', usage: null, sessionId: null, code: 1,
713
715
  stderr: err.message, raw: '', toolUses: [],
714
- runtime: runtime.name, errorClass: null,
716
+ runtime: runtime.name, errorClass: null, errorMessage: null,
715
717
  });
716
718
  });
717
719
  });
@@ -814,6 +816,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
814
816
  toolUses: parsed.toolUses,
815
817
  runtime: runtime.name,
816
818
  errorClass: errInfo.code,
819
+ errorMessage: errInfo.message || null,
817
820
  });
818
821
  };
819
822
 
@@ -836,7 +839,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
836
839
  resolve({
837
840
  text: '', usage: null, sessionId: null, code: 1,
838
841
  stderr: err.message, raw: '', toolUses: [],
839
- runtime: runtime.name, errorClass: null,
842
+ runtime: runtime.name, errorClass: null, errorMessage: null,
840
843
  });
841
844
  });
842
845
  });
@@ -493,16 +493,16 @@ function parseError(rawOutput) {
493
493
  const hasExplicitAuthFailure = /invalid api key|api key.*invalid|authentication.*fail|\bunauthorized\b|please.*log.*in|claude\.ai\/login/i.test(text);
494
494
  const hasAuthStatusCode = /\b(?:http(?:\/\d(?:\.\d)?)?|status(?:\s+code)?|statuscode|response(?:\s+status)?|api(?:\s+(?:error|response|status))?)\s*[:=]?\s*(?:401|403)\b|\b(?:401\s+unauthorized|403\s+forbidden)\b/i.test(text);
495
495
  if (hasExplicitAuthFailure || hasAuthStatusCode) {
496
- return { message: 'Claude authentication failed', code: 'auth-failure', retriable: false };
496
+ return { message: 'Claude authentication failed — run `claude auth` or check your API key, then try again.', code: 'auth-failure', retriable: false };
497
497
  }
498
498
  if (/prompt is too long|context window|context.*length.*exceeded|token limit|conversation.*too long/i.test(text)) {
499
499
  return { message: 'Claude context window exhausted', code: 'context-limit', retriable: false };
500
500
  }
501
501
  if (/budget.*exceed|max.budget.usd.*reach|cost.*limit.*exceed/i.test(lower)) {
502
- return { message: 'Claude budget cap exceeded', code: 'budget-exceeded', retriable: false };
502
+ return { message: 'Claude budget cap exceeded — check your Claude account spending limit.', code: 'budget-exceeded', retriable: false };
503
503
  }
504
504
  if (/internal error|panic|segmentation fault|claude.*crashed|fatal: claude/i.test(lower)) {
505
- return { message: 'Claude CLI crashed', code: 'crash', retriable: true };
505
+ return { message: 'Claude CLI crashed unexpectedly. Try again.', code: 'crash', retriable: true };
506
506
  }
507
507
  return { message: '', code: null, retriable: true };
508
508
  }
@@ -576,10 +576,10 @@ function parseError(rawOutput) {
576
576
  return { message: 'Copilot rejected the requested model', code: 'unknown-model', retriable: false };
577
577
  }
578
578
  if (/budget.*exceed|premium.*limit.*reach|quota.*exceed/i.test(lower)) {
579
- return { message: 'Copilot premium-request budget exceeded', code: 'budget-exceeded', retriable: false };
579
+ return { message: 'Copilot premium-request budget exceeded — check your GitHub Copilot quota.', code: 'budget-exceeded', retriable: false };
580
580
  }
581
581
  if (/internal error|panic|uncaught|copilot.*crashed|fatal: copilot/i.test(lower)) {
582
- return { message: 'Copilot CLI crashed', code: 'crash', retriable: true };
582
+ return { message: 'Copilot CLI crashed unexpectedly. Try again.', code: 'crash', retriable: true };
583
583
  }
584
584
  return { message: '', code: null, retriable: true };
585
585
  }
package/engine/shared.js CHANGED
@@ -7,7 +7,7 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const crypto = require('crypto');
9
9
 
10
- const MINIONS_DIR = process.env.MINIONS_TEST_DIR || path.resolve(__dirname, '..');
10
+ const MINIONS_DIR = process.env.MINIONS_TEST_DIR || (process.env.MINIONS_HOME ? path.resolve(process.env.MINIONS_HOME) : path.resolve(__dirname, '..'));
11
11
  const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
12
12
  const CONTROL_PATH = path.join(ENGINE_DIR, 'control.json');
13
13
  const COOLDOWNS_PATH = path.join(ENGINE_DIR, 'cooldowns.json');
package/minions.js CHANGED
@@ -19,7 +19,7 @@ const { execSync } = require('child_process');
19
19
  const { ENGINE_DEFAULTS, DEFAULT_AGENTS, DEFAULT_CLAUDE } = require('./engine/shared');
20
20
  const projectDiscovery = require('./engine/project-discovery');
21
21
 
22
- const MINIONS_HOME = __dirname;
22
+ const MINIONS_HOME = process.env.MINIONS_HOME ? path.resolve(process.env.MINIONS_HOME) : __dirname;
23
23
  const CONFIG_PATH = path.join(MINIONS_HOME, 'config.json');
24
24
 
25
25
  function loadConfig() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1753",
3
+ "version": "0.1.1754",
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"