@yemi33/minions 0.1.1602 → 0.1.1603

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,13 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1602 (2026-04-28)
3
+ ## 0.1.1603 (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
+ - improve copilot command center streaming
10
11
  - guard runtime model races
11
12
  - runtime-aware model picker + cross-runtime validation
12
13
  - 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,21 @@ 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
+ copilotMessageBuffer = '';
298
+ text = finalSummary;
299
+ if (onChunk && finalSummary !== lastTextSent) {
300
+ lastTextSent = finalSummary;
301
+ onChunk(finalSummary);
302
+ }
303
+ if (!alreadySeen && onTaskComplete) onTaskComplete({ summary: finalSummary, success: success !== false });
304
+ }
305
+
287
306
  function captureEvent(obj) {
288
307
  if (!obj || typeof obj !== 'object') return;
289
308
 
@@ -323,7 +342,11 @@ function _createStreamAccumulator({
323
342
 
324
343
  // ── Copilot shape ───────────────────────────────────────────────────────
325
344
  if (obj.type === 'result' && typeof obj.sessionId === 'string') sessionId = obj.sessionId;
345
+ if (obj.type === 'session.task_complete') {
346
+ _captureCopilotTaskComplete(obj.data?.summary, obj.data?.success);
347
+ }
326
348
  if (obj.type === 'assistant.message_delta' && typeof obj.data?.deltaContent === 'string') {
349
+ if (copilotTaskCompleteSeen) return;
327
350
  copilotMessageBuffer += obj.data.deltaContent;
328
351
  if (onChunk && copilotMessageBuffer !== lastTextSent) {
329
352
  lastTextSent = copilotMessageBuffer;
@@ -340,6 +363,10 @@ function _createStreamAccumulator({
340
363
  if (Array.isArray(obj.data.toolRequests)) {
341
364
  for (const tr of obj.data.toolRequests) {
342
365
  if (tr && tr.name) {
366
+ if (tr.name === 'task_complete') {
367
+ _captureCopilotTaskComplete(tr.arguments?.summary || tr.intentionSummary);
368
+ continue;
369
+ }
343
370
  const toolUse = { name: tr.name, input: tr.arguments || {} };
344
371
  toolUses.push(toolUse);
345
372
  if (onToolUse) onToolUse(toolUse.name, toolUse.input);
@@ -348,6 +375,10 @@ function _createStreamAccumulator({
348
375
  }
349
376
  }
350
377
  if (obj.type === 'tool.execution_start' && obj.data?.toolName) {
378
+ if (obj.data.toolName === 'task_complete') {
379
+ _captureCopilotTaskComplete(obj.data.arguments?.summary);
380
+ return;
381
+ }
351
382
  const toolUse = { name: obj.data.toolName, input: obj.data.arguments || {} };
352
383
  // Dedup: assistant.message.toolRequests already adds this — only push if
353
384
  // we haven't seen it yet (toolCallId would be the unique key, but we
@@ -381,7 +412,7 @@ function _createStreamAccumulator({
381
412
  const ev = runtime.parseStreamChunk(trimmed);
382
413
  if (ev) captureEvent(ev);
383
414
  }
384
- if (copilotMessageBuffer) {
415
+ if (copilotMessageBuffer && !copilotTaskCompleteSeen) {
385
416
  text = _streamText(copilotMessageBuffer);
386
417
  }
387
418
  // Reconciliation: if any field is still missing, ask the runtime adapter
@@ -419,6 +450,18 @@ function _resolveModelFor(callOpts) {
419
450
  return undefined;
420
451
  }
421
452
 
453
+ function _resolveRuntimeFeatureOpts({
454
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
455
+ } = {}) {
456
+ const engine = engineConfig || {};
457
+ return {
458
+ stream: stream ?? engine.copilotStreamMode,
459
+ disableBuiltinMcps: disableBuiltinMcps ?? engine.copilotDisableBuiltinMcps,
460
+ suppressAgentsMd: suppressAgentsMd ?? engine.copilotSuppressAgentsMd,
461
+ reasoningSummaries: reasoningSummaries ?? engine.copilotReasoningSummaries,
462
+ };
463
+ }
464
+
422
465
  // ─── Core LLM Call ───────────────────────────────────────────────────────────
423
466
 
424
467
  function callLLM(promptText, sysPromptText, opts = {}) {
@@ -436,6 +479,9 @@ function callLLM(promptText, sysPromptText, opts = {}) {
436
479
 
437
480
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
438
481
  const model = _resolveModelFor({ model: modelOverride, engineConfig });
482
+ const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
483
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
484
+ });
439
485
 
440
486
  let _abort = null;
441
487
  const promise = new Promise((resolve) => {
@@ -443,13 +489,25 @@ function callLLM(promptText, sysPromptText, opts = {}) {
443
489
  const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, {
444
490
  direct, label, runtime, model, maxTurns, allowedTools, effort, sessionId,
445
491
  maxBudget, bare, fallbackModel,
446
- stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
492
+ ...runtimeFeatureOpts,
447
493
  });
494
+ let taskCompleteTimer = null;
495
+ const scheduleTaskCompleteClose = () => {
496
+ if (taskCompleteTimer) return;
497
+ taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
498
+ };
499
+ const clearTaskCompleteTimer = () => {
500
+ if (taskCompleteTimer) {
501
+ clearTimeout(taskCompleteTimer);
502
+ taskCompleteTimer = null;
503
+ }
504
+ };
448
505
  const acc = _createStreamAccumulator({
449
506
  runtime,
450
507
  maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
451
508
  maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
452
509
  maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
510
+ onTaskComplete: scheduleTaskCompleteClose,
453
511
  });
454
512
 
455
513
  _abort = () => { shared.killImmediate(proc); };
@@ -461,6 +519,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
461
519
 
462
520
  proc.on('close', (code) => {
463
521
  clearTimeout(timer);
522
+ clearTaskCompleteTimer();
464
523
  for (const f of cleanupFiles) safeUnlink(f);
465
524
  const parsed = acc.finalize();
466
525
  const durationMs = Date.now() - _startMs;
@@ -486,6 +545,7 @@ function callLLM(promptText, sysPromptText, opts = {}) {
486
545
 
487
546
  proc.on('error', (err) => {
488
547
  clearTimeout(timer);
548
+ clearTaskCompleteTimer();
489
549
  for (const f of cleanupFiles) safeUnlink(f);
490
550
  shared.log('error', `LLM spawn error (${label}): ${err.message}`);
491
551
  resolve({
@@ -516,6 +576,9 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
516
576
 
517
577
  const runtime = _resolveRuntimeFor({ cli: cliOverride, engineConfig });
518
578
  const model = _resolveModelFor({ model: modelOverride, engineConfig });
579
+ const runtimeFeatureOpts = _resolveRuntimeFeatureOpts({
580
+ stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries, engineConfig,
581
+ });
519
582
 
520
583
  let _abort = null;
521
584
  const promise = new Promise((resolve) => {
@@ -523,8 +586,19 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
523
586
  const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, {
524
587
  direct, label, runtime, model, maxTurns, allowedTools, effort, sessionId,
525
588
  maxBudget, bare, fallbackModel,
526
- stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
589
+ ...runtimeFeatureOpts,
527
590
  });
591
+ let taskCompleteTimer = null;
592
+ const scheduleTaskCompleteClose = () => {
593
+ if (taskCompleteTimer) return;
594
+ taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
595
+ };
596
+ const clearTaskCompleteTimer = () => {
597
+ if (taskCompleteTimer) {
598
+ clearTimeout(taskCompleteTimer);
599
+ taskCompleteTimer = null;
600
+ }
601
+ };
528
602
  const acc = _createStreamAccumulator({
529
603
  runtime,
530
604
  maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
@@ -532,6 +606,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
532
606
  maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
533
607
  onChunk,
534
608
  onToolUse,
609
+ onTaskComplete: scheduleTaskCompleteClose,
535
610
  });
536
611
 
537
612
  _abort = () => { shared.killImmediate(proc); };
@@ -543,6 +618,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
543
618
 
544
619
  proc.on('close', (code) => {
545
620
  clearTimeout(timer);
621
+ clearTaskCompleteTimer();
546
622
  for (const f of cleanupFiles) safeUnlink(f);
547
623
  const parsed = acc.finalize();
548
624
  const durationMs = Date.now() - _startMs;
@@ -565,6 +641,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
565
641
 
566
642
  proc.on('error', (err) => {
567
643
  clearTimeout(timer);
644
+ clearTaskCompleteTimer();
568
645
  for (const f of cleanupFiles) safeUnlink(f);
569
646
  shared.log('error', `LLM-stream spawn error (${label}): ${err.message}`);
570
647
  resolve({
@@ -588,4 +665,5 @@ module.exports = {
588
665
  _resetBinCache,
589
666
  _resolveRuntimeFor,
590
667
  _resolveModelFor,
668
+ _resolveRuntimeFeatureOpts,
591
669
  };
@@ -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) {
@@ -377,7 +390,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
377
390
  }
378
391
  }
379
392
 
380
- let text = messageContents.join('') + pendingDeltaContent;
393
+ let text = taskCompleteSummary || (messageContents.join('') + pendingDeltaContent);
381
394
  if (maxTextLength && text.length > maxTextLength) {
382
395
  text = text.slice(-maxTextLength);
383
396
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1602",
3
+ "version": "0.1.1603",
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"