@yemi33/minions 0.1.1771 → 0.1.1773
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 +90 -18
- package/docs/copilot-cli-schema.md +1 -0
- package/engine/copilot-models.json +1 -1
- package/engine/runtimes/claude.js +2 -0
- package/engine/runtimes/copilot.js +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1773 (2026-05-07)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- fix doc chat sticky scroll (#2176)
|
|
7
|
+
- fix live output route and CLI docs (#2172)
|
|
8
|
+
- make API project resolution strict (#2169)
|
|
9
|
+
- fix copilot cc resume context (#2166)
|
|
10
|
+
|
|
3
11
|
## 0.1.1771 (2026-05-07)
|
|
4
12
|
|
|
5
13
|
### Features
|
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
|
@@ -10,6 +10,7 @@ const zlib = require('zlib');
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const llm = require('./engine/llm');
|
|
13
|
+
const { resolveRuntime } = require('./engine/runtimes');
|
|
13
14
|
|
|
14
15
|
// Dashboard version stamp — captured at module load so it reflects the code actually running
|
|
15
16
|
const _dashboardVersion = {
|
|
@@ -280,12 +281,23 @@ function createWorkItemWithDedup(wiPath, item, options = {}) {
|
|
|
280
281
|
return result || { created: false, item: null };
|
|
281
282
|
}
|
|
282
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
|
+
|
|
283
295
|
function resolveWorkItemsCreateTarget(projectName, projects = PROJECTS) {
|
|
284
296
|
const project = String(projectName || '').trim();
|
|
285
297
|
let targetProject = null;
|
|
286
298
|
if (project) {
|
|
287
|
-
targetProject = projects
|
|
288
|
-
if (!targetProject) return { error:
|
|
299
|
+
targetProject = findProjectByName(projects, project);
|
|
300
|
+
if (!targetProject) return { error: formatUnknownProjectError(project, projects) };
|
|
289
301
|
} else if (projects.length === 1) {
|
|
290
302
|
targetProject = projects[0];
|
|
291
303
|
}
|
|
@@ -325,7 +337,13 @@ function linkPullRequestForTracking({ url, title, project: projectName, autoObse
|
|
|
325
337
|
throw err;
|
|
326
338
|
}
|
|
327
339
|
const projects = shared.getProjects(config);
|
|
328
|
-
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
|
+
}
|
|
329
347
|
const prPath = targetProject ? shared.projectPrPath(targetProject) : path.join(MINIONS_DIR, 'pull-requests.json');
|
|
330
348
|
|
|
331
349
|
const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
|
|
@@ -1262,9 +1280,14 @@ function _readCcTabSessions({ prune = true } = {}) {
|
|
|
1262
1280
|
const CC_CARRYOVER_MAX_TURNS = 20;
|
|
1263
1281
|
const CC_CARRYOVER_PER_MSG_CHARS = 2000;
|
|
1264
1282
|
|
|
1265
|
-
function _buildTranscriptCarryover(transcript, { previousRuntime } = {}) {
|
|
1283
|
+
function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage } = {}) {
|
|
1266
1284
|
if (!Array.isArray(transcript) || transcript.length === 0) return '';
|
|
1267
|
-
|
|
1285
|
+
let filtered = transcript.filter(m => m && (m.role === 'user' || m.role === 'assistant') && typeof m.text === 'string' && m.text.trim());
|
|
1286
|
+
const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
|
|
1287
|
+
if (current && filtered.length > 0) {
|
|
1288
|
+
const last = filtered[filtered.length - 1];
|
|
1289
|
+
if (last.role === 'user' && last.text.trim() === current) filtered = filtered.slice(0, -1);
|
|
1290
|
+
}
|
|
1268
1291
|
if (filtered.length === 0) return '';
|
|
1269
1292
|
const recent = filtered.slice(-CC_CARRYOVER_MAX_TURNS);
|
|
1270
1293
|
const truncated = filtered.length > recent.length;
|
|
@@ -1281,6 +1304,19 @@ function _buildTranscriptCarryover(transcript, { previousRuntime } = {}) {
|
|
|
1281
1304
|
return `${header}\n\n${truncationNote}${lines.join('\n\n')}\n\n--- Current message follows ---`;
|
|
1282
1305
|
}
|
|
1283
1306
|
|
|
1307
|
+
function _ccRuntimeNeedsResumeCarryover(runtimeName) {
|
|
1308
|
+
try {
|
|
1309
|
+
const runtime = resolveRuntime(runtimeName);
|
|
1310
|
+
return !!runtime?.capabilities?.resumePromptCarryover;
|
|
1311
|
+
} catch {
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function _joinCcPromptParts(...parts) {
|
|
1317
|
+
return parts.filter(Boolean).join('\n\n---\n\n');
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1284
1320
|
// Load persisted CC session on startup. CC chat sessions are non-expiring;
|
|
1285
1321
|
// only restore-time validity checks here are sessionId presence (anything
|
|
1286
1322
|
// else would auto-expire the user's chat without their consent).
|
|
@@ -2415,7 +2451,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
|
|
|
2415
2451
|
* @param {number} opts.maxTurns - Max tool-use turns
|
|
2416
2452
|
* @param {string} opts.allowedTools - Comma-separated tool list
|
|
2417
2453
|
*/
|
|
2418
|
-
async function ccCall(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, systemPrompt = CC_STATIC_SYSTEM_PROMPT } = {}) {
|
|
2454
|
+
async function ccCall(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript } = {}) {
|
|
2419
2455
|
if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
|
|
2420
2456
|
if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
|
|
2421
2457
|
const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
|
|
@@ -2428,10 +2464,15 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2428
2464
|
|
|
2429
2465
|
const existing = resolveSession(store, sessionKey);
|
|
2430
2466
|
let sessionId = existing ? existing.sessionId : null;
|
|
2467
|
+
const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
|
|
2431
2468
|
|
|
2432
|
-
function buildPrompt({ includePreamble = true } = {}) {
|
|
2469
|
+
function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
|
|
2433
2470
|
const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
|
|
2434
2471
|
if (extraContext) parts.push(extraContext);
|
|
2472
|
+
if (includeCarryover) {
|
|
2473
|
+
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
|
|
2474
|
+
if (carryover) parts.push(carryover);
|
|
2475
|
+
}
|
|
2435
2476
|
parts.push(message);
|
|
2436
2477
|
return parts.join('\n\n---\n\n');
|
|
2437
2478
|
}
|
|
@@ -2440,7 +2481,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2440
2481
|
|
|
2441
2482
|
// Attempt 1: resume existing session — skip preamble (session already has context)
|
|
2442
2483
|
if (sessionId && maxTurns > 1) {
|
|
2443
|
-
const p1 = llm.callLLM(buildPrompt({ includePreamble: false }), '', {
|
|
2484
|
+
const p1 = llm.callLLM(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
|
|
2444
2485
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
2445
2486
|
engineConfig: CONFIG.engine,
|
|
2446
2487
|
});
|
|
@@ -2477,7 +2518,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2477
2518
|
}
|
|
2478
2519
|
|
|
2479
2520
|
// Attempt 2: fresh session (include preamble for full context)
|
|
2480
|
-
const freshPrompt = buildPrompt();
|
|
2521
|
+
const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
|
|
2481
2522
|
const p2 = llm.callLLM(freshPrompt, systemPrompt, {
|
|
2482
2523
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
2483
2524
|
engineConfig: CONFIG.engine,
|
|
@@ -2511,7 +2552,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2511
2552
|
return result;
|
|
2512
2553
|
}
|
|
2513
2554
|
|
|
2514
|
-
async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT } = {}) {
|
|
2555
|
+
async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript } = {}) {
|
|
2515
2556
|
if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
|
|
2516
2557
|
if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
|
|
2517
2558
|
const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
|
|
@@ -2524,10 +2565,15 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2524
2565
|
|
|
2525
2566
|
const existing = resolveSession(store, sessionKey);
|
|
2526
2567
|
let sessionId = existing ? existing.sessionId : null;
|
|
2568
|
+
const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
|
|
2527
2569
|
|
|
2528
|
-
function buildPrompt({ includePreamble = true } = {}) {
|
|
2570
|
+
function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
|
|
2529
2571
|
const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
|
|
2530
2572
|
if (extraContext) parts.push(extraContext);
|
|
2573
|
+
if (includeCarryover) {
|
|
2574
|
+
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
|
|
2575
|
+
if (carryover) parts.push(carryover);
|
|
2576
|
+
}
|
|
2531
2577
|
parts.push(message);
|
|
2532
2578
|
return parts.join('\n\n---\n\n');
|
|
2533
2579
|
}
|
|
@@ -2535,7 +2581,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2535
2581
|
let result;
|
|
2536
2582
|
|
|
2537
2583
|
if (sessionId && maxTurns > 1) {
|
|
2538
|
-
const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false }), '', {
|
|
2584
|
+
const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
|
|
2539
2585
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
2540
2586
|
engineConfig: CONFIG.engine,
|
|
2541
2587
|
onChunk,
|
|
@@ -2572,7 +2618,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2572
2618
|
}
|
|
2573
2619
|
|
|
2574
2620
|
if (onRetry) onRetry(2);
|
|
2575
|
-
const freshPrompt = buildPrompt();
|
|
2621
|
+
const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
|
|
2576
2622
|
const p2 = llm.callLLMStreaming(freshPrompt, systemPrompt, {
|
|
2577
2623
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
2578
2624
|
engineConfig: CONFIG.engine,
|
|
@@ -5754,7 +5800,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5754
5800
|
}
|
|
5755
5801
|
const wasResume = !!(body.sessionId && body.sessionId === ccSession.sessionId && ccSessionValid());
|
|
5756
5802
|
|
|
5757
|
-
const result = await ccCall(body.message, { store: 'cc' });
|
|
5803
|
+
const result = await ccCall(body.message, { store: 'cc', transcript: body.transcript });
|
|
5758
5804
|
|
|
5759
5805
|
// Non-zero exit with text = max_turns or partial success — still usable
|
|
5760
5806
|
if (!result.text) {
|
|
@@ -5989,13 +6035,21 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5989
6035
|
sessionReset = true;
|
|
5990
6036
|
sessionResetReason = 'runtimeChanged';
|
|
5991
6037
|
previousRuntime = tabEntry.runtime;
|
|
6038
|
+
} else if (tabEntry.sessionId && tabEntry.sessionId !== tabSessionId) {
|
|
6039
|
+
tabSessionId = tabEntry.sessionId;
|
|
5992
6040
|
}
|
|
5993
6041
|
}
|
|
5994
6042
|
const wasResume = !!tabSessionId;
|
|
5995
6043
|
const sessionId = tabSessionId || null;
|
|
6044
|
+
const resumeNeedsCarryover = wasResume && _ccRuntimeNeedsResumeCarryover(currentRuntime);
|
|
5996
6045
|
const preamble = wasResume ? '' : buildCCStatePreamble();
|
|
5997
|
-
const carryover = sessionReset
|
|
5998
|
-
|
|
6046
|
+
const carryover = (sessionReset || resumeNeedsCarryover)
|
|
6047
|
+
? _buildTranscriptCarryover(body.transcript, {
|
|
6048
|
+
previousRuntime: sessionReset ? previousRuntime : null,
|
|
6049
|
+
currentMessage: body.message,
|
|
6050
|
+
})
|
|
6051
|
+
: '';
|
|
6052
|
+
const prompt = _joinCcPromptParts(preamble, carryover, body.message);
|
|
5999
6053
|
|
|
6000
6054
|
const { trackEngineUsage: trackUsage } = require('./engine/llm');
|
|
6001
6055
|
const streamModel = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
|
|
@@ -6023,7 +6077,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6023
6077
|
// Resume failed (stale/expired session) — auto-retry as fresh session (skip if client already disconnected)
|
|
6024
6078
|
console.log(`[CC-stream] Resume failed (code=${result.code}) — retrying fresh`);
|
|
6025
6079
|
const freshPreamble = buildCCStatePreamble();
|
|
6026
|
-
const
|
|
6080
|
+
const freshCarryover = _buildTranscriptCarryover(body.transcript, { currentMessage: body.message });
|
|
6081
|
+
const freshPrompt = _joinCcPromptParts(freshPreamble, freshCarryover, body.message);
|
|
6027
6082
|
toolUses = []; // discard stale metadata from the failed resume attempt
|
|
6028
6083
|
const retryPromise = _invokeCcStream({
|
|
6029
6084
|
prompt: freshPrompt, sessionId: undefined, liveState, toolUses,
|
|
@@ -7083,6 +7138,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7083
7138
|
if (!url) return jsonReply(res, 400, { error: 'url required' });
|
|
7084
7139
|
|
|
7085
7140
|
reloadConfig();
|
|
7141
|
+
const explicitProjectName = String(body.project || '').trim();
|
|
7142
|
+
if (explicitProjectName) {
|
|
7143
|
+
const projects = shared.getProjects(CONFIG);
|
|
7144
|
+
if (!findProjectByName(projects, explicitProjectName)) {
|
|
7145
|
+
return jsonReply(res, 400, { error: formatUnknownProjectError(explicitProjectName, projects) }, req);
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7086
7148
|
const adoTarget = parseAdoPrMetadataTarget(url);
|
|
7087
7149
|
let initialPrData = null;
|
|
7088
7150
|
if (adoTarget) {
|
|
@@ -7092,7 +7154,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7092
7154
|
shared.log('warn', `ADO PR link metadata fetch failed for ${url}: ${e.message}`);
|
|
7093
7155
|
}
|
|
7094
7156
|
}
|
|
7095
|
-
|
|
7157
|
+
let linkResult;
|
|
7158
|
+
try {
|
|
7159
|
+
linkResult = linkPullRequestForTracking(body, CONFIG, { metadata: initialPrData });
|
|
7160
|
+
} catch (e) {
|
|
7161
|
+
return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
|
|
7162
|
+
}
|
|
7163
|
+
const { id: prId, prPath, prNum, created, linked } = linkResult;
|
|
7096
7164
|
invalidateStatusCache();
|
|
7097
7165
|
jsonReply(res, 200, { ok: true, id: prId, created, linked });
|
|
7098
7166
|
|
|
@@ -7230,6 +7298,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7230
7298
|
{ method: 'POST', path: /^\/api\/agent\/([\w-]+)\/kill$/, desc: 'Kill a running agent: stop process, clear dispatch, reset work items to pending', handler: handleAgentKill },
|
|
7231
7299
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-stream(?:\?.*)?$/, desc: 'SSE real-time live output streaming', handler: handleAgentLiveStream },
|
|
7232
7300
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live(?:\?.*)?$/, desc: 'Tail live output for a working agent', params: 'tail? (bytes, default 8192)', handler: handleAgentLive },
|
|
7301
|
+
{ 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 },
|
|
7233
7302
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/output(?:\?.*)?$/, desc: 'Fetch final output.log for an agent', handler: handleAgentOutput },
|
|
7234
7303
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)$/, desc: 'Get detailed agent info', handler: handleAgentDetail },
|
|
7235
7304
|
{ method: 'GET', path: /^\/api\/dispatch\/([\w.-]+)\/completion-report$/, desc: 'Read structured completion report for a dispatch', handler: (req, res, match) => {
|
|
@@ -7695,6 +7764,9 @@ module.exports = {
|
|
|
7695
7764
|
_createPipelineFromAction: createPipelineFromAction,
|
|
7696
7765
|
executeCCActions,
|
|
7697
7766
|
buildCCStatePreamble,
|
|
7767
|
+
_buildTranscriptCarryover,
|
|
7768
|
+
_ccRuntimeNeedsResumeCarryover,
|
|
7769
|
+
_joinCcPromptParts,
|
|
7698
7770
|
_captureApiRoutesMeta,
|
|
7699
7771
|
_formatCcApiRoutesIndex,
|
|
7700
7772
|
_formatCcCliCommandsIndex,
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
| `capabilities.modelDiscovery` | **`true`** | `GET https://api.githubcopilot.com/models` with a `gh auth token` Bearer returns HTTP 200 + a 24-model JSON catalog. |
|
|
18
18
|
| `capabilities.streaming` | **`true`** | `--stream on` (default) emits `assistant.message_delta` events incrementally; `--stream off` suppresses deltas but the final `assistant.message` always arrives. |
|
|
19
19
|
| `capabilities.sessionResume` | **`true`** | `--resume <session-id>` documented, and every `result` event emits `sessionId`. |
|
|
20
|
+
| `capabilities.resumePromptCarryover` | **`true`** | Command Center resume turns should prepend the browser's recent Q&A transcript because Copilot's session store is opaque to Minions and can resume without enough conversational context. |
|
|
20
21
|
| `capabilities.systemPromptFile` | **`false`** | No `--system-prompt-file` flag exists. Inject system prompt via a `<system>` block prepended to stdin. |
|
|
21
22
|
| `capabilities.effortLevels` | **`true`** | `--effort` accepts `low|medium|high|xhigh` (no `max`). Adapter must map `'max' → 'xhigh'`. |
|
|
22
23
|
| `capabilities.costTracking` | **`false`** | `result.usage` contains `premiumRequests` (count, not USD), no token counts, no cost. |
|
|
@@ -717,6 +717,8 @@ const capabilities = {
|
|
|
717
717
|
fallbackModel: true,
|
|
718
718
|
// Engine controls session persistence (writes session.json on completion)
|
|
719
719
|
sessionPersistenceControl: true,
|
|
720
|
+
// Claude resume reliably restores prior turns; do not duplicate browser transcript.
|
|
721
|
+
resumePromptCarryover: false,
|
|
720
722
|
// Adapter implements createStreamConsumer(ctx) — required by llm.js accumulator
|
|
721
723
|
streamConsumer: true,
|
|
722
724
|
};
|
|
@@ -874,6 +874,9 @@ const capabilities = {
|
|
|
874
874
|
fallbackModel: false,
|
|
875
875
|
// Copilot manages session state internally in ~/.copilot/session-state/
|
|
876
876
|
sessionPersistenceControl: false,
|
|
877
|
+
// CC resumes should include recent visible Q&A in stdin because Minions cannot
|
|
878
|
+
// inspect or repair Copilot's opaque session-state store when it drops context.
|
|
879
|
+
resumePromptCarryover: true,
|
|
877
880
|
// Adapter implements createStreamConsumer(ctx) — required by llm.js accumulator
|
|
878
881
|
streamConsumer: true,
|
|
879
882
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1773",
|
|
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"
|