@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 +2 -1
- package/dashboard/js/render-agents.js +1 -1
- package/engine/llm.js +81 -3
- package/engine/runtimes/copilot.js +15 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|