@yemi33/minions 0.1.1602 → 0.1.1604

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 CHANGED
@@ -1,12 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1602 (2026-04-28)
3
+ ## 0.1.1604 (2026-04-28)
4
4
 
5
5
  ### Features
6
6
  - match runtime tags to actual logos (pixel-crab Claude, mascot Copilot)
7
7
  - replace runtime text tag with inline SVG logos
8
8
 
9
9
  ### Fixes
10
+ - preserve copilot cc action blocks
11
+ - improve copilot command center streaming
10
12
  - guard runtime model races
11
13
  - runtime-aware model picker + cross-runtime validation
12
14
  - keep cc stream final text complete
@@ -22,7 +22,7 @@ const RUNTIME_TAGS = {
22
22
  copilot: {
23
23
  label: 'Copilot',
24
24
  color: '#8957e5',
25
- svg: '<svg viewBox="0 0 24 18" width="17" height="13" aria-hidden="true" focusable="false" style="display:inline-block;vertical-align:-2px" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"><path d="M12 1 C18 1 21 4 21 8 V10 C22.5 10.5 23.5 12 23.5 14 C23.5 15.5 22.5 16.5 21 16.7 V17 C21 17.6 20.5 18 19.5 18 H4.5 C3.5 18 3 17.6 3 17 V16.7 C1.5 16.5 0.5 15.5 0.5 14 C0.5 12 1.5 10.5 3 10 V8 C3 4 6 1 12 1 Z"/><rect x="3.6" y="3.4" width="7.4" height="6.2" rx="2.7"/><rect x="13" y="3.4" width="7.4" height="6.2" rx="2.7"/><rect x="11" y="5.6" width="2" height="1.6" rx="0.4"/><path d="M7.4 9.6 H16.6 V15.4 C16.6 16.4 15.8 17 14.7 17 H9.3 C8.2 17 7.4 16.4 7.4 15.4 Z"/><rect x="9.4" y="10.7" width="1.7" height="4.7" rx="0.85" fill="currentColor" stroke="none"/><rect x="12.9" y="10.7" width="1.7" height="4.7" rx="0.85" fill="currentColor" stroke="none"/></svg>',
25
+ svg: '<svg viewBox="0 0 24 18" width="13" height="13" aria-hidden="true" focusable="false" style="display:inline-block;vertical-align:-2px" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"><path d="M12 1 C18 1 21 4 21 8 V10 C22.5 10.5 23.5 12 23.5 14 C23.5 15.5 22.5 16.5 21 16.7 V17 C21 17.6 20.5 18 19.5 18 H4.5 C3.5 18 3 17.6 3 17 V16.7 C1.5 16.5 0.5 15.5 0.5 14 C0.5 12 1.5 10.5 3 10 V8 C3 4 6 1 12 1 Z"/><rect x="3.6" y="3.4" width="7.4" height="6.2" rx="2.7"/><rect x="13" y="3.4" width="7.4" height="6.2" rx="2.7"/><rect x="11" y="5.6" width="2" height="1.6" rx="0.4"/><path d="M7.4 9.6 H16.6 V15.4 C16.6 16.4 15.8 17 14.7 17 H9.3 C8.2 17 7.4 16.4 7.4 15.4 Z"/><rect x="9.4" y="10.7" width="1.7" height="4.7" rx="0.85" fill="currentColor" stroke="none"/><rect x="12.9" y="10.7" width="1.7" height="4.7" rx="0.85" fill="currentColor" stroke="none"/></svg>',
26
26
  },
27
27
  };
