@yemi33/minions 0.1.1935 → 0.1.1937

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.
@@ -397,13 +397,7 @@ function ccSwitchTab(id) {
397
397
  var html = '';
398
398
  var tools = tab._toolsUsed || [];
399
399
  if (tools.length > 0) {
400
- html += '<div style="margin-bottom:6px">';
401
- tools.forEach(function(t) {
402
- var name = typeof t === 'string' ? t : t.name;
403
- var input = typeof t === 'string' ? {} : (t.input || {});
404
- html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">&#9679;</span> ' + formatToolSummary(name, input) + '</div>';
405
- });
406
- html += '</div>';
400
+ html += '<div style="margin-bottom:6px">' + tools.map(renderToolChip).join('') + '</div>';
407
401
  }
408
402
  var text = tab._streamedText || '';
409
403
  if (text) html += renderMd(text);
@@ -779,13 +773,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
779
773
  }
780
774
  var html = '';
781
775
  if (toolsUsed.length > 0) {
782
- html += '<div style="margin-bottom:6px">';
783
- toolsUsed.forEach(function(t) {
784
- var name = typeof t === 'string' ? t : t.name;
785
- var input = typeof t === 'string' ? {} : (t.input || {});
786
- html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">&#9679;</span> ' + formatToolSummary(name, input) + '</div>';
787
- });
788
- html += '</div>';
776
+ html += '<div style="margin-bottom:6px">' + toolsUsed.map(renderToolChip).join('') + '</div>';
789
777
  }
790
778
  if (streamedText) {
791
779
  html += renderMd(streamedText);
@@ -851,10 +839,19 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
851
839
  } else if (evt.type === 'heartbeat') {
852
840
  return;
853
841
  } else if (evt.type === 'tool') {
854
- toolsUsed.push({ name: evt.name, input: evt.input || {} });
842
+ toolsUsed.push({ name: evt.name, input: evt.input || {}, id: evt.id || null, status: evt.id ? 'pending' : null });
855
843
  if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
856
844
  updateStreamDiv();
857
845
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
846
+ } else if (evt.type === 'tool-update') {
847
+ for (var ti = 0; ti < toolsUsed.length; ti++) {
848
+ if (toolsUsed[ti] && toolsUsed[ti].id === evt.id) {
849
+ toolsUsed[ti].status = evt.status;
850
+ break;
851
+ }
852
+ }
853
+ if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
854
+ updateStreamDiv();
858
855
  } else if (evt.type === 'done') {
859
856
  terminalEventSeen = true;
860
857
  _cleanupStreamDiv();
@@ -1,6 +1,26 @@
1
1
  // dashboard/js/render-utils.js — Shared formatting helpers for agent output rendering
2
2
  // Depends on: escHtml() and renderMd() from utils.js (loaded before this file)
3
3
 
4
+ // marker + color per tool-call status. ACP `tool_call_update` flips chips from
5
+ // pending → completed/failed mid-stream so the user sees real progress; the
6
+ // non-pool (Claude direct) path emits status=null and stays on the neutral dot.
7
+ var _CC_TOOL_CHIP_STYLE = {
8
+ pending: { marker: '&#9679;', color: 'var(--muted)' },
9
+ completed: { marker: '&#10003;', color: 'var(--green, #4ade80)' },
10
+ failed: { marker: '&#10005;', color: 'var(--red, #ef4444)' },
11
+ };
12
+
13
+ function renderToolChip(t) {
14
+ var name = typeof t === 'string' ? t : t.name;
15
+ var input = typeof t === 'string' ? {} : (t.input || {});
16
+ var status = typeof t === 'string' ? null : (t.status || null);
17
+ var style = _CC_TOOL_CHIP_STYLE[status] || _CC_TOOL_CHIP_STYLE.pending;
18
+ return '<div style="color:' + style.color + ';font-size:10px;font-family:monospace">'
19
+ + '<span style="flex-shrink:0">' + style.marker + '</span> '
20
+ + formatToolSummary(name, input)
21
+ + '</div>';
22
+ }
23
+
4
24
  /**
5
25
  * Returns a one-line human-readable summary for a Claude tool call.
6
26
  * @param {string} name - Tool name (e.g. 'Bash', 'Read', 'Edit')
package/dashboard.js CHANGED
@@ -2498,7 +2498,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
2498
2498
  * document body. Always re-sending extraContext is correctness-safe; the
2499
2499
  * pool's warm-process saving is preserved regardless.
2500
2500
  */
2501
- function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk, onToolUse }) {
2501
+ function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk, onToolUse, onToolUpdate }) {
2502
2502
  const oneShot = !!freshSession;
2503
2503
  const tabKey = oneShot
2504
2504
  ? 'doc-chat:fresh:' + shared.uid()
@@ -2576,9 +2576,14 @@ function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemProm
2576
2576
  try { onChunk(accumulated); } catch { /* swallow */ }
2577
2577
  }
