@yemi33/minions 0.1.2385 → 0.1.2387

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/dashboard.js CHANGED
@@ -3570,18 +3570,23 @@ function _getCcLiveStream(tabId) {
3570
3570
  function _touchCcLiveStream(state) {
3571
3571
  if (!state) return;
3572
3572
  state.updatedAt = Date.now();
3573
- // W-mpmwxni2000c25c7-b — every CC streaming progress event (onChunk,
3574
- // onToolUse, onToolUpdate in both _invokeCcStream paths) already routes
3575
- // through here for the stall detector. Piggy-back the per-turn watchdog's
3576
- // bumpTimer on the same heartbeat so a turn that's actively streaming
3577
- // tokens or running long tools can't be killed by stale inactivity. The
3578
- // watchdog installs `_bumpTimer` for the duration of the turn and removes
3579
- // it in `finally`, so late progress callbacks delivered after resolution
3580
- // are a no-op.
3573
+ // W-mpmwxni2000c25c7-b — every CC streaming progress event routes through
3574
+ // here for the stall detector. The direct path also reports startup and
3575
+ // reasoning events through callLLMStreaming.onProgress, while the pool path
3576
+ // reports chunks and tools. Piggy-back the per-turn watchdog's bumpTimer on
3577
+ // the same heartbeat so active work cannot be killed by stale inactivity.
3578
+ // The watchdog removes `_bumpTimer` in `finally`, so late callbacks are no-op.
3581
3579
  if (typeof state._bumpTimer === 'function') {
3582
3580
  try { state._bumpTimer(); } catch { /* swallow */ }
3583
3581
  }
3584
3582
  }
3583
+ function _resolveCcDirectStreamSessionId({ sessionId, hasImages, useWorkerPool }) {
3584
+ // ACP session IDs belong to the live worker process. A one-off image CLI
3585
+ // cannot resume that session while the worker owns it, so rely on the
3586
+ // transcript carryover already included in the direct prompt.
3587
+ if (hasImages && useWorkerPool) return null;
3588
+ return sessionId || null;
3589
+ }
3585
3590
  function _clearCcLiveTimers(tabId) {
3586
3591
  const state = _getCcLiveStream(tabId);
3587
3592
  if (!state) return;
@@ -9804,9 +9809,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
9804
9809
  // already-working --attachment path below, while image-less turns keep
9805
9810
  // using the fast warm pool.
9806
9811
  const hasImages = Array.isArray(images) && images.length > 0;
9807
- if (!hasImages && shared.resolveCcUseWorkerPool(engineConfig)) {
9812
+ const useWorkerPool = shared.resolveCcUseWorkerPool(engineConfig);
9813
+ if (!hasImages && useWorkerPool) {
9808
9814
  return _invokeCcStreamViaPool({ prompt, liveState, toolUses, model, effort, engineConfig, systemPrompt, tabId });
9809
9815
  }
9816
+ const directSessionId = _resolveCcDirectStreamSessionId({ sessionId, hasImages, useWorkerPool });
9810
9817
  const setStatus = (message) => {
9811
9818
  liveState.statusMessage = message;
9812
9819
  if (liveState.writer) liveState.writer({ type: 'status', message });
@@ -9819,10 +9826,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
9819
9826
  return callLLMStreaming(prompt, systemPrompt, {
9820
9827
  timeout: CC_CALL_TIMEOUT_MS, label: 'command-center', model, maxTurns,
9821
9828
  allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
9822
- sessionId, effort, direct: true,
9829
+ sessionId: directSessionId, effort, direct: true,
9823
9830
  engineConfig, images,
9831
+ onProgress: () => _touchCcLiveStream(liveState),
9824
9832
  onChunk: (text, segmentId) => {
9825
- _touchCcLiveStream(liveState);
9826
9833
  if (liveState.statusMessage) {
9827
9834
  setStatus('');
9828
9835
  }
@@ -9830,7 +9837,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
9830
9837
  if (liveState.writer) liveState.writer({ type: 'chunk', text, segmentId });
9831
9838
  },
9832
9839
  onToolUse: (name, input, id) => {
9833
- _touchCcLiveStream(liveState);
9834
9840
  if (liveState.statusMessage) {
9835
9841
  setStatus('');
9836
9842
  }
@@ -9842,7 +9848,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
9842
9848
  if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(input), id: id || null });
9843
9849
  },
9844
9850
  onToolUpdate: (id, status) => {
9845
- _touchCcLiveStream(liveState);
9846
9851
  // toolUses and liveState.tools share entry references — patch once so a
9847
9852
  // reconnect replays the resolved (green/red) state, not just pending.
9848
9853
  const entry = toolUses.find((e) => e && e.id === id);
@@ -10476,13 +10481,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10476
10481
  timeoutMs: turnTimeoutMs, label: 'command-center-stream',
10477
10482
  }, async (registerAbort, bumpTimer) => {
10478
10483
  // W-mpmwxni2000c25c7-b — no-progress semantics: install bumpTimer
10479
- // on liveState so _touchCcLiveStream (called from every onChunk /
10480
- // onToolUse / onToolUpdate in both _invokeCcStream paths) resets
10481
- // the watchdog on every liveness signal. A turn that's actively
10482
- // streaming tokens or running long tools survives indefinitely
10483
- // up to the outer CC_CALL_TIMEOUT_MS ceiling. Cleared in
10484
- // `finally` so a late progress event after resolution can't
10485
- // re-arm against a stale abort target.
10484
+ // on liveState so _touchCcLiveStream resets the watchdog on every
10485
+ // liveness signal. Direct calls include runtime startup/reasoning
10486
+ // events; pool calls include chunks and tool updates. A productive
10487
+ // turn survives up to the outer CC_CALL_TIMEOUT_MS ceiling. Cleared
10488
+ // in `finally` so late progress cannot re-arm a stale abort target.
10486
10489
  liveState._bumpTimer = bumpTimer;
10487
10490
  try {
10488
10491
  const llmPromise = _invokeCcStream({
@@ -14814,6 +14817,7 @@ module.exports = {
14814
14817
  _ccRuntimeNeedsResumeCarryover,
14815
14818
  _ccRuntimeNeedsResumeBookkeepingGuard,
14816
14819
  _joinCcPromptParts,
14820
+ _resolveCcDirectStreamSessionId, // exported for testing
14817
14821
  _captureApiRoutesMeta,
14818
14822
  _resetPublicApiRoutesCacheForTest,
14819
14823
  _formatCcApiRoutesIndex,
@@ -70,7 +70,7 @@ Any violation rejects the **whole turn** with a typed error envelope (`code: 'in
70
70
 
71
71
  **Worker-pool interaction (W-mray463s001zcee2).** The opt-in Copilot ACP worker pool (`engine.ccUseWorkerPool: true`, default ON for Copilot CC) is a **separate protocol path** from the table above — `engine/cc-worker-pool.js`'s `session/prompt` call only ever sends a single text content block (`[{ type: 'text', text: prompt }]`) and has no image/attachment content-block support today. This used to mean image turns were silently dropped whenever the pool was active, with a comment incorrectly blaming a Copilot capability gap (`imageInput:false`) that doesn't exist — Copilot's `imageInput` is `true` and fully supports images on the direct spawn path. The fix: `_invokeCcStream` (dashboard.js) now checks for a non-empty `images` array **before** routing through `resolveCcUseWorkerPool` — image-bearing turns bypass the warm pool for that turn only and cold-spawn a one-off CLI process via the working `--attachment` path (same as the non-pooled path always used), while image-less turns keep using the fast warm pool. If `engine/cc-worker-pool.js`'s ACP session protocol is later extended to carry attachments (Option A), this per-turn bypass in `_invokeCcStream` should be revisited/removed.
72
72
 
73
- Because that bypass can spend tens of seconds starting a one-off process before the first model token, the SSE stream shows a user-focused “Looking at the image…” status instead of exposing the cold-start implementation detail or leaving an apparently stalled spinner.
73
+ Because that bypass can spend tens of seconds starting a one-off process before the first model token, the SSE stream shows a user-focused “Looking at the image…” status instead of exposing the cold-start implementation detail or leaving an apparently stalled spinner. The one-off process does not resume the live worker's process-scoped ACP session ID: it starts a transport-compatible fresh session and receives prior conversation context through the existing transcript carryover. Every parsed direct-runtime event, including startup and reasoning events, resets the five-minute silence watchdog; the safety bound therefore still catches a truly silent process without killing an image turn that is making progress.
74
74
 
75
75
  `_resolveImageOpts` (in `engine/llm.js`) is pure and unit-tested: it forwards the `images` list only when `runtime.capabilities.imageInput` is truthy, otherwise returns the typed error so CC/doc-chat surface the envelope instead of a silently-text-only reply. `_spawnProcess` re-applies the same gate (`if (!caps.imageInput) adapterOpts.images = undefined`) as defense in depth. Image **filenames** are run through the untrusted-input fence (`buildSource('cc-image-filename', …)`) before they reach prompt text, since they are user-supplied.
76
76
 
package/engine/llm.js CHANGED
@@ -482,6 +482,7 @@ function _createStreamAccumulator({
482
482
  onTaskComplete = null,
483
483
  onTerminalResult = null,
484
484
  onThinking = null,
485
+ onProgress = null,
485
486
  }) {
486
487
  if (!runtime?.capabilities?.streamConsumer || typeof runtime.createStreamConsumer !== 'function') {
487
488
  throw new Error(`runtime ${runtime?.name || '<unknown>'} missing createStreamConsumer (capabilities.streamConsumer)`);
@@ -600,6 +601,11 @@ function _createStreamAccumulator({
600
601
  };
601
602
 
602
603
  const consumer = runtime.createStreamConsumer(ctx);
604
+ function consumeEvent(ev) {
605
+ if (!ev) return;
606
+ if (onProgress) onProgress(ev);
607
+ consumer.consume(ev);
608
+ }
603
609
 
604
610
  function ingestStdout(chunk) {
605
611
  const str = chunk == null ? '' : chunk.toString();
@@ -608,8 +614,7 @@ function _createStreamAccumulator({
608
614
  const lines = lineBuf.split('\n');
609
615
  lineBuf = lines.pop() || '';
610
616
  for (const line of lines) {
611
- const ev = runtime.parseStreamChunk(line);
612
- if (ev) consumer.consume(ev);
617
+ consumeEvent(runtime.parseStreamChunk(line));
613
618
  }
614
619
  }
615
620
 
@@ -619,10 +624,7 @@ function _createStreamAccumulator({
619
624
 
620
625
  function finalize() {
621
626
  const trimmed = lineBuf.trim();
622
- if (trimmed) {
623
- const ev = runtime.parseStreamChunk(trimmed);
624
- if (ev) consumer.consume(ev);
625
- }
627
+ if (trimmed) consumeEvent(runtime.parseStreamChunk(trimmed));
626
628
  if (!text && lastTaskCompleteSummary) text = lastTaskCompleteSummary;
627
629
  // Reconciliation: always let the runtime adapter re-parse the complete
628
630
  // stdout. Stream consumers emit live progress, but parseOutput() owns the
@@ -851,11 +853,14 @@ function callLLM(promptText, sysPromptText, opts = {}) {
851
853
  * onChunk(text, segmentIndex) is called for each assistant text block as it
852
854
  * arrives; segmentIndex increments across distinct text blocks so consumers can
853
855
  * keep them visually separate instead of welding them together.
856
+ * onProgress(event) is called for every parsed runtime event, including startup
857
+ * and reasoning events that do not produce a text or tool callback.
854
858
  */
855
859
  function callLLMStreaming(promptText, sysPromptText, opts = {}) {
856
860
  const {
857
861
  timeout = 120000, label = 'llm', maxTurns = 1, allowedTools = '',
858
862
  sessionId = null, onChunk = () => {}, onToolUse = null, onToolUpdate = null,
863
+ onProgress = null,
859
864
  effort = null, direct = false,
860
865
  model: modelOverride, cli: cliOverride, engineConfig,
861
866
  maxBudget, bare, fallbackModel,
@@ -903,6 +908,7 @@ function callLLMStreaming(promptText, sysPromptText, opts = {}) {
903
908
  // 'exit'/'close' when an inherited pipe keeps the parent's FDs open.
904
909
  onTerminalResult: () => scheduleExitFallback(exitCode != null ? exitCode : 0),
905
910
  onThinking: opts.onThinking || null,
911
+ onProgress,
906
912
  });
907
913
 
908
914
  _abort = () => { shared.killImmediate(proc); };
package/minions.js CHANGED
@@ -423,11 +423,13 @@ async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
423
423
  config.engine.defaultCli = detected[0];
424
424
  console.log(`\n ✓ Detected ${detected[0]} CLI — set as fleet default runtime`);
425
425
  } else if (detected.length > 1) {
426
- // Both available prefer claude (the historical default and broader skill coverage)
427
- config.engine.defaultCli = detected.includes('claude') ? 'claude' : detected[0];
426
+ // Prefer the canonical fleet default when it is installed.
427
+ config.engine.defaultCli = detected.includes(ENGINE_DEFAULTS.defaultCli)
428
+ ? ENGINE_DEFAULTS.defaultCli
429
+ : detected[0];
428
430
  console.log(`\n ✓ Detected ${detected.join(' + ')} — fleet default set to ${config.engine.defaultCli}`);
429
431
  }
430
- // If nothing detected, leave defaultCli unset (engine falls back to 'claude')
432
+ // If nothing is detected, leave defaultCli unset so the engine uses its canonical default.
431
433
  }
432
434
  saveConfig(config);
433
435
  console.log(`\n Minions initialized at ${MINIONS_HOME}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2385",
3
+ "version": "0.1.2387",
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"