@yemi33/minions 0.1.1752 → 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 +15 -0
- package/bin/minions.js +7 -3
- package/dashboard/js/modal-qa.js +75 -10
- package/dashboard.js +94 -63
- 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.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
|
+
|
|
13
|
+
## 0.1.1753 (2026-05-06)
|
|
14
|
+
|
|
15
|
+
### Other
|
|
16
|
+
- refactor(doc-chat): drop brittle regex action gate in favour of sysprompt
|
|
17
|
+
|
|
3
18
|
## 0.1.1752 (2026-05-06)
|
|
4
19
|
|
|
5
20
|
### Features
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
717
|
-
if (expandBar) expandBar.style.display = visible ? '' : 'none';
|
|
772
|
+
_setQaThreadCollapsed(visible);
|
|
718
773
|
}
|
|
719
774
|
|
|
720
|
-
function
|
|
775
|
+
function _qaIsThreadCollapsed() {
|
|
721
776
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
722
777
|
var expandBar = document.getElementById('qa-expand-bar');
|
|
723
|
-
|
|
724
|
-
|
|
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
|
@@ -1510,34 +1510,6 @@ function stripCCActionSyntax(text) {
|
|
|
1510
1510
|
return displayText.replace(/`{3,}\s*action\s*\r?\n[\s\S]*?`{3,}\n?/g, '').trim();
|
|
1511
1511
|
}
|
|
1512
1512
|
|
|
1513
|
-
function _messageRequestsOrchestration(message) {
|
|
1514
|
-
const text = String(message || '').toLowerCase();
|
|
1515
|
-
if (!text.trim()) return false;
|
|
1516
|
-
|
|
1517
|
-
const docTarget = '\\b(document|doc|text|selection|selected text|selected paragraph|selected section|paragraph|section|wording|copy|markdown|plan)\\b';
|
|
1518
|
-
const docEditVerb = '\\b(edit|rewrite|revise|update|change|rephrase|polish|format|shorten|expand|summarize|correct|add|write)\\b';
|
|
1519
|
-
const explicitDocEdit = new RegExp(`${docEditVerb}[\\s\\S]{0,120}${docTarget}|${docTarget}[\\s\\S]{0,120}${docEditVerb}`).test(text)
|
|
1520
|
-
|| /\bfix\b[\s\S]{0,80}\b(typo|typos|grammar|spelling|wording|copy|markdown)\b[\s\S]{0,80}\b(document|doc|text|selection|paragraph|section|plan)\b/.test(text);
|
|
1521
|
-
const actionTerm = '\\b(dispatch|delegate|assign|orchestrate|hand off|handoff|work item|ticket|agent|minions|watch|monitor|schedule|pipeline|meeting)\\b';
|
|
1522
|
-
const untrustedActionMention = new RegExp(
|
|
1523
|
-
`${docTarget}[\\s\\S]{0,120}\\b(says|contains|mentions|includes|reads|states|instructs|asks|tells|literal|literally)\\b[\\s\\S]{0,160}${actionTerm}`
|
|
1524
|
-
).test(text)
|
|
1525
|
-
|| new RegExp(`\\b(summarize|explain|quote|describe|analyze|extract)\\b[\\s\\S]{0,160}${docTarget}[\\s\\S]{0,160}${actionTerm}`).test(text);
|
|
1526
|
-
const explicitFollowupAction = /\b(and|then|also)\b[\s\S]{0,80}\b(dispatch|delegate|assign|orchestrate|hand off|handoff|work item|ticket|agent|minions|watch|monitor|schedule|pipeline|meeting)\b/.test(text);
|
|
1527
|
-
if (untrustedActionMention && !explicitFollowupAction) return false;
|
|
1528
|
-
|
|
1529
|
-
const dispatchAction = /\b(dispatch|delegate|assign|orchestrate|hand off|handoff)\b[\s\S]{0,120}\b(agent|dallas|ripley|lambert|rebecca|ralph|work item|task|fix|implement|explore|investigate|audit|review|test|verify|build)\b/.test(text);
|
|
1530
|
-
const workItemAction = /\b(create|open|file|add)\b[\s\S]{0,80}\b(work item|task|ticket)\b/.test(text);
|
|
1531
|
-
const stateAction = /\b(create|add|set up|start)\b[\s\S]{0,80}\b(watch|monitor|schedule|pipeline|meeting)\b/.test(text)
|
|
1532
|
-
|| /\b(watch|monitor|keep an eye on)\b[\s\S]{0,100}\b(pr|pull request|work item|build)\b/.test(text)
|
|
1533
|
-
|| /\b(cancel|retry|reopen|archive|pause|approve|reject|execute|resume|steer)\b[\s\S]{0,100}\b(plan|work item|agent|pr|pull request|schedule|pipeline)\b/.test(text);
|
|
1534
|
-
const agentEngineeringAction = /\b(minions|agent|dallas|ripley|lambert|rebecca|ralph)\b[\s\S]{0,120}\b(fix|debug|repair|investigate|audit|review|test|verify|build|refactor|implement)\b/.test(text)
|
|
1535
|
-
|| /\b(fix|debug|repair|investigate|audit|review|test|verify|build|refactor|implement)\b[\s\S]{0,120}\b(minions|agent|dallas|ripley|lambert|rebecca|ralph)\b/.test(text);
|
|
1536
|
-
const explicitActionIntent = dispatchAction || workItemAction || stateAction || agentEngineeringAction;
|
|
1537
|
-
if (explicitDocEdit && !explicitActionIntent) return false;
|
|
1538
|
-
return explicitActionIntent;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
1513
|
function _escapeRegExp(str) {
|
|
1542
1514
|
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1543
1515
|
}
|
|
@@ -2517,13 +2489,11 @@ function contentFingerprint(str) {
|
|
|
2517
2489
|
return str.length + ':' + str.charCodeAt(0) + ':' + str.charCodeAt(mid) + ':' + str.charCodeAt(str.length - 1);
|
|
2518
2490
|
}
|
|
2519
2491
|
|
|
2520
|
-
function _parseDocChatResultText(text
|
|
2492
|
+
function _parseDocChatResultText(text) {
|
|
2521
2493
|
const docDelimiter = findDocChatDocumentDelimiter(text);
|
|
2522
2494
|
if (docDelimiter) {
|
|
2523
2495
|
const answerPart = text.slice(0, docDelimiter.index).trim();
|
|
2524
|
-
const parsedActions =
|
|
2525
|
-
? parseCCActions(answerPart)
|
|
2526
|
-
: { text: stripCCActionSyntax(answerPart), actions: [] };
|
|
2496
|
+
const parsedActions = parseCCActions(answerPart);
|
|
2527
2497
|
const { text: answer, actions } = parsedActions;
|
|
2528
2498
|
let content = text.slice(docDelimiter.index + docDelimiter.length).trim();
|
|
2529
2499
|
content = content.replace(/^```\w*\n?/, '').replace(/\n?```$/, '').trim();
|
|
@@ -2534,9 +2504,7 @@ function _parseDocChatResultText(text, { allowActions = false } = {}) {
|
|
|
2534
2504
|
...(parsedActions._actionParseError ? { actionParseError: parsedActions._actionParseError } : {}),
|
|
2535
2505
|
};
|
|
2536
2506
|
}
|
|
2537
|
-
const parsedActions =
|
|
2538
|
-
? parseCCActions(text)
|
|
2539
|
-
: { text: stripCCActionSyntax(text), actions: [] };
|
|
2507
|
+
const parsedActions = parseCCActions(text);
|
|
2540
2508
|
const { text: stripped, actions } = parsedActions;
|
|
2541
2509
|
return {
|
|
2542
2510
|
answer: stripped,
|
|
@@ -2546,8 +2514,8 @@ function _parseDocChatResultText(text, { allowActions = false } = {}) {
|
|
|
2546
2514
|
};
|
|
2547
2515
|
}
|
|
2548
2516
|
|
|
2549
|
-
function _docChatDisplayText(text
|
|
2550
|
-
return _parseDocChatResultText(text
|
|
2517
|
+
function _docChatDisplayText(text) {
|
|
2518
|
+
return _parseDocChatResultText(text).answer;
|
|
2551
2519
|
}
|
|
2552
2520
|
|
|
2553
2521
|
function _formatDocChatContext({ document, title, filePath, selection, canEdit, isJson, docUnchanged }) {
|
|
@@ -2570,15 +2538,36 @@ function _formatDocChatContext({ document, title, filePath, selection, canEdit,
|
|
|
2570
2538
|
|
|
2571
2539
|
// Map errorClass codes from the runtime adapter to actionable user-facing messages.
|
|
2572
2540
|
// sessionPreserved=true means ccCall preserved the session — user can retry immediately.
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
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;
|
|
2579
2557
|
return 'Failed to process request. Try again.';
|
|
2580
2558
|
}
|
|
2581
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
|
+
|
|
2582
2571
|
// Build the doc-chat extraContext for a single ccCall pass — refreshed on retry
|
|
2583
2572
|
// so a fresh-session retry includes the full document instead of relying on the
|
|
2584
2573
|
// dead session's prior turn for context.
|
|
@@ -2612,20 +2601,54 @@ async function _retryDocChatAfterResumeFailure({ result, initialPass, freshSessi
|
|
|
2612
2601
|
// Build the {error} envelope returned to the dashboard when doc-chat ultimately
|
|
2613
2602
|
// fails. Surfaces an actionable user-facing message (via _docChatErrorMessage)
|
|
2614
2603
|
// plus the runtime's real stderr / exit code / errorClass so the UI can render
|
|
2615
|
-
// 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
|
+
|
|
2616
2618
|
function _docChatFailureResponse(label, filePath, result, sessionPreserved = false) {
|
|
2617
|
-
const
|
|
2618
|
-
|
|
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)}`);
|
|
2619
2622
|
return {
|
|
2620
|
-
answer: _docChatErrorMessage(result.errorClass, sessionPreserved),
|
|
2623
|
+
answer: _docChatErrorMessage(result.errorClass, sessionPreserved, toolUses, result.errorMessage || null),
|
|
2621
2624
|
content: null,
|
|
2622
2625
|
actions: [],
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
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),
|
|
2629
2652
|
};
|
|
2630
2653
|
}
|
|
2631
2654
|
|
|
@@ -2643,8 +2666,6 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2643
2666
|
// Skip persistDocSessions() here — the post-call cleanup below handles persistence.
|
|
2644
2667
|
}
|
|
2645
2668
|
|
|
2646
|
-
const allowActions = _messageRequestsOrchestration(message);
|
|
2647
|
-
|
|
2648
2669
|
const runOnce = async () => {
|
|
2649
2670
|
const { extraContext } = _buildDocChatPass({
|
|
2650
2671
|
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
@@ -2687,11 +2708,14 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2687
2708
|
}
|
|
2688
2709
|
|
|
2689
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;
|
|
2690
2714
|
const sessionPreserved = !!(resolveSession('doc', sessionKey)?.sessionId);
|
|
2691
2715
|
return _docChatFailureResponse('doc-chat', filePath, result, sessionPreserved);
|
|
2692
2716
|
}
|
|
2693
2717
|
|
|
2694
|
-
return _parseDocChatResultText(result.text
|
|
2718
|
+
return _parseDocChatResultText(result.text);
|
|
2695
2719
|
}
|
|
2696
2720
|
|
|
2697
2721
|
async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady, onChunk, onToolUse, onRetry }) {
|
|
@@ -2702,8 +2726,6 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2702
2726
|
docSessions.delete(sessionKey);
|
|
2703
2727
|
}
|
|
2704
2728
|
|
|
2705
|
-
const allowActions = _messageRequestsOrchestration(message);
|
|
2706
|
-
|
|
2707
2729
|
const runOnce = async () => {
|
|
2708
2730
|
const { extraContext } = _buildDocChatPass({
|
|
2709
2731
|
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
@@ -2720,7 +2742,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2720
2742
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
2721
2743
|
...(model ? { model } : {}),
|
|
2722
2744
|
onAbortReady,
|
|
2723
|
-
onChunk: (text) => { if (onChunk) onChunk(_docChatDisplayText(text
|
|
2745
|
+
onChunk: (text) => { if (onChunk) onChunk(_docChatDisplayText(text)); },
|
|
2724
2746
|
onToolUse,
|
|
2725
2747
|
onRetry,
|
|
2726
2748
|
});
|
|
@@ -2745,11 +2767,13 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2745
2767
|
}
|
|
2746
2768
|
|
|
2747
2769
|
if (result.code !== 0 || !result.text) {
|
|
2770
|
+
const recovered = _recoverPartialDocChatResponse(result, sessionKey);
|
|
2771
|
+
if (recovered) return recovered;
|
|
2748
2772
|
const sessionPreserved = !!(resolveSession('doc', sessionKey)?.sessionId);
|
|
2749
2773
|
return _docChatFailureResponse('doc-chat-stream', filePath, result, sessionPreserved);
|
|
2750
2774
|
}
|
|
2751
2775
|
|
|
2752
|
-
return _parseDocChatResultText(result.text
|
|
2776
|
+
return _parseDocChatResultText(result.text);
|
|
2753
2777
|
}
|
|
2754
2778
|
|
|
2755
2779
|
// -- POST helpers --
|
|
@@ -4858,7 +4882,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4858
4882
|
}
|
|
4859
4883
|
}
|
|
4860
4884
|
|
|
4861
|
-
const { answer, content, actions, actionParseError, error: ccError } = await ccDocCall({
|
|
4885
|
+
const { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCall({
|
|
4862
4886
|
message: body.message, document: currentContent, title: body.title,
|
|
4863
4887
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
4864
4888
|
model: body.model || undefined,
|
|
@@ -4873,6 +4897,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4873
4897
|
...(actionResults ? { actionResults } : {}),
|
|
4874
4898
|
...(actionParseError ? { actionParseError } : {}),
|
|
4875
4899
|
...(ccError ? { error: ccError } : {}),
|
|
4900
|
+
...(partial ? { partial: true, warning } : {}),
|
|
4901
|
+
...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
|
|
4876
4902
|
...extra,
|
|
4877
4903
|
});
|
|
4878
4904
|
|
|
@@ -4974,7 +5000,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4974
5000
|
|
|
4975
5001
|
try {
|
|
4976
5002
|
|
|
4977
|
-
const { answer, content, actions, actionParseError, error: ccError } = await ccDocCallStreaming({
|
|
5003
|
+
const { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCallStreaming({
|
|
4978
5004
|
message: body.message, document: currentContent, title: body.title,
|
|
4979
5005
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
4980
5006
|
model: body.model || undefined,
|
|
@@ -4992,6 +5018,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4992
5018
|
...(actionResults ? { actionResults } : {}),
|
|
4993
5019
|
...(actionParseError ? { actionParseError } : {}),
|
|
4994
5020
|
...(ccError ? { error: ccError } : {}),
|
|
5021
|
+
...(partial ? { partial: true, warning } : {}),
|
|
5022
|
+
...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
|
|
4995
5023
|
...extra,
|
|
4996
5024
|
});
|
|
4997
5025
|
|
|
@@ -7435,8 +7463,11 @@ module.exports = {
|
|
|
7435
7463
|
_meetingParticipantsFromAction: meetingParticipantsFromAction,
|
|
7436
7464
|
parsePinnedEntries,
|
|
7437
7465
|
_parseDocChatResultText,
|
|
7438
|
-
_messageRequestsOrchestration,
|
|
7439
7466
|
_formatDocChatContext,
|
|
7467
|
+
_docChatErrorMessage,
|
|
7468
|
+
_docChatPartialWarning,
|
|
7469
|
+
_docChatFailureResponse,
|
|
7470
|
+
_recoverPartialDocChatResponse,
|
|
7440
7471
|
_linkPullRequestForTracking: linkPullRequestForTracking,
|
|
7441
7472
|
_resolveSkillReadPath,
|
|
7442
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.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"
|