2578
2578
  },
2579
- onToolUse: (name, input) => {
2579
+ onToolUse: (name, input, toolCallId) => {
2580
2580
  if (onToolUse) {
2581
- try { onToolUse(name, input || {}); } catch { /* swallow */ }
2581
+ try { onToolUse(name, input || {}, toolCallId); } catch { /* swallow */ }
2582
+ }
2583
+ },
2584
+ onToolUpdate: (toolCallId, status) => {
2585
+ if (onToolUpdate) {
2586
+ try { onToolUpdate(toolCallId, status); } catch { /* swallow */ }
2582
2587
  }
2583
2588
  },
2584
2589
  onDone: () => {
@@ -2762,7 +2767,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2762
2767
  return result;
2763
2768
  }
2764
2769
 
2765
- 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, skipPreflight = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript, turnId, freshSession = false } = {}) {
2770
+ 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, skipPreflight = false, model, onAbortReady, onChunk, onToolUse, onToolUpdate, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript, turnId, freshSession = false } = {}) {
2766
2771
  if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
2767
2772
  if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
2768
2773
  const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
@@ -2793,7 +2798,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2793
2798
  const p = _invokeDocChatViaPool({
2794
2799
  prompt: poolPrompt, sessionKey, model, effort: ccEffort,
2795
2800
  engineConfig: CONFIG.engine, systemPrompt,
2796
- onChunk, onToolUse,
2801
+ onChunk, onToolUse, onToolUpdate,
2797
2802
  freshSession, timeoutMs: timeout,
2798
2803
  });
2799
2804
  if (onAbortReady) onAbortReady(p.abort);
@@ -6163,17 +6168,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6163
6168
  const abort = ccInFlightAborts.get(tabId);
6164
6169
  if (abort) { try { abort(); } catch {} }
6165
6170
  }
6166
- // Sub-task C of W-mp2w003600196c51: when the worker pool is on, abort
6167
- // must also fire `session/cancel` on the persistent ACP process so the
6168
- // remote daemon stops generating into a torn-down session. The pool
6169
- // exposes cancellation via the SessionHandle returned from getSession;
6170
- // we don't keep that handle around here, so route through closeTab to
6171
- // both cancel inflight and tear down the worker (cheaper than tracking
6172
- // per-tab handles in dashboard state, and matches "tab close" semantics
6173
- // — if the user explicitly aborted, we don't owe them a warm process).
6174
- // Off when the flag is off so legacy SIGTERM-only behavior is preserved.
6171
+ // Cancel the inflight ACP turn but keep the warm worker; closing the
6172
+ // tab here would force the next turn to pay the ~7-8 s cold-spawn.
6175
6173
  if (shared.resolveCcUseWorkerPool(CONFIG.engine)) {
6176
- try { ccWorkerPool.closeTab(tabId); } catch { /* swallow */ }
6174
+ try { ccWorkerPool.cancelInflight(tabId); } catch { /* swallow */ }
6177
6175
  }
6178
6176
  _clearCcLiveStream(tabId);
6179
6177
  _releaseCCTab(tabId);
@@ -6333,9 +6331,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6333
6331
  },
