@yemi33/minions 0.1.1932 → 0.1.1934
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/bin/minions.js +33 -4
- package/dashboard.js +31 -0
- package/engine/cc-worker-pool.js +9 -0
- package/package.json +1 -1
- package/engine/copilot-models.json +0 -5
package/bin/minions.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
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
|
|
11
|
+
* minions update [--no-wait] Update to latest version (--no-wait backgrounds the post-update restart)
|
|
12
12
|
* minions version Show installed and package versions
|
|
13
13
|
* minions doctor Check prerequisites and runtime health
|
|
14
14
|
* minions restart [--open] Start engine + dashboard (--open forces a new browser tab)
|
|
@@ -604,6 +604,28 @@ function runPostUpdateRestart() {
|
|
|
604
604
|
process.exit(1);
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
+
// Fire-and-forget variant for `minions update --no-wait`. The npm + init phases
|
|
608
|
+
// have already completed synchronously, so package-install failures surface
|
|
609
|
+
// normally; only the long-tail engine/dashboard restart + health check is
|
|
610
|
+
// backgrounded. stdio is redirected to a log so the user can postmortem if the
|
|
611
|
+
// restart fails after the parent has exited.
|
|
612
|
+
function runPostUpdateRestartDetached() {
|
|
613
|
+
const initScript = path.join(PKG_ROOT, 'bin', 'minions.js');
|
|
614
|
+
const out = _openStdioLog('update-restart-stdio.log');
|
|
615
|
+
const err = _openStdioLog('update-restart-stdio.log');
|
|
616
|
+
const proc = spawn(process.execPath, [initScript, 'restart'], {
|
|
617
|
+
cwd: process.cwd(),
|
|
618
|
+
env: { ...process.env, MINIONS_HOME },
|
|
619
|
+
stdio: ['ignore', out, err],
|
|
620
|
+
detached: true,
|
|
621
|
+
windowsHide: true,
|
|
622
|
+
});
|
|
623
|
+
proc.unref();
|
|
624
|
+
const logPath = path.join(MINIONS_HOME, 'engine', 'update-restart-stdio.log');
|
|
625
|
+
console.log(` Restart spawned in background (PID: ${proc.pid}); logs: ${logPath}`);
|
|
626
|
+
console.log(` Verify with: minions status\n`);
|
|
627
|
+
}
|
|
628
|
+
|
|
607
629
|
// ─── Version command ────────────────────────────────────────────────────────
|
|
608
630
|
|
|
609
631
|
function showVersion() {
|
|
@@ -668,7 +690,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
668
690
|
|
|
669
691
|
Setup:
|
|
670
692
|
minions init Bootstrap ~/.minions/ (first time)
|
|
671
|
-
minions update
|
|
693
|
+
minions update [--no-wait] Update to latest version (--no-wait backgrounds the restart)
|
|
672
694
|
minions version Show installed vs package version
|
|
673
695
|
minions doctor Check prerequisites and runtime health
|
|
674
696
|
minions add <project-dir> Link a project (interactive)
|
|
@@ -707,6 +729,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
707
729
|
} else if (cmd === 'init') {
|
|
708
730
|
init();
|
|
709
731
|
} else if (cmd === 'update') {
|
|
732
|
+
const noWait = rest.includes('--no-wait') || rest.includes('--detach');
|
|
710
733
|
console.log('\n Updating Minions...\n');
|
|
711
734
|
// Dev/symlink installs: PKG_ROOT === MINIONS_HOME — npm update is a no-op (symlink already
|
|
712
735
|
// points to the repo), and `minions init --force` would fail (cwd/.minions is inside PKG_ROOT).
|
|
@@ -726,9 +749,15 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
726
749
|
// the global shim while npm is still settling the updated install.
|
|
727
750
|
runPostUpdateInit();
|
|
728
751
|
}
|
|
729
|
-
// Restart engine + dashboard so they pick up the new code
|
|
752
|
+
// Restart engine + dashboard so they pick up the new code. With --no-wait the
|
|
753
|
+
// restart is spawned detached so the caller (e.g. a DevBox maintenance task)
|
|
754
|
+
// can return immediately without holding the parent open for the health poll.
|
|
730
755
|
console.log('\n Restarting engine and dashboard...\n');
|
|
731
|
-
|
|
756
|
+
if (noWait) {
|
|
757
|
+
runPostUpdateRestartDetached();
|
|
758
|
+
} else {
|
|
759
|
+
runPostUpdateRestart();
|
|
760
|
+
}
|
|
732
761
|
} else if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
733
762
|
showVersion();
|
|
734
763
|
} else if (cmd === 'add' || cmd === 'remove' || cmd === 'list' || cmd === 'scan') {
|
package/dashboard.js
CHANGED
|
@@ -6368,7 +6368,28 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6368
6368
|
let accumulated = '';
|
|
6369
6369
|
let sessionHandle = null;
|
|
6370
6370
|
let resolveResult;
|
|
6371
|
+
// Per-turn phase timing — emitted as one structured [cc-timing] log line
|
|
6372
|
+
// so we can attribute Copilot CC slowness (e.g. 36s) to spawn vs first-byte
|
|
6373
|
+
// vs streaming vs trailing inference. Parallels engine.js's [spawn-timing].
|
|
6374
|
+
const _tStart = Date.now();
|
|
6375
|
+
let _tFirstByte = null;
|
|
6376
|
+
let _tFirstTool = null;
|
|
6377
|
+
let _chunkCount = 0;
|
|
6378
|
+
let _toolUseCount = 0;
|
|
6371
6379
|
const promise = new Promise((resolve) => { resolveResult = resolve; });
|
|
6380
|
+
const _emitTimingLog = (lifecycle, sessionReady, streamEnd, outcome) => {
|
|
6381
|
+
try {
|
|
6382
|
+
const _diff = (a, b) => (a != null && b != null) ? (b - a) : null;
|
|
6383
|
+
const timings = {
|
|
6384
|
+
get_session: _diff(_tStart, sessionReady),
|
|
6385
|
+
first_byte: _diff(sessionReady, _tFirstByte),
|
|
6386
|
+
first_tool: _diff(sessionReady, _tFirstTool),
|
|
6387
|
+
stream: _diff(sessionReady, streamEnd),
|
|
6388
|
+
total: _diff(_tStart, streamEnd),
|
|
6389
|
+
};
|
|
6390
|
+
shared.log('info', `[cc-timing] tab=${resolvedTabId} lifecycle=${lifecycle || 'unknown'} outcome=${outcome} chunks=${_chunkCount} tools=${_toolUseCount} ${JSON.stringify(timings)}`);
|
|
6391
|
+
} catch { /* telemetry is best-effort */ }
|
|
6392
|
+
};
|
|
6372
6393
|
promise.abort = () => {
|
|
6373
6394
|
cancelled = true;
|
|
6374
6395
|
try { sessionHandle && sessionHandle.cancel(); } catch { /* swallow */ }
|
|
@@ -6383,6 +6404,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6383
6404
|
systemPromptHash: _ccPromptHash,
|
|
6384
6405
|
});
|
|
6385
6406
|
} catch (err) {
|
|
6407
|
+
_emitTimingLog(null, null, Date.now(), 'spawn-failed');
|
|
6386
6408
|
return resolveResult({
|
|
6387
6409
|
text: '',
|
|
6388
6410
|
sessionId: null,
|
|
@@ -6392,19 +6414,26 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6392
6414
|
stderr: String((err && err.message) || err || 'cc-worker-pool spawn failed'),
|
|
6393
6415
|
});
|
|
6394
6416
|
}
|
|
6417
|
+
const _tSessionReady = Date.now();
|
|
6418
|
+
const _lifecycle = sessionHandle.lifecycle || 'unknown';
|
|
6395
6419
|
if (cancelled) {
|
|
6396
6420
|
try { sessionHandle.cancel(); } catch { /* swallow */ }
|
|
6421
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'cancelled-pre-stream');
|
|
6397
6422
|
return resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
6398
6423
|
}
|
|
6399
6424
|
await sessionHandle.stream(prompt, {
|
|
6400
6425
|
systemPromptText: systemPrompt,
|
|
6401
6426
|
onChunk: (delta) => {
|
|
6427
|
+
if (_tFirstByte == null) _tFirstByte = Date.now();
|
|
6428
|
+
_chunkCount += 1;
|
|
6402
6429
|
accumulated += delta;
|
|
6403
6430
|
_touchCcLiveStream(liveState);
|
|
6404
6431
|
liveState.text = accumulated;
|
|
6405
6432
|
if (liveState.writer) liveState.writer({ type: 'chunk', text: accumulated });
|
|
6406
6433
|
},
|
|
6407
6434
|
onToolUse: (name, input) => {
|
|
6435
|
+
if (_tFirstTool == null) _tFirstTool = Date.now();
|
|
6436
|
+
_toolUseCount += 1;
|
|
6408
6437
|
_touchCcLiveStream(liveState);
|
|
6409
6438
|
const safeInput = input || {};
|
|
6410
6439
|
if (Array.isArray(toolUses)) toolUses.push({ name, input: safeInput });
|
|
@@ -6412,9 +6441,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6412
6441
|
if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(safeInput) });
|
|
6413
6442
|
},
|
|
6414
6443
|
onDone: () => {
|
|
6444
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'done');
|
|
6415
6445
|
resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
6416
6446
|
},
|
|
6417
6447
|
onError: (err) => {
|
|
6448
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), cancelled ? 'cancelled' : 'error');
|
|
6418
6449
|
resolveResult({
|
|
6419
6450
|
text: accumulated,
|
|
6420
6451
|
sessionId: sessionHandle.sessionId,
|
package/engine/cc-worker-pool.js
CHANGED
|
@@ -483,6 +483,12 @@ async function getSession({ tabId, model, effort, mcpServers, systemPromptHash,
|
|
|
483
483
|
if (!tabId) throw new Error('cc-worker-pool.getSession: tabId is required');
|
|
484
484
|
const mcpServersHash = _hashMcpServers(mcpServers);
|
|
485
485
|
let worker = _tabs.get(tabId);
|
|
486
|
+
// Track which lifecycle path we took so the dashboard's [cc-timing] log can
|
|
487
|
+
// attribute warm-reuse vs new-session vs cold-spawn. One of:
|
|
488
|
+
// 'warm-reuse' — proc + session both reused (no spawn, no session/new)
|
|
489
|
+
// 'new-session' — proc reused, fresh session/new (sysprompt hash changed)
|
|
490
|
+
// 'cold-spawn' — fresh proc + initialize + session/new
|
|
491
|
+
let lifecycle = 'warm-reuse';
|
|
486
492
|
|
|
487
493
|
if (worker) {
|
|
488
494
|
if (worker.killed) {
|
|
@@ -501,6 +507,7 @@ async function getSession({ tabId, model, effort, mcpServers, systemPromptHash,
|
|
|
501
507
|
// caller has rotated in (Bug B / issue #2479).
|
|
502
508
|
await worker.newSession({ mcpServers, systemPromptHash, model, effort });
|
|
503
509
|
worker.lastUsedAt = _internals.now();
|
|
510
|
+
lifecycle = 'new-session';
|
|
504
511
|
} else {
|
|
505
512
|
// Warm reuse — only update bookkeeping. model/effort changes on a
|
|
506
513
|
// warm session are tracked here but not pushed via configOptions in
|
|
@@ -524,12 +531,14 @@ async function getSession({ tabId, model, effort, mcpServers, systemPromptHash,
|
|
|
524
531
|
try { worker.close(); } catch { /* already torn down */ }
|
|
525
532
|
throw err;
|
|
526
533
|
}
|
|
534
|
+
lifecycle = 'cold-spawn';
|
|
527
535
|
}
|
|
528
536
|
|
|
529
537
|
_ensureReaper();
|
|
530
538
|
|
|
531
539
|
return {
|
|
532
540
|
sessionId: worker.sessionId,
|
|
541
|
+
lifecycle,
|
|
533
542
|
stream: (promptText, opts) => worker.stream(promptText, opts),
|
|
534
543
|
cancel: () => worker.cancel(),
|
|
535
544
|
close: () => closeTab(tabId),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1934",
|
|
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"
|