@yemi33/minions 0.1.1912 → 0.1.1914

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/README.md CHANGED
@@ -565,14 +565,12 @@ Engine behavior is controlled via `config.json`. Key settings:
565
565
  | `autoDecompose` | true | Auto-decompose `implement:large` items into sub-tasks before dispatch |
566
566
  | `autoApprovePlans` | false | Auto-approve PRDs without waiting for human approval |
567
567
  | `evalLoop` | true | Auto-dispatch review → fix cycles after implementation completes |
568
- | `evalMaxIterations` | 3 | Max minion review → fix cycles before pausing minion review automation; human feedback, build fixes, and conflict fixes continue |
569
568
  | `evalMaxCost` | null | USD ceiling per work item across all eval iterations (null = no limit) |
570
569
  | `meetingRoundTimeout` | 600000 (10min) | Timeout per meeting round before auto-advance |
571
570
  | `ccModel` | `sonnet` | Model for Command Center and doc-chat (sonnet/haiku/opus) |
572
571
  | `ccEffort` | null | Effort level for CC/doc-chat (null/low/medium/high) |
573
572
  | `agentEffort` | null | Override effort level for all agent dispatches |
574
573
  | `maxTurnsByType` | `{}` | Per-type max-turns override (e.g., `{"explore": 40, "fix": 100}`) |
575
- | `maxBuildFixAttempts` | 3 | Max auto-fix dispatches per PR before escalating |
576
574
 
577
575
  Per-type max-turns defaults (when `maxTurnsByType` not set): explore=30, ask=20, review=30, plan=30, decompose=15, plan-to-prd=20, implement=75, fix=75, test=50, verify=100. Configurable from the Settings page.
578
576
 