28
28
  function _runtimeTagHtml(runtime) {
package/engine/llm.js CHANGED
@@ -22,6 +22,7 @@ const { resolveRuntime } = require('./runtimes');
22
22
 
23
23
  const MINIONS_DIR = shared.MINIONS_DIR;
24
24
  const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
25
+ const COPILOT_TASK_COMPLETE_GRACE_MS = 3000;
25
26
 
26
27
  // ─── Engine-Usage Metrics ────────────────────────────────────────────────────
27
28
 
@@ -259,6 +260,7 @@ function _createStreamAccumulator({
259
260
  maxTextLength = 0,
260
261
  onChunk = null,
261
262
  onToolUse = null,
263
+ onTaskComplete = null,
262
264
  }) {
263
265
  let stdout = '';
264
266
  let stderr = '';
@@ -274,6 +276,8 @@ function _createStreamAccumulator({
274
276
  // narration ("I'll inspect...") that is only progress text, so terminal text
275
277
  // comes from non-tool assistant messages or trailing deltas.
276
278
  let copilotMessageBuffer = '';
279
+ let copilotTaskCompleteSeen = false;
280
+ let copilotTaskCompleteSummary = '';
277
281
 
278
282
  function _streamText(value) {
279
283
  return maxTextLength ? value.slice(-maxTextLength) : value;
@@ -284,6 +288,24 @@ function _createStreamAccumulator({
284
288
  return Array.isArray(requests) && requests.length > 0;
285
289
  }
286
290
 
291
+ function _captureCopilotTaskComplete(summary, success = true) {
292
+ if (typeof summary !== 'string' || !summary) return;
293
+ const finalSummary = _streamText(summary);
294
+ const alreadySeen = copilotTaskCompleteSeen && copilotTaskCompleteSummary === finalSummary;
295
+ copilotTaskCompleteSeen = true;
296
+ copilotTaskCompleteSummary = finalSummary;
297
+ const hadText = !!text;
298
+ if (!hadText) {
299
+ text = finalSummary;
300
+ if (onChunk && finalSummary !== lastTextSent) {
301
+ lastTextSent = finalSummary;
302
+ onChunk(finalSummary);
303
+ }
304
+ }
305
+ copilotMessageBuffer = '';
306
+ if (!alreadySeen && onTaskComplete) onTaskComplete({ summary: finalSummary, success: success !== false });
307
+ }
308
+
287
309
  function captureEvent(obj) {
288
310
  if (!obj || typeof obj !== 'object') return;
289
311
 
@@ -323,7 +345,11 @@ function _createStreamAccumulator({
323
345
 
324
346
  // ── Copilot shape ───────────────────────────────────────────────────────
325
347
  if (obj.type === 'result' && typeof obj.sessionId === 'string') sessionId = obj.sessionId;
348
+ if (obj.type === 'session.task_complete') {
349
+ _captureCopilotTaskComplete(obj.data?.summary, obj.data?.success);
350
+ }
326
351
  if (obj.type === 'assistant.message_delta' && typeof obj.data?.deltaContent === 'string') {
352
+ if (copilotTaskCompleteSeen) return;
327
353
  copilotMessageBuffer += obj.data.deltaContent;
328
354
  if (onChunk && copilotMessageBuffer !== lastTextSent) {
329
355
  lastTextSent = copilotMessageBuffer;
@@ -340,6 +366,10 @@ function _createStreamAccumulator({
340
366
  if (Array.isArray(obj.data.toolRequests)) {
341
367
  for (const tr of obj.data.toolRequests) {
342
368
  if (tr && tr.name) {
369
+ if (tr.name === 'task_complete') {
370
+ _captureCopilotTaskComplete(tr.arguments?.summary || tr.intentionSummary);
371
+ continue;
372
+ }
343
373
  const toolUse = { name: tr.name, input: tr.arguments || {} };
344
374
  toolUses.push(toolUse);
345
375
  if (onToolUse) onToolUse(toolUse.name, toolUse.input);
@@ -348,6 +378,10 @@ function _createStreamAccumulator({
348
378
  }
349
379
  }
350
380
  if (obj.type === 'tool.execution_start' && obj.data?.toolName) {
381
+ if (obj.data.toolName === 'task_complete') {
382
+ _captureCopilotTaskComplete(obj.data.arguments?.summary);
383
+ return;
384
+ }
351
385
  const toolUse = { name: obj.data.toolName, input: obj.data.arguments || {} };
352
386
  // Dedup: assistant.message.toolRequests already adds this — only push if
353
387
  // we haven't seen it yet (toolCallId would be the unique key, but we
@@ -381,9 +415,10 @@ function _createStreamAccumulator({
381
415
  const ev = runtime.parseStreamChunk(trimmed);
382
416
  if (ev) captureEvent(ev);
383
417
  }
384
- if (copilotMessageBuffer) {
418
+ if (copilotMessageBuffer && !copilotTaskCompleteSeen) {
385
419
  text = _streamText(copilotMessageBuffer);
386
420
  }
421
+ if (!text && copilotTaskCompleteSummary) text = copilotTaskCompleteSummary;
387
422
  // Reconciliation: if any field is still missing, ask the runtime adapter
388
423
  // to re-parse the whole stdout. parseOutput() may catch a result event
389
424
  // that was malformed when streamed in chunks.
@@ -419,6 +454,18 @@ function _resolveModelFor(callOpts) {
419
454
  return undefined;
420
455
  }
421
456
 
457
+ function _resolveRuntimeFeatureOpts({
458
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
459
+ } = {}) {
460
+ const engine = engineConfig || {};
461
+ return {
462
+ stream: stream ?? engine.copilotStreamMode,
463
+ disableBuiltinMcps: disableBuiltinMcps ?? engine.copilotDisableBuiltinMcps,
464
+ suppressAgentsMd: suppressAgentsMd ?? engine.copilotSuppressAgentsMd,
465
+ reasoningSummaries: reasoningSummaries ?? engine.copilotReasoningSummaries,
466
+ };
467
+ }
468
+
422
469
  // ─── Core LLM Call ───────────────────────────────────────────────────────────
423
470
 
424
471
  function callLLM(promptText, sysPromptText, opts = {}) {
@@ -436,6 +483,9 @@ function callLLM(promptText, sysPromptText, opts = {}) {
436
483
 
437
484
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
438
485
  const model = _resolveModelFor({ model: modelOverride, engineConfig });
486
+ const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
487
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
488
+ });
439
489
 
440
490
  let _abort = null;
441
491
  const promise = new Promise((resolve) => {
@@ -443,13 +493,25 @@ function callLLM(promptText, sysPromptText, opts = {}) {
443
493
  const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, {
444
494
  direct, label, runtime, model, maxTurns, allowedTools, effort, sessionId,
445
495
  maxBudget, bare, fallbackModel,
446
- stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
496
+ ...runtimeFeatureOpts,
447
497
  });
498
+ let taskCompleteTimer = null;
499
+ const scheduleTaskCompleteClose = () => {
500
+ if (taskCompleteTimer) return;
501
+ taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
502
+ };
503
+ const clearTaskCompleteTimer = () => {
504
+ if (taskCompleteTimer) {
505
+ clearTimeout(taskCompleteTimer);
506
+ taskCompleteTimer = null;
507
+ }
508
+ };
448
509
  const acc = _createStreamAccumulator({
449
510
  runtime,
450
511
  maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
451
512
  maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
452
513
  maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
514
+ onTaskComplete: scheduleTaskCompleteClose,
453
515
  });
454
516
 
455
517
  _abort = () => { shared.killImmediate(proc); };
@@ -461,6 +523,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
461
523
 
462
524
  proc.on('close', (code) => {
463
525
  clearTimeout(timer);
526
+ clearTaskCompleteTimer();
464
527
  for (const f of cleanupFiles) safeUnlink(f);
465
528
  const parsed = acc.finalize();
466
529
  const durationMs = Date.now() - _startMs;
@@ -486,6 +549,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
486
549
 
487
550
  proc.on('error', (err) => {
488
551
  clearTimeout(timer);
552
+ clearTaskCompleteTimer();
489
553
  for (const f of cleanupFiles) safeUnlink(f);
490
554
  shared.log('error', `LLM spawn error (${label}): ${err.message}`);
491
555
  resolve({
@@ -516,6 +580,9 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
516
580
 
517
581
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
518
582
  const model = _resolveModelFor({ model: modelOverride, engineConfig });
583
+ const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
584
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
585
+ });
519
586
 
520
587
  let _abort = null;
521
588
  const promise = new Promise((resolve) => {
@@ -523,8 +590,19 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
523
590
  const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, {
524
591
  direct, label, runtime, model, maxTurns, allowedTools, effort, sessionId,
525
592
  maxBudget, bare, fallbackModel,
526
- stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
593
+ ...runtimeFeatureOpts,
527
594
  });
595
+ let taskCompleteTimer = null;
596
+ const scheduleTaskCompleteClose = () => {
597
+ if (taskCompleteTimer) return;
598
+ taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
599
+ };
600
+ const clearTaskCompleteTimer = () => {
601
+ if (taskCompleteTimer) {
602
+ clearTimeout(taskCompleteTimer);
603
+ taskCompleteTimer = null;
604
+ }
605
+ };
528
606
  const acc = _createStreamAccumulator({
529
607
  runtime,
530
608
  maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
@@ -532,6 +610,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
532
610
  maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
533
611
  onChunk,
534
612
  onToolUse,
613
+ onTaskComplete: scheduleTaskCompleteClose,
535
614
  });
536
615
 
537
616
  _abort = () => { shared.killImmediate(proc); };
@@ -543,6 +622,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
543
622
 
544
623
  proc.on('close', (code) => {
545
624
  clearTimeout(timer);
625
+ clearTaskCompleteTimer();
546
626
  for (const f of cleanupFiles) safeUnlink(f);
547
627
  const parsed = acc.finalize();
548
628
  const durationMs = Date.now() - _startMs;
@@ -565,6 +645,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
565
645
 
566
646
  proc.on('error', (err) => {
567
647
  clearTimeout(timer);
648
+ clearTaskCompleteTimer();
568
649
  for (const f of cleanupFiles) safeUnlink(f);
569
650
  shared.log('error', `LLM-stream spawn error (${label}): ${err.message}`);
570
651
  resolve({
@@ -588,4 +669,5 @@ module.exports = {
588
669
  _resetBinCache,
589
670
  _resolveRuntimeFor,
590
671
  _resolveModelFor,
672
+ _resolveRuntimeFeatureOpts,
591
673
  };
@@ -325,6 +325,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
325
325
  let outputTokensTotal = 0;
326
326
  let turnEndCount = 0;
327
327
  let pendingDeltaContent = '';
328
+ let taskCompleteSummary = '';
328
329
 
329
330
  for (const rawLine of safeRaw.split('\n')) {
330
331
  const line = rawLine.trim();
@@ -336,17 +337,29 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
336
337
  const type = obj.type;
337
338
  if (type === 'assistant.message_delta') {
338
339
  const delta = obj.data?.deltaContent;
339
- if (typeof delta === 'string') pendingDeltaContent += delta;
340
+ if (typeof delta === 'string' && !taskCompleteSummary) pendingDeltaContent += delta;
340
341
  } else if (type === 'assistant.message') {
341
342
  const content = obj.data?.content;
342
343
  const toolRequests = obj.data?.toolRequests;
343
344
  const hasToolRequests = Array.isArray(toolRequests) && toolRequests.length > 0;
345
+ if (hasToolRequests) {
346
+ for (const tr of toolRequests) {
347
+ const summary = tr?.name === 'task_complete' ? tr.arguments?.summary || tr.intentionSummary : '';
348
+ if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
349
+ }
350
+ }
344
351
  if (typeof content === 'string') {
345
352
  if (content && !hasToolRequests) messageContents.push(content);
346
353
  pendingDeltaContent = '';
347
354
  }
348
355
  const ot = obj.data?.outputTokens;
349
356
  if (typeof ot === 'number') outputTokensTotal += ot;
357
+ } else if (type === 'tool.execution_start') {
358
+ const summary = obj.data?.toolName === 'task_complete' ? obj.data?.arguments?.summary : '';
359
+ if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
360
+ } else if (type === 'session.task_complete') {
361
+ const summary = obj.data?.summary;
362
+ if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
350
363
  } else if (type === 'assistant.turn_end') {
351
364
  turnEndCount += 1;
352
365
  } else if (type === 'session.tools_updated' && model == null) {
@@ -378,6 +391,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
378
391
  }
379
392
 
380
393
  let text = messageContents.join('') + pendingDeltaContent;
394
+ if (!text && taskCompleteSummary) text = taskCompleteSummary;
381
395
  if (maxTextLength && text.length > maxTextLength) {
382
396
  text = text.slice(-maxTextLength);
383
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1602",
3
+ "version": "0.1.1604",
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"