@yemi33/minions 0.1.1931 → 0.1.1933
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/dashboard.js +56 -10
- package/engine/cc-worker-pool.js +65 -4
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -2480,8 +2480,10 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
|
|
|
2480
2480
|
* contract SSE consumers depend on).
|
|
2481
2481
|
* - `usage` is `{}` because ACP `session/update` notifications don't
|
|
2482
2482
|
* surface token counts; trackEngineUsage is a no-op on `{}`.
|
|
2483
|
-
* - Tool calls are
|
|
2484
|
-
*
|
|
2483
|
+
* - Tool calls are surfaced via the optional `onToolUse(name, input)`
|
|
2484
|
+
* callback (ACP `tool_call` notification, mapped to Claude-style
|
|
2485
|
+
* {name, input}). `tool_call_update` (results) is ignored to avoid
|
|
2486
|
+
* double chips.
|
|
2485
2487
|
* - Honors `timeoutMs`. On timeout: cancels the prompt, closes the tab
|
|
2486
2488
|
* (so the next call rebuilds against a clean process), resolves with
|
|
2487
2489
|
* `{ code: 1, stderr: 'doc-chat-pool: timeout after Xms' }`. The
|
|
@@ -2496,7 +2498,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
|
|
|
2496
2498
|
* document body. Always re-sending extraContext is correctness-safe; the
|
|
2497
2499
|
* pool's warm-process saving is preserved regardless.
|
|
2498
2500
|
*/
|
|
2499
|
-
function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk }) {
|
|
2501
|
+
function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk, onToolUse }) {
|
|
2500
2502
|
const oneShot = !!freshSession;
|
|
2501
2503
|
const tabKey = oneShot
|
|
2502
2504
|
? 'doc-chat:fresh:' + shared.uid()
|
|
@@ -2574,6 +2576,11 @@ function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemProm
|
|
|
2574
2576
|
try { onChunk(accumulated); } catch { /* swallow */ }
|
|
2575
2577
|
}
|
|
2576
2578
|
},
|
|
2579
|
+
onToolUse: (name, input) => {
|
|
2580
|
+
if (onToolUse) {
|
|
2581
|
+
try { onToolUse(name, input || {}); } catch { /* swallow */ }
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2577
2584
|
onDone: () => {
|
|
2578
2585
|
finalize({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
2579
2586
|
},
|
|
@@ -2786,7 +2793,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2786
2793
|
const p = _invokeDocChatViaPool({
|
|
2787
2794
|
prompt: poolPrompt, sessionKey, model, effort: ccEffort,
|
|
2788
2795
|
engineConfig: CONFIG.engine, systemPrompt,
|
|
2789
|
-
onChunk,
|
|
2796
|
+
onChunk, onToolUse,
|
|
2790
2797
|
freshSession, timeoutMs: timeout,
|
|
2791
2798
|
});
|
|
2792
2799
|
if (onAbortReady) onAbortReady(p.abort);
|
|
@@ -6311,7 +6318,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6311
6318
|
*/
|
|
6312
6319
|
function _invokeCcStream({ prompt, sessionId, liveState, toolUses, model, effort, maxTurns, engineConfig, systemPrompt = CC_STATIC_SYSTEM_PROMPT, tabId }) {
|
|
6313
6320
|
if (shared.resolveCcUseWorkerPool(engineConfig)) {
|
|
6314
|
-
return _invokeCcStreamViaPool({ prompt, liveState, model, effort, engineConfig, systemPrompt, tabId });
|
|
6321
|
+
return _invokeCcStreamViaPool({ prompt, liveState, toolUses, model, effort, engineConfig, systemPrompt, tabId });
|
|
6315
6322
|
}
|
|
6316
6323
|
const { callLLMStreaming } = require('./engine/llm');
|
|
6317
6324
|
return callLLMStreaming(prompt, systemPrompt, {
|
|
@@ -6345,22 +6352,44 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6345
6352
|
* callLLMStreaming's contract is "full accumulated text"; we accumulate
|
|
6346
6353
|
* here so `liveState.text` and downstream chunk events keep the same
|
|
6347
6354
|
* semantics consumers already depend on.
|
|
6348
|
-
* - Tool calls are
|
|
6349
|
-
* `tool_call`
|
|
6350
|
-
*
|
|
6351
|
-
*
|
|
6355
|
+
* - Tool calls are surfaced via the pool's `onToolUse` callback (ACP
|
|
6356
|
+
* `tool_call` notification, mapped to Claude-style {name, input} so the
|
|
6357
|
+
* dashboard's existing formatToolSummary chips render unchanged).
|
|
6358
|
+
* `tool_call_update` events (status: completed) are intentionally
|
|
6359
|
+
* ignored — surfacing results too would double the chip count.
|
|
6352
6360
|
* - `usage` is reported as an empty object — ACP doesn't expose token
|
|
6353
6361
|
* counts in the in-flight session/update notifications, and the pool's
|
|
6354
6362
|
* long-lived process makes per-turn usage attribution non-trivial.
|
|
6355
6363
|
* trackEngineUsage is a no-op on `{}`.
|
|
6356
6364
|
*/
|
|
6357
|
-
function _invokeCcStreamViaPool({ prompt, liveState, model, effort, engineConfig, systemPrompt, tabId }) {
|
|
6365
|
+
function _invokeCcStreamViaPool({ prompt, liveState, toolUses, model, effort, engineConfig, systemPrompt, tabId }) {
|
|
6358
6366
|
const resolvedTabId = tabId || 'default';
|
|
6359
6367
|
let cancelled = false;
|
|
6360
6368
|
let accumulated = '';
|
|
6361
6369
|
let sessionHandle = null;
|
|
6362
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;
|
|
6363
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
|
+
};
|
|
6364
6393
|
promise.abort = () => {
|
|
6365
6394
|
cancelled = true;
|
|
6366
6395
|
try { sessionHandle && sessionHandle.cancel(); } catch { /* swallow */ }
|
|
@@ -6375,6 +6404,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6375
6404
|
systemPromptHash: _ccPromptHash,
|
|
6376
6405
|
});
|
|
6377
6406
|
} catch (err) {
|
|
6407
|
+
_emitTimingLog(null, null, Date.now(), 'spawn-failed');
|
|
6378
6408
|
return resolveResult({
|
|
6379
6409
|
text: '',
|
|
6380
6410
|
sessionId: null,
|
|
@@ -6384,22 +6414,38 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6384
6414
|
stderr: String((err && err.message) || err || 'cc-worker-pool spawn failed'),
|
|
6385
6415
|
});
|
|
6386
6416
|
}
|
|
6417
|
+
const _tSessionReady = Date.now();
|
|
6418
|
+
const _lifecycle = sessionHandle.lifecycle || 'unknown';
|
|
6387
6419
|
if (cancelled) {
|
|
6388
6420
|
try { sessionHandle.cancel(); } catch { /* swallow */ }
|
|
6421
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'cancelled-pre-stream');
|
|
6389
6422
|
return resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
6390
6423
|
}
|
|
6391
6424
|
await sessionHandle.stream(prompt, {
|
|
6392
6425
|
systemPromptText: systemPrompt,
|
|
6393
6426
|
onChunk: (delta) => {
|
|
6427
|
+
if (_tFirstByte == null) _tFirstByte = Date.now();
|
|
6428
|
+
_chunkCount += 1;
|
|
6394
6429
|
accumulated += delta;
|
|
6395
6430
|
_touchCcLiveStream(liveState);
|
|
6396
6431
|
liveState.text = accumulated;
|
|
6397
6432
|
if (liveState.writer) liveState.writer({ type: 'chunk', text: accumulated });
|
|
6398
6433
|
},
|
|
6434
|
+
onToolUse: (name, input) => {
|
|
6435
|
+
if (_tFirstTool == null) _tFirstTool = Date.now();
|
|
6436
|
+
_toolUseCount += 1;
|
|
6437
|
+
_touchCcLiveStream(liveState);
|
|
6438
|
+
const safeInput = input || {};
|
|
6439
|
+
if (Array.isArray(toolUses)) toolUses.push({ name, input: safeInput });
|
|
6440
|
+
if (Array.isArray(liveState.tools)) liveState.tools.push({ name, input: safeInput });
|
|
6441
|
+
if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(safeInput) });
|
|
6442
|
+
},
|
|
6399
6443
|
onDone: () => {
|
|
6444
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'done');
|
|
6400
6445
|
resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
6401
6446
|
},
|
|
6402
6447
|
onError: (err) => {
|
|
6448
|
+
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), cancelled ? 'cancelled' : 'error');
|
|
6403
6449
|
resolveResult({
|
|
6404
6450
|
text: accumulated,
|
|
6405
6451
|
sessionId: sessionHandle.sessionId,
|
package/engine/cc-worker-pool.js
CHANGED
|
@@ -251,10 +251,21 @@ class Worker {
|
|
|
251
251
|
if (text && this.inflight.onChunk) {
|
|
252
252
|
try { this.inflight.onChunk(text); } catch { /* swallow */ }
|
|
253
253
|
}
|
|
254
|
+
} else if (update.sessionUpdate === 'tool_call' && this.inflight.onToolUse) {
|
|
255
|
+
// ACP `tool_call` (status: pending, fired at invocation time) is the
|
|
256
|
+
// pool's equivalent of Claude's tool_use event. We map kinds to
|
|
257
|
+
// Claude-style tool names so the dashboard's existing
|
|
258
|
+
// formatToolSummary (Bash → "$ <cmd>", Read → "Reading <path>", etc.)
|
|
259
|
+
// works unchanged. Status updates (`tool_call_update`, status:
|
|
260
|
+
// completed) carry the result and are ignored here — surfacing
|
|
261
|
+
// results too would double the chip count without adding info the
|
|
262
|
+
// user can act on.
|
|
263
|
+
const mapped = _mapAcpToolCallToToolUse(update);
|
|
264
|
+
if (mapped) {
|
|
265
|
+
try { this.inflight.onToolUse(mapped.name, mapped.input); }
|
|
266
|
+
catch { /* swallow */ }
|
|
267
|
+
}
|
|
254
268
|
}
|
|
255
|
-
// Other update kinds (available_commands_update, tool_call, ...) are
|
|
256
|
-
// ignored in sub-task B. Sub-task C/D will surface tool_call to the
|
|
257
|
-
// dashboard's onToolUse callback.
|
|
258
269
|
}
|
|
259
270
|
}
|
|
260
271
|
|
|
@@ -279,7 +290,7 @@ class Worker {
|
|
|
279
290
|
|
|
280
291
|
// ── Stream a single turn ───────────────────────────────────────────────
|
|
281
292
|
stream(promptText, opts = {}) {
|
|
282
|
-
const { onChunk, onDone, onError, signal, systemPromptText } = opts;
|
|
293
|
+
const { onChunk, onToolUse, onDone, onError, signal, systemPromptText } = opts;
|
|
283
294
|
if (this.killed) {
|
|
284
295
|
const err = new Error('cc-worker-pool: tab is closed');
|
|
285
296
|
if (onError) try { onError(err); } catch { /* swallow */ }
|
|
@@ -307,6 +318,7 @@ class Worker {
|
|
|
307
318
|
id,
|
|
308
319
|
sessionId: this.sessionId,
|
|
309
320
|
onChunk,
|
|
321
|
+
onToolUse,
|
|
310
322
|
onDone,
|
|
311
323
|
onError,
|
|
312
324
|
signal,
|
|
@@ -425,12 +437,58 @@ function _extractChunkText(content) {
|
|
|
425
437
|
return '';
|
|
426
438
|
}
|
|
427
439
|
|
|
440
|
+
// Map an ACP `tool_call` session/update notification to the {name, input} shape
|
|
441
|
+
// the dashboard's formatToolSummary already understands. ACP's `kind` is a
|
|
442
|
+
// coarse category (execute|read|edit|search|fetch|think|other); we translate to
|
|
443
|
+
// the closest Claude tool name so the existing chip formatters keep working
|
|
444
|
+
// (Bash → "$ <cmd>", Read → "Reading <path>", etc.). Unknown kinds fall back
|
|
445
|
+
// to ACP's human-readable `title` with the raw input attached, which renders
|
|
446
|
+
// through the default `<title>(<key>: <val>)` formatter.
|
|
447
|
+
function _mapAcpToolCallToToolUse(update) {
|
|
448
|
+
if (!update || update.sessionUpdate !== 'tool_call') return null;
|
|
449
|
+
const rawInput = (update.rawInput && typeof update.rawInput === 'object') ? update.rawInput : {};
|
|
450
|
+
const kind = String(update.kind || '').toLowerCase();
|
|
451
|
+
const title = update.title || '';
|
|
452
|
+
// For kinds with a clear Claude-tool equivalent, use that name + raw input.
|
|
453
|
+
switch (kind) {
|
|
454
|
+
case 'execute':
|
|
455
|
+
return { name: 'Bash', input: rawInput };
|
|
456
|
+
case 'read':
|
|
457
|
+
return { name: 'Read', input: rawInput };
|
|
458
|
+
case 'edit':
|
|
459
|
+
return { name: 'Edit', input: rawInput };
|
|
460
|
+
case 'search': {
|
|
461
|
+
// Heuristic: Grep needs a pattern; Glob needs a glob pattern.
|
|
462
|
+
// ACP doesn't distinguish, so prefer Grep when a `path` hint is present
|
|
463
|
+
// (matches the dashboard's Grep formatter "Searching <pat> in <path>").
|
|
464
|
+
const isGrep = typeof rawInput.path === 'string' || typeof rawInput.regex === 'string';
|
|
465
|
+
return { name: isGrep ? 'Grep' : 'Glob', input: rawInput };
|
|
466
|
+
}
|
|
467
|
+
case 'fetch':
|
|
468
|
+
return { name: 'WebFetch', input: rawInput };
|
|
469
|
+
case 'think':
|
|
470
|
+
// No equivalent Claude tool; show the title so the user sees Copilot's
|
|
471
|
+
// own description of what it's thinking about.
|
|
472
|
+
return { name: title || 'Think', input: rawInput };
|
|
473
|
+
default:
|
|
474
|
+
// Fallback: show ACP's title and pass rawInput through. The dashboard's
|
|
475
|
+
// default formatter renders this as `<title>(<key>: <val>)`.
|
|
476
|
+
return { name: title || kind || 'Tool', input: rawInput };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
428
480
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
429
481
|
|
|
430
482
|
async function getSession({ tabId, model, effort, mcpServers, systemPromptHash, cwd } = {}) {
|
|
431
483
|
if (!tabId) throw new Error('cc-worker-pool.getSession: tabId is required');
|
|
432
484
|
const mcpServersHash = _hashMcpServers(mcpServers);
|
|
433
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';
|
|
434
492
|
|
|
435
493
|
if (worker) {
|
|
436
494
|
if (worker.killed) {
|
|
@@ -449,6 +507,7 @@ async function getSession({ tabId, model, effort, mcpServers, systemPromptHash,
|
|
|
449
507
|
// caller has rotated in (Bug B / issue #2479).
|
|
450
508
|
await worker.newSession({ mcpServers, systemPromptHash, model, effort });
|
|
451
509
|
worker.lastUsedAt = _internals.now();
|
|
510
|
+
lifecycle = 'new-session';
|
|
452
511
|
} else {
|
|
453
512
|
// Warm reuse — only update bookkeeping. model/effort changes on a
|
|
454
513
|
// warm session are tracked here but not pushed via configOptions in
|
|
@@ -472,12 +531,14 @@ async function getSession({ tabId, model, effort, mcpServers, systemPromptHash,
|
|
|
472
531
|
try { worker.close(); } catch { /* already torn down */ }
|
|
473
532
|
throw err;
|
|
474
533
|
}
|
|
534
|
+
lifecycle = 'cold-spawn';
|
|
475
535
|
}
|
|
476
536
|
|
|
477
537
|
_ensureReaper();
|
|
478
538
|
|
|
479
539
|
return {
|
|
480
540
|
sessionId: worker.sessionId,
|
|
541
|
+
lifecycle,
|
|
481
542
|
stream: (promptText, opts) => worker.stream(promptText, opts),
|
|
482
543
|
cancel: () => worker.cancel(),
|
|
483
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.1933",
|
|
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"
|