6334
6332
  onToolUse: (name, input) => {
6335
6333
  _touchCcLiveStream(liveState);
6336
- toolUses.push({ name, input: input || {} });
6337
- liveState.tools.push({ name, input: input || {} });
6338
- if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(input) });
6334
+ const entry = { name, input: input || {}, id: null, status: null };
6335
+ toolUses.push(entry);
6336
+ liveState.tools.push(entry);
6337
+ if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(input), id: null });
6339
6338
  },
6340
6339
  });
6341
6340
  }
@@ -6431,14 +6430,26 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6431
6430
  liveState.text = accumulated;
6432
6431
  if (liveState.writer) liveState.writer({ type: 'chunk', text: accumulated });
6433
6432
  },
6434
- onToolUse: (name, input) => {
6433
+ onToolUse: (name, input, toolCallId) => {
6435
6434
  if (_tFirstTool == null) _tFirstTool = Date.now();
6436
6435
  _toolUseCount += 1;
6437
6436
  _touchCcLiveStream(liveState);
6438
6437
  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) });
6438
+ const entry = { name, input: safeInput, id: toolCallId || null, status: toolCallId ? 'pending' : null };
6439
+ if (Array.isArray(toolUses)) toolUses.push(entry);
6440
+ if (Array.isArray(liveState.tools)) liveState.tools.push(entry);
6441
+ if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(safeInput), id: toolCallId || null });
6442
+ },
6443
+ onToolUpdate: (toolCallId, status) => {
6444
+ _touchCcLiveStream(liveState);
6445
+ // toolUses and liveState.tools hold the same entry references (both
6446
+ // populated via onToolUse above), so mutating in one side reflects
6447
+ // in the other. Patch once.
6448
+ if (Array.isArray(toolUses)) {
6449
+ const entry = toolUses.find((e) => e && e.id === toolCallId);
6450
+ if (entry) entry.status = status;
6451
+ }
6452
+ if (liveState.writer) liveState.writer({ type: 'tool-update', id: toolCallId, status });
6442
6453
  },
6443
6454
  onDone: () => {
6444
6455
  _emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'done');
@@ -252,17 +252,19 @@ class Worker {
252
252
  try { this.inflight.onChunk(text); } catch { /* swallow */ }
253
253
  }
254
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.
255
+ // ACP `tool_call` (pending) Claude-style {name, input} via
256
+ // _mapAcpToolCallToToolUse so the dashboard's formatToolSummary
257
+ // formatters (Bash "$ <cmd>", etc.) work unchanged.
263
258
  const mapped = _mapAcpToolCallToToolUse(update);
264
259
  if (mapped) {
265
- try { this.inflight.onToolUse(mapped.name, mapped.input); }
260
+ try { this.inflight.onToolUse(mapped.name, mapped.input, update.toolCallId); }
261
+ catch { /* swallow */ }
262
+ }
263
+ } else if (update.sessionUpdate === 'tool_call_update' && this.inflight.onToolUpdate) {
264
+ // Terminal-state updates only (completed/failed). In-progress updates
265
+ // exist in the ACP spec but add chip churn without actionable info.
266
+ if (update.status === 'completed' || update.status === 'failed') {
267
+ try { this.inflight.onToolUpdate(update.toolCallId, update.status); }
266
268
  catch { /* swallow */ }
267
269
  }
268
270
  }