@@ -230,6 +230,7 @@ async function openSettings() {
230
230
  settingsToggle('Copilot: suppress AGENTS.md', 'set-copilotSuppressAgentsMd', e.copilotSuppressAgentsMd !== false, '--no-custom-instructions: stops AGENTS.md auto-load from fighting Minions playbook prompts') +
231
231
  settingsToggle('Copilot: reasoning summaries', 'set-copilotReasoningSummaries', !!e.copilotReasoningSummaries, '--enable-reasoning-summaries (Anthropic-family models only)') +
232
232
  settingsToggle('Disable model discovery', 'set-disableModelDiscovery', !!e.disableModelDiscovery, 'Skip /api/runtimes/<name>/models REST calls fleet-wide. Settings UI falls back to free-text.') +
233
+ settingsToggle('Use persistent Copilot worker pool (faster CC responses)', 'set-ccUseWorkerPool', !!e.ccUseWorkerPool, 'Experimental — sub-task C of W-mp2w003600196c51 (CC perf). When ON, Command Center routes through engine/cc-worker-pool.js (one persistent `copilot --acp` process per CC tab) instead of spawning a fresh CLI per turn. Saves ~14s of cold-start cost on warm follow-up turns. Engine/agent dispatch path is unchanged. Off by default.') +
233
234
  '</div>' +
234
235
  '<div style="display:grid;grid-template-columns:1fr 3fr;gap:8px;margin-top:8px">' +
235
236
  '<div>' +
@@ -625,6 +626,7 @@ async function saveSettings() {
625
626
  copilotReasoningSummaries: !!document.getElementById('set-copilotReasoningSummaries')?.checked,
626
627
  maxBudgetUsd: (document.getElementById('set-maxBudgetUsd')?.value ?? '').trim(),
627
628
  disableModelDiscovery: !!document.getElementById('set-disableModelDiscovery')?.checked,
629
+ ccUseWorkerPool: !!document.getElementById('set-ccUseWorkerPool')?.checked,
628
630
  maxTurnsByType: (function() {
629
631
  var mbt = {};
630
632
  var types = ['explore', 'ask', 'review', 'implement', 'fix', 'test', 'verify', 'plan', 'decompose'];
package/dashboard.js CHANGED
@@ -32,6 +32,7 @@ const dispatchMod = require('./engine/dispatch');
32
32
  const steering = require('./engine/steering');
33
33
  const projectDiscovery = require('./engine/project-discovery');
34
34
  const features = require('./engine/features');
35
+ const ccWorkerPool = require('./engine/cc-worker-pool');
35
36
  const os = require('os');
36
37
 
37
38
  const { safeRead, safeReadDir, safeWrite, safeJson, safeJsonObj, safeJsonArr, safeJsonNoRestore, safeUnlink, mutateJsonFileLocked, mutateTextFileLocked, mutateControl, mutateCooldowns, mutateWorkItems, getProjects: _getProjects, DONE_STATUSES, WI_STATUS, WORK_TYPE, reopenWorkItem } = shared;
@@ -5826,6 +5827,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5826
5827
  const abort = ccInFlightAborts.get(tabId);
5827
5828
  if (abort) { try { abort(); } catch {} }
5828
5829
  }
5830
+ // Sub-task C of W-mp2w003600196c51: when the worker pool is on, abort
5831
+ // must also fire `session/cancel` on the persistent ACP process so the
5832
+ // remote daemon stops generating into a torn-down session. The pool
5833
+ // exposes cancellation via the SessionHandle returned from getSession;
5834
+ // we don't keep that handle around here, so route through closeTab to
5835
+ // both cancel inflight and tear down the worker (cheaper than tracking
5836
+ // per-tab handles in dashboard state, and matches "tab close" semantics
5837
+ // — if the user explicitly aborted, we don't owe them a warm process).
5838
+ // Off when the flag is off so legacy SIGTERM-only behavior is preserved.
5839
+ if (CONFIG.engine && CONFIG.engine.ccUseWorkerPool) {
5840
+ try { ccWorkerPool.closeTab(tabId); } catch { /* swallow */ }
5841
+ }
5829
5842
  _clearCcLiveStream(tabId);
5830
5843
  _releaseCCTab(tabId);
5831
5844
  return jsonReply(res, 200, { ok: true });
@@ -5846,6 +5859,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5846
5859
  const sessions = _filterCcTabSessions(raw);
5847
5860
  return sessions.filter(s => s.id !== id);
5848
5861
  }, { defaultValue: [] });
5862
+ // Sub-task C of W-mp2w003600196c51: tear down the persistent ACP worker
5863
+ // for this tab so we don't leak a Copilot process after the user closes
5864
+ // the tab. closeTab is a no-op when the pool has no entry for the tabId,
5865
+ // so it's safe to call regardless of whether the flag is on.
5866
+ try { ccWorkerPool.closeTab(id); } catch { /* swallow */ }
5849
5867
  return jsonReply(res, 200, { ok: true });
5850
5868
  }
5851
5869
 
@@ -5875,6 +5893,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5875
5893
  if (body.sessionId && ccSession._promptHash && ccSession._promptHash !== _ccPromptHash) {
5876
5894
  ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
5877
5895
  sessionReset = true;
5896
+ // Sub-task C of W-mp2w003600196c51: drop the persistent ACP worker
5897
+ // for this tab so the next turn rebuilds against the new prompt.
5898
+ // The pool's getSession() handles systemPromptHash deltas via
5899
+ // newSession() (warm-process reuse), but evicting the tab here is
5900
+ // belt-and-suspenders — matches the spec's "call closeTab on
5901
+ // _ccPromptHash change" requirement and matches the way we just
5902
+ // dropped ccSession entirely.
5903
+ try { ccWorkerPool.closeTab(tabId); } catch { /* swallow */ }
5878
5904
  }
5879
5905
  const wasResume = !!(body.sessionId && body.sessionId === ccSession.sessionId && ccSessionValid());
5880
5906
 
@@ -5943,8 +5969,21 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5943
5969
  * onChunk/onToolUse shape — only `sessionId` differs (set on
5944
5970
  * initial call, undefined on retry). Hoisted to keep the two call sites
5945
5971
  * in lock-step.
