@yemi33/minions 0.1.1753 → 0.1.1755
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 +15 -0
- package/bin/minions.js +7 -3
- package/dashboard/js/modal-qa.js +91 -16
- package/dashboard.js +86 -18
- package/engine/copilot-models.json +1 -1
- package/engine/llm.js +5 -2
- package/engine/runtimes/claude.js +3 -3
- package/engine/runtimes/copilot.js +2 -2
- package/engine/shared.js +1 -1
- package/minions.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1755 (2026-05-06)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- keep queued strips pinned below progress UX and answers
|
|
7
|
+
|
|
8
|
+
## 0.1.1754 (2026-05-06)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- canonical .minions home wins over nearest copied tree
|
|
12
|
+
- recover partial responses instead of always returning "Failed"
|
|
13
|
+
- surface adapter-supplied error messages instead of hardcoding Claude
|
|
14
|
+
|
|
15
|
+
### Other
|
|
16
|
+
- refactor(doc-chat): dedupe error envelope shape into _buildDocChatErrorEnvelope
|
|
17
|
+
|
|
3
18
|
## 0.1.1753 (2026-05-06)
|
|
4
19
|
|
|
5
20
|
### 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
|
}
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -30,13 +30,23 @@ const QA_QUEUE_CAP = 10; // max queued messages
|
|
|
30
30
|
const QA_STREAM_STALL_MS = 6 * 60 * 1000; // allow the full doc-chat timeout before treating heartbeat-only streams as stalled
|
|
31
31
|
let _qaSessionKey = ''; // key for current conversation (title or filePath)
|
|
32
32
|
|
|
33
|
+
// Insert html at the bottom of the thread but above any pending "Queued: ..."
|
|
34
|
+
// strips, so queued messages always remain visually below the active progress
|
|
35
|
+
// UX / answer / errors.
|
|
36
|
+
function _qaInsertBeforeQueued(container, html) {
|
|
37
|
+
if (!container) return;
|
|
38
|
+
const firstQueued = container.querySelector && container.querySelector('.qa-queued-item');
|
|
39
|
+
if (firstQueued) firstQueued.insertAdjacentHTML('beforebegin', html);
|
|
40
|
+
else container.insertAdjacentHTML('beforeend', html);
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
function _renderQaUserMessage(thread, message, selection) {
|
|
34
44
|
let qHtml = '<div class="modal-qa-q">' + escHtml(message);
|
|
35
45
|
if (selection) {
|
|
36
46
|
qHtml += '<span class="selection-ref">Re: "' + escHtml(selection.slice(0, 100)) + ((selection.length > 100) ? '...' : '') + '"</span>';
|
|
37
47
|
}
|
|
38
48
|
qHtml += '</div>';
|
|
39
|
-
thread
|
|
49
|
+
_qaInsertBeforeQueued(thread, qHtml);
|
|
40
50
|
thread.scrollTop = thread.scrollHeight;
|
|
41
51
|
_showThreadWrap();
|
|
42
52
|
}
|
|
@@ -184,6 +194,31 @@ function _qaBuildLoadingHtml(loadingId, queueCount) {
|
|
|
184
194
|
qaQueueBadge + '</div>';
|
|
185
195
|
}
|
|
186
196
|
|
|
197
|
+
// Render the raw error envelope (stderr + metadata) returned by ccDocCallStreaming
|
|
198
|
+
// when the runtime fails. Always shown — collapsed by default — so the user can
|
|
199
|
+
// inspect the actual CLI output instead of relying on the friendly summary.
|
|
200
|
+
function _qaBuildRawErrorHtml(err) {
|
|
201
|
+
if (!err) return '';
|
|
202
|
+
const meta = [];
|
|
203
|
+
if (err.runtime) meta.push('runtime: ' + escHtml(String(err.runtime)));
|
|
204
|
+
if (err.errorClass) meta.push('class: ' + escHtml(String(err.errorClass)));
|
|
205
|
+
if (err.code !== null && err.code !== undefined) meta.push('exit: ' + escHtml(String(err.code)));
|
|
206
|
+
const metaLine = meta.length
|
|
207
|
+
? '<div style="font-size:10px;color:var(--muted);margin-bottom:4px">' + meta.join(' · ') + '</div>'
|
|
208
|
+
: '';
|
|
209
|
+
const adapterMessage = err.errorMessage
|
|
210
|
+
? '<div style="font-size:11px;color:var(--text);margin-bottom:6px;white-space:pre-wrap">' + escHtml(String(err.errorMessage)) + '</div>'
|
|
211
|
+
: '';
|
|
212
|
+
const stderrText = err.stderr ? String(err.stderr) : '';
|
|
213
|
+
const stderrBlock = stderrText
|
|
214
|
+
? '<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>'
|
|
215
|
+
: '<div style="font-size:11px;color:var(--muted)">(no stderr captured)</div>';
|
|
216
|
+
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)">' +
|
|
217
|
+
'<summary style="cursor:pointer;font-size:11px;color:var(--red)">Raw error output</summary>' +
|
|
218
|
+
'<div style="margin-top:6px">' + metaLine + adapterMessage + stderrBlock + '</div>' +
|
|
219
|
+
'</details>';
|
|
220
|
+
}
|
|
221
|
+
|
|
187
222
|
function _qaBuildAssistantHtml(text, opts) {
|
|
188
223
|
const body = opts?.isError ? escHtml(text) : renderMd(text);
|
|
189
224
|
const style = opts?.isError
|
|
@@ -231,12 +266,14 @@ function _qaMutateThreadHtml(key, mutate) {
|
|
|
231
266
|
mutate(tmp);
|
|
232
267
|
const html = tmp.innerHTML;
|
|
233
268
|
if (_qaIsActiveSession(key)) {
|
|
269
|
+
const wasCollapsed = _qaIsThreadCollapsed();
|
|
234
270
|
const thread = _qaThreadEl();
|
|
235
271
|
if (thread) {
|
|
236
272
|
thread.innerHTML = html;
|
|
237
273
|
thread.scrollTop = thread.scrollHeight;
|
|
238
274
|
}
|
|
239
|
-
|
|
275
|
+
if (wasCollapsed) _setQaThreadCollapsed(true);
|
|
276
|
+
else _showThreadWrap();
|
|
240
277
|
}
|
|
241
278
|
return html;
|
|
242
279
|
}
|
|
@@ -415,7 +452,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
415
452
|
const loadingId = 'chat-loading-' + Date.now();
|
|
416
453
|
const loadingHtml = _qaBuildLoadingHtml(loadingId, runtime.queue.length);
|
|
417
454
|
const startThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
418
|
-
tmp
|
|
455
|
+
_qaInsertBeforeQueued(tmp, loadingHtml);
|
|
419
456
|
});
|
|
420
457
|
_qaPersistSession(sessionKey, {
|
|
421
458
|
threadHtml: startThreadHtml,
|
|
@@ -544,17 +581,47 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
544
581
|
clearInterval(qaTimer);
|
|
545
582
|
_clearQaStreamWatchdog();
|
|
546
583
|
const qaElapsed = Math.round((Date.now() - qaStartTime) / 1000);
|
|
547
|
-
|
|
584
|
+
// Pick the border that best reflects what actually happened:
|
|
585
|
+
// - partial recovery (work landed despite a non-zero exit) \u2192 orange
|
|
586
|
+
// (checked first because partial responses now also carry an error
|
|
587
|
+
// envelope so the raw stderr can be surfaced)
|
|
588
|
+
// - hard error (no recoverable answer) \u2192 red
|
|
589
|
+
// - successful edit \u2192 green
|
|
590
|
+
// - normal answer \u2192 blue
|
|
591
|
+
const borderColor = evt.partial
|
|
592
|
+
? 'var(--orange)'
|
|
593
|
+
: evt.error
|
|
594
|
+
? 'var(--red)'
|
|
595
|
+
: evt.edited ? 'var(--green)' : 'var(--blue)';
|
|
548
596
|
const suffix = evt.edited ? '\n\n\u2713 Document saved.' : '';
|
|
549
|
-
|
|
597
|
+
// Fall back to the live-streamed text when the backend produced no final
|
|
598
|
+
// answer \u2014 covers the "stream had visible chunks then returned empty" case.
|
|
599
|
+
const finalText = (evt.text && evt.text.trim()) ? evt.text : (streamedText || '');
|
|
600
|
+
let bodyText = finalText + suffix;
|
|
601
|
+
if (evt.partial && evt.warning) {
|
|
602
|
+
bodyText += '\n\n_' + evt.warning + '_';
|
|
603
|
+
}
|
|
604
|
+
// On hard failures, surface tool side-effects so the user knows what
|
|
605
|
+
// landed before the runtime gave up \u2014 silent destruction was the worst
|
|
606
|
+
// class of "Failed to process request" UX.
|
|
607
|
+
if (evt.error && Array.isArray(evt.toolUses) && evt.toolUses.length > 0) {
|
|
608
|
+
const names = evt.toolUses.slice(0, 8).map(t => t.name).join(', ');
|
|
609
|
+
const more = evt.toolUses.length > 8 ? '\u2026' : '';
|
|
610
|
+
bodyText += '\n\n_Tools that ran before the failure: ' + names + more + ' \u2014 files or state may have been modified._';
|
|
611
|
+
}
|
|
612
|
+
const answerHtml = _qaBuildAssistantHtml(bodyText, { borderColor, elapsed: qaElapsed });
|
|
613
|
+
// On any runtime failure, surface the raw error so users can debug it
|
|
614
|
+
// directly instead of guessing what the friendly summary was hiding.
|
|
615
|
+
const rawErrorHtml = evt.error ? _qaBuildRawErrorHtml(evt.error) : '';
|
|
550
616
|
let updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
551
617
|
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
552
618
|
if (loadingEl) loadingEl.remove();
|
|
553
|
-
tmp
|
|
619
|
+
_qaInsertBeforeQueued(tmp, answerHtml);
|
|
620
|
+
if (rawErrorHtml) _qaInsertBeforeQueued(tmp, rawErrorHtml);
|
|
554
621
|
});
|
|
555
622
|
|
|
556
623
|
runtime.history.push({ role: 'user', text: message });
|
|
557
|
-
runtime.history.push({ role: 'assistant', text:
|
|
624
|
+
runtime.history.push({ role: 'assistant', text: finalText || '' });
|
|
558
625
|
if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
|
|
559
626
|
|
|
560
627
|
_qaNotifySidebar(capturedFilePath);
|
|
@@ -564,7 +631,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
564
631
|
} else if (evt.actionParseError) {
|
|
565
632
|
const warning = '<div class="modal-qa-a" style="color:var(--red)">Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>';
|
|
566
633
|
updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
567
|
-
tmp
|
|
634
|
+
_qaInsertBeforeQueued(tmp, warning);
|
|
568
635
|
});
|
|
569
636
|
}
|
|
570
637
|
|
|
@@ -641,7 +708,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
641
708
|
const updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
642
709
|
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
643
710
|
if (loadingEl) loadingEl.remove();
|
|
644
|
-
tmp
|
|
711
|
+
_qaInsertBeforeQueued(tmp, messageHtml);
|
|
645
712
|
});
|
|
646
713
|
_qaPersistSession(sessionKey, {
|
|
647
714
|
threadHtml: updatedThreadHtml,
|
|
@@ -676,7 +743,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
676
743
|
const nextThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
677
744
|
const queuedEl = tmp.querySelector('.qa-queued-item');
|
|
678
745
|
if (queuedEl) queuedEl.remove();
|
|
679
|
-
tmp
|
|
746
|
+
_qaInsertBeforeQueued(tmp, _qaBuildUserMessageHtml(next.message, next.selection));
|
|
680
747
|
});
|
|
681
748
|
_qaPersistSession(sessionKey, {
|
|
682
749
|
threadHtml: nextThreadHtml,
|
|
@@ -710,18 +777,26 @@ function qaAbort() {
|
|
|
710
777
|
|
|
711
778
|
function toggleDocChat() {
|
|
712
779
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
713
|
-
var expandBar = document.getElementById('qa-expand-bar');
|
|
714
780
|
if (!wrap) return;
|
|
715
781
|
var visible = wrap.style.display !== 'none';
|
|
716
|
-
|
|
717
|
-
if (expandBar) expandBar.style.display = visible ? '' : 'none';
|
|
782
|
+
_setQaThreadCollapsed(visible);
|
|
718
783
|
}
|
|
719
784
|
|
|
720
|
-
function
|
|
785
|
+
function _qaIsThreadCollapsed() {
|
|
721
786
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
722
787
|
var expandBar = document.getElementById('qa-expand-bar');
|
|
723
|
-
|
|
724
|
-
|
|
788
|
+
return !!(wrap && wrap.style.display === 'none' && expandBar && expandBar.style.display !== 'none');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function _setQaThreadCollapsed(collapsed) {
|
|
792
|
+
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
793
|
+
var expandBar = document.getElementById('qa-expand-bar');
|
|
794
|
+
if (wrap) wrap.style.display = collapsed ? 'none' : '';
|
|
795
|
+
if (expandBar) expandBar.style.display = collapsed ? '' : 'none';
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function _showThreadWrap() {
|
|
799
|
+
_setQaThreadCollapsed(false);
|
|
725
800
|
}
|
|
726
801
|
|
|
727
802
|
// ── 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
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2541
|
+
// toolUses=[] from result.toolUses lets the message warn that tools may have already
|
|
2542
|
+
// modified files/state before the failure — the 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
|
|
2586
|
-
|
|
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
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
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,
|
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.
|
|
3
|
+
"version": "0.1.1755",
|
|
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"
|