@yemi33/minions 0.1.1587 → 0.1.1589

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/engine/shared.js CHANGED
@@ -641,72 +641,35 @@ function gitEnv() {
641
641
  return { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' };
642
642
  }
643
643
 
644
- // ── Claude Output Parsing ───────────────────────────────────────────────────
644
+ // ── Stream-JSON Output Parsing (runtime-aware delegator) ────────────────────
645
645
 
646
646
  /**
647
- * Parse stream-json output from claude CLI. Returns { text, usage }.
648
- * Single source of truth — used by llm.js, consolidation.js, and lifecycle.js.
647
+ * Parse stream-json output from a CLI runtime. Returns { text, usage, sessionId, model }.
648
+ *
649
+ * As of P-7e3a8b1c this is a thin delegator over the runtime adapter registry —
650
+ * the actual parsing logic lives in `engine/runtimes/<name>.parseOutput()`.
651
+ * Kept on `shared` for backward compat with all existing callers (llm.js,
652
+ * consolidation.js, lifecycle.js, meeting.js, timeout.js).
653
+ *
654
+ * Signatures supported:
655
+ * parseStreamJsonOutput(raw)
656
+ * parseStreamJsonOutput(raw, optsObj) ← legacy form (engine/llm.js still uses this)
657
+ * parseStreamJsonOutput(raw, runtimeName)
658
+ * parseStreamJsonOutput(raw, runtimeName, optsObj)
659
+ *
660
+ * `runtimeName` defaults to `'claude'`. Unknown runtime names throw via the
661
+ * registry — surfaces misconfiguration immediately at the parse site.
649
662
  */
650
- function parseStreamJsonOutput(raw, { maxTextLength = 0 } = {}) {
651
- let text = '';
652
- let usage = null;
653
- let sessionId = null;
654
- let model = null;
655
-
656
- function extractResult(obj) {
657
- if (obj.type !== 'result') return false;
658
- // Slice from the tail, not the head — review VERDICTs, structured completion
659
- // blocks, PR URLs, and agent conclusions all appear at the END of the output.
660
- // Head-slicing truncated VERDICTs and caused review work items to be
661
- // re-dispatched up to maxRetries times despite successful completion (#1234).
662
- if (obj.result) text = maxTextLength ? obj.result.slice(-maxTextLength) : obj.result;
663
- if (obj.session_id) sessionId = obj.session_id;
664
- if (obj.total_cost_usd || obj.usage) {
665
- usage = {
666
- costUsd: obj.total_cost_usd || 0,
667
- inputTokens: obj.usage?.input_tokens || 0,
668
- outputTokens: obj.usage?.output_tokens || 0,
669
- cacheRead: obj.usage?.cache_read_input_tokens || obj.usage?.cacheReadInputTokens || 0,
670
- cacheCreation: obj.usage?.cache_creation_input_tokens || obj.usage?.cacheCreationInputTokens || 0,
671
- durationMs: obj.duration_ms || 0,
672
- numTurns: obj.num_turns || 0,
673
- };
674
- }
675
- return true;
676
- }
677
-
678
- const lines = raw.split('\n');
679
- // Scan forward for model from init message (appears early in output)
680
- for (let i = 0; i < Math.min(lines.length, 10); i++) {
681
- const line = lines[i].trim();
682
- if (!line || !line.startsWith('{')) continue;
683
- try {
684
- const obj = JSON.parse(line);
685
- if (obj.type === 'system' && obj.subtype === 'init' && obj.model) { model = obj.model; break; }
686
- } catch {}
687
- }
688
- // Scan backward for result (appears at end of output)
689
- for (let i = lines.length - 1; i >= 0; i--) {
690
- const line = lines[i].trim();
691
- if (!line) continue;
692
- // Handle JSON array format (--output-format json)
693
- if (line.startsWith('[')) {
694
- try {
695
- const arr = JSON.parse(line);
696
- for (let j = arr.length - 1; j >= 0; j--) {
697
- if (extractResult(arr[j])) break;
698
- }
699
- if (text || usage) break;
700
- } catch {}
701
- }
702
- // Handle newline-delimited format (--output-format stream-json)
703
- if (line.startsWith('{')) {
704
- try {
705
- if (extractResult(JSON.parse(line))) break;
706
- } catch {}
707
- }
663
+ function parseStreamJsonOutput(raw, runtimeName, opts) {
664
+ // Backward-compat: callers passing `(raw, optsObject)` — second arg is opts, not name
665
+ if (runtimeName != null && typeof runtimeName === 'object') {
666
+ opts = runtimeName;
667
+ runtimeName = undefined;
708
668
  }
709
- return { text, usage, sessionId, model };
669
+ // Lazy require to avoid a circular dep at module init (runtimes/claude.js
670
+ // doesn't import shared, but downstream adapters might).
671
+ const { resolveRuntime } = require('./runtimes');
672
+ return resolveRuntime(runtimeName).parseOutput(raw, opts || {});
710
673
  }
711
674
 
712
675
  // ── Knowledge Base ──────────────────────────────────────────────────────────
@@ -776,8 +739,25 @@ const ENGINE_DEFAULTS = {
776
739
  prMergeMethod: 'squash', // merge method: squash, merge, rebase
777
740
  ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
778
741
  agentBusyReassignMs: 600000, // 10min — reassign work item to another agent if preferred agent is busy beyond this threshold
779
- ccModel: 'sonnet', // model for Command Center and doc-chat (sonnet, haiku, opus)
780
742
  ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
743
+
744
+ // ── Runtime fleet (P-3b8e5f1d) ──────────────────────────────────────────────
745
+ // Single source of truth for which CLI runtime + model every spawn uses.
746
+ // Engine code MUST go through the resolveAgent*/resolveCc* helpers below;
747
+ // never read these fields directly. New runtimes are added by registering
748
+ // an adapter in engine/runtimes/index.js — these defaults stay stable.
749
+ defaultCli: 'claude', // fleet-wide CLI runtime (must be a key in engine/runtimes/index.js)
750
+ defaultModel: undefined, // fleet-wide model; undefined = let the runtime adapter pick its own default
751
+ ccCli: undefined, // CC/doc-chat CLI override; undefined = inherit defaultCli (independent of agent path)
752
+ ccModel: undefined, // CC/doc-chat model override; undefined = inherit defaultModel
753
+ claudeBareMode: false, // Claude --bare: suppress CLAUDE.md auto-discovery (per-agent override: agents.<id>.bareMode)
754
+ claudeFallbackModel: undefined,// Claude --fallback-model on rate-limit / overload (Claude-only)
755
+ copilotDisableBuiltinMcps: true, // Copilot --disable-builtin-mcps: keep github-mcp-server out so it can't bypass pull-requests.json tracking
756
+ copilotSuppressAgentsMd: true, // Copilot --no-custom-instructions: stop AGENTS.md auto-load from fighting Minions playbook prompts
757
+ copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
758
+ copilotReasoningSummaries: false, // Copilot --enable-reasoning-summaries (Anthropic-family models only)
759
+ 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
760
+ disableModelDiscovery: false, // skip runtime.listModels() REST calls fleet-wide (settings UI falls back to free-text)
781
761
  heartbeatTimeouts: {}, // populated after WORK_TYPE is defined (below)
782
762
  maxPendingContexts: 20, // cap pendingContexts arrays in cooldowns.json to prevent unbounded growth
783
763
  maxPendingContextEntryBytes: 256 * 1024, // 256 KB — cap each pendingContexts entry to prevent huge PR comments from bloating cooldowns.json
@@ -797,6 +777,10 @@ const ENGINE_DEFAULTS = {
797
777
  maxMeetingHumanNotesBytes: 2 * 1024, // cap human note bullet lists injected into meeting prompts
798
778
  maxPipelineMeetingContextBytes: 16 * 1024, // cap aggregated meeting/dependency context for pipeline plan generation
799
779
  notesArchiveMaxFiles: 2000, // keep notes/archive bounded during periodic cleanup
780
+ // Backward-compat: keep `engine.claude.*` field family deprecation tracker. Listed here so preflight
781
+ // knows which subkeys to flag as deprecated. Do not consume `claude.*` in new code — use the runtime
782
+ // adapter system (engine/runtimes/) and the resolveAgent*/resolveCc* helpers instead.
783
+ _deprecatedConfigClaudeFields: ['binary', 'outputFormat', 'allowedTools', 'maxTurns', 'effort', 'budgetCap'],
800
784
  // Teams integration — config.teams shape: { enabled, appId, appPassword, certPath, privateKeyPath, tenantId, notifyEvents, ccMirror, inboxPollInterval }
801
785
  // Auth modes: (1) appId + appPassword (client secret), or (2) appId + certPath + privateKeyPath + tenantId (certificate)
802
786
  teams: {
@@ -812,6 +796,254 @@ const ENGINE_DEFAULTS = {
812
796
  },
813
797
  };
814
798
 
799
+ // ─── Runtime Fleet Resolution (P-3b8e5f1d) ──────────────────────────────────
800
+ //
801
+ // Six helpers that are the single source of truth for "which CLI runtime + model
802
+ // + budget + bare-mode applies to this spawn?". Engine code MUST go through
803
+ // these — never read `agent.cli`, `engine.defaultCli`, etc. directly. Future
804
+ // agents adding new resolution rules should extend these helpers, not bypass
805
+ // them.
806
+ //
807
+ // Independence rule: the agent path (`resolveAgent*`) and the CC path
808
+ // (`resolveCc*`) do not fall through to each other. CC overrides via
809
+ // `engine.ccCli` / `engine.ccModel` are CC-only. Per-agent overrides via
810
+ // `agents.<id>.cli` / `agents.<id>.model` are agent-only. A user who wants
811
+ // fleet-wide change sets `engine.defaultCli` / `engine.defaultModel`.
812
+ //
813
+ // Empty strings (`''`) are treated as "unset" so the dashboard's "Default
814
+ // (CLI chooses)" option (which submits an empty string) clears the override
815
+ // instead of pinning the runtime to nothing.
816
+
817
+ function _isMeaningful(v) {
818
+ return v !== undefined && v !== null && v !== '';
819
+ }
820
+
821
+ /**
822
+ * Resolve the CLI runtime for a per-agent spawn. Priority:
823
+ * 1. `agent.cli` — per-agent override
824
+ * 2. `engine.defaultCli` — fleet default
825
+ * 3. `ENGINE_DEFAULTS.defaultCli` ('claude') — hardcoded fallback
826
+ *
827
+ * Does NOT fall through to `engine.ccCli`. CC and agents are independent paths.
828
+ */
829
+ function resolveAgentCli(agent, engine) {
830
+ if (agent && _isMeaningful(agent.cli)) return String(agent.cli);
831
+ if (engine && _isMeaningful(engine.defaultCli)) return String(engine.defaultCli);
832
+ return ENGINE_DEFAULTS.defaultCli;
833
+ }
834
+
835
+ /**
836
+ * Resolve the CLI runtime for the Command Center / doc-chat. Priority:
837
+ * 1. `engine.ccCli` — CC-only override
838
+ * 2. `engine.defaultCli` — fleet default
839
+ * 3. `ENGINE_DEFAULTS.defaultCli` ('claude') — hardcoded fallback
840
+ *
841
+ * Does NOT inspect any agent overrides. CC has no notion of "which agent" —
842
+ * it's a fleet-wide singleton.
843
+ */
844
+ function resolveCcCli(engine) {
845
+ if (engine && _isMeaningful(engine.ccCli)) return String(engine.ccCli);
846
+ if (engine && _isMeaningful(engine.defaultCli)) return String(engine.defaultCli);
847
+ return ENGINE_DEFAULTS.defaultCli;
848
+ }
849
+
850
+ /**
851
+ * Resolve the model for a per-agent spawn. Priority:
852
+ * 1. `agent.model` — per-agent override
853
+ * 2. `engine.defaultModel` — fleet default
854
+ * 3. `undefined` — let the runtime adapter pick its own default
855
+ *
856
+ * Returning `undefined` is intentional: it tells the adapter to omit the
857
+ * `--model` flag entirely so the underlying CLI uses whatever the user has
858
+ * configured globally (Claude defaults to its own preferred model, Copilot
859
+ * to the user's `~/.copilot/settings.json` model).
860
+ */
861
+ function resolveAgentModel(agent, engine) {
862
+ if (agent && _isMeaningful(agent.model)) return String(agent.model);
863
+ if (engine && _isMeaningful(engine.defaultModel)) return String(engine.defaultModel);
864
+ return undefined;
865
+ }
866
+
867
+ /**
868
+ * Resolve the model for the Command Center / doc-chat. Priority:
869
+ * 1. `engine.ccModel` — CC-only override
870
+ * 2. `engine.defaultModel` — fleet default (CC inherits this when ccModel unset)
871
+ * 3. `undefined` — let the runtime adapter pick
872
+ */
873
+ function resolveCcModel(engine) {
874
+ if (engine && _isMeaningful(engine.ccModel)) return String(engine.ccModel);
875
+ if (engine && _isMeaningful(engine.defaultModel)) return String(engine.defaultModel);
876
+ return undefined;
877
+ }
878
+
879
+ /**
880
+ * Resolve the per-spawn USD budget cap. Priority:
881
+ * 1. `agent.maxBudgetUsd` — per-agent override
882
+ * 2. `engine.maxBudgetUsd` — fleet default
883
+ * 3. `undefined` — no cap
884
+ *
885
+ * Uses nullish coalescing so a literal `0` is honored as a valid cap (for
886
+ * read-only / dry-run agents) instead of being treated as "no cap" — the
887
+ * acceptance criteria are explicit about this.
888
+ */
889
+ function resolveAgentMaxBudget(agent, engine) {
890
+ const a = agent ? agent.maxBudgetUsd : undefined;
891
+ if (a !== undefined && a !== null) {
892
+ const n = typeof a === 'number' ? a : Number(a);
893
+ if (!Number.isNaN(n)) return n;
894
+ }
895
+ const e = engine ? engine.maxBudgetUsd : undefined;
896
+ if (e !== undefined && e !== null) {
897
+ const n = typeof e === 'number' ? e : Number(e);
898
+ if (!Number.isNaN(n)) return n;
899
+ }
900
+ return undefined;
901
+ }
902
+
903
+ /**
904
+ * Resolve whether this agent should run in Claude `--bare` mode. Priority:
905
+ * 1. `agent.bareMode` — per-agent override (boolean)
906
+ * 2. `engine.claudeBareMode` — fleet default
907
+ * 3. `false` — hardcoded fallback
908
+ *
909
+ * Strict undefined/null check (not falsy) so a per-agent `false` correctly
910
+ * overrides an engine `true`.
911
+ */
912
+ function resolveAgentBareMode(agent, engine) {
913
+ const a = agent ? agent.bareMode : undefined;
914
+ if (a !== undefined && a !== null) return !!a;
915
+ const e = engine ? engine.claudeBareMode : undefined;
916
+ if (e !== undefined && e !== null) return !!e;
917
+ return false;
918
+ }
919
+
920
+ // ─── Legacy ccModel → defaultModel Migration ─────────────────────────────────
921
+ //
922
+ // Pre-P-3b8e5f1d, `engine.ccModel` was the single fleet-wide model knob (it
923
+ // was also used for agent dispatch via fall-through). The new architecture
924
+ // makes `defaultModel` the fleet knob and demotes `ccModel` to a CC-only
925
+ // override. To avoid breaking installs that have only `ccModel` configured,
926
+ // promote `ccModel` to act as `defaultModel` in memory at startup, log a
927
+ // deprecation notice once, and leave the on-disk config alone (so a future
928
+ // admin edit can decide whether to keep `ccModel` as override or remove it).
929
+
930
+ let _legacyCcModelMigrationLogged = false;
931
+
932
+ /**
933
+ * If `config.engine.ccModel` is set but `config.engine.defaultModel` is unset,
934
+ * mutate the in-memory `config.engine` so `defaultModel` mirrors `ccModel` and
935
+ * log a one-time deprecation notice. Does NOT write to disk.
936
+ *
937
+ * Returns `true` when the migration was applied (useful for tests).
938
+ *
939
+ * The dedup flag is module-scoped so the warning fires once per Node process
940
+ * even if multiple subsystems independently call this — e.g., engine startup
941
+ * + a settings-reset path + a hot-reload tick.
942
+ */
943
+ function applyLegacyCcModelMigration(config, { logger = log } = {}) {
944
+ if (!config || !config.engine || typeof config.engine !== 'object') return false;
945
+ const e = config.engine;
946
+ if (_isMeaningful(e.defaultModel)) return false;
947
+ if (!_isMeaningful(e.ccModel)) return false;
948
+ e.defaultModel = e.ccModel;
949
+ if (!_legacyCcModelMigrationLogged) {
950
+ _legacyCcModelMigrationLogged = true;
951
+ try {
952
+ logger('warn', 'ccModel is now a CC-specific override; set defaultModel to apply fleet-wide');
953
+ } catch { /* logger may not be wired during tests — best-effort */ }
954
+ }
955
+ return true;
956
+ }
957
+
958
+ /** Test helper: reset the dedup flag so repeated tests can re-trigger the log. */
959
+ function _resetLegacyCcModelMigrationFlag() {
960
+ _legacyCcModelMigrationLogged = false;
961
+ }
962
+
963
+ // ─── Runtime Config Preflight Warnings ──────────────────────────────────────
964
+ //
965
+ // Emit non-fatal warnings about runtime/CLI configuration drift. Consumed by
966
+ // engine/preflight.js (which converts the entries to `{ name, ok: 'warn',
967
+ // message }` shape) and surfaced via `minions doctor`.
968
+ //
969
+ // The function is pure: takes the config and the list of registered runtime
970
+ // names, returns warning objects. No FS, no console writes — preflight owns
971
+ // presentation.
972
+
973
+ /**
974
+ * Inspect runtime fleet config and return warning entries for misconfiguration.
975
+ *
976
+ * Warnings emitted:
977
+ * - Unknown CLI: any `cli` value (per-agent, ccCli, defaultCli) not in
978
+ * `registeredRuntimes`. Each unknown value produces one entry.
979
+ * - Deprecated `config.claude.*` fields: presence of any field in
980
+ * `ENGINE_DEFAULTS._deprecatedConfigClaudeFields` under `config.claude`.
981
+ * - Bare-mode misconfig: `engine.claudeBareMode === true` paired with
982
+ * CC running on the Claude runtime (resolved via `resolveCcCli`) and no
983
+ * explicit `engine.ccSystemPrompt` configured. `--bare` strips
984
+ * CLAUDE.md auto-discovery, so users should know CC will lose project
985
+ * context unless they wire an explicit system prompt.
986
+ *
987
+ * Returns: `{ id, message }[]` — `id` is a stable kebab-case identifier so
988
+ * tests can assert specific warnings without matching message text.
989
+ */
990
+ function runtimeConfigWarnings(config, registeredRuntimes) {
991
+ const warnings = [];
992
+ if (!config || typeof config !== 'object') return warnings;
993
+ const known = new Set(Array.isArray(registeredRuntimes) ? registeredRuntimes : []);
994
+ const engine = config.engine || {};
995
+ const agents = config.agents || {};
996
+
997
+ // 1. Unknown CLI values across the fleet.
998
+ const seen = new Set();
999
+ const checkCli = (label, value) => {
1000
+ if (!_isMeaningful(value)) return;
1001
+ const key = `${label}:${value}`;
1002
+ if (seen.has(key)) return;
1003
+ seen.add(key);
1004
+ if (known.size > 0 && !known.has(String(value))) {
1005
+ const knownList = Array.from(known).sort().join(', ');
1006
+ warnings.push({
1007
+ id: 'unknown-cli',
1008
+ message: `Unknown CLI runtime "${value}" (${label}). Registered runtimes: ${knownList}`,
1009
+ });
1010
+ }
1011
+ };
1012
+ checkCli('engine.defaultCli', engine.defaultCli);
1013
+ checkCli('engine.ccCli', engine.ccCli);
1014
+ for (const [agentId, agent] of Object.entries(agents)) {
1015
+ if (agent && typeof agent === 'object') checkCli(`agents.${agentId}.cli`, agent.cli);
1016
+ }
1017
+
1018
+ // 2. Deprecated `config.claude.*` fields.
1019
+ const claude = config.claude;
1020
+ if (claude && typeof claude === 'object') {
1021
+ const deprecatedKeys = ENGINE_DEFAULTS._deprecatedConfigClaudeFields || [];
1022
+ const present = deprecatedKeys.filter(k => Object.prototype.hasOwnProperty.call(claude, k));
1023
+ if (present.length > 0) {
1024
+ warnings.push({
1025
+ id: 'deprecated-config-claude',
1026
+ message: `config.claude.{${present.join(',')}} is deprecated. Use the runtime adapter (engine/runtimes/) and resolveAgent*/resolveCc* helpers instead.`,
1027
+ });
1028
+ }
1029
+ }
1030
+
1031
+ // 3. Bare-mode misconfig: claudeBareMode + Claude as CC runtime + no explicit
1032
+ // CC system prompt. `--bare` suppresses CLAUDE.md auto-discovery; CC will
1033
+ // lose project context unless the user wires an explicit prompt.
1034
+ if (engine.claudeBareMode === true) {
1035
+ const ccCli = resolveCcCli(engine);
1036
+ if (ccCli === 'claude' && !_isMeaningful(engine.ccSystemPrompt)) {
1037
+ warnings.push({
1038
+ id: 'bare-mode-misconfig',
1039
+ message: 'engine.claudeBareMode is true but CC runs on Claude with no engine.ccSystemPrompt — CLAUDE.md auto-discovery is suppressed and CC will lose project context.',
1040
+ });
1041
+ }
1042
+ }
1043
+
1044
+ return warnings;
1045
+ }
1046
+
815
1047
  // ─── Status & Type Constants ─────────────────────────────────────────────────
816
1048
 
817
1049
  const WI_STATUS = {
@@ -1834,6 +2066,10 @@ module.exports = {
1834
2066
  KB_CATEGORIES,
1835
2067
  classifyInboxItem,
1836
2068
  ENGINE_DEFAULTS,
2069
+ resolveAgentCli, resolveCcCli, resolveAgentModel, resolveCcModel,
2070
+ resolveAgentMaxBudget, resolveAgentBareMode,
2071
+ applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
2072
+ runtimeConfigWarnings,
1837
2073
  WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, DISPATCH_RESULT, trackReviewMetric, queuePlanToPrd,
1838
2074
  WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS,
1839
2075
  PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,