@wrongstack/cli 0.299.0 → 0.301.0

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.
Files changed (82) hide show
  1. package/dist/{acp-S4LZQB66.js → acp-DATHHPNT.js} +36 -17
  2. package/dist/acp-server-agent.d.ts +1 -1
  3. package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
  4. package/dist/{auth-G5YKKSRH.js → auth-JASC4K3F.js} +151 -38
  5. package/dist/auth-menu/loopback-server.d.ts +41 -55
  6. package/dist/auth-menu/openai-codex-oauth.d.ts +36 -123
  7. package/dist/auto-discover-providers-DRW4OZHG.js +8 -0
  8. package/dist/boot/dispatch-webui.d.ts +3 -0
  9. package/dist/boot/launch-menu.d.ts +2 -4
  10. package/dist/boot/tool-registry.d.ts +3 -0
  11. package/dist/chimera-reviewer-policy.d.ts +10 -3
  12. package/dist/chimera-work-registry.d.ts +19 -0
  13. package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
  14. package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
  15. package/dist/{chunk-2SLSUDYS.js → chunk-2KDOFTTM.js} +312 -211
  16. package/dist/{chunk-TGDHN4LM.js → chunk-4GVH7V3S.js} +4 -24
  17. package/dist/chunk-CSAPOCBP.js +24 -0
  18. package/dist/{chunk-66T43Q4Y.js → chunk-ETOEGL3V.js} +6 -9
  19. package/dist/{chunk-PEKUKUCH.js → chunk-GUHQQG63.js} +32 -14
  20. package/dist/chunk-KE7E7DPX.js +38 -0
  21. package/dist/{chunk-ZNZJ34RF.js → chunk-MHF2IJDE.js} +21 -4
  22. package/dist/{chunk-4HH3YGG4.js → chunk-MRLYDHQ7.js} +8 -5
  23. package/dist/{chunk-L5CE52XW.js → chunk-O7XNGCRT.js} +317 -309
  24. package/dist/chunk-PYTFS4MC.js +238 -0
  25. package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
  26. package/dist/{chunk-4U3DMA2Y.js → chunk-ZECLGUTF.js} +28 -21
  27. package/dist/{cli-main-3MQDFSYK.js → cli-main-XWC57MGE.js} +849 -242
  28. package/dist/{execution-ANOYMWPO.js → execution-LHMSMDHB.js} +174 -55
  29. package/dist/execution-chimera-cascade.d.ts +3 -4
  30. package/dist/execution-chimera-review.d.ts +2 -3
  31. package/dist/execution-cleanup.d.ts +3 -2
  32. package/dist/execution.d.ts +1 -1
  33. package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
  34. package/dist/fleet/budget-source.d.ts +18 -0
  35. package/dist/fleet/host-context.d.ts +1 -1
  36. package/dist/fleet/host-helpers.d.ts +14 -0
  37. package/dist/fleet/host-status.d.ts +31 -0
  38. package/dist/fleet/host-types.d.ts +8 -0
  39. package/dist/fleet/host.d.ts +5 -0
  40. package/dist/fleet/status-broadcast.d.ts +1 -1
  41. package/dist/{hq-BDO56CX6.js → hq-LN5CJHCM.js} +22 -3
  42. package/dist/hq-server/auth-state.d.ts +29 -1
  43. package/dist/hq-server/auth.d.ts +16 -0
  44. package/dist/hq-server/client-address.d.ts +56 -0
  45. package/dist/hq-server/login-attempt-store.d.ts +30 -10
  46. package/dist/hq-server/routes/auth-handlers.d.ts +66 -26
  47. package/dist/hq-server/routes.d.ts +23 -1
  48. package/dist/hq-server/types.d.ts +10 -0
  49. package/dist/hq-server/ws.d.ts +17 -6
  50. package/dist/{hq-server-RJLJJZU7.js → hq-server-S7HZG3PA.js} +3 -2
  51. package/dist/hq-server.d.ts +19 -0
  52. package/dist/index.js +69 -104
  53. package/dist/live-settings-input.d.ts +5 -0
  54. package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
  55. package/dist/{modeldiag-HENYJ4N7.js → modeldiag-3DOK4BWW.js} +3 -3
  56. package/dist/{plugin-usage-VTL7ZTVC.js → plugin-usage-YEVLOR33.js} +7 -3
  57. package/dist/{plugins-ETY6W3DC.js → plugins-K3BAUP4D.js} +3 -3
  58. package/dist/profile-config-path.d.ts +0 -2
  59. package/dist/provider-helpers.d.ts +0 -5
  60. package/dist/{providers-models-GDELBHZK.js → providers-models-WEZ4EGLL.js} +38 -12
  61. package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
  62. package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
  63. package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
  64. package/dist/slash-commands/command-context.d.ts +19 -0
  65. package/dist/subcommands/flags.d.ts +23 -0
  66. package/dist/terminal-format.d.ts +15 -0
  67. package/dist/webui-server/setup-events.d.ts +17 -0
  68. package/dist/webui-server/terminal-log-view.d.ts +29 -12
  69. package/dist/{webui-server-PCLSXX6L.js → webui-server-DD35QFNO.js} +113 -50
  70. package/dist/webui-server-options.d.ts +16 -0
  71. package/dist/wiring/brain-and-orchestration.d.ts +4 -1
  72. package/dist/wiring/director-setup.d.ts +4 -0
  73. package/dist/wiring/fallback-gate.d.ts +31 -0
  74. package/dist/wiring/fleet-command-handlers.d.ts +1 -1
  75. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
  76. package/dist/wiring/management-tools.d.ts +9 -1
  77. package/dist/wiring/pipeline.d.ts +5 -5
  78. package/dist/wiring/plugins.d.ts +3 -10
  79. package/dist/wiring/provider-utility-tools.d.ts +27 -1
  80. package/dist/wiring/tools.d.ts +4 -5
  81. package/package.json +23 -23
  82. package/dist/chunk-GO3TJICK.js +0 -489
@@ -1,3 +1,6 @@
1
+ import {
2
+ setupPlugins
3
+ } from "./chunk-ZECLGUTF.js";
1
4
  import {
2
5
  runProjectPicker
3
6
  } from "./chunk-5N34EWJR.js";
@@ -37,13 +40,10 @@ import {
37
40
  matchTaskNode,
38
41
  sddState
39
42
  } from "./chunk-XTIXVJXP.js";
40
- import {
41
- setupPlugins
42
- } from "./chunk-4U3DMA2Y.js";
43
43
  import {
44
44
  PLUGIN_AUDIT_ENTRIES,
45
45
  runPluginManagementCommand
46
- } from "./chunk-PEKUKUCH.js";
46
+ } from "./chunk-GUHQQG63.js";
47
47
  import {
48
48
  configureSimpleUiRuntimeContext,
49
49
  detectProjectFacts,
@@ -70,7 +70,7 @@ import {
70
70
  loadCachedAcpRegistry,
71
71
  refreshAcpRegistry,
72
72
  setupProvider
73
- } from "./chunk-4HH3YGG4.js";
73
+ } from "./chunk-MRLYDHQ7.js";
74
74
  import {
75
75
  addCustomProvider,
76
76
  addKeyForCatalogProvider,
@@ -80,17 +80,18 @@ import {
80
80
  runClaudeOAuthLogin,
81
81
  runCopilotOAuthLogin,
82
82
  validateFamily
83
- } from "./chunk-L5CE52XW.js";
83
+ } from "./chunk-O7XNGCRT.js";
84
84
  import {
85
85
  LOCAL_LLM_PRESETS,
86
86
  parseSpawnFlags
87
- } from "./chunk-ZNZJ34RF.js";
87
+ } from "./chunk-MHF2IJDE.js";
88
88
  import {
89
- buildPickableProviders
90
- } from "./chunk-66T43Q4Y.js";
89
+ buildPickableProviders,
90
+ visibleModelIds
91
+ } from "./chunk-ETOEGL3V.js";
91
92
  import {
92
93
  runCodexOAuthLogin
93
- } from "./chunk-GO3TJICK.js";
94
+ } from "./chunk-PYTFS4MC.js";
94
95
  import {
95
96
  activeLabel,
96
97
  loadConfigProviders,
@@ -101,10 +102,10 @@ import {
101
102
  } from "./chunk-PJHET45A.js";
102
103
  import {
103
104
  appendHistory
104
- } from "./chunk-YWUPHRGB.js";
105
+ } from "./chunk-263G3FMB.js";
105
106
  import {
106
107
  activeProfileConfigPath
107
- } from "./chunk-DPJZQCVP.js";
108
+ } from "./chunk-YMXXOOFN.js";
108
109
  import {
109
110
  __require
110
111
  } from "./chunk-7OCVIDC7.js";
@@ -1566,6 +1567,7 @@ function registerBuiltinTools(deps) {
1566
1567
  tier,
1567
1568
  contextTool: createContextManagerTool({ compactor: deps.compactor }),
1568
1569
  memory: { enabled: deps.config.features.memory, store: deps.memoryStore },
1570
+ nextSteps: { enabled: deps.config.tools?.nextsteps?.enabled === true },
1569
1571
  coordinationTools: [
1570
1572
  makeMailboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events }),
1571
1573
  makeMailSendTool({ projectDir: deps.wpaths.projectDir, events: deps.events }),
@@ -2781,6 +2783,9 @@ import {
2781
2783
  } from "@wrongstack/core/execution";
2782
2784
  import { TOKENS as TOKENS6 } from "@wrongstack/core/kernel";
2783
2785
  import { createMcpControlTool, createMcpUseTool } from "@wrongstack/core/tools";
2786
+ import {
2787
+ normalizeTokenSavingTier as normalizeTokenSavingTier2
2788
+ } from "@wrongstack/core/types";
2784
2789
 
2785
2790
  // src/boot/brain-decision-log.ts
2786
2791
  var MAX_BRAIN_LOG_ENTRIES = 20;
@@ -2875,9 +2880,7 @@ import { wstackGlobalRoot } from "@wrongstack/core/utils";
2875
2880
 
2876
2881
  // src/fleet/host-helpers.ts
2877
2882
  import * as path2 from "node:path";
2878
- import {
2879
- makeSubagentResultTool
2880
- } from "@wrongstack/core/coordination";
2883
+ import { makeSubagentResultTool } from "@wrongstack/core/coordination";
2881
2884
  import { ToolCapabilities, WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
2882
2885
  import { makePreferSideConflictResolver } from "@wrongstack/sdd";
2883
2886
  function isInsideDirectory(root, candidate) {
@@ -2953,10 +2956,13 @@ var DEFAULT_SUBAGENT_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
2953
2956
  "collab_debug",
2954
2957
  "fleet_emit"
2955
2958
  ]);
2959
+ var NEVER_SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["nextsteps"]);
2956
2960
  function selectSubagentTools(allTools, directorToolsByName, allow) {
2957
2961
  if (!allow || allow.length === 0) {
2958
2962
  const visible = new Map(
2959
- allTools.filter((tool) => !DEFAULT_SUBAGENT_HIDDEN_TOOLS.has(tool.name)).map((tool) => [tool.name, tool])
2963
+ allTools.filter(
2964
+ (tool) => !DEFAULT_SUBAGENT_HIDDEN_TOOLS.has(tool.name) && !NEVER_SUBAGENT_TOOLS.has(tool.name)
2965
+ ).map((tool) => [tool.name, tool])
2960
2966
  );
2961
2967
  visible.set("submit_result", makeSubagentResultTool());
2962
2968
  return Array.from(visible.values());
@@ -2964,12 +2970,20 @@ function selectSubagentTools(allTools, directorToolsByName, allow) {
2964
2970
  const allowSet = new Set(allow);
2965
2971
  const result = /* @__PURE__ */ new Map();
2966
2972
  for (const tool of allTools) {
2967
- if (allowSet.has(tool.name)) result.set(tool.name, tool);
2973
+ if (allowSet.has(tool.name) && !NEVER_SUBAGENT_TOOLS.has(tool.name))
2974
+ result.set(tool.name, tool);
2968
2975
  }
2969
2976
  for (const name of allowSet) {
2977
+ if (NEVER_SUBAGENT_TOOLS.has(name)) continue;
2970
2978
  const directorTool = directorToolsByName.get(name);
2971
2979
  if (directorTool && !result.has(name)) result.set(name, directorTool);
2972
2980
  }
2981
+ const missing = [...allowSet].filter(
2982
+ (name) => !NEVER_SUBAGENT_TOOLS.has(name) && !result.has(name)
2983
+ );
2984
+ if (missing.length > 0) {
2985
+ throw new Error(`Subagent tool contract is not registered: ${missing.sort().join(", ")}`);
2986
+ }
2973
2987
  result.set("submit_result", makeSubagentResultTool());
2974
2988
  return Array.from(result.values());
2975
2989
  }
@@ -3263,6 +3277,10 @@ function registerDirectorStatsBridge(input) {
3263
3277
  ...u.toolCalls !== void 0 ? { toolCalls: u.toolCalls } : {}
3264
3278
  };
3265
3279
  });
3280
+ const budgetSnap = director.fleetManager?.budgetSnapshot?.();
3281
+ const maxSpawns = budgetSnap?.maxSpawns ?? director.maxSpawns;
3282
+ const usedSpawns = budgetSnap?.usedSpawns ?? director.spawnCount;
3283
+ const remainingSpawns = budgetSnap?.remainingSpawns ?? Math.max(0, (Number.isFinite(maxSpawns) ? maxSpawns : Number.POSITIVE_INFINITY) - usedSpawns);
3266
3284
  events.emit("coordinator.stats", {
3267
3285
  sessionId,
3268
3286
  total: payload.total,
@@ -3273,6 +3291,11 @@ function registerDirectorStatsBridge(input) {
3273
3291
  pending: payload.pending,
3274
3292
  completed: payload.completed,
3275
3293
  ...usage?.total?.cost !== void 0 ? { totalCostUsd: usage.total.cost } : {},
3294
+ maxSpawns,
3295
+ usedSpawns,
3296
+ remainingSpawns,
3297
+ ...budgetSnap?.checkpointMaxSpawns !== void 0 ? { checkpointMaxSpawns: budgetSnap.checkpointMaxSpawns } : {},
3298
+ ...budgetSnap?.ceilingMismatch ? { ceilingMismatch: true } : {},
3276
3299
  subagentStatuses
3277
3300
  });
3278
3301
  });
