@yemi33/minions 0.1.1742 → 0.1.1744
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 +8 -0
- package/engine/copilot-models.json +1 -1
- package/engine/llm.js +69 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/llm.js
CHANGED
|
@@ -23,8 +23,12 @@ const { resolveRuntime } = require('./runtimes');
|
|
|
23
23
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
24
24
|
const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
|
|
25
25
|
const COPILOT_TASK_COMPLETE_GRACE_MS = 3000;
|
|
26
|
-
const LLM_EXIT_SETTLE_GRACE_MS = 1000;
|
|
27
26
|
const MISSING_RUNTIME_EXIT_CODE = 78;
|
|
27
|
+
// When the spawned process emits 'exit' but 'close' is delayed (a detached
|
|
28
|
+
// grandchild inherited stdio), wait this long for trailing stdout data to
|
|
29
|
+
// drain into our buffer before finalizing on the exit fallback path. 'close'
|
|
30
|
+
// is preferred; this is a safety net so callers don't hang on inherited pipes.
|
|
31
|
+
const EXIT_DRAIN_FALLBACK_MS = 100;
|
|
28
32
|
|
|
29
33
|
// ─── Engine-Usage Metrics ────────────────────────────────────────────────────
|
|
30
34
|
//
|
|
@@ -423,6 +427,7 @@ function _createStreamAccumulator({
|
|
|
423
427
|
onChunk = null,
|
|
424
428
|
onToolUse = null,
|
|
425
429
|
onTaskComplete = null,
|
|
430
|
+
onTerminalResult = null,
|
|
426
431
|
onThinking = null,
|
|
427
432
|
}) {
|
|
428
433
|
if (!runtime?.capabilities?.streamConsumer || typeof runtime.createStreamConsumer !== 'function') {
|
|
@@ -438,6 +443,7 @@ function _createStreamAccumulator({
|
|
|
438
443
|
let lastTextSent = '';
|
|
439
444
|
let thinkingSent = false;
|
|
440
445
|
let taskCompleteFired = false;
|
|
446
|
+
let terminalResultFired = false;
|
|
441
447
|
let lastTaskCompleteSummary = '';
|
|
442
448
|
const toolUses = [];
|
|
443
449
|
|
|
@@ -462,8 +468,18 @@ function _createStreamAccumulator({
|
|
|
462
468
|
// override any streamed text (Claude's `result`, Copilot's final
|
|
463
469
|
// assistant.message). onChunk is NOT fired here; this is the
|
|
464
470
|
// authoritative final-text path, not a streaming chunk.
|
|
471
|
+
//
|
|
472
|
+
// Fire onTerminalResult once on the first non-empty terminal text so
|
|
473
|
+
// callers can early-resolve without waiting for the OS-level 'exit' /
|
|
474
|
+
// 'close' events — those can be delayed indefinitely on Linux when a
|
|
475
|
+
// detached grandchild has inherited the stdout pipe (e.g. Claude/Copilot
|
|
476
|
+
// CLIs that spawn background workers).
|
|
465
477
|
if (typeof value !== 'string') return;
|
|
466
478
|
text = _streamText(value);
|
|
479
|
+
if (value && onTerminalResult && !terminalResultFired) {
|
|
480
|
+
terminalResultFired = true;
|
|
481
|
+
onTerminalResult();
|
|
482
|
+
}
|
|
467
483
|
},
|
|
468
484
|
pushToolUse(name, input) {
|
|
469
485
|
if (!name) return;
|
|
@@ -610,8 +626,6 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
610
626
|
maxBudget, bare, fallbackModel,
|
|
611
627
|
...runtimeFeatureOpts,
|
|
612
628
|
});
|
|
613
|
-
let settled = false;
|
|
614
|
-
let exitSettleTimer = null;
|
|
615
629
|
let taskCompleteTimer = null;
|
|
616
630
|
const scheduleTaskCompleteClose = () => {
|
|
617
631
|
if (taskCompleteTimer) return;
|
|
@@ -623,12 +637,23 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
623
637
|
taskCompleteTimer = null;
|
|
624
638
|
}
|
|
625
639
|
};
|
|
640
|
+
let resolved = false;
|
|
641
|
+
let exitFallbackTimer = null;
|
|
642
|
+
let exitCode = null;
|
|
643
|
+
const scheduleExitFallback = (code) => {
|
|
644
|
+
if (resolved || exitFallbackTimer) return;
|
|
645
|
+
exitFallbackTimer = setTimeout(() => finalizeAndResolve(code), EXIT_DRAIN_FALLBACK_MS);
|
|
646
|
+
};
|
|
626
647
|
const acc = _createStreamAccumulator({
|
|
627
648
|
runtime,
|
|
628
649
|
maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
|
|
629
650
|
maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
|
|
630
651
|
maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
|
|
631
652
|
onTaskComplete: scheduleTaskCompleteClose,
|
|
653
|
+
// Terminal text from the runtime adapter signals the LLM has logically
|
|
654
|
+
// completed — kick the drain timer so we don't block on a delayed
|
|
655
|
+
// 'exit'/'close' when an inherited pipe keeps the parent's FDs open.
|
|
656
|
+
onTerminalResult: () => scheduleExitFallback(exitCode != null ? exitCode : 0),
|
|
632
657
|
});
|
|
633
658
|
|
|
634
659
|
_abort = () => { shared.killImmediate(proc); };
|
|
@@ -638,16 +663,14 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
638
663
|
|
|
639
664
|
const timer = setTimeout(() => { shared.killImmediate(proc); }, timeout);
|
|
640
665
|
|
|
641
|
-
|
|
642
|
-
if (
|
|
643
|
-
|
|
666
|
+
const finalizeAndResolve = (code) => {
|
|
667
|
+
if (resolved) return;
|
|
668
|
+
resolved = true;
|
|
644
669
|
clearTimeout(timer);
|
|
645
|
-
if (exitSettleTimer) clearTimeout(exitSettleTimer);
|
|
646
670
|
clearTaskCompleteTimer();
|
|
671
|
+
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
647
672
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
648
673
|
const parsed = acc.finalize();
|
|
649
|
-
try { proc.stdout?.destroy(); } catch {}
|
|
650
|
-
try { proc.stderr?.destroy(); } catch {}
|
|
651
674
|
const durationMs = Date.now() - _startMs;
|
|
652
675
|
const usage = parsed.usage ? { ...parsed.usage, durationMs } : { durationMs };
|
|
653
676
|
// parseError lets the adapter classify obvious failure modes (auth /
|
|
@@ -667,20 +690,22 @@ function callLLM(promptText, sysPromptText, opts = {}) {
|
|
|
667
690
|
runtime: runtime.name,
|
|
668
691
|
errorClass: errInfo.code,
|
|
669
692
|
});
|
|
670
|
-
}
|
|
693
|
+
};
|
|
671
694
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
695
|
+
// 'close' fires after stdio streams close; if a detached grandchild
|
|
696
|
+
// inherited stdout, that can be delayed indefinitely. 'exit' fires when
|
|
697
|
+
// the child itself exits — schedule a short drain window then resolve.
|
|
698
|
+
// On Linux, 'exit' itself can be delayed by an inherited pipe handle, so
|
|
699
|
+
// the accumulator's onTerminalResult provides a third early-resolve path.
|
|
700
|
+
proc.on('exit', (code) => { exitCode = code; scheduleExitFallback(code); });
|
|
701
|
+
proc.on('close', (code) => { finalizeAndResolve(code); });
|
|
677
702
|
|
|
678
703
|
proc.on('error', (err) => {
|
|
679
|
-
if (
|
|
680
|
-
|
|
704
|
+
if (resolved) return;
|
|
705
|
+
resolved = true;
|
|
681
706
|
clearTimeout(timer);
|
|
682
|
-
if (exitSettleTimer) clearTimeout(exitSettleTimer);
|
|
683
707
|
clearTaskCompleteTimer();
|
|
708
|
+
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
684
709
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
685
710
|
shared.log('error', `LLM spawn error (${label}): ${err.message}`);
|
|
686
711
|
resolve({
|
|
@@ -726,8 +751,6 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
726
751
|
maxBudget, bare, fallbackModel,
|
|
727
752
|
...runtimeFeatureOpts,
|
|
728
753
|
});
|
|
729
|
-
let settled = false;
|
|
730
|
-
let exitSettleTimer = null;
|
|
731
754
|
let taskCompleteTimer = null;
|
|
732
755
|
const scheduleTaskCompleteClose = () => {
|
|
733
756
|
if (taskCompleteTimer) return;
|
|
@@ -739,6 +762,13 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
739
762
|
taskCompleteTimer = null;
|
|
740
763
|
}
|
|
741
764
|
};
|
|
765
|
+
let resolved = false;
|
|
766
|
+
let exitFallbackTimer = null;
|
|
767
|
+
let exitCode = null;
|
|
768
|
+
const scheduleExitFallback = (code) => {
|
|
769
|
+
if (resolved || exitFallbackTimer) return;
|
|
770
|
+
exitFallbackTimer = setTimeout(() => finalizeAndResolve(code), EXIT_DRAIN_FALLBACK_MS);
|
|
771
|
+
};
|
|
742
772
|
const acc = _createStreamAccumulator({
|
|
743
773
|
runtime,
|
|
744
774
|
maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
|
|
@@ -747,6 +777,10 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
747
777
|
onChunk,
|
|
748
778
|
onToolUse,
|
|
749
779
|
onTaskComplete: scheduleTaskCompleteClose,
|
|
780
|
+
// Terminal text from the runtime adapter signals the LLM has logically
|
|
781
|
+
// completed — kick the drain timer so we don't block on a delayed
|
|
782
|
+
// 'exit'/'close' when an inherited pipe keeps the parent's FDs open.
|
|
783
|
+
onTerminalResult: () => scheduleExitFallback(exitCode != null ? exitCode : 0),
|
|
750
784
|
onThinking: opts.onThinking || null,
|
|
751
785
|
});
|
|
752
786
|
|
|
@@ -757,16 +791,14 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
757
791
|
|
|
758
792
|
const timer = setTimeout(() => { shared.killImmediate(proc); }, timeout);
|
|
759
793
|
|
|
760
|
-
|
|
761
|
-
if (
|
|
762
|
-
|
|
794
|
+
const finalizeAndResolve = (code) => {
|
|
795
|
+
if (resolved) return;
|
|
796
|
+
resolved = true;
|
|
763
797
|
clearTimeout(timer);
|
|
764
|
-
if (exitSettleTimer) clearTimeout(exitSettleTimer);
|
|
765
798
|
clearTaskCompleteTimer();
|
|
799
|
+
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
766
800
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
767
801
|
const parsed = acc.finalize();
|
|
768
|
-
try { proc.stdout?.destroy(); } catch {}
|
|
769
|
-
try { proc.stderr?.destroy(); } catch {}
|
|
770
802
|
const durationMs = Date.now() - _startMs;
|
|
771
803
|
const usage = parsed.usage ? { ...parsed.usage, durationMs } : { durationMs };
|
|
772
804
|
const errInfo = code !== 0
|
|
@@ -783,23 +815,22 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
|
|
|
783
815
|
runtime: runtime.name,
|
|
784
816
|
errorClass: errInfo.code,
|
|
785
817
|
});
|
|
786
|
-
}
|
|
818
|
+
};
|
|
787
819
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
});
|
|
820
|
+
// 'close' fires after stdio streams close; if a detached grandchild
|
|
821
|
+
// inherited stdout, that can be delayed indefinitely. 'exit' fires when
|
|
822
|
+
// the child itself exits — schedule a short drain window then resolve.
|
|
823
|
+
// On Linux, 'exit' itself can be delayed by an inherited pipe handle, so
|
|
824
|
+
// the accumulator's onTerminalResult provides a third early-resolve path.
|
|
825
|
+
proc.on('exit', (code) => { exitCode = code; scheduleExitFallback(code); });
|
|
826
|
+
proc.on('close', (code) => { finalizeAndResolve(code); });
|
|
796
827
|
|
|
797
828
|
proc.on('error', (err) => {
|
|
798
|
-
if (
|
|
799
|
-
|
|
829
|
+
if (resolved) return;
|
|
830
|
+
resolved = true;
|
|
800
831
|
clearTimeout(timer);
|
|
801
|
-
if (exitSettleTimer) clearTimeout(exitSettleTimer);
|
|
802
832
|
clearTaskCompleteTimer();
|
|
833
|
+
if (exitFallbackTimer) { clearTimeout(exitFallbackTimer); exitFallbackTimer = null; }
|
|
803
834
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
804
835
|
shared.log('error', `LLM-stream spawn error (${label}): ${err.message}`);
|
|
805
836
|
resolve({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1744",
|
|
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"
|