@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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1744 (2026-05-06)
4
+
5
+ ### Fixes
6
+ - isolate meeting tests off live MEETINGS_DIR (#2111)
7
+
8
+ ### Other
9
+ - test(github): add unit tests for slug-backoff and verdict helpers (#2092)
10
+
3
11
  ## 0.1.1742 (2026-05-06)
4
12
 
5
13
  ### Fixes
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-06T01:07:10.189Z"
4
+ "cachedAt": "2026-05-06T01:39:28.821Z"
5
5
  }
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
- function finish(code) {
642
- if (settled) return;
643
- settled = true;
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
- proc.on('close', finish);
673
- proc.on('exit', (code) => {
674
- if (settled) return;
675
- exitSettleTimer = setTimeout(() => finish(code), LLM_EXIT_SETTLE_GRACE_MS);
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 (settled) return;
680
- settled = true;
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
- function finish(code) {
761
- if (settled) return;
762
- settled = true;
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
- proc.on('close', finish);
789
- proc.on('exit', (code) => {
790
- // 'close' waits for stdio to close. If the runtime spawned a detached
791
- // grandchild that inherited stdout/stderr, the OS pipe stays open and
792
- // 'close' may never fire. Fall back to 'exit' after a drain window.
793
- if (settled) return;
794
- exitSettleTimer = setTimeout(() => finish(code), LLM_EXIT_SETTLE_GRACE_MS);
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 (settled) return;
799
- settled = true;
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.1742",
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"