@yemi33/minions 0.1.1802 → 0.1.1804
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 +10 -0
- package/engine/copilot-models.json +1 -1
- package/engine/llm.js +0 -29
- package/engine/runtimes/copilot.js +12 -8
- package/engine.js +51 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/llm.js
CHANGED
|
@@ -22,7 +22,6 @@ 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 = 15000;
|
|
26
25
|
const MISSING_RUNTIME_EXIT_CODE = 78;
|
|
27
26
|
// When the spawned process emits 'exit' but 'close' is delayed (a detached
|
|
28
27
|
// grandchild inherited stdio), wait this long for trailing stdout data to
|
|
@@ -627,17 +626,6 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
627
626
|
maxBudget, bare, fallbackModel,
|
|
628
627
|
...runtimeFeatureOpts,
|
|
629
628
|
});
|
|
630
|
-
let taskCompleteTimer = null;
|
|
631
|
-
const scheduleTaskCompleteClose = () => {
|
|
632
|
-
if (taskCompleteTimer) return;
|
|
633
|
-
taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
|
|
634
|
-
};
|
|
635
|
-
const clearTaskCompleteTimer = () => {
|
|
636
|
-
if (taskCompleteTimer) {
|
|
637
|
-
clearTimeout(taskCompleteTimer);
|
|
638
|
-
taskCompleteTimer = null;
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
629
|
let resolved = false;
|
|
642
630
|
let exitFallbackTimer = null;
|
|
643
631
|
let exitCode = null;
|
|
@@ -650,7 +638,6 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
650
638
|
maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
|
|
651
639
|
maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
|
|
652
640
|
maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
|
|
653
|
-
onTaskComplete: scheduleTaskCompleteClose,
|
|
654
641
|
// Terminal text from the runtime adapter signals the LLM has logically
|
|
655
642
|
// completed — kick the drain timer so we don't block on a delayed
|
|
656
643
|
// 'exit'/'close' when an inherited pipe keeps the parent's FDs open.
|
|
@@ -668,7 +655,6 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
668
655
|
if (resolved) return;
|
|
669
656
|
resolved = true;
|
|
670
657
|
clearTimeout(timer);
|
|
671
|
-
clearTaskCompleteTimer();
|
|
672
658
|
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
673
659
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
674
660
|
const parsed = acc.finalize();
|
|
@@ -706,7 +692,6 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
706
692
|
if (resolved) return;
|
|
707
693
|
resolved = true;
|
|
708
694
|
clearTimeout(timer);
|
|
709
|
-
clearTaskCompleteTimer();
|
|
710
695
|
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
711
696
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
712
697
|
shared.log('error', `LLM spawn error (${label}): ${err.message}`);
|
|
@@ -753,17 +738,6 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
753
738
|
maxBudget, bare, fallbackModel,
|
|
754
739
|
...runtimeFeatureOpts,
|
|
755
740
|
});
|
|
756
|
-
let taskCompleteTimer = null;
|
|
757
|
-
const scheduleTaskCompleteClose = () => {
|
|
758
|
-
if (taskCompleteTimer) return;
|
|
759
|
-
taskCompleteTimer = setTimeout(() => { try { shared.killImmediate(proc); } catch {} }, COPILOT_TASK_COMPLETE_GRACE_MS);
|
|
760
|
-
};
|
|
761
|
-
const clearTaskCompleteTimer = () => {
|
|
762
|
-
if (taskCompleteTimer) {
|
|
763
|
-
clearTimeout(taskCompleteTimer);
|
|
764
|
-
taskCompleteTimer = null;
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
741
|
let resolved = false;
|
|
768
742
|
let exitFallbackTimer = null;
|
|
769
743
|
let exitCode = null;
|
|
@@ -778,7 +752,6 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
778
752
|
maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
|
|
779
753
|
onChunk,
|
|
780
754
|
onToolUse,
|
|
781
|
-
onTaskComplete: scheduleTaskCompleteClose,
|
|
782
755
|
// Terminal text from the runtime adapter signals the LLM has logically
|
|
783
756
|
// completed — kick the drain timer so we don't block on a delayed
|
|
784
757
|
// 'exit'/'close' when an inherited pipe keeps the parent's FDs open.
|
|
@@ -797,7 +770,6 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
797
770
|
if (resolved) return;
|
|
798
771
|
resolved = true;
|
|
799
772
|
clearTimeout(timer);
|
|
800
|
-
clearTaskCompleteTimer();
|
|
801
773
|
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
802
774
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
803
775
|
const parsed = acc.finalize();
|
|
@@ -832,7 +804,6 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
832
804
|
if (resolved) return;
|
|
833
805
|
resolved = true;
|
|
834
806
|
clearTimeout(timer);
|
|
835
|
-
clearTaskCompleteTimer();
|
|
836
807
|
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
837
808
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
838
809
|
shared.log('error', `LLM-stream spawn error (${label}): ${err.message}`);
|
|
@@ -550,7 +550,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
|
|
|
550
550
|
const type = obj.type;
|
|
551
551
|
if (type === 'assistant.message_delta') {
|
|
552
552
|
const delta = obj.data?.deltaContent;
|
|
553
|
-
if (typeof delta === 'string'
|
|
553
|
+
if (typeof delta === 'string') pendingDeltaContent += delta;
|
|
554
554
|
} else if (type === 'assistant.message') {
|
|
555
555
|
const content = obj.data?.content;
|
|
556
556
|
const toolRequests = obj.data?.toolRequests;
|
|
@@ -818,12 +818,13 @@ function createStreamConsumer(ctx) {
|
|
|
818
818
|
// assistant messages or trailing deltas.
|
|
819
819
|
let copilotMessageBuffer = '';
|
|
820
820
|
let terminalText = '';
|
|
821
|
+
let taskCompleteSummary = '';
|
|
821
822
|
|
|
822
|
-
function _captureTaskComplete(summary, success = true) {
|
|
823
|
+
function _captureTaskComplete(summary, success = true, { clearBuffer = false } = {}) {
|
|
823
824
|
if (typeof summary !== 'string' || !summary) return;
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
|
|
825
|
+
taskCompleteSummary = summary;
|
|
826
|
+
if (clearBuffer) copilotMessageBuffer = '';
|
|
827
|
+
if (!terminalText && !copilotMessageBuffer) {
|
|
827
828
|
ctx.pushText(summary);
|
|
828
829
|
}
|
|
829
830
|
ctx.notifyTaskComplete(summary, success !== false);
|
|
@@ -837,7 +838,8 @@ function createStreamConsumer(ctx) {
|
|
|
837
838
|
// The result event is the first Copilot event that contains the resumable
|
|
838
839
|
// sessionId. Do not mark the earlier assistant.message as terminal or
|
|
839
840
|
// Minions can resolve before session persistence data is available.
|
|
840
|
-
|
|
841
|
+
const finalText = terminalText || copilotMessageBuffer || taskCompleteSummary;
|
|
842
|
+
if (finalText) ctx.setText(finalText);
|
|
841
843
|
}
|
|
842
844
|
|
|
843
845
|
if (obj.type === 'session.task_complete') {
|
|
@@ -877,7 +879,7 @@ function createStreamConsumer(ctx) {
|
|
|
877
879
|
for (const tr of data.toolRequests) {
|
|
878
880
|
if (!tr || !tr.name) continue;
|
|
879
881
|
if (tr.name === 'task_complete') {
|
|
880
|
-
_captureTaskComplete(tr.arguments?.summary || tr.intentionSummary);
|
|
882
|
+
_captureTaskComplete(tr.arguments?.summary || tr.intentionSummary, true, { clearBuffer: true });
|
|
881
883
|
continue;
|
|
882
884
|
}
|
|
883
885
|
ctx.pushToolUse(tr.name, tr.arguments || {});
|
|
@@ -888,7 +890,7 @@ function createStreamConsumer(ctx) {
|
|
|
888
890
|
|
|
889
891
|
if (obj.type === 'tool.execution_start' && obj.data?.toolName) {
|
|
890
892
|
if (obj.data.toolName === 'task_complete') {
|
|
891
|
-
_captureTaskComplete(obj.data.arguments?.summary);
|
|
893
|
+
_captureTaskComplete(obj.data.arguments?.summary, true, { clearBuffer: true });
|
|
892
894
|
return;
|
|
893
895
|
}
|
|
894
896
|
const name = obj.data.toolName;
|
|
@@ -903,6 +905,8 @@ function createStreamConsumer(ctx) {
|
|
|
903
905
|
|
|
904
906
|
function reset() {
|
|
905
907
|
copilotMessageBuffer = '';
|
|
908
|
+
terminalText = '';
|
|
909
|
+
taskCompleteSummary = '';
|
|
906
910
|
}
|
|
907
911
|
|
|
908
912
|
return { consume, reset };
|
package/engine.js
CHANGED
|
@@ -407,6 +407,50 @@ function mergePendingSteeringEntries(...groups) {
|
|
|
407
407
|
return merged;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
function promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutputPath) {
|
|
411
|
+
if (!procInfo || procInfo._steeringMessage) return { status: 'none', entries: [] };
|
|
412
|
+
if (runtime?.capabilities?.midRunSessionId !== false) return { status: 'none', entries: [] };
|
|
413
|
+
|
|
414
|
+
const startedAtMs = Date.parse(procInfo.startedAt);
|
|
415
|
+
const runStartMs = Number.isFinite(startedAtMs) ? startedAtMs : 0;
|
|
416
|
+
const pendingPaths = new Set((procInfo._pendingSteeringFiles || []).map(entry => entry?.path || entry).filter(Boolean));
|
|
417
|
+
const deferredPaths = new Set((procInfo._deferredSteeringFiles || []).filter(Boolean));
|
|
418
|
+
const unread = steering.listUnreadSteeringMessages(agentId).filter(entry => entry.message.trim());
|
|
419
|
+
const entriesByPath = new Map(unread.map(entry => [entry.path, entry]));
|
|
420
|
+
const pendingDeferred = Array.from(deferredPaths)
|
|
421
|
+
.map(filePath => entriesByPath.get(filePath))
|
|
422
|
+
.filter(Boolean);
|
|
423
|
+
const lateCheckpoint = unread.filter(entry =>
|
|
424
|
+
entry.createdAtMs >= runStartMs
|
|
425
|
+
&& !pendingPaths.has(entry.path)
|
|
426
|
+
&& !deferredPaths.has(entry.path)
|
|
427
|
+
);
|
|
428
|
+
const checkpointEntries = mergePendingSteeringEntries(pendingDeferred, lateCheckpoint);
|
|
429
|
+
if (checkpointEntries.length === 0) {
|
|
430
|
+
delete procInfo._deferredSteeringFiles;
|
|
431
|
+
return { status: 'none', entries: [] };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!procInfo.sessionId) {
|
|
435
|
+
log('warn', `Steering: ${agentId} exited before a resumable sessionId was available - ${checkpointEntries.length} message(s) remain pending`);
|
|
436
|
+
try { fs.appendFileSync(liveOutputPath, `\n[steering-pending] Agent exited before a resumable session was available. Your message remains unread and will be retried on the next dispatch.\n`); } catch {}
|
|
437
|
+
return { status: 'pending', entries: checkpointEntries };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (pendingDeferred.length > 0) {
|
|
441
|
+
log('info', `Steering: delivering ${pendingDeferred.length} deferred message(s) for ${agentId} at resumable checkpoint`);
|
|
442
|
+
}
|
|
443
|
+
if (lateCheckpoint.length > 0) {
|
|
444
|
+
log('info', `Steering: delivering ${lateCheckpoint.length} late checkpoint message(s) for ${agentId} at resumable checkpoint`);
|
|
445
|
+
}
|
|
446
|
+
procInfo._steeringMessage = checkpointEntries.map(entry => entry.message.trim()).join('\n\n');
|
|
447
|
+
procInfo._steeringSessionId = procInfo.sessionId;
|
|
448
|
+
procInfo._steeringEntry = checkpointEntries;
|
|
449
|
+
procInfo._steeringDeferredCheckpoint = true;
|
|
450
|
+
delete procInfo._deferredSteeringFiles;
|
|
451
|
+
return { status: 'promoted', entries: checkpointEntries };
|
|
452
|
+
}
|
|
453
|
+
|
|
410
454
|
// Resolve dependency plan item IDs to their PR branches
|
|
411
455
|
function resolveDependencyBranches(depIds, sourcePlan, project, config) {
|
|
412
456
|
const results = []; // [{ branch, prId }]
|
|
@@ -1193,6 +1237,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1193
1237
|
const MAX_OUTPUT = 1024 * 1024; // 1MB
|
|
1194
1238
|
let stdout = '';
|
|
1195
1239
|
let stderr = '';
|
|
1240
|
+
let steeringAckStdout = '';
|
|
1196
1241
|
const sessionCaptureState = { sessionLineBuffer: '' };
|
|
1197
1242
|
let _trustCheckDone = false;
|
|
1198
1243
|
const _spawnTime = Date.now();
|
|
@@ -1207,6 +1252,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1207
1252
|
const chunk = data.toString();
|
|
1208
1253
|
realActivityMap.set(id, Date.now());
|
|
1209
1254
|
if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
|
|
1255
|
+
if (steeringAckStdout.length < MAX_OUTPUT) steeringAckStdout += chunk.slice(0, MAX_OUTPUT - steeringAckStdout.length);
|
|
1210
1256
|
try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
|
|
1211
1257
|
|
|
1212
1258
|
// Trust gate detection: check first 30s of output for trust/permission prompts
|
|
@@ -1254,26 +1300,8 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1254
1300
|
}
|
|
1255
1301
|
|
|
1256
1302
|
const procInfo = activeProcesses.get(id);
|
|
1257
|
-
ackPendingSteeringFiles(agentId, procInfo,
|
|
1258
|
-
|
|
1259
|
-
if (procInfo?._deferredSteeringFiles?.length && procInfo.sessionId) {
|
|
1260
|
-
const deferredPaths = new Set(procInfo._deferredSteeringFiles);
|
|
1261
|
-
const pendingDeferred = steering.listUnreadSteeringMessages(agentId)
|
|
1262
|
-
.filter(entry => deferredPaths.has(entry.path) && entry.message.trim());
|
|
1263
|
-
if (pendingDeferred.length > 0) {
|
|
1264
|
-
log('info', `Steering: delivering ${pendingDeferred.length} deferred message(s) for ${agentId} at resumable checkpoint`);
|
|
1265
|
-
procInfo._steeringMessage = pendingDeferred.map(entry => entry.message.trim()).join('\n\n');
|
|
1266
|
-
procInfo._steeringSessionId = procInfo.sessionId;
|
|
1267
|
-
procInfo._steeringEntry = pendingDeferred;
|
|
1268
|
-
procInfo._steeringDeferredCheckpoint = true;
|
|
1269
|
-
delete procInfo._deferredSteeringFiles;
|
|
1270
|
-
} else {
|
|
1271
|
-
delete procInfo._deferredSteeringFiles;
|
|
1272
|
-
}
|
|
1273
|
-
} else if (procInfo?._deferredSteeringFiles?.length) {
|
|
1274
|
-
log('warn', `Steering: ${agentId} exited before a resumable sessionId was available — message remains pending`);
|
|
1275
|
-
try { fs.appendFileSync(liveOutputPath, `\n[steering-pending] Agent exited before a resumable session was available. Your message remains unread and will be retried on the next dispatch.\n`); } catch {}
|
|
1276
|
-
}
|
|
1303
|
+
ackPendingSteeringFiles(agentId, procInfo, steeringAckStdout);
|
|
1304
|
+
promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutputPath);
|
|
1277
1305
|
|
|
1278
1306
|
// Check if this was a steering kill — re-spawn with resume
|
|
1279
1307
|
if (procInfo?._steeringMessage) {
|
|
@@ -1401,6 +1429,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1401
1429
|
),
|
|
1402
1430
|
});
|
|
1403
1431
|
|
|
1432
|
+
steeringAckStdout = '';
|
|
1404
1433
|
// Live steering kills discard partial old output. Deferred checkpoint
|
|
1405
1434
|
// steering keeps the completed turn output so completion parsing still
|
|
1406
1435
|
// sees the original work if the follow-up only acknowledges steering.
|
|
@@ -1414,6 +1443,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1414
1443
|
const chunk = data.toString();
|
|
1415
1444
|
realActivityMap.set(id, Date.now());
|
|
1416
1445
|
if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
|
|
1446
|
+
if (steeringAckStdout.length < MAX_OUTPUT) steeringAckStdout += chunk.slice(0, MAX_OUTPUT - steeringAckStdout.length);
|
|
1417
1447
|
try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
|
|
1418
1448
|
const resumeInfo = activeProcesses.get(id);
|
|
1419
1449
|
markRuntimeResumeOutputSeen(resumeInfo);
|
|
@@ -4719,6 +4749,7 @@ module.exports = {
|
|
|
4719
4749
|
parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
|
|
4720
4750
|
isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, // exported for testing
|
|
4721
4751
|
_maxTurnsForType, buildProjectContext, normalizeAc, _buildAgentSpawnFlags, _classifyAgentFailure, // exported for testing
|
|
4752
|
+
promoteCheckpointSteeringForClose, // exported for testing
|
|
4722
4753
|
normalizePrBranch, resolvePrBranch, prCausePart, getPrCauseHead, getPrCauseBase, getPrAutomationCauseKey, getPrAutomationDispatchKey, // exported for testing
|
|
4723
4754
|
|
|
4724
4755
|
// Playbooks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1804",
|
|
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"
|