@@ -290,7 +292,7 @@ class Worker {
290
292
 
291
293
  // ── Stream a single turn ───────────────────────────────────────────────
292
294
  stream(promptText, opts = {}) {
293
- const { onChunk, onToolUse, onDone, onError, signal, systemPromptText } = opts;
295
+ const { onChunk, onToolUse, onToolUpdate, onDone, onError, signal, systemPromptText } = opts;
294
296
  if (this.killed) {
295
297
  const err = new Error('cc-worker-pool: tab is closed');
296
298
  if (onError) try { onError(err); } catch { /* swallow */ }
@@ -319,6 +321,7 @@ class Worker {
319
321
  sessionId: this.sessionId,
320
322
  onChunk,
321
323
  onToolUse,
324
+ onToolUpdate,
322
325
  onDone,
323
326
  onError,
324
327
  signal,
@@ -449,31 +452,44 @@ function _mapAcpToolCallToToolUse(update) {
449
452
  const rawInput = (update.rawInput && typeof update.rawInput === 'object') ? update.rawInput : {};
450
453
  const kind = String(update.kind || '').toLowerCase();
451
454
  const title = update.title || '';
452
- // For kinds with a clear Claude-tool equivalent, use that name + raw input.
455
+
456
+ // Field-name normalization: Copilot ACP and Claude tool_use use different
457
+ // keys for the same concept (Copilot `path`, Claude `file_path`). Normalize
458
+ // here so the dashboard's formatToolSummary — written against Claude's
459
+ // names — produces the same chip text on both runtimes.
460
+ const filePath = rawInput.file_path || rawInput.path || rawInput.filePath || '';
461
+
462
+ // Pattern (Grep) — Copilot uses `paths` (plural) for the search scope where
463
+ // Claude's Grep takes `path`.
464
+ if (typeof rawInput.pattern === 'string') {
465
+ return {
466
+ name: 'Grep',
467
+ input: { pattern: rawInput.pattern, path: rawInput.paths || rawInput.path || '.' },
468
+ };
469
+ }
470
+
453
471
  switch (kind) {
454
472
  case 'execute':
455
473
  return { name: 'Bash', input: rawInput };
456
474
  case 'read':
457
- return { name: 'Read', input: rawInput };
475
+ // ACP overloads `read` for both file-view and grep — the pattern check
476
+ // above already handled grep, so this branch is the file-view case.
477
+ return { name: 'Read', input: { file_path: filePath } };
458
478
  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
- }
479
+ return { name: 'Edit', input: { file_path: filePath } };
480
+ case 'search':
481
+ // Pattern check above handled the grep case; arriving here means glob.
482
+ return { name: 'Glob', input: rawInput };
467
483
  case 'fetch':
468
484
  return { name: 'WebFetch', input: rawInput };
469
485
  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
486
  return { name: title || 'Think', input: rawInput };
473
487
  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 };
488
+ // Unknown kind — use ACP's human-readable title as the chip label and
489
+ // drop rawInput so formatToolSummary's default branch shows just the
490
+ // title (avoids `<title>(<key>: <val>)` clutter when the input shape
491
+ // is unfamiliar).
492
+ return { name: title || kind || 'Tool', input: {} };
477
493
  }
478
494
  }
479
495
 
@@ -552,6 +568,18 @@ function closeTab(tabId) {
552
568
  worker.close();
553
569
  }
554
570
 
571
+ // Cancel the currently in-flight prompt on this tab without killing the
572
+ // worker. Sends ACP `session/cancel` so the remote daemon stops generating;
573
+ // the warm process + initialized MCP servers + session state are preserved
574
+ // so the next prompt skips the ~7-8 s cold-spawn cost. No-op if the tab has
575
+ // no worker or no inflight prompt.
576
+ function cancelInflight(tabId) {
577
+ const worker = _tabs.get(tabId);
578
+ if (!worker || worker.killed || !worker.inflight) return false;
579
+ try { worker.cancel(); } catch { /* swallow */ }
580
+ return true;
581
+ }
582
+
555
583
  function shutdown() {
556
584
  for (const worker of _tabs.values()) {
557
585
  try { worker.close(); } catch { /* swallow */ }
@@ -583,6 +611,7 @@ function _reapIdleTabs() {
583
611
  module.exports = {
584
612
  getSession,
585
613
  closeTab,
614
+ cancelInflight,
586
615
  shutdown,
587
616
  // Exposed for unit tests; engine code MUST go through the public API.
588
617
  _internals,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1935",
3
+ "version": "0.1.1937",
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"
@@ -1,5 +0,0 @@
1
- {
2
- "runtime": "copilot",
3
- "models": null,
4
- "cachedAt": "2026-05-14T04:24:41.192Z"
5
- }