@yemi33/minions 0.1.1772 → 0.1.1774
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 +8 -0
- package/README.md +10 -0
- package/bin/minions.js +15 -1
- package/dashboard/js/modal-qa.js +37 -4
- package/dashboard.js +110 -45
- package/engine/copilot-models.json +1 -1
- package/engine/runtimes/copilot.js +59 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -127,21 +127,31 @@ minions work "Explore the codebase and document the architecture"
|
|
|
127
127
|
| `minions init` | Bootstrap `~/.minions/` with default agents and config |
|
|
128
128
|
| `minions update` | Update to latest version (npm update + apply) |
|
|
129
129
|
| `minions version` | Show installed vs package version |
|
|
130
|
+
| `minions doctor` | Check prerequisites and runtime health |
|
|
130
131
|
| `minions scan [dir] [depth]` | Scan for git repos and multi-select to add (default: ~, depth 3) |
|
|
131
132
|
| `minions add <dir>` | Link a single project (auto-detects settings from git, prompts to confirm) |
|
|
132
133
|
| `minions remove <dir-or-name> [--keep-data \| --purge --force]` | Unlink a project: cancels pending work items, drains dispatch + kills active agents, cleans worktrees, disables linked schedules, archives `projects/<name>/` to `projects/.archived/<name>-YYYYMMDD/`. Use `--keep-data` to leave the data dir in place, or `--purge --force` to delete it. |
|
|
133
134
|
| `minions list` | List all linked projects with descriptions |
|
|
135
|
+
| `minions restart` | Start engine and dashboard together (recommended after reboot) |
|
|
134
136
|
| `minions start` | Start engine daemon (ticks every 60s, auto-syncs MCP servers) |
|
|
135
137
|
| `minions stop` | Stop the engine |
|
|
136
138
|
| `minions status` | Show agents, projects, dispatch queue, quality metrics |
|
|
137
139
|
| `minions pause` / `resume` | Pause/resume dispatching |
|
|
138
140
|
| `minions dispatch` | Force a dispatch cycle |
|
|
139
141
|
| `minions discover` | Dry-run work discovery |
|
|
142
|
+
| `minions queue` | Show dispatch queue (pending, active, completed) |
|
|
143
|
+
| `minions sources` | Show work source status per project |
|
|
140
144
|
| `minions work <title> [opts]` | Add to central work queue |
|
|
141
145
|
| `minions spawn <agent> <prompt>` | Manually spawn an agent |
|
|
142
146
|
| `minions plan <file\|text> [proj]` | Run a plan |
|
|
147
|
+
| `minions kill` | Kill all active agents and reset their dispatches to pending |
|
|
148
|
+
| `minions complete <dispatch-id>` | Manually mark a dispatch as completed |
|
|
149
|
+
| `minions config set-cli <R> [--model M]` | Persist the default runtime/model without starting the engine |
|
|
150
|
+
| `minions mcp-sync` | Sync MCP servers from `~/.claude.json` |
|
|
143
151
|
| `minions cleanup` | Run cleanup manually (temp files, worktrees, zombies) |
|
|
144
152
|
| `minions dash` | Open dashboard (starts if not already running, port 7331) |
|
|
153
|
+
| `minions nuke --confirm` | Factory reset runtime state and reset config to defaults |
|
|
154
|
+
| `minions uninstall --confirm` | Remove Minions state and uninstall the npm package |
|
|
145
155
|
|
|
146
156
|
You can also run scripts directly: `node ~/.minions/engine.js start`, `node ~/.minions/dashboard.js`, etc.
|
|
147
157
|
|
package/bin/minions.js
CHANGED
|
@@ -8,18 +8,29 @@
|
|
|
8
8
|
* minions add <project-dir> Link a project (interactive)
|
|
9
9
|
* minions remove <project-dir> Unlink a project
|
|
10
10
|
* minions list List linked projects
|
|
11
|
+
* minions update Update to latest version
|
|
12
|
+
* minions version Show installed and package versions
|
|
13
|
+
* minions doctor Check prerequisites and runtime health
|
|
14
|
+
* minions restart Start engine + dashboard
|
|
11
15
|
* minions start Start the engine
|
|
12
16
|
* minions stop Stop the engine
|
|
13
17
|
* minions status Show engine status
|
|
14
18
|
* minions pause / resume Pause/resume dispatching
|
|
19
|
+
* minions queue Show dispatch queue
|
|
20
|
+
* minions sources Show work source status
|
|
15
21
|
* minions dash Start the dashboard
|
|
16
22
|
* minions work <title> [opts-json] Add a work item
|
|
17
23
|
* minions spawn <agent> <prompt> Manually spawn an agent
|
|
18
24
|
* minions dispatch Force a dispatch cycle
|
|
19
25
|
* minions discover Dry-run work discovery
|
|
20
26
|
* minions cleanup Run cleanup manually
|
|
27
|
+
* minions kill Kill active agents and reset to pending
|
|
28
|
+
* minions complete <dispatch-id> Mark a dispatch completed
|
|
29
|
+
* minions config set-cli <R> [--model M] Persist default runtime/model
|
|
21
30
|
* minions plan <file|text> [proj] Run a plan
|
|
22
|
-
* minions
|
|
31
|
+
* minions mcp-sync Sync MCP servers from ~/.claude.json
|
|
32
|
+
* minions nuke --confirm Factory reset runtime state/config
|
|
33
|
+
* minions uninstall --confirm Remove Minions and uninstall package
|
|
23
34
|
*/
|
|
24
35
|
|
|
25
36
|
const fs = require('fs');
|
|
@@ -642,6 +653,9 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
642
653
|
minions plan <file|text> [proj] Run a plan
|
|
643
654
|
minions kill Kill all active agents and reset to pending
|
|
644
655
|
minions complete <dispatch-id> Manually mark a dispatch as completed
|
|
656
|
+
minions config set-cli <R> [--model M]
|
|
657
|
+
Persist default runtime/model without starting
|
|
658
|
+
minions mcp-sync Sync MCP servers from ~/.claude.json
|
|
645
659
|
minions cleanup Clean temp files, worktrees, zombies
|
|
646
660
|
minions nuke --confirm Factory reset (delete state, reset config to defaults)
|
|
647
661
|
minions uninstall --confirm Remove everything + uninstall npm package
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -30,6 +30,32 @@ 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
|
+
const QA_STICKY_BOTTOM_PX = 80;
|
|
34
|
+
let _qaThreadShouldFollow = true;
|
|
35
|
+
|
|
36
|
+
function _qaIsNearThreadBottom(thread) {
|
|
37
|
+
if (!thread) return true;
|
|
38
|
+
return thread.scrollHeight - thread.scrollTop - thread.clientHeight <= QA_STICKY_BOTTOM_PX;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _qaShouldFollowThread(thread) {
|
|
42
|
+
return _qaThreadShouldFollow && _qaIsNearThreadBottom(thread);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _qaSetThreadFollowFromScroll(thread) {
|
|
46
|
+
_qaThreadShouldFollow = _qaIsNearThreadBottom(thread);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _qaScrollThreadToBottom(thread) {
|
|
50
|
+
if (!thread) return;
|
|
51
|
+
thread.scrollTop = thread.scrollHeight;
|
|
52
|
+
_qaThreadShouldFollow = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _qaMaybeScrollThreadToBottom(thread, shouldFollow) {
|
|
56
|
+
if (shouldFollow) _qaScrollThreadToBottom(thread);
|
|
57
|
+
}
|
|
58
|
+
|
|
33
59
|
// Insert html at the bottom of the thread but above any pending "Queued: ..."
|
|
34
60
|
// strips, so queued messages always remain visually below the active progress
|
|
35
61
|
// UX / answer / errors.
|
|
@@ -47,7 +73,7 @@ function _renderQaUserMessage(thread, message, selection) {
|
|
|
47
73
|
}
|
|
48
74
|
qHtml += '</div>';
|
|
49
75
|
_qaInsertBeforeQueued(thread, qHtml);
|
|
50
|
-
thread
|
|
76
|
+
_qaScrollThreadToBottom(thread);
|
|
51
77
|
_showThreadWrap();
|
|
52
78
|
}
|
|
53
79
|
const _qaSessions = new Map(); // persist conversations across modal open/close {key → {history, threadHtml}}
|
|
@@ -91,6 +117,10 @@ function _qaThreadEl() {
|
|
|
91
117
|
return document.getElementById('modal-qa-thread');
|
|
92
118
|
}
|
|
93
119
|
|
|
120
|
+
document.addEventListener('scroll', function(e) {
|
|
121
|
+
if (e.target && e.target.id === 'modal-qa-thread') _qaSetThreadFollowFromScroll(e.target);
|
|
122
|
+
}, true);
|
|
123
|
+
|
|
94
124
|
function _qaThreadHtml() {
|
|
95
125
|
return (_qaThreadEl() || {}).innerHTML || '';
|
|
96
126
|
}
|
|
@@ -308,8 +338,9 @@ function _qaMutateThreadHtml(key, mutate) {
|
|
|
308
338
|
const wasCollapsed = _qaIsThreadCollapsed();
|
|
309
339
|
const thread = _qaThreadEl();
|
|
310
340
|
if (thread) {
|
|
341
|
+
const shouldFollow = _qaShouldFollowThread(thread);
|
|
311
342
|
thread.innerHTML = html;
|
|
312
|
-
thread
|
|
343
|
+
_qaMaybeScrollThreadToBottom(thread, shouldFollow);
|
|
313
344
|
}
|
|
314
345
|
if (wasCollapsed) _setQaThreadCollapsed(true);
|
|
315
346
|
else _showThreadWrap();
|
|
@@ -386,7 +417,7 @@ function _initQaSession() {
|
|
|
386
417
|
_showThreadWrap();
|
|
387
418
|
requestAnimationFrame(function() {
|
|
388
419
|
var thread = document.getElementById('modal-qa-thread');
|
|
389
|
-
|
|
420
|
+
_qaScrollThreadToBottom(thread);
|
|
390
421
|
});
|
|
391
422
|
if (_qaQueue.length > 0 && !_qaProcessing) {
|
|
392
423
|
setTimeout(_qaResumeQueuedMessages, 0);
|
|
@@ -394,6 +425,7 @@ function _initQaSession() {
|
|
|
394
425
|
} else {
|
|
395
426
|
_qaHistory = [];
|
|
396
427
|
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
428
|
+
_qaThreadShouldFollow = true;
|
|
397
429
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
398
430
|
var expandBar = document.getElementById('qa-expand-bar');
|
|
399
431
|
if (wrap) wrap.style.display = 'none';
|
|
@@ -410,6 +442,7 @@ function clearQaConversation() {
|
|
|
410
442
|
_qaProcessing = false;
|
|
411
443
|
_qaAbortController = null;
|
|
412
444
|
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
445
|
+
_qaThreadShouldFollow = true;
|
|
413
446
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
414
447
|
var expandBar = document.getElementById('qa-expand-bar');
|
|
415
448
|
if (wrap) wrap.style.display = 'none';
|
|
@@ -455,7 +488,7 @@ function modalSend() {
|
|
|
455
488
|
}
|
|
456
489
|
_qaQueue.push({ message, selection });
|
|
457
490
|
thread.insertAdjacentHTML('beforeend', _qaBuildQueuedHtml(message));
|
|
458
|
-
thread
|
|
491
|
+
_qaScrollThreadToBottom(thread);
|
|
459
492
|
_showThreadWrap();
|
|
460
493
|
_qaSaveActiveSessionState();
|
|
461
494
|
return;
|
package/dashboard.js
CHANGED
|
@@ -281,12 +281,23 @@ function createWorkItemWithDedup(wiPath, item, options = {}) {
|
|
|
281
281
|
return result || { created: false, item: null };
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
function formatUnknownProjectError(projectName, projects = []) {
|
|
285
|
+
const known = projects.map(p => p.name).filter(Boolean).join(', ') || '(none configured)';
|
|
286
|
+
return `Project "${projectName}" not found. Known projects: ${known}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function findProjectByName(projects, projectName) {
|
|
290
|
+
const name = String(projectName || '').trim().toLowerCase();
|
|
291
|
+
if (!name) return null;
|
|
292
|
+
return projects.find(p => p.name?.toLowerCase() === name) || null;
|
|
293
|
+
}
|
|
294
|
+
|
|
284
295
|
function resolveWorkItemsCreateTarget(projectName, projects = PROJECTS) {
|
|
285
296
|
const project = String(projectName || '').trim();
|
|
286
297
|
let targetProject = null;
|
|
287
298
|
if (project) {
|
|
288
|
-
targetProject = projects
|
|
289
|
-
if (!targetProject) return { error:
|
|
299
|
+
targetProject = findProjectByName(projects, project);
|
|
300
|
+
if (!targetProject) return { error: formatUnknownProjectError(project, projects) };
|
|
290
301
|
} else if (projects.length === 1) {
|
|
291
302
|
targetProject = projects[0];
|
|
292
303
|
}
|
|
@@ -326,7 +337,13 @@ function linkPullRequestForTracking({ url, title, project: projectName, autoObse
|
|
|
326
337
|
throw err;
|
|
327
338
|
}
|
|
328
339
|
const projects = shared.getProjects(config);
|
|
329
|
-
const
|
|
340
|
+
const explicitProjectName = String(projectName || '').trim();
|
|
341
|
+
const targetProject = explicitProjectName ? findProjectByName(projects, explicitProjectName) : (projects[0] || null);
|
|
342
|
+
if (explicitProjectName && !targetProject) {
|
|
343
|
+
const err = new Error(formatUnknownProjectError(explicitProjectName, projects));
|
|
344
|
+
err.statusCode = 400;
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
330
347
|
const prPath = targetProject ? shared.projectPrPath(targetProject) : path.join(MINIONS_DIR, 'pull-requests.json');
|
|
331
348
|
|
|
332
349
|
const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
|
|
@@ -2704,23 +2721,19 @@ function _formatDocChatContext({ document, title, filePath, selection, canEdit,
|
|
|
2704
2721
|
return context;
|
|
2705
2722
|
}
|
|
2706
2723
|
|
|
2707
|
-
// Map
|
|
2724
|
+
// Map runtime failures to user-facing messages.
|
|
2708
2725
|
// sessionPreserved=true means ccCall preserved the session — user can retry immediately.
|
|
2709
2726
|
// toolUses=[] from result.toolUses lets the message warn that tools may have already
|
|
2710
2727
|
// modified files/state before the failure — the user shouldn't assume nothing happened.
|
|
2711
|
-
// errorMessage is the runtime adapter's own
|
|
2712
|
-
// authoritative
|
|
2713
|
-
// dashboard falls back to generic copy only when the adapter didn't supply one.
|
|
2728
|
+
// errorMessage is the runtime adapter's own text from parseError; when present it
|
|
2729
|
+
// is authoritative and is shown directly rather than replaced with dashboard copy.
|
|
2714
2730
|
function _docChatErrorMessage(errorClass, sessionPreserved = false, toolUses = [], errorMessage = null) {
|
|
2715
2731
|
const tools = Array.isArray(toolUses) ? toolUses : [];
|
|
2716
2732
|
const toolHint = tools.length > 0
|
|
2717
2733
|
? ` (${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.)`
|
|
2718
2734
|
: '';
|
|
2719
|
-
|
|
2720
|
-
if (
|
|
2721
|
-
if (errorClass === 'budget-exceeded') return (errorMessage || 'Runtime budget exceeded — check your account or quota.') + toolHint;
|
|
2722
|
-
if (errorClass === 'crash') return (errorMessage || 'Runtime crashed unexpectedly. Try again.') + toolHint;
|
|
2723
|
-
if (errorClass === 'unknown-model') return (errorMessage || 'Configured model is not valid for the active runtime. Update engine.ccModel or engine.defaultModel.') + toolHint;
|
|
2735
|
+
const directError = typeof errorMessage === 'string' ? errorMessage.trim() : '';
|
|
2736
|
+
if (directError) return directError + toolHint;
|
|
2724
2737
|
if (sessionPreserved) return 'Temporary connection issue — your conversation is intact, send your message again.' + toolHint;
|
|
2725
2738
|
if (tools.length > 0) return 'The agent stopped responding before producing a final answer.' + toolHint;
|
|
2726
2739
|
return 'Failed to process request. Try again.';
|
|
@@ -2729,14 +2742,26 @@ function _docChatErrorMessage(errorClass, sessionPreserved = false, toolUses = [
|
|
|
2729
2742
|
// Secondary note rendered alongside a recovered partial answer — distinct from the
|
|
2730
2743
|
// hard-failure message because the answer/actions/document-edit DID land. The user
|
|
2731
2744
|
// just needs to know the run wasn't clean.
|
|
2732
|
-
function _docChatPartialWarning(errorClass) {
|
|
2733
|
-
|
|
2734
|
-
if (
|
|
2735
|
-
if (errorClass === 'budget-exceeded') return 'Note: runtime budget exceeded — answer recovered, but further calls may fail.';
|
|
2736
|
-
if (errorClass === 'crash') return 'Note: runtime crashed before clean exit, but a complete response was recovered.';
|
|
2745
|
+
function _docChatPartialWarning(errorClass, errorMessage = null) {
|
|
2746
|
+
const directError = typeof errorMessage === 'string' ? errorMessage.trim() : '';
|
|
2747
|
+
if (directError) return `Note: ${directError}`;
|
|
2737
2748
|
return 'Note: the agent exited unexpectedly. A complete response was recovered — verify any saved files or dispatched actions.';
|
|
2738
2749
|
}
|
|
2739
2750
|
|
|
2751
|
+
function _docChatResultHasVisibleError(result) {
|
|
2752
|
+
if (!result) return false;
|
|
2753
|
+
if (result.errorClass) return true;
|
|
2754
|
+
if (typeof result.errorMessage === 'string' && result.errorMessage.trim()) return true;
|
|
2755
|
+
// stderr without a classified/runtime error is often CLI diagnostics; hard
|
|
2756
|
+
// failures with no answer still surface stderr through _docChatFailureResponse.
|
|
2757
|
+
return false;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function _docChatResultLooksSuccessful(result) {
|
|
2761
|
+
if (!result || !result.text) return false;
|
|
2762
|
+
return result.code === 0 || !_docChatResultHasVisibleError(result);
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2740
2765
|
// Build the doc-chat extraContext for a single ccCall pass — refreshed on retry
|
|
2741
2766
|
// so a fresh-session retry includes the full document instead of relying on the
|
|
2742
2767
|
// dead session's prior turn for context.
|
|
@@ -2813,7 +2838,7 @@ function _recoverPartialDocChatResponse(result, sessionKey) {
|
|
|
2813
2838
|
return {
|
|
2814
2839
|
...parsed,
|
|
2815
2840
|
partial: true,
|
|
2816
|
-
warning: _docChatPartialWarning(result.errorClass),
|
|
2841
|
+
warning: _docChatPartialWarning(result.errorClass, result.errorMessage || result.stderr || null),
|
|
2817
2842
|
toolUses: Array.isArray(result.toolUses) ? result.toolUses : [],
|
|
2818
2843
|
// Recovery path still attaches the raw runtime failure — the answer landed
|
|
2819
2844
|
// despite a non-zero exit; users still benefit from seeing why.
|
|
@@ -2821,6 +2846,41 @@ function _recoverPartialDocChatResponse(result, sessionKey) {
|
|
|
2821
2846
|
};
|
|
2822
2847
|
}
|
|
2823
2848
|
|
|
2849
|
+
function _shouldSuppressDocChatPostPatchError(ccError, finalize) {
|
|
2850
|
+
if (!finalize || finalize.edited !== true) return false;
|
|
2851
|
+
if (!ccError || ccError.errorClass !== 'unknown-model') return false;
|
|
2852
|
+
return String(ccError.runtime || '').toLowerCase() === 'copilot';
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function _buildDocChatResponsePayload({
|
|
2856
|
+
answer,
|
|
2857
|
+
actions,
|
|
2858
|
+
actionResults,
|
|
2859
|
+
actionParseError,
|
|
2860
|
+
ccError,
|
|
2861
|
+
partial,
|
|
2862
|
+
warning,
|
|
2863
|
+
toolUses,
|
|
2864
|
+
finalize,
|
|
2865
|
+
} = {}) {
|
|
2866
|
+
const final = finalize || { edited: false, content: null, answerSuffix: '' };
|
|
2867
|
+
const suppressPostPatchError = _shouldSuppressDocChatPostPatchError(ccError, final);
|
|
2868
|
+
const visibleAnswer = suppressPostPatchError && !partial ? 'Updated the document.' : answer;
|
|
2869
|
+
const finalAnswer = final.answerSuffix ? visibleAnswer + final.answerSuffix : visibleAnswer;
|
|
2870
|
+
return {
|
|
2871
|
+
ok: !(ccError && !suppressPostPatchError),
|
|
2872
|
+
answer: finalAnswer,
|
|
2873
|
+
actions,
|
|
2874
|
+
...(actionResults ? { actionResults } : {}),
|
|
2875
|
+
...(actionParseError ? { actionParseError } : {}),
|
|
2876
|
+
...(ccError && !suppressPostPatchError ? { error: ccError } : {}),
|
|
2877
|
+
...(partial && !suppressPostPatchError ? { partial: true, warning } : {}),
|
|
2878
|
+
...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
|
|
2879
|
+
edited: final.edited,
|
|
2880
|
+
...(final.edited && final.content !== null ? { content: final.content } : {}),
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2824
2884
|
|
|
2825
2885
|
// True when the file is a meeting JSON whose status forbids edits. Loaded
|
|
2826
2886
|
// fresh on each call because meeting status can change while a doc-chat is
|
|
@@ -2973,7 +3033,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2973
3033
|
// bleed into future interactions under the same key.
|
|
2974
3034
|
docSessions.delete(sessionKey);
|
|
2975
3035
|
schedulePersistDocSessions();
|
|
2976
|
-
} else if (result
|
|
3036
|
+
} else if (_docChatResultLooksSuccessful(result) && result.sessionId) {
|
|
2977
3037
|
// Store doc hash for next call's unchanged check
|
|
2978
3038
|
const session = resolveSession('doc', sessionKey);
|
|
2979
3039
|
if (session) session._docHash = initialPass.docHash;
|
|
@@ -2983,7 +3043,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2983
3043
|
return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
|
|
2984
3044
|
}
|
|
2985
3045
|
|
|
2986
|
-
if (
|
|
3046
|
+
if (!_docChatResultLooksSuccessful(result)) {
|
|
2987
3047
|
// Try to salvage a parseable answer / action / document edit before failing.
|
|
2988
3048
|
const recovered = _recoverPartialDocChatResponse(result, sessionKey);
|
|
2989
3049
|
if (recovered) return recovered;
|
|
@@ -3034,7 +3094,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
3034
3094
|
if (freshSession && sessionKey) {
|
|
3035
3095
|
docSessions.delete(sessionKey);
|
|
3036
3096
|
schedulePersistDocSessions();
|
|
3037
|
-
} else if (result
|
|
3097
|
+
} else if (_docChatResultLooksSuccessful(result) && result.sessionId) {
|
|
3038
3098
|
const session = resolveSession('doc', sessionKey);
|
|
3039
3099
|
if (session) session._docHash = initialPass.docHash;
|
|
3040
3100
|
}
|
|
@@ -3043,7 +3103,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
3043
3103
|
return { answer: result.text || result.stderr || 'Minions runtime is not installed or configured.', content: null, actions: [] };
|
|
3044
3104
|
}
|
|
3045
3105
|
|
|
3046
|
-
if (
|
|
3106
|
+
if (!_docChatResultLooksSuccessful(result)) {
|
|
3047
3107
|
const recovered = _recoverPartialDocChatResponse(result, sessionKey);
|
|
3048
3108
|
if (recovered) return recovered;
|
|
3049
3109
|
const sessionPreserved = !!(resolveSession('doc', sessionKey)?.sessionId);
|
|
@@ -5184,20 +5244,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5184
5244
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
5185
5245
|
originalContent: currentContent, delimiterContent: content,
|
|
5186
5246
|
});
|
|
5187
|
-
const
|
|
5188
|
-
|
|
5189
|
-
return jsonReply(res, 200, {
|
|
5190
|
-
ok: !ccError,
|
|
5191
|
-
answer: finalAnswer,
|
|
5192
|
-
actions,
|
|
5193
|
-
...(actionResults ? { actionResults } : {}),
|
|
5194
|
-
...(actionParseError ? { actionParseError } : {}),
|
|
5195
|
-
...(ccError ? { error: ccError } : {}),
|
|
5196
|
-
...(partial ? { partial: true, warning } : {}),
|
|
5197
|
-
...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
|
|
5198
|
-
edited: finalize.edited,
|
|
5199
|
-
...(finalize.edited && finalize.content !== null ? { content: finalize.content } : {}),
|
|
5247
|
+
const payload = _buildDocChatResponsePayload({
|
|
5248
|
+
answer, actions, actionResults, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
5200
5249
|
});
|
|
5250
|
+
_docDone = true;
|
|
5251
|
+
return jsonReply(res, 200, payload);
|
|
5201
5252
|
} finally { _docAbort = null; _docDone = true; docChatInFlight.delete(docKey); }
|
|
5202
5253
|
} catch (e) { return jsonReply(res, e.statusCode || 500, { error: e.message }); }
|
|
5203
5254
|
}
|
|
@@ -5286,18 +5337,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5286
5337
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
5287
5338
|
originalContent: currentContent, delimiterContent: content,
|
|
5288
5339
|
});
|
|
5289
|
-
const
|
|
5340
|
+
const payload = _buildDocChatResponsePayload({
|
|
5341
|
+
answer, actions, actionResults, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
5342
|
+
});
|
|
5343
|
+
const { answer: finalAnswer, ...donePayload } = payload;
|
|
5290
5344
|
writeDocEvent({
|
|
5291
5345
|
type: 'done',
|
|
5292
5346
|
text: finalAnswer,
|
|
5293
|
-
|
|
5294
|
-
...(actionResults ? { actionResults } : {}),
|
|
5295
|
-
...(actionParseError ? { actionParseError } : {}),
|
|
5296
|
-
...(ccError ? { error: ccError } : {}),
|
|
5297
|
-
...(partial ? { partial: true, warning } : {}),
|
|
5298
|
-
...(Array.isArray(toolUses) && toolUses.length ? { toolUses } : {}),
|
|
5299
|
-
edited: finalize.edited,
|
|
5300
|
-
...(finalize.edited && finalize.content !== null ? { content: finalize.content } : {}),
|
|
5347
|
+
...donePayload,
|
|
5301
5348
|
});
|
|
5302
5349
|
_docStreamEnded = true;
|
|
5303
5350
|
res.end();
|
|
@@ -7121,6 +7168,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7121
7168
|
if (!url) return jsonReply(res, 400, { error: 'url required' });
|
|
7122
7169
|
|
|
7123
7170
|
reloadConfig();
|
|
7171
|
+
const explicitProjectName = String(body.project || '').trim();
|
|
7172
|
+
if (explicitProjectName) {
|
|
7173
|
+
const projects = shared.getProjects(CONFIG);
|
|
7174
|
+
if (!findProjectByName(projects, explicitProjectName)) {
|
|
7175
|
+
return jsonReply(res, 400, { error: formatUnknownProjectError(explicitProjectName, projects) }, req);
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7124
7178
|
const adoTarget = parseAdoPrMetadataTarget(url);
|
|
7125
7179
|
let initialPrData = null;
|
|
7126
7180
|
if (adoTarget) {
|
|
@@ -7130,7 +7184,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7130
7184
|
shared.log('warn', `ADO PR link metadata fetch failed for ${url}: ${e.message}`);
|
|
7131
7185
|
}
|
|
7132
7186
|
}
|
|
7133
|
-
|
|
7187
|
+
let linkResult;
|
|
7188
|
+
try {
|
|
7189
|
+
linkResult = linkPullRequestForTracking(body, CONFIG, { metadata: initialPrData });
|
|
7190
|
+
} catch (e) {
|
|
7191
|
+
return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
|
|
7192
|
+
}
|
|
7193
|
+
const { id: prId, prPath, prNum, created, linked } = linkResult;
|
|
7134
7194
|
invalidateStatusCache();
|
|
7135
7195
|
jsonReply(res, 200, { ok: true, id: prId, created, linked });
|
|
7136
7196
|
|
|
@@ -7268,6 +7328,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7268
7328
|
{ method: 'POST', path: /^\/api\/agent\/([\w-]+)\/kill$/, desc: 'Kill a running agent: stop process, clear dispatch, reset work items to pending', handler: handleAgentKill },
|
|
7269
7329
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-stream(?:\?.*)?$/, desc: 'SSE real-time live output streaming', handler: handleAgentLiveStream },
|
|
7270
7330
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live(?:\?.*)?$/, desc: 'Tail live output for a working agent', params: 'tail? (bytes, default 8192)', handler: handleAgentLive },
|
|
7331
|
+
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-output(?:\?.*)?$/, desc: 'Tail live output for a working agent (alias for /live)', params: 'tail? (bytes, default 8192)', handler: handleAgentLive },
|
|
7271
7332
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/output(?:\?.*)?$/, desc: 'Fetch final output.log for an agent', handler: handleAgentOutput },
|
|
7272
7333
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)$/, desc: 'Get detailed agent info', handler: handleAgentDetail },
|
|
7273
7334
|
{ method: 'GET', path: /^\/api\/dispatch\/([\w.-]+)\/completion-report$/, desc: 'Read structured completion report for a dispatch', handler: (req, res, match) => {
|
|
@@ -7722,6 +7783,10 @@ module.exports = {
|
|
|
7722
7783
|
_docChatPartialWarning,
|
|
7723
7784
|
_docChatFailureResponse,
|
|
7724
7785
|
_recoverPartialDocChatResponse,
|
|
7786
|
+
_docChatResultHasVisibleError,
|
|
7787
|
+
_docChatResultLooksSuccessful,
|
|
7788
|
+
_shouldSuppressDocChatPostPatchError,
|
|
7789
|
+
_buildDocChatResponsePayload,
|
|
7725
7790
|
_linkPullRequestForTracking: linkPullRequestForTracking,
|
|
7726
7791
|
_resolveSkillReadPath,
|
|
7727
7792
|
DOC_CHAT_DOCUMENT_DELIMITER,
|
|
@@ -634,25 +634,77 @@ function parseStreamChunk(line) {
|
|
|
634
634
|
|
|
635
635
|
// ── Error Normalization ─────────────────────────────────────────────────────
|
|
636
636
|
|
|
637
|
-
function
|
|
637
|
+
function _collectErrorSignal(rawOutput) {
|
|
638
638
|
const text = rawOutput == null ? '' : String(rawOutput);
|
|
639
|
+
if (!text) return '';
|
|
640
|
+
|
|
641
|
+
const signals = [];
|
|
642
|
+
let sawJsonLine = false;
|
|
643
|
+
for (const rawLine of text.split('\n')) {
|
|
644
|
+
const line = rawLine.trim();
|
|
645
|
+
if (!line) continue;
|
|
646
|
+
if (!line.startsWith('{')) {
|
|
647
|
+
signals.push(line);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
let obj;
|
|
652
|
+
try { obj = JSON.parse(line); } catch { continue; }
|
|
653
|
+
if (!obj || typeof obj !== 'object') continue;
|
|
654
|
+
sawJsonLine = true;
|
|
655
|
+
|
|
656
|
+
const type = String(obj.type || '');
|
|
657
|
+
const subtype = String(obj.subtype || '');
|
|
658
|
+
const eventType = String(obj.event?.type || '');
|
|
659
|
+
const isErrorEvent = type === 'error'
|
|
660
|
+
|| eventType === 'error'
|
|
661
|
+
|| subtype.startsWith('error')
|
|
662
|
+
|| obj.is_error === true
|
|
663
|
+
|| obj.error != null
|
|
664
|
+
|| obj.data?.error != null;
|
|
665
|
+
if (!isErrorEvent) continue;
|
|
666
|
+
|
|
667
|
+
for (const value of [
|
|
668
|
+
obj.error,
|
|
669
|
+
obj.message,
|
|
670
|
+
obj.stderr,
|
|
671
|
+
obj.result,
|
|
672
|
+
obj.data?.error,
|
|
673
|
+
obj.data?.message,
|
|
674
|
+
obj.data?.stderr,
|
|
675
|
+
obj.event?.error,
|
|
676
|
+
obj.event?.message,
|
|
677
|
+
subtype,
|
|
678
|
+
]) {
|
|
679
|
+
if (typeof value === 'string' && value.trim()) signals.push(value.trim());
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (signals.length > 0) return signals.join('\n');
|
|
684
|
+
return sawJsonLine ? '' : text;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function parseError(rawOutput) {
|
|
688
|
+
const text = _collectErrorSignal(rawOutput);
|
|
639
689
|
if (!text) return { message: '', code: null, retriable: true };
|
|
640
690
|
const lower = text.toLowerCase();
|
|
641
691
|
|
|
642
|
-
|
|
643
|
-
|
|
692
|
+
const hasExplicitAuthFailure = /not authenticated|copilot login|please.*log.*in|\bunauthorized\b/i.test(text);
|
|
693
|
+
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);
|
|
694
|
+
if (hasExplicitAuthFailure || hasAuthStatusCode) {
|
|
695
|
+
return { message: text, code: 'auth-failure', retriable: false };
|
|
644
696
|
}
|
|
645
697
|
if (/rate limit|too many requests|\b429\b/i.test(text)) {
|
|
646
|
-
return { message:
|
|
698
|
+
return { message: text, code: 'rate-limit', retriable: true };
|
|
647
699
|
}
|
|
648
700
|
if (/unknown model|model not found|model.*invalid|invalid model/i.test(text)) {
|
|
649
|
-
return { message:
|
|
701
|
+
return { message: text, code: 'unknown-model', retriable: false };
|
|
650
702
|
}
|
|
651
703
|
if (/budget.*exceed|premium.*limit.*reach|quota.*exceed/i.test(lower)) {
|
|
652
|
-
return { message:
|
|
704
|
+
return { message: text, code: 'budget-exceeded', retriable: false };
|
|
653
705
|
}
|
|
654
706
|
if (/internal error|panic|uncaught|copilot.*crashed|fatal: copilot/i.test(lower)) {
|
|
655
|
-
return { message:
|
|
707
|
+
return { message: text, code: 'crash', retriable: true };
|
|
656
708
|
}
|
|
657
709
|
return { message: '', code: null, retriable: true };
|
|
658
710
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1774",
|
|
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"
|