5972
+ *
5973
+ * Sub-task C of W-mp2w003600196c51 (CC perf): when
5974
+ * `engineConfig.ccUseWorkerPool` is true the call routes through
5975
+ * engine/cc-worker-pool.js (`copilot --acp`, one persistent process per
5976
+ * CC tab) instead of spawning a fresh CLI per turn. The pool path
5977
+ * preserves the existing onChunk/onToolUse/result shape so callers
5978
+ * (handleCommandCenterStream, retry path) need no change. Engine/agent
5979
+ * dispatch path is intentionally NOT routed through the pool; that
5980
+ * lives on the per-dispatch _spawnProcess model in engine.js (regression
5981
+ * test enforces engine.js does not import cc-worker-pool).
5946
5982
  */
5947
- function _invokeCcStream({ prompt, sessionId, liveState, toolUses, model, effort, maxTurns, engineConfig, systemPrompt = CC_STATIC_SYSTEM_PROMPT }) {
5983
+ function _invokeCcStream({ prompt, sessionId, liveState, toolUses, model, effort, maxTurns, engineConfig, systemPrompt = CC_STATIC_SYSTEM_PROMPT, tabId }) {
5984
+ if (engineConfig && engineConfig.ccUseWorkerPool) {
5985
+ return _invokeCcStreamViaPool({ prompt, liveState, model, effort, engineConfig, systemPrompt, tabId });
5986
+ }
5948
5987
  const { callLLMStreaming } = require('./engine/llm');
5949
5988
  return callLLMStreaming(prompt, systemPrompt, {
5950
5989
  timeout: CC_CALL_TIMEOUT_MS, label: 'command-center', model, maxTurns,
@@ -5965,6 +6004,87 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5965
6004
  });
5966
6005
  }
5967
6006
 
6007
+ /**
6008
+ * Pool-routed implementation of _invokeCcStream. Keeps the public contract
6009
+ * identical: returns a Promise that resolves to a result envelope shaped like
6010
+ * callLLMStreaming's (`{ text, sessionId, code, usage, raw, stderr,
6011
+ * missingRuntime }`) and exposes an `.abort` property so the SSE handler can
6012
+ * cancel the in-flight stream by calling `_ccStreamAbort()`.
6013
+ *
6014
+ * Differences vs the direct path:
6015
+ * - Pool's `onChunk(text)` from agent_message_chunk is a DELTA, but
6016
+ * callLLMStreaming's contract is "full accumulated text"; we accumulate
6017
+ * here so `liveState.text` and downstream chunk events keep the same
6018
+ * semantics consumers already depend on.
6019
+ * - Tool calls are not surfaced in sub-task B (the pool ignores
6020
+ * `tool_call` notifications). `toolUses` stays empty on this path; if
6021
+ * sub-task C/D adds tool_call surfacing in the pool we'll plumb a
6022
+ * callback here too.
6023
+ * - `usage` is reported as an empty object — ACP doesn't expose token
6024
+ * counts in the in-flight session/update notifications, and the pool's
6025
+ * long-lived process makes per-turn usage attribution non-trivial.
6026
+ * trackEngineUsage is a no-op on `{}`.
6027
+ */
6028
+ function _invokeCcStreamViaPool({ prompt, liveState, model, effort, engineConfig, systemPrompt, tabId }) {
6029
+ const resolvedTabId = tabId || 'default';
6030
+ let cancelled = false;
6031
+ let accumulated = '';
6032
+ let sessionHandle = null;
6033
+ let resolveResult;
6034
+ const promise = new Promise((resolve) => { resolveResult = resolve; });
6035
+ promise.abort = () => {
6036
+ cancelled = true;
6037
+ try { sessionHandle && sessionHandle.cancel(); } catch { /* swallow */ }
6038
+ };
6039
+ (async () => {
6040
+ try {
6041
+ sessionHandle = await ccWorkerPool.getSession({
6042
+ tabId: resolvedTabId,
6043
+ model,
6044
+ effort,
6045
+ mcpServers: (engineConfig && engineConfig.mcpServers) || [],
6046
+ systemPromptHash: _ccPromptHash,
6047
+ });
6048
+ } catch (err) {
6049
+ return resolveResult({
6050
+ text: '',
6051
+ sessionId: null,
6052
+ code: 1,
6053
+ usage: {},
6054
+ raw: '',
6055
+ stderr: String((err && err.message) || err || 'cc-worker-pool spawn failed'),
6056
+ });
6057
+ }
6058
+ if (cancelled) {
6059
+ try { sessionHandle.cancel(); } catch { /* swallow */ }
6060
+ return resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
6061
+ }
6062
+ await sessionHandle.stream(prompt, {
6063
+ systemPromptText: systemPrompt,
6064
+ onChunk: (delta) => {
6065
+ accumulated += delta;
6066
+ _touchCcLiveStream(liveState);
6067
+ liveState.text = accumulated;
6068
+ if (liveState.writer) liveState.writer({ type: 'chunk', text: accumulated });
6069
+ },
6070
+ onDone: () => {
6071
+ resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
6072
+ },
6073
+ onError: (err) => {
6074
+ resolveResult({
6075
+ text: accumulated,
6076
+ sessionId: sessionHandle.sessionId,
6077
+ code: cancelled ? 0 : 1,
6078
+ usage: {},
6079
+ raw: accumulated,
6080
+ stderr: String((err && err.message) || err || 'cc-worker-pool stream error'),
6081
+ });
6082
+ },
6083
+ });
6084
+ })();
6085
+ return promise;
6086
+ }
6087
+
5968
6088
  async function handleCommandCenterStream(req, res) {
5969
6089
  // SSE Origin gate (belt-and-suspenders: the top-level dispatcher has
5970
6090
  // already rejected disallowed origins on POST, but validate again here
@@ -6106,6 +6226,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6106
6226
  tabSessionId = null;
6107
6227
  sessionReset = true;
6108
6228
  sessionResetReason = 'promptChanged';
6229
+ // Sub-task C of W-mp2w003600196c51: matched dashboard reload with
6230
+ // a new prompt template — evict the persistent ACP worker so the
6231
+ // next turn rebuilds against the new prompt. (See same hook in
6232
+ // handleCommandCenter above.)
6233
+ try { ccWorkerPool.closeTab(body.tabId || 'default'); } catch { /* swallow */ }
6109
6234
  } else if (tabEntry.runtime && tabEntry.runtime !== currentRuntime) {
6110
6235
  tabSessionId = null;
6111
6236
  sessionReset = true;
@@ -6149,6 +6274,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6149
6274
  model: streamModel, effort: streamEffort, maxTurns: ccMaxTurns,
6150
6275
  engineConfig: CONFIG.engine,
6151
6276
  systemPrompt: turnSystemPrompt,
6277
+ tabId,
6152
6278
  });