@@ -3341,7 +3364,8 @@ import {
3341
3364
  Agent,
3342
3365
  Context,
3343
3366
  createDefaultPipelines,
3344
- createFallbackModelExtension
3367
+ createFallbackModelExtension,
3368
+ renderInstructionLayer
3345
3369
  } from "@wrongstack/core/agent";
3346
3370
  import {
3347
3371
  applyProjectAgentConfig,
@@ -3366,9 +3390,14 @@ import { EventBus } from "@wrongstack/core/kernel";
3366
3390
  import { AutoApprovePermissionPolicy } from "@wrongstack/core/security";
3367
3391
 
3368
3392
  // src/fleet/host-context.ts
3393
+ import {
3394
+ missingRequiredRuntimeTools,
3395
+ missingRuntimeCapabilities,
3396
+ runtimeToolReferencesFromText
3397
+ } from "@wrongstack/core/agent-catalog";
3369
3398
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
3370
3399
  import { getSageRetrieval } from "@wrongstack/sage";
3371
- async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3400
+ async function resolveHostSubagentSkillContent(deps, roster, subCfg, availableToolNames = []) {
3372
3401
  const rosterSkillNames = subCfg.role ? roster[subCfg.role]?.skillNames : void 0;
3373
3402
  const skillNames = [...new Set(subCfg.skillNames ?? rosterSkillNames ?? [])];
3374
3403
  const directContent = subCfg.skillContent?.trim();
@@ -3381,8 +3410,14 @@ async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3381
3410
  try {
3382
3411
  const manifest = await deps.skillLoader.find(skillName);
3383
3412
  if (!manifest) continue;
3413
+ if (missingRuntimeCapabilities(manifest.requiredCapabilities, availableToolNames).length > 0 || missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
3414
+ continue;
3415
+ }
3384
3416
  const body = (await deps.skillLoader.readSaveBody(skillName)).trim();
3385
3417
  if (!body) continue;
3418
+ if (missingRequiredRuntimeTools(runtimeToolReferencesFromText(body), availableToolNames).length > 0) {
3419
+ continue;
3420
+ }
3386
3421
  const entry = `## Skill: ${skillName}
3387
3422
 
3388
3423
  ${body.slice(0, maxCharsPerSkill)}`;
@@ -3798,16 +3833,26 @@ ${message} Falling back to the assigned checkout.` : `${message} Falling back to
3798
3833
  onlineAgents = await subagentMailbox.getAgentStatuses();
3799
3834
  } catch {
3800
3835
  }
3836
+ const subagentTools = host.filterTools(effectiveCfg.tools);
3801
3837
  const baseSystem = await host.deps.systemPromptBuilder.build({
3802
3838
  cwd: subCwd,
3803
3839
  projectRoot: host.deps.projectRoot,
3804
- tools: host.filterTools(effectiveCfg.tools),
3840
+ tools: subagentTools,
3841
+ catalogTools: subagentTools,
3805
3842
  model: effModel,
3806
3843
  provider: effProvider,
3807
3844
  subagent: true,
3808
3845
  onlineAgents
3809
3846
  });
3810
- baseSystem.unshift({ type: "text", text: DEFAULT_SUBAGENT_BASELINE });
3847
+ baseSystem.unshift({
3848
+ type: "text",
3849
+ text: renderInstructionLayer(DEFAULT_SUBAGENT_BASELINE, {
3850
+ toolNames: new Set(subagentTools.map((tool) => tool.name)),
3851
+ tier: "off",
3852
+ subagent: true,
3853
+ strictToolReferences: true
3854
+ })
3855
+ });
3811
3856
  if (availabilityNotice) baseSystem.push({ type: "text", text: availabilityNotice });
3812
3857
  const audienceMemory = await retrieveHostSubagentMemory(
3813
3858
  host.deps,
@@ -3825,7 +3870,8 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
3825
3870
  const roleSkillContent = await resolveHostSubagentSkillContent(
3826
3871
  host.deps,
3827
3872
  host.roster,
3828
- effectiveCfg
3873
+ effectiveCfg,
3874
+ subagentTools.map((tool) => tool.name)
3829
3875
  );
3830
3876
  if (roleSkillContent) {
3831
3877
  for (let index = baseSystem.length - 1; index >= 0; index--) {
@@ -3872,7 +3918,8 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
3872
3918
  projectRoot: host.deps.projectRoot,
3873
3919
  allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
3874
3920
  model: effModel,
3875
- tools: host.filterTools(tools),
3921
+ tools: subagentTools,
3922
+ catalogTools: subagentTools,
3876
3923
  agentId: subagentName,
3877
3924
  agentName: effectiveCfg.name ?? subagentName
3878
3925
  });
@@ -4125,6 +4172,9 @@ import {
4125
4172
  var DEFAULT_MIN_INTERVAL_MS = 15e3;
4126
4173
  var DEFAULT_GLOBAL_PER_MINUTE = 20;
4127
4174
  var BODY_MAX = 200;
4175
+ function isSilentInfrastructureAgent(name) {
4176
+ return name.trim().toLowerCase() === "chimera-review";
4177
+ }
4128
4178
  function createFleetStatusBroadcaster(opts) {
4129
4179
  const cfg = opts.config;
4130
4180
  const minIntervalMs = cfg?.minIntervalMsPerAgent ?? DEFAULT_MIN_INTERVAL_MS;
@@ -4144,6 +4194,7 @@ function createFleetStatusBroadcaster(opts) {
4144
4194
  let droppedSinceLastSend = 0;
4145
4195
  const announcedSpawn = /* @__PURE__ */ new Set();
4146
4196
  const announcedBudget = /* @__PURE__ */ new Set();
4197
+ const silentInfrastructureAgents = /* @__PURE__ */ new Set();
4147
4198
  const disposers = [];
4148
4199
  let stopped = false;
4149
4200
  function underGlobalCap(now) {
@@ -4208,9 +4259,14 @@ function createFleetStatusBroadcaster(opts) {
4208
4259
  opts.events.on("subagent.spawned", (e) => {
4209
4260
  if (announcedSpawn.has(e.subagentId)) return;
4210
4261
  announcedSpawn.add(e.subagentId);
4262
+ const name = e.name ?? nameOf(e.subagentId);
4263
+ if (isSilentInfrastructureAgent(name)) {
4264
+ silentInfrastructureAgents.add(e.subagentId);
4265
+ return;
4266
+ }
4211
4267
  broadcast(e.subagentId, {
4212
- subject: `[${e.name ?? nameOf(e.subagentId)}] joined the fleet`,
4213
- body: `${e.name ?? nameOf(e.subagentId)} spawned${e.model ? ` on ${e.model}` : ""}${e.description ? ` \u2014 ${e.description.slice(0, 80)}` : ""}`,
4268
+ subject: `[${name}] joined the fleet`,
4269
+ body: `${name} spawned${e.model ? ` on ${e.model}` : ""}${e.description ? ` \u2014 ${e.description.slice(0, 80)}` : ""}`,
4214
4270
  priority: "low"
4215
4271
  });
4216
4272
  }),
@@ -4224,6 +4280,9 @@ function createFleetStatusBroadcaster(opts) {
4224
4280
  opts.events.on("subagent.task_completed", (e) => {
4225
4281
  heartbeat({ agentId: e.subagentId, status: "idle" });
4226
4282
  const name = nameOf(e.subagentId);
4283
+ if (silentInfrastructureAgents.has(e.subagentId) || isSilentInfrastructureAgent(name)) {
4284
+ return;
4285
+ }
4227
4286
  const ok = e.status === "success";
4228
4287
  const secs = Math.round(e.durationMs / 1e3);
4229
4288
  broadcast(e.subagentId, {
@@ -4234,6 +4293,9 @@ function createFleetStatusBroadcaster(opts) {
4234
4293
  }),
4235
4294
  opts.events.on("subagent.budget_warning", (e) => {
4236
4295
  if (!broadcastBudgetWarnings) return;
4296
+ if (silentInfrastructureAgents.has(e.subagentId) || isSilentInfrastructureAgent(nameOf(e.subagentId))) {
4297
+ return;
4298
+ }
4237
4299
  const key = `${e.subagentId}|${e.kind}`;
4238
4300
  if (announcedBudget.has(key)) return;
4239
4301
  announcedBudget.add(key);
@@ -4260,6 +4322,7 @@ function createFleetStatusBroadcaster(opts) {
4260
4322
  void mb().deregisterAgent(e.subagentId).catch(() => {
4261
4323
  });
4262
4324
  announcedSpawn.delete(e.subagentId);
4325
+ silentInfrastructureAgents.delete(e.subagentId);
4263
4326
  lastSentAt.delete(e.subagentId);
4264
4327
  for (const key of announcedBudget) {
4265
4328
  if (key.startsWith(`${e.subagentId}|`)) announcedBudget.delete(key);
@@ -4366,7 +4429,47 @@ function buildFleetHostStatus(input) {
4366
4429
  const completedCount = completed.length;
4367
4430
  const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
4368
4431
  const summary = !input.coordinatorStatus && !input.completedResults ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
4369
- return { pending, completed, live, summary };
4432
+ return {
4433
+ pending,
4434
+ completed,
4435
+ live,
4436
+ summary,
4437
+ ...input.budget ? { budget: input.budget } : {}
4438
+ };
4439
+ }
4440
+ function formatFleetBudgetLines(budget, colorize) {
4441
+ const bold = colorize?.bold ?? ((s) => s);
4442
+ const dim = colorize?.dim ?? ((s) => s);
4443
+ const amber = colorize?.amber ?? ((s) => s);
4444
+ const fmtNum = (n) => Number.isFinite(n) ? String(n) : "\u221E";
4445
+ const sourceSuffix = budget.effectiveSource ? ` ${dim(`(source: ${budget.effectiveSource})`)}` : "";
4446
+ const lines = [
4447
+ bold("Budget"),
4448
+ ` maxConcurrent: ${budget.maxConcurrent} \xB7 activeAgents: ${budget.activeAgents}`,
4449
+ ` maxSpawns: ${fmtNum(budget.maxSpawns)} \xB7 usedSpawns: ${budget.usedSpawns} \xB7 remainingSpawns: ${fmtNum(budget.remainingSpawns)}${sourceSuffix}`
4450
+ ];
4451
+ if (budget.maxTokens !== void 0 && Number.isFinite(budget.maxTokens) && budget.maxTokens < Number.POSITIVE_INFINITY) {
4452
+ lines.push(
4453
+ ` tokens: ${budget.usedTokens ?? 0} / ${fmtNum(budget.maxTokens)} remaining ${fmtNum(budget.remainingTokens ?? 0)}`
4454
+ );
4455
+ }
4456
+ if (budget.maxCostUsd !== void 0 && Number.isFinite(budget.maxCostUsd) && budget.maxCostUsd < Number.POSITIVE_INFINITY) {
4457
+ const used = budget.usedCostUsd ?? 0;
4458
+ const rem = budget.remainingCostUsd ?? 0;
4459
+ lines.push(
4460
+ ` costUsd: ${used.toFixed(4)} / ${budget.maxCostUsd.toFixed(4)} remaining ${rem.toFixed(4)}`
4461
+ );
4462
+ }
4463
+ if (budget.ceilingMismatch && budget.checkpointMaxSpawns !== void 0) {
4464
+ lines.push(
4465
+ amber(
4466
+ ` \u26A0 checkpoint maxSpawns was ${budget.checkpointMaxSpawns}; live ceiling is ${fmtNum(budget.maxSpawns)}`
4467
+ )
4468
+ );
4469
+ } else if (budget.checkpointMaxSpawns !== void 0) {
4470
+ lines.push(dim(` checkpointMaxSpawns: ${budget.checkpointMaxSpawns}`));
4471
+ }
4472
+ return lines;
4370
4473
  }
4371
4474
  function aggregateFleetUsage(completed) {
4372
4475
  const bySubagent = /* @__PURE__ */ new Map();
@@ -5074,9 +5177,44 @@ var MultiAgentHost = class {
5074
5177
  coordinatorStatus: this.director ? this.getCoordinator().getStatus() : null,
5075
5178
  fleetStatus: this.fleetManager?.getFleetStatus() ?? null,
5076
5179
  completedResults: this.director ? this.director.completedResults() : null,
5077
- shadowTaskIds: this.shadowTaskIds
5180
+ shadowTaskIds: this.shadowTaskIds,
5181
+ budget: this.budgetView()
5078
5182
  });
5079
5183
  }
5184
+ /**
5185
+ * Read-only concurrency + lifetime spawn budget for `/fleet status` and
5186
+ * resume diagnostics. Safe when no director/fleet is active yet.
5187
+ */
5188
+ budgetView() {
5189
+ const snap = this.fleetManager?.budgetSnapshot?.();
5190
+ const maxSpawns = snap?.maxSpawns ?? this.opts.maxSpawns ?? this.director?.maxSpawns ?? Number.POSITIVE_INFINITY;
5191
+ const usedSpawns = snap?.usedSpawns ?? this.director?.spawnCount ?? 0;
5192
+ const remainingSpawns = snap?.remainingSpawns ?? Math.max(0, (Number.isFinite(maxSpawns) ? maxSpawns : Number.POSITIVE_INFINITY) - usedSpawns);
5193
+ const live = this.director ? this.getCoordinator().getStatus().subagents.filter((s) => s.status === "running" || s.status === "idle").length : 0;
5194
+ const maxConcurrentSource = this.opts.budgetSources?.maxConcurrent ?? "default";
5195
+ const maxSpawnsSource = this.opts.budgetSources?.maxSpawns ?? "default";
5196
+ const effectiveSource = `maxConcurrent=${maxConcurrentSource}, maxSpawns=${maxSpawnsSource}`;
5197
+ return {
5198
+ maxConcurrent: this.getMaxConcurrent(),
5199
+ activeAgents: live,
5200
+ maxSpawns,
5201
+ usedSpawns,
5202
+ remainingSpawns,
5203
+ maxConcurrentSource,
5204
+ maxSpawnsSource,
5205
+ effectiveSource,
5206
+ ...snap ? {
5207
+ maxTokens: snap.maxTokens,
5208
+ usedTokens: snap.usedTokens,
5209
+ remainingTokens: snap.remainingTokens,
5210
+ maxCostUsd: snap.maxCostUsd,
5211
+ usedCostUsd: snap.usedCostUsd,
5212
+ remainingCostUsd: snap.remainingCostUsd,
5213
+ ...snap.checkpointMaxSpawns !== void 0 ? { checkpointMaxSpawns: snap.checkpointMaxSpawns } : {},
5214
+ ...snap.ceilingMismatch ? { ceilingMismatch: true } : {}
5215
+ } : {}
5216
+ };
5217
+ }
5080
5218
  usage() {
5081
5219
  const completed = this.director ? this.director.completedResults().filter((r) => !this.shadowTaskIds.has(r.taskId)) : [];
5082
5220
  return aggregateFleetUsage(completed);
@@ -5176,7 +5314,10 @@ function resolveProviderCfg(config, providerId) {
5176
5314
  function buildProviderForId(args, providerId) {
5177
5315
  const { cfg, factoryType } = resolveProviderCfg(args.config, providerId);
5178
5316
  const useRegistry = !!args.config.features.modelsRegistry && args.providerRegistry.has(factoryType);
5179
- return useRegistry ? args.providerRegistry.create(cfg, factoryType) : makeProviderFromConfig2(providerId, cfg);
5317
+ return useRegistry ? args.providerRegistry.create(cfg, factoryType) : makeProviderFromConfig2(
5318
+ providerId,
5319
+ factoryType === "ai-gateway" ? { ...cfg, type: factoryType } : cfg
5320
+ );
5180
5321
  }
5181
5322
 
5182
5323
  // src/wiring/brain-and-orchestration.ts
@@ -5415,8 +5556,12 @@ function setupBrainAndOrchestration(deps) {
5415
5556
  maxCostUsd: config.fleet.budget.maxCostUsd
5416
5557
  }
5417
5558
  } : {},
5418
- maxSpawns: config.fleet?.budget?.maxSpawns,
5559
+ maxSpawns: deps.maxSpawns ?? config.fleet?.budget?.maxSpawns,
5419
5560
  maxConcurrent,
5561
+ budgetSources: {
5562
+ maxConcurrent: deps.maxConcurrentSource ?? "default",
5563
+ maxSpawns: deps.maxSpawnsSource ?? "default"
5564
+ },
5420
5565
  getLeaderMaxContext: () => effectiveMaxContextRef.current,
5421
5566
  brain,
5422
5567
  agentMonitor,
@@ -5438,6 +5583,7 @@ function setupBrainAndOrchestration(deps) {
5438
5583
  events
5439
5584
  })
5440
5585
  );
5586
+ toolRegistry.exposeToProvider("delegate");
5441
5587
  toolRegistry.register(
5442
5588
  createMcpControlTool({
5443
5589
  getConfig: () => configStore.get(),
@@ -5445,13 +5591,17 @@ function setupBrainAndOrchestration(deps) {
5445
5591
  registry: mcpRegistry
5446
5592
  })
5447
5593
  );
5448
- if (config.features.tokenSavingMode) {
5449
- toolRegistry.register(
5450
- createMcpUseTool({
5451
- registry: mcpRegistry,
5452
- toolRegistry
5453
- })
5454
- );
5594
+ if (normalizeTokenSavingTier2(config.features.tokenSavingMode) !== "off") {
5595
+ toolRegistry.exposeToProvider("mcp_control");
5596
+ }
5597
+ toolRegistry.register(
5598
+ createMcpUseTool({
5599
+ registry: mcpRegistry,
5600
+ toolRegistry
5601
+ })
5602
+ );
5603
+ if (normalizeTokenSavingTier2(config.features.tokenSavingMode) !== "off") {
5604
+ toolRegistry.exposeToProvider("mcp_use");
5455
5605
  }
5456
5606
  return {
5457
5607
  brain,
@@ -5933,13 +6083,78 @@ import * as path10 from "node:path";
5933
6083
  import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
5934
6084
  import { createAgentMonitorService } from "@wrongstack/core/coordination";
5935
6085
  import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
6086
+
6087
+ // src/fleet/budget-source.ts
6088
+ import { DEFAULT_MAX_FLEET_SPAWNS as DEFAULT_MAX_FLEET_SPAWNS3 } from "@wrongstack/core/coordination";
6089
+ function positiveInt(raw) {
6090
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.floor(raw);
6091
+ if (typeof raw === "string" && raw.trim() !== "") {
6092
+ const n = Number.parseInt(raw, 10);
6093
+ if (Number.isFinite(n) && n > 0) return n;
6094
+ }
6095
+ return void 0;
6096
+ }
6097
+ function resolveFleetBudgetSources(input) {
6098
+ const flags = input.flags ?? {};
6099
+ const env = input.env ?? process.env;
6100
+ const config = input.config;
6101
+ const defaultMaxConcurrent = input.defaultMaxConcurrent ?? 4;
6102
+ const defaultMaxSpawns = input.defaultMaxSpawns ?? DEFAULT_MAX_FLEET_SPAWNS3;
6103
+ const maxConcurrentFlag = positiveInt(flags["max-concurrent"]);
6104
+ const maxConcurrentEnv = positiveInt(env["WRONGSTACK_MAX_CONCURRENT"]);
6105
+ const maxConcurrentProfile = positiveInt(config?.maxConcurrent);
6106
+ let maxConcurrent;
6107
+ let maxConcurrentSource;
6108
+ if (maxConcurrentFlag !== void 0) {
6109
+ maxConcurrent = maxConcurrentFlag;
6110
+ maxConcurrentSource = "cli-flag";
6111
+ } else if (maxConcurrentEnv !== void 0) {
6112
+ maxConcurrent = maxConcurrentEnv;
6113
+ maxConcurrentSource = "env";
6114
+ } else if (maxConcurrentProfile !== void 0) {
6115
+ maxConcurrent = maxConcurrentProfile;
6116
+ maxConcurrentSource = "profile";
6117
+ } else {
6118
+ maxConcurrent = defaultMaxConcurrent;
6119
+ maxConcurrentSource = "default";
6120
+ }
6121
+ const maxSpawnsFlag = positiveInt(flags["max-spawns"]);
6122
+ const maxSpawnsEnv = positiveInt(env["WRONGSTACK_MAX_SPAWNS"]);
6123
+ const maxSpawnsProfile = positiveInt(config?.fleet?.budget?.maxSpawns);
6124
+ let maxSpawns;
6125
+ let maxSpawnsSource;
6126
+ if (maxSpawnsFlag !== void 0) {
6127
+ maxSpawns = maxSpawnsFlag;
6128
+ maxSpawnsSource = "cli-flag";
6129
+ } else if (maxSpawnsEnv !== void 0) {
6130
+ maxSpawns = maxSpawnsEnv;
6131
+ maxSpawnsSource = "env";
6132
+ } else if (maxSpawnsProfile !== void 0) {
6133
+ maxSpawns = maxSpawnsProfile;
6134
+ maxSpawnsSource = "profile";
6135
+ } else {
6136
+ maxSpawns = defaultMaxSpawns;
6137
+ maxSpawnsSource = "default";
6138
+ }
6139
+ return {
6140
+ maxConcurrent,
6141
+ maxConcurrentSource,
6142
+ maxSpawns,
6143
+ maxSpawnsSource
6144
+ };
6145
+ }
6146
+
6147
+ // src/wiring/director-setup.ts
5936
6148
  function setupDirectorAndAutonomy(deps) {
5937
6149
  const { flags, config, wpaths, session, events, autonomyModeRef } = deps;
5938
6150
  const directorMode = true;
5939
- const maxConcurrentFromFlag = typeof flags["max-concurrent"] === "string" ? Number.parseInt(flags["max-concurrent"], 10) : void 0;
5940
- const maxConcurrentFromEnv = typeof process.env["WRONGSTACK_MAX_CONCURRENT"] === "string" ? Number.parseInt(process.env["WRONGSTACK_MAX_CONCURRENT"], 10) : void 0;
5941
- const maxConcurrentFromConfig = typeof config.maxConcurrent === "number" && config.maxConcurrent > 0 ? config.maxConcurrent : void 0;
5942
- const maxConcurrent = Number.isFinite(maxConcurrentFromFlag) && maxConcurrentFromFlag > 0 ? maxConcurrentFromFlag : Number.isFinite(maxConcurrentFromEnv) && maxConcurrentFromEnv > 0 ? maxConcurrentFromEnv : Number.isFinite(maxConcurrentFromConfig) && maxConcurrentFromConfig > 0 ? maxConcurrentFromConfig : void 0;
6151
+ const {
6152
+ maxConcurrent: resolvedMaxConcurrent,
6153
+ maxConcurrentSource,
6154
+ maxSpawns,
6155
+ maxSpawnsSource
6156
+ } = resolveFleetBudgetSources({ flags, config });
6157
+ const maxConcurrent = maxConcurrentSource === "default" ? void 0 : resolvedMaxConcurrent;
5943
6158
  const autonomyMode = (() => {
5944
6159
  const v = flags["autonomy"];
5945
6160
  if (v === "auto" || v === "suggest" || v === "eternal" || v === "eternal-parallel") return v;
@@ -5986,6 +6201,9 @@ function setupDirectorAndAutonomy(deps) {
5986
6201
  director: null,
5987
6202
  directorMode,
5988
6203
  maxConcurrent,
6204
+ maxSpawns,
6205
+ maxConcurrentSource,
6206
+ maxSpawnsSource,
5989
6207
  autonomyMode,
5990
6208
  nextPredictEnabled,
5991
6209
  currentSuggestions,
@@ -6105,6 +6323,7 @@ ${color6.dim("\u2500".repeat(40))}` : "";
6105
6323
  onAgents: (subagentId) => formatAgents(input, subagentId),
6106
6324
  onFleet: (action, target) => handleFleetAction(input, action, target),
6107
6325
  onFleetStatus: () => input.getDirector()?.status() ?? null,
6326
+ onFleetBudget: () => input.multiAgentHost.budgetView(),
6108
6327
  onFleetUsage: () => input.getDirector()?.snapshot() ?? null,
6109
6328
  onFleetKill: () => killFleet2(input.getDirector()),
6110
6329
  onFleetTerminate: async (subagentId) => {
@@ -6223,6 +6442,15 @@ async function handleFleetAction(input, action, target) {
6223
6442
  function formatFleetStatus(host) {
6224
6443
  const status = host.status();
6225
6444
  const lines = [color6.bold("Fleet status"), ` ${status.summary}`];
6445
+ const budget = status.budget ?? host.budgetView();
6446
+ lines.push(
6447
+ "",
6448
+ ...formatFleetBudgetLines(budget, {
6449
+ bold: color6.bold,
6450
+ dim: color6.dim,
6451
+ amber: color6.amber
6452
+ })
6453
+ );
6226
6454
  const icons = { running: "\u25CF", idle: "\u25CB", stopped: "\u2298" };
6227
6455
  const active2 = status.live.filter((agent) => agent.status === "running" || agent.status === "idle");
6228
6456
  if (active2.length > 0) {
@@ -6762,6 +6990,7 @@ function setupHqTelemetry(deps) {
6762
6990
  // src/wiring/lifecycle-plugins.ts
6763
6991
  import * as path15 from "node:path";
6764
6992
  import { getSharedProjectMailbox as getSharedProjectMailbox8 } from "@wrongstack/core/coordination";
6993
+ import { CouncilOrchestrator, OneShotOrchestrator as OneShotOrchestrator2 } from "@wrongstack/core/execution";
6765
6994
  import { countShellHooks, HookRegistry, HookRunner, shellHooksEqual } from "@wrongstack/core/hooks";
6766
6995
  import { allServers } from "@wrongstack/core/infrastructure";
6767
6996
  import { TOKENS as TOKENS8 } from "@wrongstack/core/kernel";
@@ -6769,7 +6998,7 @@ import { NotifierImpl } from "@wrongstack/core/notifications";
6769
6998
  import {
6770
6999
  SlashCommandRegistry
6771
7000
  } from "@wrongstack/core/registry";
6772
- import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
7001
+ import { normalizeTokenSavingTier as normalizeTokenSavingTier3 } from "@wrongstack/core/types";
6773
7002
  import {
6774
7003
  createVaultBackedMcpAuthorizationProviderFactory,
6775
7004
  MCPAuthorizationManager,
@@ -7041,13 +7270,19 @@ function setupMetrics(params) {
7041
7270
  }
7042
7271
 
7043
7272
  // src/wiring/pipeline.ts
7044
- import { Agent as Agent2, createDefaultPipelines as createDefaultPipelines2 } from "@wrongstack/core/agent";
7045
- import { applyModelRuntime as applyModelRuntime2, AutoCompactionMiddleware } from "@wrongstack/core/execution";
7046
- import { createSessionEventBridge as createSessionEventBridge2, resolveAuditLevel } from "@wrongstack/core/storage";
7273
+ import {
7274
+ Agent as Agent2,
7275
+ createDefaultPipelines as createDefaultPipelines2
7276
+ } from "@wrongstack/core/agent";
7277
+ import {
7278
+ AutoCompactionMiddleware,
7279
+ applyModelRuntime as applyModelRuntime2,
7280
+ ToolExecutor as ToolExecutor2
7281
+ } from "@wrongstack/core/execution";
7047
7282
  import { TOKENS as TOKENS7 } from "@wrongstack/core/kernel";
7048
- import { estimateRequestTokensCalibrated } from "@wrongstack/core/utils";
7283
+ import { createSessionEventBridge as createSessionEventBridge2, resolveAuditLevel } from "@wrongstack/core/storage";
7049
7284
  import { resolveContextWindowPolicy } from "@wrongstack/core/types";
7050
- import { ToolExecutor as ToolExecutor2 } from "@wrongstack/core/execution";
7285
+ import { estimateRequestTokensCalibrated } from "@wrongstack/core/utils";
7051
7286
 
7052
7287
  // src/wiring/mailbox-bridge-bootstrap.ts
7053
7288
  import { resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
@@ -7159,7 +7394,7 @@ async function bootstrapMailboxBridgeAtStartup(params) {
7159
7394
  if (!params.projectRoot) {
7160
7395
  return null;
7161
7396
  }
7162
- const mode = params.config?.features?.mailboxBridge ?? "auto";
7397
+ const mode = params.config?.features?.mailboxBridge ?? "off";
7163
7398
  if (mode === "off") {
7164
7399
  return null;
7165
7400
  }
@@ -7181,10 +7416,10 @@ async function bootstrapMailboxBridgeAtStartup(params) {
7181
7416
  switch (handle.source) {
7182
7417
  case "joined":
7183
7418
  case "spawned":
7184
- params.logger.debug(
7185
- `mailbox bridge ready via ${handle.source} on ${params.source} boot`,
7186
- { url: handle.url, lockPath: handle.lockPath }
7187
- );
7419
+ params.logger.debug(`mailbox bridge ready via ${handle.source} on ${params.source} boot`, {
7420
+ url: handle.url,
7421
+ lockPath: handle.lockPath
7422
+ });
7188
7423
  break;
7189
7424
  case "unhealthy":
7190
7425
  params.logger.warn(
@@ -7350,6 +7585,133 @@ function createAgent(params) {
7350
7585
  });
7351
7586
  }
7352
7587
 
7588
+ // src/wiring/provider-utility-tools.ts
7589
+ import { createContextManagerTool as createContextManagerTool2 } from "@wrongstack/core/infrastructure";
7590
+ import { createCouncilTool, createOneShotLLMTool } from "@wrongstack/core/tools";
7591
+ import {
7592
+ createCouncilPersonaRegistry,
7593
+ createCouncilProfileRegistry,
7594
+ OneShotOrchestrator
7595
+ } from "@wrongstack/core/execution";
7596
+ import { ModelRouter } from "@wrongstack/core/models";
7597
+ async function adoptResumedProvider(input) {
7598
+ if (!input.resumedProvider && !input.resumedModel) return;
7599
+ const config = input.getConfig();
7600
+ const provider = input.resumedProvider?.trim() || config.provider;
7601
+ const model = input.resumedModel?.trim() || config.model;
7602
+ if (provider === config.provider && model === config.model) return;
7603
+ const error = await input.switchProviderAndModel(provider, model);
7604
+ if (error) {
7605
+ const current = input.getConfig();
7606
+ input.logger.warn(
7607
+ `Resume: could not switch to the session's model ${provider}/${model} (${error}); continuing with ${current.provider}/${current.model}.`
7608
+ );
7609
+ }
7610
+ }
7611
+ function createLiveModelRouter(getConfig) {
7612
+ return {
7613
+ pickForTask(role, description) {
7614
+ const config = getConfig();
7615
+ const router = new ModelRouter({
7616
+ ...config.modelMatrix ? { matrix: config.modelMatrix } : {},
7617
+ config: {
7618
+ provider: config.provider,
7619
+ model: config.model,
7620
+ ...config.providers ? { providers: config.providers } : {}
7621
+ }
7622
+ });
7623
+ return router.pickForTask(role, description);
7624
+ }
7625
+ };
7626
+ }
7627
+ function buildCouncilRegistries(cfg) {
7628
+ const base = {
7629
+ ...cfg?.defaultProfile ? { defaultProfile: cfg.defaultProfile } : {},
7630
+ ...cfg?.maxConcurrency !== void 0 ? { maxConcurrency: cfg.maxConcurrency } : {}
7631
+ };
7632
+ if (!cfg?.personas?.length && !cfg?.profiles?.length) return base;
7633
+ try {
7634
+ const personas = createCouncilPersonaRegistry(cfg.personas ?? []);
7635
+ const profiles = createCouncilProfileRegistry(
7636
+ cfg.profiles ?? [],
7637
+ personas
7638
+ );
7639
+ return { ...base, personas, profiles };
7640
+ } catch (error) {
7641
+ console.warn(
7642
+ `Council tool: ignoring tools.council personas/profiles \u2014 ${error instanceof Error ? error.message : String(error)}. Falling back to the built-in lenses and panels.`
7643
+ );
7644
+ return base;
7645
+ }
7646
+ }
7647
+ function registerProviderUtilityTools(input) {
7648
+ const config = input.getConfig();
7649
+ const llmTool = createOneShotLLMTool({
7650
+ buildProvider: input.buildProvider,
7651
+ getConfig: input.getConfig,
7652
+ fallbackProfileManager: input.fallbackProfileManager,
7653
+ defaultProvider: config.provider,
7654
+ defaultModel: config.model,
7655
+ wrapProviderCall: input.wrapProviderCall
7656
+ });
7657
+ registerOrOverride(input.toolRegistry, "llm", llmTool);
7658
+ const councilOrchestrator = new OneShotOrchestrator({
7659
+ buildProvider: input.buildProvider,
7660
+ getConfig: input.getConfig,
7661
+ fallbackProfileManager: input.fallbackProfileManager,
7662
+ statusTracker: input.statusTracker,
7663
+ // Council profiles route each seat by ROLE ('planner', 'critic',
7664
+ // 'analyst', 'security-reviewer'…) rather than pinning models. Without a
7665
+ // router those hints were inert: every seat fell through to the session
7666
+ // provider/model, so a three-seat panel asked one model three times and
7667
+ // reported a distinctness warning on every single call.
7668
+ modelRouter: createLiveModelRouter(input.getConfig),
7669
+ wrapProviderCall: input.wrapProviderCall
7670
+ });
7671
+ registerOrOverride(
7672
+ input.toolRegistry,
7673
+ "council",
7674
+ createCouncilTool({
7675
+ caller: councilOrchestrator,
7676
+ fallbackProfileManager: input.fallbackProfileManager,
7677
+ ...buildCouncilRegistries(config.tools?.council)
7678
+ })
7679
+ );
7680
+ try {
7681
+ const summarizer = new OneShotOrchestrator({
7682
+ buildProvider: input.buildProvider,
7683
+ getConfig: input.getConfig,
7684
+ fallbackProfileManager: input.fallbackProfileManager,
7685
+ statusTracker: input.statusTracker,
7686
+ wrapProviderCall: input.wrapProviderCall
7687
+ });
7688
+ input.toolRegistry.override(
7689
+ "context_manager",
7690
+ createContextManagerTool2({
7691
+ compactor: input.compactor,
7692
+ summarizer: async (messages) => {
7693
+ const result = await summarizer.call({
7694
+ system: "Summarize concisely. Keep decisions and key facts.",
7695
+ messages,
7696
+ model: "deepseek-chat",
7697
+ maxTokens: 1024,
7698
+ timeoutMs: 3e4
7699
+ });
7700
+ return result.text || "(summary unavailable)";
7701
+ }
7702
+ })
7703
+ );
7704
+ } catch {
7705
+ }
7706
+ }
7707
+ function registerOrOverride(registry, name, tool) {
7708
+ try {
7709
+ registry.register(tool);
7710
+ } catch {
7711
+ registry.override(name, tool);
7712
+ }
7713
+ }
7714
+
7353
7715
  // src/wiring/lifecycle-plugins.ts
7354
7716
  async function setupLifecycleAndPlugins(deps) {
7355
7717
  const {
@@ -7506,7 +7868,7 @@ async function setupLifecycleAndPlugins(deps) {
7506
7868
  toolRegistry,
7507
7869
  events,
7508
7870
  log: logger,
7509
- lazyMode: normalizeTokenSavingTier2(config.features.tokenSavingMode) !== "off",
7871
+ lazyMode: normalizeTokenSavingTier3(config.features.tokenSavingMode) !== "off",
7510
7872
  cacheDir: wpaths.cacheDir,
7511
7873
  authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
7512
7874
  store: mcpTokenStore
@@ -7532,6 +7894,31 @@ async function setupLifecycleAndPlugins(deps) {
7532
7894
  () => hqPublisherRef.current
7533
7895
  );
7534
7896
  const notifier = new NotifierImpl();
7897
+ const getLiveConfig = () => configStore.get();
7898
+ const fallbackProfileManager = container.resolve(TOKENS8.FallbackProfileManager);
7899
+ const statusTracker = container.resolve(TOKENS8.ProviderModelStatusTracker);
7900
+ const buildUtilityProvider = (providerId) => buildProviderForIdRuntimeFn({ config: getLiveConfig(), providerRegistry }, providerId);
7901
+ const pluginOneShot = new OneShotOrchestrator2({
7902
+ buildProvider: buildUtilityProvider,
7903
+ getConfig: getLiveConfig,
7904
+ fallbackProfileManager,
7905
+ statusTracker,
7906
+ modelRouter: createLiveModelRouter(getLiveConfig),
7907
+ logger,
7908
+ // Route plugin completions through the same extension chain the agent loop
7909
+ // uses. `prompt-firewall` (credential redaction), `llm-cache`,
7910
+ // `model-router` and the token budgeter all live on `wrapProviderRunner`,
7911
+ // and `api.llm` reached the provider without any of them. Resolved per
7912
+ // call so a plugin enabled mid-session joins the chain.
7913
+ wrapProviderCall: (request, inner) => agent.extensions.wrapProviderRunner((_ctx, req) => inner(req), {
7914
+ exclude: ["fallback-model"]
7915
+ })(agent.ctx, request)
7916
+ });
7917
+ const pluginCouncil = new CouncilOrchestrator({
7918
+ caller: pluginOneShot,
7919
+ fallbackProfileManager,
7920
+ ...buildCouncilRegistries(getLiveConfig().tools?.council)
7921
+ });
7535
7922
  const pluginHost = await setupPlugins({
7536
7923
  config,
7537
7924
  container,
@@ -7572,7 +7959,9 @@ async function setupLifecycleAndPlugins(deps) {
7572
7959
  model: config.model,
7573
7960
  getProvider: () => context.provider,
7574
7961
  getModel: () => context.model,
7575
- createProvider: (name) => buildProviderForIdRuntimeFn({ config: configStore.get(), providerRegistry }, name)
7962
+ createProvider: (name) => buildProviderForIdRuntimeFn({ config: configStore.get(), providerRegistry }, name),
7963
+ oneShot: (input) => pluginOneShot.call(input),
7964
+ council: (question) => pluginCouncil.ask(question)
7576
7965
  }
7577
7966
  });
7578
7967
  return {
@@ -7593,12 +7982,16 @@ async function setupLifecycleAndPlugins(deps) {
7593
7982
 
7594
7983
  // src/wiring/management-tools.ts
7595
7984
  import * as fs5 from "node:fs/promises";
7596
- import { createFallbackManageTools, createPluginManagerTool } from "@wrongstack/core/tools";
7985
+ import {
7986
+ createFallbackManageTools,
7987
+ createPluginManagerTool
7988
+ } from "@wrongstack/core/tools";
7597
7989
  function registerCliManagementTools({
7598
7990
  toolRegistry,
7599
7991
  configStore,
7600
7992
  profileConfigPath,
7601
- stdinInteractive
7993
+ stdinInteractive,
7994
+ getHookRunner
7602
7995
  }) {
7603
7996
  const fallbackManageTools = createFallbackManageTools({
7604
7997
  getConfig: () => configStore.get(),
@@ -7628,6 +8021,7 @@ ${prompt}
7628
8021
  for (const tool of fallbackManageTools) toolRegistry.register(tool);
7629
8022
  toolRegistry.register(
7630
8023
  createPluginManagerTool({
8024
+ ...getHookRunner ? { getHookRunner } : {},
7631
8025
  getConfig: () => configStore.get(),
7632
8026
  catalog: PLUGIN_AUDIT_ENTRIES.map((entry) => {
7633
8027
  const aliases = [
@@ -7672,6 +8066,61 @@ import {
7672
8066
  } from "@wrongstack/core/storage";
7673
8067
  import { withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
7674
8068
  import { getSageService } from "@wrongstack/sage";
8069
+
8070
+ // src/wiring/fallback-gate.ts
8071
+ function createFallbackGate(_events) {
8072
+ const gateFn = (params) => {
8073
+ const { events: bus, sessionId, from, status, candidates, autoSwitchSeconds, requestId } = params;
8074
+ return runGate(bus, { sessionId, from, status, candidates, autoSwitchSeconds, requestId });
8075
+ };
8076
+ return gateFn;
8077
+ }
8078
+ function runGate(events, gateParams) {
8079
+ const { sessionId, from, status, candidates, autoSwitchSeconds, requestId } = gateParams;
8080
+ if (events.listenerCount("provider.fallback_pending") === 0) {
8081
+ return Promise.resolve(null);
8082
+ }
8083
+ return new Promise((resolve8) => {
8084
+ let settled = false;
8085
+ let timer;
8086
+ let deadline;
8087
+ const cleanup = () => {
8088
+ if (timer) clearTimeout(timer);
8089
+ if (deadline) clearTimeout(deadline);
8090
+ events.off("provider.fallback_choice", handler);
8091
+ };
8092
+ const finish = (result) => {
8093
+ if (settled) return;
8094
+ settled = true;
8095
+ cleanup();
8096
+ resolve8(result);
8097
+ };
8098
+ const handler = (payload) => {
8099
+ const p = payload;
8100
+ if (p?.requestId !== requestId) return;
8101
+ if (p.autoSwitch === true) {
8102
+ finish(null);
8103
+ return;
8104
+ }
8105
+ if (typeof p.providerId === "string" && typeof p.model === "string") {
8106
+ finish({ providerId: p.providerId, model: p.model });
8107
+ }
8108
+ };
8109
+ events.on("provider.fallback_choice", handler);
8110
+ deadline = setTimeout(() => finish(null), Math.round(autoSwitchSeconds * 1500));
8111
+ events.emit("provider.fallback_pending", {
8112
+ ...sessionId ? { sessionId } : {},
8113
+ from,
8114
+ status,
8115
+ candidates,
8116
+ autoSwitchSeconds,
8117
+ requestId,
8118
+ timestamp: Date.now()
8119
+ });
8120
+ });
8121
+ }
8122
+
8123
+ // src/wiring/provider-runtime-setup.ts
7675
8124
  function serializeProviderRuntimeSnapshot(snapshot) {
7676
8125
  return JSON.stringify(snapshot);
7677
8126
  }
@@ -7736,6 +8185,12 @@ function setupProviderRuntime(deps) {
7736
8185
  events,
7737
8186
  logger,
7738
8187
  statusTracker,
8188
+ // Fallback gate — shows a modal with countdown + manual pick in every
8189
+ // UI surface (TUI/WebUI/SimpleUI) before switching models. The gate
8190
+ // emits provider.fallback_pending and waits for provider.fallback_choice
8191
+ // or the auto-switch countdown.
8192
+ fallbackGate: createFallbackGate(events),
8193
+ fallbackGateSeconds: 7,
7739
8194
  ...cfg.fallbackStickiness?.primaryProbeInterval !== void 0 ? { primaryCooldownMs: cfg.fallbackStickiness.primaryProbeInterval } : {},
7740
8195
  ...cfg.fallbackStickiness?.stickyFallbackTurns !== void 0 ? { stickyFallbackTurns: cfg.fallbackStickiness.stickyFallbackTurns } : {}
7741
8196
  })
@@ -7783,19 +8238,34 @@ function setupProviderRuntime(deps) {
7783
8238
  if (process.env["WRONGSTACK_DISABLE_CONFIG_WATCH"] !== "1") {
7784
8239
  const activeProfile = typeof cfg.activeProfile === "string" && cfg.activeProfile ? cfg.activeProfile : "default";
7785
8240
  const configLayers = [
7786
- { path: wpaths.profileConfig(activeProfile), priority: 1 },
7787
- { path: wpaths.projectLocalConfig, priority: 2 },
7788
- { path: wpaths.inProjectConfig, priority: 3 }
8241
+ { path: wpaths.profileConfig(activeProfile), priority: 1, trusted: true },
8242
+ { path: wpaths.projectLocalConfig, priority: 2, trusted: true },
8243
+ { path: wpaths.inProjectConfig, priority: 3, trusted: false }
7789
8244
  ];
8245
+ const sanitizeUntrustedSnapshot = (snap, path32) => {
8246
+ const { providers, apiKey, baseUrl, ...rest } = snap;
8247
+ const dropped = [
8248
+ ...Object.keys(providers ?? {}).length > 0 ? ["providers"] : [],
8249
+ ...apiKey !== void 0 ? ["apiKey"] : [],
8250
+ ...baseUrl !== void 0 ? ["baseUrl"] : []
8251
+ ];
8252
+ if (dropped.length > 0) {
8253
+ logger.warn(
8254
+ `Config watcher: ignored credential field(s) from the repo-committed config "${path32}": ${dropped.join(", ")}. Only your profile config may set these.`
8255
+ );
8256
+ }
8257
+ return { ...rest, providers: {}, snapshotHasProviders: false };
8258
+ };
7790
8259
  const readMergedSnapshot = async () => {
7791
8260
  let merged;
7792
8261
  for (const layer of configLayers.sort((a, b) => a.priority - b.priority)) {
7793
- const snap = await readProviderSnapshot(
8262
+ const raw = await readProviderSnapshot(
7794
8263
  layer.path,
7795
8264
  vault,
7796
8265
  (msg) => logger.warn(`Config watcher (${layer.path}): ${msg}`)
7797
8266
  );
7798
- if (!snap) continue;
8267
+ if (!raw) continue;
8268
+ const snap = layer.trusted ? raw : sanitizeUntrustedSnapshot(raw, layer.path);
7799
8269
  if (!merged) {
7800
8270
  merged = {
7801
8271
  ...snap,
@@ -7977,130 +8447,6 @@ function warnUnlessMissing(logger, operation, error) {
7977
8447
  }
7978
8448
  }
7979
8449
 
7980
- // src/wiring/provider-utility-tools.ts
7981
- import { createContextManagerTool as createContextManagerTool2 } from "@wrongstack/core/infrastructure";
7982
- import { createCouncilTool, createOneShotLLMTool } from "@wrongstack/core/tools";
7983
- import {
7984
- createCouncilPersonaRegistry,
7985
- createCouncilProfileRegistry,
7986
- OneShotOrchestrator
7987
- } from "@wrongstack/core/execution";
7988
- import { ModelRouter } from "@wrongstack/core/models";
7989
- async function adoptResumedProvider(input) {
7990
- if (!input.resumedProvider && !input.resumedModel) return;
7991
- const config = input.getConfig();
7992
- const provider = input.resumedProvider?.trim() || config.provider;
7993
- const model = input.resumedModel?.trim() || config.model;
7994
- if (provider === config.provider && model === config.model) return;
7995
- const error = await input.switchProviderAndModel(provider, model);
7996
- if (error) {
7997
- const current = input.getConfig();
7998
- input.logger.warn(
7999
- `Resume: could not switch to the session's model ${provider}/${model} (${error}); continuing with ${current.provider}/${current.model}.`
8000
- );
8001
- }
8002
- }
8003
- function createLiveModelRouter(getConfig) {
8004
- return {
8005
- pickForTask(role, description) {
8006
- const config = getConfig();
8007
- const router = new ModelRouter({
8008
- ...config.modelMatrix ? { matrix: config.modelMatrix } : {},
8009
- config: {
8010
- provider: config.provider,
8011
- model: config.model,
8012
- ...config.providers ? { providers: config.providers } : {}
8013
- }
8014
- });
8015
- return router.pickForTask(role, description);
8016
- }
8017
- };
8018
- }
8019
- function buildCouncilRegistries(cfg) {
8020
- const base = {
8021
- ...cfg?.defaultProfile ? { defaultProfile: cfg.defaultProfile } : {},
8022
- ...cfg?.maxConcurrency !== void 0 ? { maxConcurrency: cfg.maxConcurrency } : {}
8023
- };
8024
- if (!cfg?.personas?.length && !cfg?.profiles?.length) return base;
8025
- try {
8026
- const personas = createCouncilPersonaRegistry(cfg.personas ?? []);
8027
- const profiles = createCouncilProfileRegistry(
8028
- cfg.profiles ?? [],
8029
- personas
8030
- );
8031
- return { ...base, personas, profiles };
8032
- } catch (error) {
8033
- console.warn(
8034
- `Council tool: ignoring tools.council personas/profiles \u2014 ${error instanceof Error ? error.message : String(error)}. Falling back to the built-in lenses and panels.`
8035
- );
8036
- return base;
8037
- }
8038
- }
8039
- function registerProviderUtilityTools(input) {
8040
- const config = input.getConfig();
8041
- const llmTool = createOneShotLLMTool({
8042
- buildProvider: input.buildProvider,
8043
- getConfig: input.getConfig,
8044
- fallbackProfileManager: input.fallbackProfileManager,
8045
- defaultProvider: config.provider,
8046
- defaultModel: config.model
8047
- });
8048
- registerOrOverride(input.toolRegistry, "llm", llmTool);
8049
- const councilOrchestrator = new OneShotOrchestrator({
8050
- buildProvider: input.buildProvider,
8051
- getConfig: input.getConfig,
8052
- fallbackProfileManager: input.fallbackProfileManager,
8053
- statusTracker: input.statusTracker,
8054
- // Council profiles route each seat by ROLE ('planner', 'critic',
8055
- // 'analyst', 'security-reviewer'…) rather than pinning models. Without a
8056
- // router those hints were inert: every seat fell through to the session
8057
- // provider/model, so a three-seat panel asked one model three times and
8058
- // reported a distinctness warning on every single call.
8059
- modelRouter: createLiveModelRouter(input.getConfig)
8060
- });
8061
- registerOrOverride(
8062
- input.toolRegistry,
8063
- "council",
8064
- createCouncilTool({
8065
- caller: councilOrchestrator,
8066
- fallbackProfileManager: input.fallbackProfileManager,
8067
- ...buildCouncilRegistries(config.tools?.council)
8068
- })
8069
- );
8070
- try {
8071
- const summarizer = new OneShotOrchestrator({
8072
- buildProvider: input.buildProvider,
8073
- getConfig: input.getConfig,
8074
- fallbackProfileManager: input.fallbackProfileManager,
8075
- statusTracker: input.statusTracker
8076
- });
8077
- input.toolRegistry.override(
8078
- "context_manager",
8079
- createContextManagerTool2({
8080
- compactor: input.compactor,
8081
- summarizer: async (messages) => {
8082
- const result = await summarizer.call({
8083
- system: "Summarize concisely. Keep decisions and key facts.",
8084
- messages,
8085
- model: "deepseek-chat",
8086
- maxTokens: 1024,
8087
- timeoutMs: 3e4
8088
- });
8089
- return result.text || "(summary unavailable)";
8090
- }
8091
- })
8092
- );
8093
- } catch {
8094
- }
8095
- }
8096
- function registerOrOverride(registry, name, tool) {
8097
- try {
8098
- registry.register(tool);
8099
- } catch {
8100
- registry.override(name, tool);
8101
- }
8102
- }
8103
-
8104
8450
  // src/wiring/runtime-controller-deps.ts
8105
8451
  import { resolveConfiguredRefinerRef, resolveEnhanceFallbackRef } from "@wrongstack/core/execution";
8106
8452
  function createRuntimeControllerDeps(input) {
@@ -9055,8 +9401,9 @@ async function setupSession(params) {
9055
9401
  const tCounts = {};
9056
9402
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
9057
9403
  const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
9404
+ const spawnPart = typeof dirState.maxSpawns === "number" && Number.isFinite(dirState.maxSpawns) ? `, spawns ${dirState.spawnCount}/${dirState.maxSpawns} (checkpoint ceiling; live profile may differ)` : `, spawns used ${dirState.spawnCount}`;
9058
9405
  renderer.writeInfo(
9059
- `Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`
9406
+ `Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}${spawnPart}.`
9060
9407
  );
9061
9408
  }
9062
9409
  } catch {
@@ -10584,15 +10931,16 @@ ${color13.dim("YOLO enabled; tool calls run without approval unless an explicit
10584
10931
  // src/slash-commands/brain.ts
10585
10932
  import { randomUUID as randomUUID5 } from "node:crypto";
10586
10933
  import { readFile as readFile7 } from "node:fs/promises";
10934
+ import { parseModelRef } from "@wrongstack/core/agent";
10587
10935
  import { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
10588
10936
  import { color as color14 } from "@wrongstack/core/utils";
10589
- import { parseModelRef } from "@wrongstack/core/agent";
10590
10937
  var RISK_LEVELS = /* @__PURE__ */ new Set(["off", "low", "medium", "high", "all"]);
10591
10938
  var COUNCIL_RISKS = /* @__PURE__ */ new Set(["medium", "high", "critical"]);
10592
10939
  var COUNCIL_DISTINCTNESS = /* @__PURE__ */ new Set(["none", "model", "provider"]);
10593
10940
  var COUNCIL_NUMERIC_OPS = {
10594
10941
  timeout: "perCallTimeoutMs",
10595
10942
  concurrency: "maxConcurrency",
10943
+ votertokens: "voterMaxTokens",
10596
10944
  judgetokens: "judgeMaxTokens"
10597
10945
  };
10598
10946
  var PERSONA_SHORTHANDS = new Set(BUILTIN_COUNCIL_PERSONA_IDS);
@@ -10607,7 +10955,14 @@ var DENY_TERMINAL_VALUES = ["never", "when-decided", "always"];
10607
10955
  var TRACE_CONTENT_VALUES = ["none", "redacted", "full"];
10608
10956
  var TERMINAL_POLICY_VALUES = ["conservative", "deny-all", "continue-on-recommended"];
10609
10957
  var MONITOR_POLICY_VALUES = ["llm", "steer", "observe"];
10610
- var DETERMINISTIC_TIER_NAMES = ["rule", "policy", "heuristic", "cache", "ledger-guard", "terminal"];
10958
+ var DETERMINISTIC_TIER_NAMES = [
10959
+ "rule",
10960
+ "policy",
10961
+ "heuristic",
10962
+ "cache",
10963
+ "ledger-guard",
10964
+ "terminal"
10965
+ ];
10611
10966
  function parseRefEntry(ref) {
10612
10967
  const parsed = parseModelRef(ref);
10613
10968
  const model = parsed.model?.trim();
@@ -10675,7 +11030,7 @@ function buildBrainCommand(opts) {
10675
11030
  " /brain council personas List the built-in decision lenses",
10676
11031
  " /brain council judge <ref|auto> | quorum <0..1> | approval <0..1>",
10677
11032
  " /brain council distinctness <none|model|provider> warn on a non-diverse panel",
10678
- " /brain council timeout|concurrency|judgetokens <n|default>",
11033
+ " /brain council timeout|concurrency|votertokens|judgetokens <n|default>",
10679
11034
  " /brain ledger [n] Show the last n rows (default 15) of the persistent decision ledger",
10680
11035
  " /brain ledger on|off | autodeny <n>",
10681
11036
  " /brain stats Per-tier decision counts: how often the Brain actually calls a model",
@@ -10787,7 +11142,10 @@ function buildBrainCommand(opts) {
10787
11142
  return { message: msg2 };
10788
11143
  }
10789
11144
  if (ref.toLowerCase() === "session") {
10790
- return applyPatch({ models: null }, (s) => `Brain decision model: ${color14.cyan("session model")} (pool cleared)${s.councilLabels.length === 0 ? "" : " \u2014 council dissolved"}`);
11145
+ return applyPatch(
11146
+ { models: null },
11147
+ (s) => `Brain decision model: ${color14.cyan("session model")} (pool cleared)${s.councilLabels.length === 0 ? "" : " \u2014 council dissolved"}`
11148
+ );
10791
11149
  }
10792
11150
  return applyPatch(
10793
11151
  { models: [ref] },
@@ -10803,7 +11161,10 @@ function buildBrainCommand(opts) {
10803
11161
  return { message: msg2 };
10804
11162
  }
10805
11163
  if (op === "clear") {
10806
- return applyPatch({ models: null }, () => `Brain LLM pool cleared \u2014 using the ${color14.cyan("session model")}`);
11164
+ return applyPatch(
11165
+ { models: null },
11166
+ () => `Brain LLM pool cleared \u2014 using the ${color14.cyan("session model")}`
11167
+ );
10807
11168
  }
10808
11169
  if (op === "add") {
10809
11170
  const ref = rest[1];
@@ -10813,7 +11174,10 @@ function buildBrainCommand(opts) {
10813
11174
  return { message: msg2 };
10814
11175
  }
10815
11176
  const current = (snapshot?.models ?? []).map(compactEntry);
10816
- return applyPatch({ models: [...current, ref] }, (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))}`);
11177
+ return applyPatch(
11178
+ { models: [...current, ref] },
11179
+ (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))}`
11180
+ );
10817
11181
  }
10818
11182
  if (op === "remove") {
10819
11183
  const target = rest[1];
@@ -10823,16 +11187,24 @@ function buildBrainCommand(opts) {
10823
11187
  return { message: msg2 };
10824
11188
  }
10825
11189
  const current = snapshot.models.map(compactEntry);
10826
- const idx = /^\d+$/.test(target) ? Number.parseInt(target, 10) - 1 : current.findIndex((c) => c === target || c.endsWith(`/${target}`) || c === parseRefEntry(target)?.model);
11190
+ const idx = /^\d+$/.test(target) ? Number.parseInt(target, 10) - 1 : current.findIndex(
11191
+ (c) => c === target || c.endsWith(`/${target}`) || c === parseRefEntry(target)?.model
11192
+ );
10827
11193
  if (idx < 0 || idx >= current.length) {
10828
11194
  const msg2 = `No pool entry matches "${target}". Current pool: ${current.join(", ") || "(empty)"}`;
10829
11195
  opts.renderer.writeWarning(msg2);
10830
11196
  return { message: msg2 };
10831
11197
  }
10832
11198
  const next = current.filter((_, i) => i !== idx);
10833
- return applyPatch({ models: next.length > 0 ? next : null }, (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))}`);
11199
+ return applyPatch(
11200
+ { models: next.length > 0 ? next : null },
11201
+ (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))}`
11202
+ );
10834
11203
  }
10835
- return applyPatch({ models: rest }, (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))} ${color14.dim(`(${s.poolLabels.length}/${rest.length} resolved)`)}`);
11204
+ return applyPatch(
11205
+ { models: rest },
11206
+ (s) => `Brain LLM pool: ${color14.cyan(poolSummary(s))} ${color14.dim(`(${s.poolLabels.length}/${rest.length} resolved)`)}`
11207
+ );
10836
11208
  }
10837
11209
  if (subcommand === "strategy") {
10838
11210
  const strategy = (rest[0] ?? "").toLowerCase();
@@ -10882,7 +11254,10 @@ function buildBrainCommand(opts) {
10882
11254
  return { message: msg3 };
10883
11255
  }
10884
11256
  if (op === "on" || op === "off") {
10885
- return applyPatch({ council: { enabled: op === "on" } }, (s) => `Brain ${councilSummary(s)}${op === "on" && !s.council.enabled ? " \u2014 needs \u22652 resolvable voters (set /brain council voters or a \u22652-model pool)" : ""}`);
11257
+ return applyPatch(
11258
+ { council: { enabled: op === "on" } },
11259
+ (s) => `Brain ${councilSummary(s)}${op === "on" && !s.council.enabled ? " \u2014 needs \u22652 resolvable voters (set /brain council voters or a \u22652-model pool)" : ""}`
11260
+ );
10886
11261
  }
10887
11262
  if (op === "minrisk") {
10888
11263
  const level = (rest[1] ?? "").toLowerCase();
@@ -10891,7 +11266,10 @@ function buildBrainCommand(opts) {
10891
11266
  opts.renderer.writeWarning(msg3);
10892
11267
  return { message: msg3 };
10893
11268
  }
10894
- return applyPatch({ council: { minRisk: level } }, (s) => `Brain council risk floor set to ${color14.cyan(s.council.minRisk)} \u2014 questions at/above it convene the council`);
11269
+ return applyPatch(
11270
+ { council: { minRisk: level } },
11271
+ (s) => `Brain council risk floor set to ${color14.cyan(s.council.minRisk)} \u2014 questions at/above it convene the council`
11272
+ );
10895
11273
  }
10896
11274
  if (op === "voters") {
10897
11275
  const seats = rest.slice(1).map(parseSeat);
@@ -10900,7 +11278,10 @@ function buildBrainCommand(opts) {
10900
11278
  opts.renderer.writeWarning(msg3);
10901
11279
  return { message: msg3 };
10902
11280
  }
10903
- return applyPatch({ council: { voters: seats } }, councilSummary);
11281
+ return applyPatch(
11282
+ { council: { voters: seats } },
11283
+ councilSummary
11284
+ );
10904
11285
  }
10905
11286
  if (op === "personas") {
10906
11287
  const lines = [
@@ -10921,7 +11302,10 @@ function buildBrainCommand(opts) {
10921
11302
  opts.renderer.writeWarning(msg3);
10922
11303
  return { message: msg3 };
10923
11304
  }
10924
- return applyPatch({ council: { judge: ref.toLowerCase() === "auto" ? null : ref } }, (s) => `Brain council judge: ${color14.cyan(s.council.judge ? compactEntry(s.council.judge) : "auto")}${s.council.judge ? "" : color14.dim(judgeSummary(s).replace(/^, judge: /, " \u2192 "))}`);
11305
+ return applyPatch(
11306
+ { council: { judge: ref.toLowerCase() === "auto" ? null : ref } },
11307
+ (s) => `Brain council judge: ${color14.cyan(s.council.judge ? compactEntry(s.council.judge) : "auto")}${s.council.judge ? "" : color14.dim(judgeSummary(s).replace(/^, judge: /, " \u2192 "))}`
11308
+ );
10925
11309
  }
10926
11310
  if (op === "quorum" || op === "approval") {
10927
11311
  const value = Number(rest[1]);
@@ -10956,7 +11340,7 @@ function buildBrainCommand(opts) {
10956
11340
  return `Brain council ${op} set to ${color14.cyan(applied === void 0 ? "default" : String(applied))}`;
10957
11341
  });
10958
11342
  }
10959
- const msg2 = `Unknown council subcommand: ${op}. Use on, off, minrisk, voters, personas, judge, quorum, approval, distinctness, timeout, concurrency, or judgetokens.`;
11343
+ const msg2 = `Unknown council subcommand: ${op}. Use on, off, minrisk, voters, personas, judge, quorum, approval, distinctness, timeout, concurrency, votertokens, or judgetokens.`;
10960
11344
  opts.renderer.writeWarning(msg2);
10961
11345
  return { message: msg2 };
10962
11346
  }
@@ -11077,7 +11461,9 @@ ${color14.dim(decision.rationale)}` : ""}`;
11077
11461
  lines.push(` decision timeout: ${color14.cyan(`${snapshot.decisionTimeoutMs}ms`)}`);
11078
11462
  }
11079
11463
  if (snapshot?.humanTimeoutMs) {
11080
- lines.push(` human timeout: ${color14.cyan(`${snapshot.humanTimeoutMs}ms`)} ${color14.dim("(then terminal policy)")}`);
11464
+ lines.push(
11465
+ ` human timeout: ${color14.cyan(`${snapshot.humanTimeoutMs}ms`)} ${color14.dim("(then terminal policy)")}`
11466
+ );
11081
11467
  }
11082
11468
  const councilSeats = opts.brainSettings?.councilLabels ?? [];
11083
11469
  if (councilSeats.length > 0) {
@@ -11296,7 +11682,9 @@ ${color14.dim(decision.rationale)}` : ""}`;
11296
11682
  ` enabled ${snapshot.trace.enabled ? color14.cyan("yes") : color14.dim("no")}`,
11297
11683
  ` content ${color14.cyan(snapshot.trace.content)}`,
11298
11684
  snapshot.trace.path ? ` path ${color14.dim(snapshot.trace.path)}` : "",
11299
- color14.dim(" Records tiers, every pool target (incl. failures), council votes and tokens.")
11685
+ color14.dim(
11686
+ " Records tiers, every pool target (incl. failures), council votes and tokens."
11687
+ )
11300
11688
  ].filter(Boolean).join("\n");
11301
11689
  opts.renderer.write(msg2);
11302
11690
  return { message: msg2 };
@@ -11388,7 +11776,9 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11388
11776
  ` enabled ${m.enabled === false ? color14.dim("no") : color14.cyan("yes")}`,
11389
11777
  ` policy ${color14.cyan(m.policy ?? "llm")}`,
11390
11778
  color14.dim(" Signals: tool-failure streak, error storm, agent stall, file churn."),
11391
- color14.dim(" Changes apply live; a change restarts the watchers, resetting signal counters.")
11779
+ color14.dim(
11780
+ " Changes apply live; a change restarts the watchers, resetting signal counters."
11781
+ )
11392
11782
  ].join("\n");
11393
11783
  opts.renderer.write(msg2);
11394
11784
  return { message: msg2 };
@@ -11426,7 +11816,10 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11426
11816
  }
11427
11817
  const snapshot = opts.brainRuntime?.getSnapshot();
11428
11818
  if (snapshot?.cache.enabled) {
11429
- lines.push("", color14.dim(` cache: ${snapshot.cache.hits} hit / ${snapshot.cache.misses} miss`));
11819
+ lines.push(
11820
+ "",
11821
+ color14.dim(` cache: ${snapshot.cache.hits} hit / ${snapshot.cache.misses} miss`)
11822
+ );
11430
11823
  }
11431
11824
  if (snapshot?.circuit && snapshot.circuit.state !== "closed") {
11432
11825
  lines.push(color14.dim(` circuit: ${snapshot.circuit.state}`));
@@ -11437,7 +11830,10 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11437
11830
  lines.push(color14.dim(` ${fmtAge(w.at).padEnd(8)} ${w.outcome}`));
11438
11831
  }
11439
11832
  }
11440
- lines.push("", color14.dim(` Based on the last ${log.length} logged decision(s) of this session.`));
11833
+ lines.push(
11834
+ "",
11835
+ color14.dim(` Based on the last ${log.length} logged decision(s) of this session.`)
11836
+ );
11441
11837
  const msg2 = lines.join("\n");
11442
11838
  opts.renderer.write(msg2);
11443
11839
  return { message: msg2 };
@@ -11574,6 +11970,7 @@ function buildClearCommand(opts) {
11574
11970
  ctx.contextEvidence = createContextEvidenceState();
11575
11971
  ctx.toolAdjacencyDirty = false;
11576
11972
  ctx.pendingPostToolContext = void 0;
11973
+ ctx.clearMemoryEvidence?.();
11577
11974
  ctx.lastRequestTokens = void 0;
11578
11975
  ctx.lastRealInputTokens = void 0;
11579
11976
  for (const key of Object.keys(ctx.meta)) ctx.state.deleteMeta(key);
@@ -11861,8 +12258,17 @@ function buildCompactCommand(opts) {
11861
12258
 
11862
12259
  // src/slash-commands/context.ts
11863
12260
  import * as fs7 from "node:fs/promises";
11864
- import { atomicWrite as atomicWrite3, color as color18, getContextBreakdown, repairToolUseAdjacency } from "@wrongstack/core/utils";
11865
- import { formatContextWindowModeList, getContextWindowMode, resolveContextWindowPolicy as resolveContextWindowPolicy2 } from "@wrongstack/core/types";
12261
+ import {
12262
+ formatContextWindowModeList,
12263
+ getContextWindowMode,
12264
+ resolveContextWindowPolicy as resolveContextWindowPolicy2
12265
+ } from "@wrongstack/core/types";
12266
+ import {
12267
+ atomicWrite as atomicWrite3,
12268
+ color as color18,
12269
+ getContextBreakdown,
12270
+ repairToolUseAdjacency
12271
+ } from "@wrongstack/core/utils";
11866
12272
  function buildContextCommand(opts) {
11867
12273
  return {
11868
12274
  name: "context",
@@ -12095,6 +12501,7 @@ function cacheMechanismLabel(providerId, cacheControl) {
12095
12501
  if (id.includes("google") || id.includes("gemini")) {
12096
12502
  return "implicit (auto) + explicit cachedContents (opt-in)";
12097
12503
  }
12504
+ if (id.includes("gateway")) return "automatic (Gateway-managed, per routed upstream)";
12098
12505
  if (cacheControl === "auto") return "automatic + prompt_cache_key routing";
12099
12506
  return "none / provider-managed";
12100
12507
  }
@@ -12133,7 +12540,14 @@ function safeBreakdown(ctx) {
12133
12540
  return getContextBreakdown(ctx);
12134
12541
  } catch (err) {
12135
12542
  if (process.env["DEBUG_WS"]) {
12136
- console.warn(JSON.stringify({ level: "warn", event: "context.breakdown_failed", message: String(err), timestamp: Date.now() }));
12543
+ console.warn(
12544
+ JSON.stringify({
12545
+ level: "warn",
12546
+ event: "context.breakdown_failed",
12547
+ message: String(err),
12548
+ timestamp: Date.now()
12549
+ })
12550
+ );
12137
12551
  }
12138
12552
  return null;
12139
12553
  }
@@ -12161,7 +12575,7 @@ function renderBreakdown(bd, detailed) {
12161
12575
  `${color18.bold("Breakdown")} (${headerSuffix})`,
12162
12576
  ` system: ${K(bd.system.total)} (${PCT(bd.system.total, limit)})`,
12163
12577
  ` tools: ${K(bd.tools.total)} (${PCT(bd.tools.total, limit)}) \xB7 builtin ${K(bd.tools.builtin)}, mcp ${K(bd.tools.mcp)} across ${bd.tools.count} defs`,
12164
- ` history: ${K(bd.history.total)} (${PCT(bd.history.total, limit)}) \xB7 text ${K(bd.history.text)}, tool results ${K(bd.history.toolResults)}`,
12578
+ ` history: ${K(bd.history.total)} (${PCT(bd.history.total, limit)}) \xB7 text ${K(bd.history.text)}, tool inputs ${K(bd.history.toolInputs ?? 0)}, results ${K(bd.history.toolResults)}, thinking ${K(bd.history.thinking ?? 0)}`,
12165
12579
  ` volatile: ${K(bd.volatile.total)} (${PCT(bd.volatile.total, limit)}) \xB7 ledger ${K(bd.volatile.ledger)}, nextsteps ${K(bd.volatile.nextsteps)}`,
12166
12580
  ` ${color18.bold("total")}: ${K(bd.total)}${overBy > 0 ? "" : ` (${PCT(bd.total, limit)})`}`
12167
12581
  ];
@@ -12174,7 +12588,9 @@ function renderBreakdown(bd, detailed) {
12174
12588
  if (sources.length > 0) {
12175
12589
  const top = detailed ? sources : sources.slice(0, 3);
12176
12590
  out.push(
12177
- ` ${color18.dim("heaviest static:")} ${top.map(([src, tokens]) => `${SOURCE_LABELS[src] ?? src} ${K(tokens)}`).join(", ")}`
12591
+ ` ${color18.dim("heaviest static:")} ${top.map(
12592
+ ([src, tokens]) => `${SOURCE_LABELS[src] ?? src} ${K(tokens)}`
12593
+ ).join(", ")}`
12178
12594
  );
12179
12595
  if (detailed && bd.tools.total > 0 && (bd.system.bySource["tool-usage"] ?? 0) > 0) {
12180
12596
  out.push(
@@ -13097,6 +13513,58 @@ function diagnoseConfig(cfg, plugins = []) {
13097
13513
  }
13098
13514
  }
13099
13515
  }
13516
+ if ("fleet" in fixed) {
13517
+ if (!isPlainObject(fixed["fleet"])) {
13518
+ findings.push({
13519
+ path: "fleet",
13520
+ problem: `expected object, got ${JSON.stringify(fixed["fleet"])}`,
13521
+ severity: "error",
13522
+ fix: "removed (built-in defaults apply)"
13523
+ });
13524
+ delete fixed["fleet"];
13525
+ } else {
13526
+ const fleet = fixed["fleet"];
13527
+ if ("budget" in fleet) {
13528
+ if (!isPlainObject(fleet["budget"])) {
13529
+ findings.push({
13530
+ path: "fleet.budget",
13531
+ problem: `expected object, got ${JSON.stringify(fleet["budget"])}`,
13532
+ severity: "error",
13533
+ fix: "removed (built-in defaults apply)"
13534
+ });
13535
+ delete fleet["budget"];
13536
+ } else {
13537
+ const budget = fleet["budget"];
13538
+ for (const key of ["maxSpawns", "maxTokens", "maxCostUsd"]) {
13539
+ if (!(key in budget)) continue;
13540
+ const v = budget[key];
13541
+ const n = coerceNumber(v);
13542
+ if (n === void 0) {
13543
+ delete budget[key];
13544
+ findings.push({
13545
+ path: `fleet.budget.${key}`,
13546
+ problem: `expected a non-negative number, got ${JSON.stringify(v)}`,
13547
+ severity: "error",
13548
+ fix: "removed (built-in default applies)"
13549
+ });
13550
+ } else {
13551
+ const clamped = Math.max(0, n);
13552
+ const next = key === "maxCostUsd" ? clamped : Math.floor(clamped);
13553
+ if (next !== v) {
13554
+ budget[key] = next;
13555
+ findings.push({
13556
+ path: `fleet.budget.${key}`,
13557
+ problem: `expected a non-negative ${key === "maxCostUsd" ? "number" : "integer"}, got ${JSON.stringify(v)}`,
13558
+ severity: "error",
13559
+ fix: `set to ${next}`
13560
+ });
13561
+ }
13562
+ }
13563
+ }
13564
+ }
13565
+ }
13566
+ }
13567
+ }
13100
13568
  for (const key of ["provider", "model"]) {
13101
13569
  if (key in fixed && typeof fixed[key] !== "string") {
13102
13570
  findings.push({
@@ -13380,7 +13848,7 @@ function buildDoctorCommand(opts) {
13380
13848
  ].join("\n");
13381
13849
  async function loadPluginSchemas() {
13382
13850
  try {
13383
- const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-ETY6W3DC.js");
13851
+ const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-K3BAUP4D.js");
13384
13852
  const settled = await Promise.allSettled(BUILTIN_PLUGIN_FACTORIES.map((f) => f()));
13385
13853
  const loaded = [];
13386
13854
  for (const s of settled) {
@@ -15297,6 +15765,17 @@ async function handleStatus(opts, cmd) {
15297
15765
  lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
15298
15766
  }
15299
15767
  }
15768
+ const budget = opts.onFleetBudget?.();
15769
+ if (budget) {
15770
+ lines.push(
15771
+ "",
15772
+ ...formatFleetBudgetLines(budget, {
15773
+ bold: color26.bold,
15774
+ dim: color26.dim,
15775
+ amber: color26.amber
15776
+ })
15777
+ );
15778
+ }
15300
15779
  const msg2 = lines.join("\n");
15301
15780
  opts.renderer.write(msg2);
15302
15781
  return { message: msg2 };
@@ -20462,6 +20941,7 @@ ${targetMode.description}`
20462
20941
 
20463
20942
  // src/slash-commands/modelcaps.ts
20464
20943
  import * as fs14 from "node:fs/promises";
20944
+ import { hasProviderCredential } from "@wrongstack/core/models";
20465
20945
  import { color as color36 } from "@wrongstack/core/utils";
20466
20946
  function fmtTokens(n) {
20467
20947
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
@@ -20569,13 +21049,7 @@ function buildModelCapsCommand(opts) {
20569
21049
  }
20570
21050
  const config = opts.configStore.get();
20571
21051
  const configProviders = config?.providers ?? {};
20572
- function hasKey(providerId) {
20573
- const pc = configProviders[providerId];
20574
- if (!pc) return false;
20575
- if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
20576
- if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
20577
- return false;
20578
- }
21052
+ const hasKey = (provider) => hasProviderCredential(provider, { providers: configProviders });
20579
21053
  const lines = [
20580
21054
  `${color36.bold("Available Models")} ${color36.dim("\u2014 capacities + pricing")}`,
20581
21055
  ""
@@ -20585,7 +21059,7 @@ function buildModelCapsCommand(opts) {
20585
21059
  if (trimmed && !trimmed.includes("/") && !prov.id.toLowerCase().includes(trimmed) && !prov.name.toLowerCase().includes(trimmed)) {
20586
21060
  continue;
20587
21061
  }
20588
- const keyed = hasKey(prov.id);
21062
+ const keyed = hasKey(prov);
20589
21063
  const marker = keyed ? color36.green("\u25CF") : color36.dim("\u25CB");
20590
21064
  lines.push(` ${marker} ${color36.bold(prov.id.padEnd(16))} ${color36.dim(`(${prov.name})`)}`);
20591
21065
  const models = prov.models ?? [];
@@ -23502,6 +23976,26 @@ import { fallbackProfileChain } from "@wrongstack/core/agent";
23502
23976
  import { decryptConfigSecrets as decryptConfigSecrets4, encryptConfigSecrets as encryptConfigSecrets4, noOpVault as noOpVault6 } from "@wrongstack/core/security";
23503
23977
  import { MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, resolveModelMatrix, resolveModelTargetFromEntry } from "@wrongstack/core/coordination";
23504
23978
  import { parseModelRef as parseModelRef3 } from "@wrongstack/core/agent";
23979
+ import { catalogProviderIdFor } from "@wrongstack/providers";
23980
+ async function resolveCatalogModelIds(registry, config, providerIds) {
23981
+ const out = /* @__PURE__ */ new Map();
23982
+ if (!registry) return out;
23983
+ await Promise.all(
23984
+ providerIds.map(async (id) => {
23985
+ try {
23986
+ const catalogId = catalogProviderIdFor(id, config.providers?.[id]?.type);
23987
+ const provider = await registry.getProvider(catalogId);
23988
+ if (provider) out.set(id, provider.models.map((m) => m.id));
23989
+ } catch {
23990
+ }
23991
+ })
23992
+ );
23993
+ return out;
23994
+ }
23995
+ function summarizeModelIds(ids, limit = 8) {
23996
+ if (ids.length <= limit) return ids.join(", ");
23997
+ return `${ids.slice(0, limit).join(", ")} ${color44.dim(`\u2026 +${ids.length - limit} more`)}`;
23998
+ }
23505
23999
  function providerHasKey(entry) {
23506
24000
  if (!entry) return false;
23507
24001
  if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
@@ -23703,9 +24197,10 @@ function buildSetModelCommand(opts) {
23703
24197
  );
23704
24198
  const matrix = config.modelMatrix ?? {};
23705
24199
  if (sub === "list") {
24200
+ const catalogModels = await resolveCatalogModelIds(opts.modelsRegistry, config, keyed);
23706
24201
  const provLines = keyed.map((id) => {
23707
- const models = config.providers?.[id]?.models ?? [];
23708
- const ms = models.length ? models.join(", ") : color44.dim("(any model id accepted)");
24202
+ const models = visibleModelIds(id, config, catalogModels.get(id) ?? []);
24203
+ const ms = models.length ? summarizeModelIds(models) : color44.dim("(any model id accepted)");
23709
24204
  return ` ${color44.cyan(id.padEnd(16))} ${ms}`;
23710
24205
  });
23711
24206
  const roles = Object.keys(AGENT_CATALOG2).sort();
@@ -23781,6 +24276,11 @@ function buildSetModelCommand(opts) {
23781
24276
  if (sub === "doctor") {
23782
24277
  const issues = [];
23783
24278
  const warnings = [];
24279
+ const doctorCatalogModels = await resolveCatalogModelIds(
24280
+ opts.modelsRegistry,
24281
+ config,
24282
+ keyed
24283
+ );
23784
24284
  for (const [key, entry] of Object.entries(matrix)) {
23785
24285
  const kind = matrixKeyKind(key);
23786
24286
  if (kind === "unknown") {
@@ -23803,9 +24303,14 @@ function buildSetModelCommand(opts) {
23803
24303
  const effectiveProvider = entry.provider ?? config.provider;
23804
24304
  const provCfg = config.providers?.[effectiveProvider];
23805
24305
  if (entry.model && provCfg?.models && provCfg.models.length > 0) {
23806
- if (!provCfg.models.includes(entry.model)) {
24306
+ const reachable = visibleModelIds(
24307
+ effectiveProvider,
24308
+ config,
24309
+ doctorCatalogModels.get(effectiveProvider) ?? []
24310
+ );
24311
+ if (!reachable.includes(entry.model)) {
23807
24312
  warnings.push(
23808
- `${color44.amber("\u26A0")} ${color44.amber(key)}: model "${entry.model}" not in ${effectiveProvider}'s model list (${provCfg.models.join(", ")})`
24313
+ `${color44.amber("\u26A0")} ${color44.amber(key)}: model "${entry.model}" not in ${effectiveProvider}'s model list (${summarizeModelIds(reachable)})`
23809
24314
  );
23810
24315
  }
23811
24316
  }
@@ -25070,8 +25575,13 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
25070
25575
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25071
25576
  cwd: resolved,
25072
25577
  stdio: "inherit",
25073
- detached: false,
25074
- signal: AbortSignal.timeout(3e4)
25578
+ detached: false
25579
+ // NO abort signal: this child is a long-lived INTERACTIVE session that
25580
+ // is meant to outlive the spawn call. The 30 s timeout fired 30 seconds
25581
+ // after "Switched to <project> … (current session stays open)" and
25582
+ // SIGTERM'd the new session mid-typing, with no message. Sibling
25583
+ // long-lived spawns pass no signal (slash-commands/mailbox-serve.ts:98),
25584
+ // and boot/tui-project-spawn.ts documents this exact bug being removed.
25075
25585
  });
25076
25586
  child.on("error", (err) => {
25077
25587
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25207,8 +25717,13 @@ async function spawnInProject(opts, _ctx, root, projectName) {
25207
25717
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25208
25718
  cwd: root,
25209
25719
  stdio: "inherit",
25210
- detached: false,
25211
- signal: AbortSignal.timeout(3e4)
25720
+ detached: false
25721
+ // NO abort signal: this child is a long-lived INTERACTIVE session that
25722
+ // is meant to outlive the spawn call. The 30 s timeout fired 30 seconds
25723
+ // after "Switched to <project> … (current session stays open)" and
25724
+ // SIGTERM'd the new session mid-typing, with no message. Sibling
25725
+ // long-lived spawns pass no signal (slash-commands/mailbox-serve.ts:98),
25726
+ // and boot/tui-project-spawn.ts documents this exact bug being removed.
25212
25727
  });
25213
25728
  child.on("error", (err) => {
25214
25729
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25244,8 +25759,13 @@ async function handleNewSession(_opts, _ctx) {
25244
25759
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25245
25760
  cwd: process.cwd(),
25246
25761
  stdio: "inherit",
25247
- detached: false,
25248
- signal: AbortSignal.timeout(3e4)
25762
+ detached: false
25763
+ // NO abort signal: this child is a long-lived INTERACTIVE session that
25764
+ // is meant to outlive the spawn call. The 30 s timeout fired 30 seconds
25765
+ // after "Switched to <project> … (current session stays open)" and
25766
+ // SIGTERM'd the new session mid-typing, with no message. Sibling
25767
+ // long-lived spawns pass no signal (slash-commands/mailbox-serve.ts:98),
25768
+ // and boot/tui-project-spawn.ts documents this exact bug being removed.
25249
25769
  });
25250
25770
  child.on("error", (err) => {
25251
25771
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25292,6 +25812,7 @@ async function handlePrevSessions(opts, _ctx) {
25292
25812
  import { spawn as spawn8 } from "node:child_process";
25293
25813
  import * as fsp3 from "node:fs/promises";
25294
25814
  import * as path27 from "node:path";
25815
+ import { emitReviewIfChanged } from "@wrongstack/core/plugin";
25295
25816
  async function runGit2(args, cwd) {
25296
25817
  return new Promise((resolve8) => {
25297
25818
  const child = spawn8("git", args, {
@@ -25385,16 +25906,29 @@ function buildReviewCommand(opts) {
25385
25906
  enabled: true,
25386
25907
  provider: ctx.provider.id,
25387
25908
  model: ctx.model,
25388
- maxFiles: limit
25909
+ maxFiles: limit,
25910
+ autoFix: "off",
25911
+ cascadeOn: "off",
25912
+ maxCascadeDepth: 0
25389
25913
  },
25390
25914
  cwd,
25391
25915
  files: filesWithContent
25392
25916
  };
25393
- opts.events.emitCustom("chimera.review_needed", payload);
25917
+ const emitted = await emitReviewIfChanged(
25918
+ { events: opts.events, emitCustom: opts.events.emitCustom.bind(opts.events) },
25919
+ payload
25920
+ );
25921
+ if (!emitted) {
25922
+ return {
25923
+ message: "\u{1F982} Chimera review skipped \u2014 this file content already has a review in progress."
25924
+ };
25925
+ }
25926
+ const skipped = filesWithContent.length - emitted.files.length;
25394
25927
  const note = truncated ? `
25395
25928
  ${existing.length - limit} more changed file(s) were not included \u2014 raise the cap with /review --limit ${existing.length}.` : "";
25929
+ const skippedNote = skipped > 0 ? ` ${skipped} file(s) already under review were skipped.` : "";
25396
25930
  return {
25397
- message: `\u{1F982} Chimera review triggered for ${filesWithContent.length} file(s).
25931
+ message: `\u{1F982} Chimera review triggered for ${emitted.files.length} file(s).${skippedNote}
25398
25932
  The review report will appear in chat history shortly.${note}`
25399
25933
  };
25400
25934
  }
@@ -25516,6 +26050,7 @@ function buildSettingsCommand(opts) {
25516
26050
  " /settings context-strategy hybrid|intelligent|selective Compactor strategy",
25517
26051
  " /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
25518
26052
  " /settings token-saving off|minimal|light|medium|aggressive Token-saving mode",
26053
+ " /settings nextsteps-tool on|off Give the leader a `nextsteps` tool alongside the <nextsteps> block (next session)",
25519
26054
  " /settings mcp on|off Load MCP servers declared in config",
25520
26055
  " /settings plugins on|off Load npm plugins declared in config",
25521
26056
  " /settings memory on|off Register remember/forget tools",
@@ -25565,6 +26100,7 @@ function buildSettingsCommand(opts) {
25565
26100
  const contextAutoCompact = context?.autoCompact !== false;
25566
26101
  const features = opts.configStore.get().features;
25567
26102
  const tokenSavingTier = features?.tokenSavingMode ?? "off";
26103
+ const nextStepsToolEnabled = opts.configStore.get().tools?.nextsteps?.enabled === true;
25568
26104
  const maxConcurrent = opts.configStore.get().maxConcurrent ?? 4;
25569
26105
  const titleAnimation = autonomy?.terminalTitleAnimation !== false;
25570
26106
  const modelRuntime = opts.configStore.get().modelRuntime;
@@ -25607,6 +26143,7 @@ function buildSettingsCommand(opts) {
25607
26143
  ` context strategy: ${color48.cyan(contextStrategy)} ${color48.dim("change: /settings context-strategy hybrid|intelligent|selective")}`,
25608
26144
  ` context auto-compact: ${contextAutoCompact ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings context-auto-compact on|off")}`,
25609
26145
  ` token-saving: ${color48.cyan(tokenSavingTier)} ${color48.dim("change: /settings token-saving off|minimal|light|medium|aggressive")}`,
26146
+ ` nextsteps tool: ${nextStepsToolEnabled ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings nextsteps-tool on|off")}`,
25610
26147
  ` MCP features: ${feats?.mcp !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings mcp on|off")}`,
25611
26148
  ` plugin features: ${feats?.plugins !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings plugins on|off")}`,
25612
26149
  ` memory features: ${feats?.memory !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings memory on|off")}`,
@@ -26036,6 +26573,21 @@ function buildSettingsCommand(opts) {
26036
26573
  message: `${color48.green("\u2713")} context auto-compact \u2192 ${on ? color48.cyan("on") : color48.dim("off")} ${color48.dim("auto-compact context when thresholds crossed")}`
26037
26574
  };
26038
26575
  }
26576
+ if (sub === "nextsteps-tool") {
26577
+ const raw = (rest[0] ?? "").toLowerCase();
26578
+ if (!["on", "off"].includes(raw)) {
26579
+ return { message: `${color48.amber("Usage:")} /settings nextsteps-tool on|off` };
26580
+ }
26581
+ const on = raw === "on";
26582
+ await persistConfigSetting(persistDeps, (cfg) => {
26583
+ const tools = cfg.tools ?? {};
26584
+ tools.nextsteps = { enabled: on };
26585
+ cfg.tools = tools;
26586
+ });
26587
+ return {
26588
+ message: `${color48.green("\u2713")} nextsteps tool \u2192 ${on ? color48.cyan("on") : color48.dim("off")} ${color48.dim("takes effect in the next session")}`
26589
+ };
26590
+ }
26039
26591
  if (sub === "token-saving") {
26040
26592
  const raw = (rest[0] ?? "").toLowerCase();
26041
26593
  const tiers = ["off", "minimal", "light", "medium", "aggressive"];
@@ -29846,11 +30398,13 @@ async function runInteractive(cliCtx) {
29846
30398
  wpaths
29847
30399
  });
29848
30400
  const stdinInteractive = process.stdin.isTTY;
30401
+ const hookRunnerRef = { current: null };
29849
30402
  registerCliManagementTools({
29850
30403
  toolRegistry,
29851
30404
  configStore,
29852
30405
  profileConfigPath,
29853
- stdinInteractive
30406
+ stdinInteractive,
30407
+ getHookRunner: () => hookRunnerRef.current
29854
30408
  });
29855
30409
  const { metricsSink, healthRegistry, metricsStatus } = (() => {
29856
30410
  const ms = setupMetrics({
@@ -29884,7 +30438,8 @@ async function runInteractive(cliCtx) {
29884
30438
  const systemPrompt = await promptBuilder.build({
29885
30439
  cwd,
29886
30440
  projectRoot,
29887
- tools: toolRegistry.list(),
30441
+ tools: toolRegistry.listForProvider(),
30442
+ catalogTools: toolRegistry.list(),
29888
30443
  provider: config.provider,
29889
30444
  model: config.model,
29890
30445
  onlineAgents
@@ -29998,7 +30553,8 @@ async function runInteractive(cliCtx) {
29998
30553
  slashRegistry,
29999
30554
  hqPublisherRef,
30000
30555
  brainMailbox,
30001
- pluginHost
30556
+ pluginHost,
30557
+ hookRunner
30002
30558
  } = await setupLifecycleAndPlugins({
30003
30559
  flags,
30004
30560
  config,
@@ -30035,6 +30591,7 @@ async function runInteractive(cliCtx) {
30035
30591
  logger,
30036
30592
  teardownHandlers
30037
30593
  });
30594
+ hookRunnerRef.current = hookRunner;
30038
30595
  const fallbackProfileManager = container.resolve(TOKENS9.FallbackProfileManager);
30039
30596
  const statusTracker = await setupProviderStatus({
30040
30597
  events,
@@ -30079,11 +30636,21 @@ async function runInteractive(cliCtx) {
30079
30636
  getConfig: () => config,
30080
30637
  fallbackProfileManager,
30081
30638
  statusTracker,
30639
+ wrapProviderCall: (request, inner) => agent.extensions.wrapProviderRunner(
30640
+ (_ctx, wrappedRequest) => inner(wrappedRequest),
30641
+ // OneShotOrchestrator owns the selected provider and fallback chain.
30642
+ // The agent-loop fallback wrapper routes from and mutates agent.ctx,
30643
+ // which may describe a different provider/model than this utility call.
30644
+ { exclude: ["fallback-model"] }
30645
+ )(agent.ctx, request),
30082
30646
  compactor: container.resolve(TOKENS9.Compactor)
30083
30647
  });
30084
30648
  let {
30085
30649
  director,
30086
30650
  maxConcurrent,
30651
+ maxSpawns,
30652
+ maxConcurrentSource,
30653
+ maxSpawnsSource,
30087
30654
  autonomyMode,
30088
30655
  nextPredictEnabled,
30089
30656
  currentSuggestions,
@@ -30136,6 +30703,9 @@ async function runInteractive(cliCtx) {
30136
30703
  stateCheckpointPath,
30137
30704
  fleetRootForPromotion,
30138
30705
  maxConcurrent,
30706
+ maxSpawns,
30707
+ maxConcurrentSource,
30708
+ maxSpawnsSource,
30139
30709
  effectiveMaxContextRef,
30140
30710
  mcpRegistry,
30141
30711
  sessResult,
@@ -30176,11 +30746,30 @@ async function runInteractive(cliCtx) {
30176
30746
  for (const tool of director.tools(FLEET_ROSTER5)) {
30177
30747
  toolRegistry.register(tool);
30178
30748
  }
30179
- renderer.writeInfo(`Director mode enabled. Roster: ${Object.keys(FLEET_ROSTER5).join(", ")}`);
30180
- renderer.writeInfo(` fleet root \u2192 ${fleetRoot}`);
30181
- renderer.writeInfo(` manifest \u2192 ${manifestPath}`);
30182
- renderer.writeInfo(` scratchpad \u2192 ${sharedScratchpadPath}`);
30183
- renderer.writeInfo(` subagents \u2192 ${subagentSessionsRoot}`);
30749
+ const browserSurface = flags.webui === true || flags.simpleui === true;
30750
+ if (browserSurface) {
30751
+ renderer.writeInfo(
30752
+ `Director mode enabled (${Object.keys(FLEET_ROSTER5).length} roles) \u2192 ${fleetRoot}`
30753
+ );
30754
+ } else {
30755
+ renderer.writeInfo(`Director mode enabled. Roster: ${Object.keys(FLEET_ROSTER5).join(", ")}`);
30756
+ renderer.writeInfo(` fleet root \u2192 ${fleetRoot}`);
30757
+ renderer.writeInfo(` manifest \u2192 ${manifestPath}`);
30758
+ renderer.writeInfo(` scratchpad \u2192 ${sharedScratchpadPath}`);
30759
+ renderer.writeInfo(` subagents \u2192 ${subagentSessionsRoot}`);
30760
+ }
30761
+ if (priorFleetState) {
30762
+ const budget = multiAgentHost.budgetView();
30763
+ const fmt = (n) => Number.isFinite(n) ? String(n) : "\u221E";
30764
+ renderer.writeInfo(
30765
+ ` fleet budget \u2192 ${budget.usedSpawns}/${fmt(budget.maxSpawns)} spawns used (${fmt(budget.remainingSpawns)} remaining; maxConcurrent ${budget.maxConcurrent})`
30766
+ );
30767
+ if (budget.ceilingMismatch && budget.checkpointMaxSpawns !== void 0) {
30768
+ renderer.writeInfo(
30769
+ ` \u26A0 checkpoint maxSpawns was ${budget.checkpointMaxSpawns}; live ceiling is ${fmt(budget.maxSpawns)}`
30770
+ );
30771
+ }
30772
+ }
30184
30773
  } else {
30185
30774
  renderer.writeInfo(`Running without Director \u2014 fleet orchestration tools disabled.`);
30186
30775
  }
@@ -30374,7 +30963,25 @@ async function runInteractive(cliCtx) {
30374
30963
  effectiveMaxContext: effectiveMaxContextRef.current,
30375
30964
  configRef,
30376
30965
  autonomyModeRef,
30377
- logger
30966
+ logger,
30967
+ // WITHOUT this the helper primed an engine and dropped it: the only live
30968
+ // holder is the `eternalEngine` binding below, written solely by the slash
30969
+ // handler. `--eternal "<mission>"` therefore forced YOLO on and set the
30970
+ // mode to 'eternal', then the REPL found no engine, printed "no engine
30971
+ // wired — falling back to off" on EVERY loop iteration without actually
30972
+ // flipping the mode back or `continue`-ing, and left the user staring at
30973
+ // unconfirmed tool calls. `cli-eternal-flag.test.ts` passes this ref, so
30974
+ // the test exercised a wiring production never had.
30975
+ // The live holder is `EternalAutonomyEngine | null`; the ref contract is
30976
+ // `| undefined`. Bridge rather than widen either side.
30977
+ eternalEngineRef: {
30978
+ get current() {
30979
+ return eternalEngine ?? void 0;
30980
+ },
30981
+ set current(engine) {
30982
+ eternalEngine = engine ?? null;
30983
+ }
30984
+ }
30378
30985
  });
30379
30986
  config = configRef.current;
30380
30987
  if (eternalFlag.length > 0) {
@@ -30404,7 +31011,7 @@ async function runInteractive(cliCtx) {
30404
31011
  onEvent: evOn
30405
31012
  });
30406
31013
  const savedProviderCfg = config.providers?.[config.provider];
30407
- const { execute } = await import("./execution-ANOYMWPO.js");
31014
+ const { execute } = await import("./execution-LHMSMDHB.js");
30408
31015
  const stopHeapWatchdog = startSharedHeapWatchdog({
30409
31016
  collectStats: () => {
30410
31017
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -30636,4 +31243,4 @@ export {
30636
31243
  CLI_VERSION,
30637
31244
  runInteractive
30638
31245
  };
30639
- //# sourceMappingURL=cli-main-3MQDFSYK.js.map
31246
+ //# sourceMappingURL=cli-main-XWC57MGE.js.map