6153
6279
  _ccStreamAbort = llmPromise.abort;
6154
6280
  liveState.abortFn = _ccStreamAbort;
@@ -6174,6 +6300,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6174
6300
  model: streamModel, effort: streamEffort, maxTurns: ccMaxTurns,
6175
6301
  engineConfig: CONFIG.engine,
6176
6302
  systemPrompt: turnSystemPrompt,
6303
+ tabId,
6177
6304
  });
6178
6305
  _ccStreamAbort = retryPromise.abort;
6179
6306
  liveState.abortFn = _ccStreamAbort;
@@ -34,7 +34,7 @@ How the engine manages the lifecycle of a PR from creation through review, fix,
34
34
 
35
35
  When multiple problems coexist, earlier triggers get the first chance to enqueue work. The local `fixDispatched` flag is declared before the fix triggers and set after human-feedback, review-feedback, and build-failure dispatches. Conflict fixes run last and explicitly require `!fixDispatched`, so any earlier successful fix dispatch suppresses the conflict fix for that PR in the same discovery pass. Build fixes are evaluated after human and minion review feedback, but the build-fix condition itself is not gated by `!fixDispatched`.
36
36
 
37
- `evalMaxIterations` only applies to the minion review loop: initial minion review, minion re-review, and minion review-feedback fixes. It does not gate human-feedback fixes, build-failure fixes, or merge-conflict fixes.
37
+ The engine does not cap review→fix cycles or build-fix attempts. Each trigger evaluates its own gates on every discovery pass; loops stop only when the underlying condition clears (reviewer approves, build passes, conflict resolves, human feedback handled). Operators who need to halt automation on a runaway PR should disable the relevant feature flag (`evalLoop`, `autoFixBuilds`, `autoFixConflicts`) or close the PR.
38
38
 
39
39
  ### A. Human comments (`humanFeedback.pendingFix`)
40
40
 
@@ -42,16 +42,14 @@ When multiple problems coexist, earlier triggers get the first chance to enqueue
42
42
  - Agent comments filtered out via `/\bMinions\s*\(/i` regex on comment body
43
43
  - Coalesces multiple comments arriving during cooldown into single fix
44
44
  - Routes to author
45
- - Not gated by `_evalEscalated` — humans can always force more fixes via PR comments even after the minion review loop escalates.
46
45
  - Fix agents must treat human comments as claims to verify, not commands. They inspect or reproduce each claimed issue, make the smallest correct fix only when the claim is valid, and otherwise reply with evidence-backed rationale.
47
46
 
48
47
  ### B. Review feedback (`changes-requested`)
49
48
 
50
- - Gate: `reviewStatus === 'changes-requested'` + `!awaitingReReview` + `!evalEscalated` + not dispatched + not on cooldown
49
+ - Gate: `reviewStatus === 'changes-requested'` + `!awaitingReReview` + not dispatched + not on cooldown
51
50
  - Routes to PR author via `_author_` routing token
52
51
  - `review_note` = reviewer's feedback
53
52
  - Sets `fixDispatched = true` — prevents the later conflict fix from also firing this pass
54
- - **Review-loop escalation**: after `evalMaxIterations` review→fix cycles (default 3), `_evalEscalated` is set on the PR and *only this trigger plus minion review/re-review* stop. Triggers A (human comments), C (build failures), and D (merge conflicts) keep running. The dashboard PR row distinguishes the two states with separate badges (review badge `review-escalated` vs. build badge `build-escalated`).
55
53
  - Fix agents validate each requested-change claim before editing. Invalid, stale, already-addressed, out-of-scope, or harmful feedback should get a respectful evidence-backed rebuttal rather than a success-shaped code change.
56
54
 
57
55
  ### C. Build failures (`buildStatus === 'failing'`)
@@ -59,8 +57,6 @@ When multiple problems coexist, earlier triggers get the first chance to enqueue
59
57
  - Gate: provider polling enabled (`adoPollEnabled` or `ghPollEnabled`) + `autoFixBuilds` + `buildStatus === 'failing'` + grace period expired
60
58
  - **Grace period** (`_buildFixPushedAt`): after fix dispatches, waits `buildFixGracePeriod` (default 10min, configurable in `ENGINE_DEFAULTS`) for CI to run before re-dispatching. Cleared when poller detects build status transition (CI actually ran).
61
59
  - **Error logs**: GitHub fetches annotations (failures only, not warnings) + Actions job log (always). ADO queries builds API directly (not status checks), fetches build timeline → failed task logs (up to 10 per build, up to 10 failing pipelines).
62
- - **Build-fix escalation**: after 3 failed attempts, writes an inbox alert, sets `buildFixEscalated = true`, and stops *only this trigger* (auto-dispatch for build fixes). The counter resets when the build recovers. Independent of `_evalEscalated`.
63
- - Not gated by `_evalEscalated` — build-fix is mechanical and runs even if the review loop has escalated.
64
60
  - Sets `fixDispatched = true` after dispatch so the later conflict trigger is suppressed in the same pass.
65
61
 
66
62
  ### D. Merge conflicts (`_mergeConflict`)
@@ -88,7 +84,7 @@ When multiple problems coexist, earlier triggers get the first chance to enqueue
88
84
  - Fix agent pushes → `_buildFixPushedAt` stamped
89
85
  - Poller detects new commit → CI starts → `buildStatus` transitions (`failing` → `running`)
90
86
  - `_buildFixPushedAt` cleared on any transition
91
- - If CI passes → `buildFixAttempts` reset, `buildErrorLog` cleared → done
87
+ - If CI passes → `buildErrorLog` cleared → done
92
88
  - If CI fails again → fresh error logs fetched → new fix dispatches immediately (grace already cleared by transition)
93
89
 
94
90
  ## Race prevention
@@ -125,10 +121,6 @@ When multiple problems coexist, earlier triggers get the first chance to enqueue
125
121
  | `reviewStatus` | Poller + post-completion | `pending` / `approved` / `changes-requested` / `waiting` |
126
122
  | `buildStatus` | Poller | `none` / `passing` / `failing` / `running` |
127
123
  | `buildErrorLog` | Poller | Actual CI error output for fix agents |
128
- | `buildFixAttempts` | Discovery (on dispatch) | Counter for build-fix escalation cap |
129
- | `buildFixEscalated` | Discovery (on cap) | Stops *build-fix* auto-dispatch only (review/re-review and other fix triggers continue) |
130
- | `_reviewFixCycles` | Discovery (on dispatch) | Counter for review→fix cycle cap (`evalMaxIterations`) |
131
- | `_evalEscalated` | Discovery (on cap) | Stops *review/re-review and review-feedback fix* auto-dispatch only (build-fix, conflict-fix, and human-feedback fix continue). Cleared when reviewer eventually approves the PR. |
132
124
  | `_buildFixPushedAt` | Discovery (on dispatch) | Grace period timestamp |
133
125
  | `_buildFailNotified` | Discovery | Dedup for inbox alert |
134
126
  | `lastPushedAt` | Poller (new commit) | Tracks latest push for re-review logic |
package/engine/cli.js CHANGED
@@ -992,6 +992,7 @@ const commands = {
992
992
  'claudeBareMode', 'claudeFallbackModel',
993
993
  'copilotDisableBuiltinMcps', 'copilotSuppressAgentsMd', 'copilotStreamMode', 'copilotReasoningSummaries',
994
994
  'maxBudgetUsd', 'disableModelDiscovery',
995
+ 'ccUseWorkerPool',
995
996
  ];
996
997
  const activeFlags = [];
997
998
  for (const f of flagFields) {
package/engine/shared.js CHANGED
@@ -1119,6 +1119,7 @@ const ENGINE_DEFAULTS = {
1119
1119
  copilotSuppressAgentsMd: true, // Copilot --no-custom-instructions: stop AGENTS.md auto-load from fighting Minions playbook prompts
1120
1120
  copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
1121
1121
  copilotReasoningSummaries: false, // Copilot --enable-reasoning-summaries (Anthropic-family models only)
1122
+ ccUseWorkerPool: false, // Sub-task C of W-mp2w003600196c51 (CC perf): when true, _invokeCcStream routes through engine/cc-worker-pool.js (persistent `copilot --acp` per CC tab) instead of spawning a fresh CLI per turn. Off by default — opt-in feature flag. Engine/agent dispatch path stays per-process regardless.
1122
1123
  maxBudgetUsd: undefined, // fleet USD ceiling for --max-budget-usd (per-agent override: agents.<id>.maxBudgetUsd). Honors 0 via ?? so a literal cap of $0 works
1123
1124
  disableModelDiscovery: false, // skip runtime.listModels() REST calls fleet-wide (settings UI falls back to free-text)
1124
1125
  maxPendingContexts: 20, // cap pendingContexts arrays in cooldowns.json to prevent unbounded growth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1912",
3
+ "version": "0.1.1914",
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"
@@ -1,5 +0,0 @@
1
- {
2
- "runtime": "copilot",
3
- "models": null,
4
- "cachedAt": "2026-05-13T19:11:26.190Z"
5
- }