@wrongstack/cli 0.300.0 → 0.302.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 (62) hide show
  1. package/dist/{acp-IDNS2YVE.js → acp-DATHHPNT.js} +34 -15
  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-6T6ZOT2R.js → auth-PJD3JRH2.js} +150 -37
  5. package/dist/boot/dispatch-webui.d.ts +3 -16
  6. package/dist/boot/launch-menu.d.ts +2 -4
  7. package/dist/boot/tool-registry.d.ts +3 -0
  8. package/dist/boot/webui-session-child.d.ts +91 -0
  9. package/dist/chimera-reviewer-policy.d.ts +10 -3
  10. package/dist/chimera-work-registry.d.ts +19 -0
  11. package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
  12. package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
  13. package/dist/{chunk-GYDQABMA.js → chunk-2KDOFTTM.js} +44 -32
  14. package/dist/{chunk-V76R7DC5.js → chunk-3MOUBRKZ.js} +311 -309
  15. package/dist/chunk-CSAPOCBP.js +24 -0
  16. package/dist/{chunk-N7ULWBO5.js → chunk-ETOEGL3V.js} +3 -7
  17. package/dist/{chunk-3MWUZMOL.js → chunk-GUHQQG63.js} +14 -3
  18. package/dist/chunk-KE7E7DPX.js +38 -0
  19. package/dist/{chunk-PEMN4T5O.js → chunk-MT6FGXO3.js} +23 -4
  20. package/dist/chunk-PFVVUH5B.js +127 -0
  21. package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
  22. package/dist/{chunk-4WJKVQJY.js → chunk-ZECLGUTF.js} +15 -3
  23. package/dist/cli-context.d.ts +2 -0
  24. package/dist/{cli-main-7B3G2YSG.js → cli-main-EOJ74CI4.js} +336 -99
  25. package/dist/execute-deps.d.ts +3 -0
  26. package/dist/{execution-SHGM7BVC.js → execution-CQ772VPS.js} +246 -97
  27. package/dist/execution-chimera-cascade.d.ts +3 -4
  28. package/dist/execution-chimera-review.d.ts +2 -3
  29. package/dist/execution-cleanup.d.ts +3 -2
  30. package/dist/execution.d.ts +1 -1
  31. package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
  32. package/dist/fleet/host-context.d.ts +1 -1
  33. package/dist/fleet/host-helpers.d.ts +14 -0
  34. package/dist/fleet/status-broadcast.d.ts +1 -1
  35. package/dist/{hq-DVDD4XPV.js → hq-LN5CJHCM.js} +2 -2
  36. package/dist/hq-server/routes.d.ts +10 -1
  37. package/dist/hq-server/ws.d.ts +17 -6
  38. package/dist/{hq-server-DQHLNHDL.js → hq-server-S7HZG3PA.js} +3 -2
  39. package/dist/index.js +142 -100
  40. package/dist/live-settings-input.d.ts +5 -0
  41. package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
  42. package/dist/{modeldiag-YOQZHBQV.js → modeldiag-3DOK4BWW.js} +2 -2
  43. package/dist/{plugin-usage-EOG4ET4S.js → plugin-usage-YEVLOR33.js} +7 -3
  44. package/dist/{plugins-56CMWMPR.js → plugins-K3BAUP4D.js} +3 -3
  45. package/dist/profile-config-path.d.ts +0 -2
  46. package/dist/provider-helpers.d.ts +0 -5
  47. package/dist/{providers-models-K6RT7ABI.js → providers-models-WEZ4EGLL.js} +6 -5
  48. package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
  49. package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
  50. package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
  51. package/dist/subcommands/flags.d.ts +23 -0
  52. package/dist/terminal-format.d.ts +15 -0
  53. package/dist/{webui-server-G3EKFXLL.js → webui-server-NFNRGNNT.js} +54 -29
  54. package/dist/webui-server-options.d.ts +7 -0
  55. package/dist/wiring/brain-and-orchestration.d.ts +1 -1
  56. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
  57. package/dist/wiring/management-tools.d.ts +9 -1
  58. package/dist/wiring/pipeline.d.ts +5 -5
  59. package/dist/wiring/plugins.d.ts +2 -3
  60. package/dist/wiring/provider-utility-tools.d.ts +1 -0
  61. package/dist/wiring/tools.d.ts +4 -5
  62. package/package.json +23 -23
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  setupPlugins
3
- } from "./chunk-4WJKVQJY.js";
3
+ } from "./chunk-ZECLGUTF.js";
4
4
  import {
5
5
  runProjectPicker
6
6
  } from "./chunk-5N34EWJR.js";
@@ -43,7 +43,7 @@ import {
43
43
  import {
44
44
  PLUGIN_AUDIT_ENTRIES,
45
45
  runPluginManagementCommand
46
- } from "./chunk-3MWUZMOL.js";
46
+ } from "./chunk-GUHQQG63.js";
47
47
  import {
48
48
  configureSimpleUiRuntimeContext,
49
49
  detectProjectFacts,
@@ -80,15 +80,15 @@ import {
80
80
  runClaudeOAuthLogin,
81
81
  runCopilotOAuthLogin,
82
82
  validateFamily
83
- } from "./chunk-V76R7DC5.js";
83
+ } from "./chunk-3MOUBRKZ.js";
84
84
  import {
85
85
  LOCAL_LLM_PRESETS,
86
86
  parseSpawnFlags
87
- } from "./chunk-PEMN4T5O.js";
87
+ } from "./chunk-MT6FGXO3.js";
88
88
  import {
89
89
  buildPickableProviders,
90
90
  visibleModelIds
91
- } from "./chunk-N7ULWBO5.js";
91
+ } from "./chunk-ETOEGL3V.js";
92
92
  import {
93
93
  runCodexOAuthLogin
94
94
  } from "./chunk-PYTFS4MC.js";
@@ -102,10 +102,10 @@ import {
102
102
  } from "./chunk-PJHET45A.js";
103
103
  import {
104
104
  appendHistory
105
- } from "./chunk-YWUPHRGB.js";
105
+ } from "./chunk-263G3FMB.js";
106
106
  import {
107
107
  activeProfileConfigPath
108
- } from "./chunk-DPJZQCVP.js";
108
+ } from "./chunk-YMXXOOFN.js";
109
109
  import {
110
110
  __require
111
111
  } from "./chunk-7OCVIDC7.js";
@@ -1567,6 +1567,7 @@ function registerBuiltinTools(deps) {
1567
1567
  tier,
1568
1568
  contextTool: createContextManagerTool({ compactor: deps.compactor }),
1569
1569
  memory: { enabled: deps.config.features.memory, store: deps.memoryStore },
1570
+ nextSteps: { enabled: deps.config.tools?.nextsteps?.enabled === true },
1570
1571
  coordinationTools: [
1571
1572
  makeMailboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events }),
1572
1573
  makeMailSendTool({ projectDir: deps.wpaths.projectDir, events: deps.events }),
@@ -2782,6 +2783,9 @@ import {
2782
2783
  } from "@wrongstack/core/execution";
2783
2784
  import { TOKENS as TOKENS6 } from "@wrongstack/core/kernel";
2784
2785
  import { createMcpControlTool, createMcpUseTool } from "@wrongstack/core/tools";
2786
+ import {
2787
+ normalizeTokenSavingTier as normalizeTokenSavingTier2
2788
+ } from "@wrongstack/core/types";
2785
2789
 
2786
2790
  // src/boot/brain-decision-log.ts
2787
2791
  var MAX_BRAIN_LOG_ENTRIES = 20;
@@ -2876,9 +2880,7 @@ import { wstackGlobalRoot } from "@wrongstack/core/utils";
2876
2880
 
2877
2881
  // src/fleet/host-helpers.ts
2878
2882
  import * as path2 from "node:path";
2879
- import {
2880
- makeSubagentResultTool
2881
- } from "@wrongstack/core/coordination";
2883
+ import { makeSubagentResultTool } from "@wrongstack/core/coordination";
2882
2884
  import { ToolCapabilities, WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
2883
2885
  import { makePreferSideConflictResolver } from "@wrongstack/sdd";
2884
2886
  function isInsideDirectory(root, candidate) {
@@ -2954,10 +2956,13 @@ var DEFAULT_SUBAGENT_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
2954
2956
  "collab_debug",
2955
2957
  "fleet_emit"
2956
2958
  ]);
2959
+ var NEVER_SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["nextsteps"]);
2957
2960
  function selectSubagentTools(allTools, directorToolsByName, allow) {
2958
2961
  if (!allow || allow.length === 0) {
2959
2962
  const visible = new Map(
2960
- 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])
2961
2966
  );
2962
2967
  visible.set("submit_result", makeSubagentResultTool());
2963
2968
  return Array.from(visible.values());
@@ -2965,12 +2970,20 @@ function selectSubagentTools(allTools, directorToolsByName, allow) {
2965
2970
  const allowSet = new Set(allow);
2966
2971
  const result = /* @__PURE__ */ new Map();
2967
2972
  for (const tool of allTools) {
2968
- 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);
2969
2975
  }
2970
2976
  for (const name of allowSet) {
2977
+ if (NEVER_SUBAGENT_TOOLS.has(name)) continue;
2971
2978
  const directorTool = directorToolsByName.get(name);
2972
2979
  if (directorTool && !result.has(name)) result.set(name, directorTool);
2973
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
+ }
2974
2987
  result.set("submit_result", makeSubagentResultTool());
2975
2988
  return Array.from(result.values());
2976
2989
  }
@@ -3351,7 +3364,8 @@ import {
3351
3364
  Agent,
3352
3365
  Context,
3353
3366
  createDefaultPipelines,
3354
- createFallbackModelExtension
3367
+ createFallbackModelExtension,
3368
+ renderInstructionLayer
3355
3369
  } from "@wrongstack/core/agent";
3356
3370
  import {
3357
3371
  applyProjectAgentConfig,
@@ -3376,9 +3390,14 @@ import { EventBus } from "@wrongstack/core/kernel";
3376
3390
  import { AutoApprovePermissionPolicy } from "@wrongstack/core/security";
3377
3391
 
3378
3392
  // src/fleet/host-context.ts
3393
+ import {
3394
+ missingRequiredRuntimeTools,
3395
+ missingRuntimeCapabilities,
3396
+ runtimeToolReferencesFromText
3397
+ } from "@wrongstack/core/agent-catalog";
3379
3398
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
3380
3399
  import { getSageRetrieval } from "@wrongstack/sage";
3381
- async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3400
+ async function resolveHostSubagentSkillContent(deps, roster, subCfg, availableToolNames = []) {
3382
3401
  const rosterSkillNames = subCfg.role ? roster[subCfg.role]?.skillNames : void 0;
3383
3402
  const skillNames = [...new Set(subCfg.skillNames ?? rosterSkillNames ?? [])];
3384
3403
  const directContent = subCfg.skillContent?.trim();
@@ -3391,8 +3410,14 @@ async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3391
3410
  try {
3392
3411
  const manifest = await deps.skillLoader.find(skillName);
3393
3412
  if (!manifest) continue;
3413
+ if (missingRuntimeCapabilities(manifest.requiredCapabilities, availableToolNames).length > 0 || missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
3414
+ continue;
3415
+ }
3394
3416
  const body = (await deps.skillLoader.readSaveBody(skillName)).trim();
3395
3417
  if (!body) continue;
3418
+ if (missingRequiredRuntimeTools(runtimeToolReferencesFromText(body), availableToolNames).length > 0) {
3419
+ continue;
3420
+ }
3396
3421
  const entry = `## Skill: ${skillName}
3397
3422
 
3398
3423
  ${body.slice(0, maxCharsPerSkill)}`;
@@ -3808,16 +3833,26 @@ ${message} Falling back to the assigned checkout.` : `${message} Falling back to
3808
3833
  onlineAgents = await subagentMailbox.getAgentStatuses();
3809
3834
  } catch {
3810
3835
  }
3836
+ const subagentTools = host.filterTools(effectiveCfg.tools);
3811
3837
  const baseSystem = await host.deps.systemPromptBuilder.build({
3812
3838
  cwd: subCwd,
3813
3839
  projectRoot: host.deps.projectRoot,
3814
- tools: host.filterTools(effectiveCfg.tools),
3840
+ tools: subagentTools,
3841
+ catalogTools: subagentTools,
3815
3842
  model: effModel,
3816
3843
  provider: effProvider,
3817
3844
  subagent: true,
3818
3845
  onlineAgents
3819
3846
  });
3820
- 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
+ });
3821
3856
  if (availabilityNotice) baseSystem.push({ type: "text", text: availabilityNotice });
3822
3857
  const audienceMemory = await retrieveHostSubagentMemory(
3823
3858
  host.deps,
@@ -3835,7 +3870,8 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
3835
3870
  const roleSkillContent = await resolveHostSubagentSkillContent(
3836
3871
  host.deps,
3837
3872
  host.roster,
3838
- effectiveCfg
3873
+ effectiveCfg,
3874
+ subagentTools.map((tool) => tool.name)
3839
3875
  );
3840
3876
  if (roleSkillContent) {
3841
3877
  for (let index = baseSystem.length - 1; index >= 0; index--) {
@@ -3882,7 +3918,8 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
3882
3918
  projectRoot: host.deps.projectRoot,
3883
3919
  allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
3884
3920
  model: effModel,
3885
- tools: host.filterTools(tools),
3921
+ tools: subagentTools,
3922
+ catalogTools: subagentTools,
3886
3923
  agentId: subagentName,
3887
3924
  agentName: effectiveCfg.name ?? subagentName
3888
3925
  });
@@ -4135,6 +4172,9 @@ import {
4135
4172
  var DEFAULT_MIN_INTERVAL_MS = 15e3;
4136
4173
  var DEFAULT_GLOBAL_PER_MINUTE = 20;
4137
4174
  var BODY_MAX = 200;
4175
+ function isSilentInfrastructureAgent(name) {
4176
+ return name.trim().toLowerCase() === "chimera-review";
4177
+ }
4138
4178
  function createFleetStatusBroadcaster(opts) {
4139
4179
  const cfg = opts.config;
4140
4180
  const minIntervalMs = cfg?.minIntervalMsPerAgent ?? DEFAULT_MIN_INTERVAL_MS;
@@ -4154,6 +4194,7 @@ function createFleetStatusBroadcaster(opts) {
4154
4194
  let droppedSinceLastSend = 0;
4155
4195
  const announcedSpawn = /* @__PURE__ */ new Set();
4156
4196
  const announcedBudget = /* @__PURE__ */ new Set();
4197
+ const silentInfrastructureAgents = /* @__PURE__ */ new Set();
4157
4198
  const disposers = [];
4158
4199
  let stopped = false;
4159
4200
  function underGlobalCap(now) {
@@ -4218,9 +4259,14 @@ function createFleetStatusBroadcaster(opts) {
4218
4259
  opts.events.on("subagent.spawned", (e) => {
4219
4260
  if (announcedSpawn.has(e.subagentId)) return;
4220
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
+ }
4221
4267
  broadcast(e.subagentId, {
4222
- subject: `[${e.name ?? nameOf(e.subagentId)}] joined the fleet`,
4223
- 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)}` : ""}`,
4224
4270
  priority: "low"
4225
4271
  });
4226
4272
  }),
@@ -4234,6 +4280,9 @@ function createFleetStatusBroadcaster(opts) {
4234
4280
  opts.events.on("subagent.task_completed", (e) => {
4235
4281
  heartbeat({ agentId: e.subagentId, status: "idle" });
4236
4282
  const name = nameOf(e.subagentId);
4283
+ if (silentInfrastructureAgents.has(e.subagentId) || isSilentInfrastructureAgent(name)) {
4284
+ return;
4285
+ }
4237
4286
  const ok = e.status === "success";
4238
4287
  const secs = Math.round(e.durationMs / 1e3);
4239
4288
  broadcast(e.subagentId, {
@@ -4244,6 +4293,9 @@ function createFleetStatusBroadcaster(opts) {
4244
4293
  }),
4245
4294
  opts.events.on("subagent.budget_warning", (e) => {
4246
4295
  if (!broadcastBudgetWarnings) return;
4296
+ if (silentInfrastructureAgents.has(e.subagentId) || isSilentInfrastructureAgent(nameOf(e.subagentId))) {
4297
+ return;
4298
+ }
4247
4299
  const key = `${e.subagentId}|${e.kind}`;
4248
4300
  if (announcedBudget.has(key)) return;
4249
4301
  announcedBudget.add(key);
@@ -4270,6 +4322,7 @@ function createFleetStatusBroadcaster(opts) {
4270
4322
  void mb().deregisterAgent(e.subagentId).catch(() => {
4271
4323
  });
4272
4324
  announcedSpawn.delete(e.subagentId);
4325
+ silentInfrastructureAgents.delete(e.subagentId);
4273
4326
  lastSentAt.delete(e.subagentId);
4274
4327
  for (const key of announcedBudget) {
4275
4328
  if (key.startsWith(`${e.subagentId}|`)) announcedBudget.delete(key);
@@ -5530,6 +5583,7 @@ function setupBrainAndOrchestration(deps) {
5530
5583
  events
5531
5584
  })
5532
5585
  );
5586
+ toolRegistry.exposeToProvider("delegate");
5533
5587
  toolRegistry.register(
5534
5588
  createMcpControlTool({
5535
5589
  getConfig: () => configStore.get(),
@@ -5537,13 +5591,17 @@ function setupBrainAndOrchestration(deps) {
5537
5591
  registry: mcpRegistry
5538
5592
  })
5539
5593
  );
5540
- if (config.features.tokenSavingMode) {
5541
- toolRegistry.register(
5542
- createMcpUseTool({
5543
- registry: mcpRegistry,
5544
- toolRegistry
5545
- })
5546
- );
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");
5547
5605
  }
5548
5606
  return {
5549
5607
  brain,
@@ -6940,7 +6998,7 @@ import { NotifierImpl } from "@wrongstack/core/notifications";
6940
6998
  import {
6941
6999
  SlashCommandRegistry
6942
7000
  } from "@wrongstack/core/registry";
6943
- import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
7001
+ import { normalizeTokenSavingTier as normalizeTokenSavingTier3 } from "@wrongstack/core/types";
6944
7002
  import {
6945
7003
  createVaultBackedMcpAuthorizationProviderFactory,
6946
7004
  MCPAuthorizationManager,
@@ -7212,13 +7270,19 @@ function setupMetrics(params) {
7212
7270
  }
7213
7271
 
7214
7272
  // src/wiring/pipeline.ts
7215
- import { Agent as Agent2, createDefaultPipelines as createDefaultPipelines2 } from "@wrongstack/core/agent";
7216
- import { applyModelRuntime as applyModelRuntime2, AutoCompactionMiddleware } from "@wrongstack/core/execution";
7217
- 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";
7218
7282
  import { TOKENS as TOKENS7 } from "@wrongstack/core/kernel";
7219
- import { estimateRequestTokensCalibrated } from "@wrongstack/core/utils";
7283
+ import { createSessionEventBridge as createSessionEventBridge2, resolveAuditLevel } from "@wrongstack/core/storage";
7220
7284
  import { resolveContextWindowPolicy } from "@wrongstack/core/types";
7221
- import { ToolExecutor as ToolExecutor2 } from "@wrongstack/core/execution";
7285
+ import { estimateRequestTokensCalibrated } from "@wrongstack/core/utils";
7222
7286
 
7223
7287
  // src/wiring/mailbox-bridge-bootstrap.ts
7224
7288
  import { resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
@@ -7330,7 +7394,7 @@ async function bootstrapMailboxBridgeAtStartup(params) {
7330
7394
  if (!params.projectRoot) {
7331
7395
  return null;
7332
7396
  }
7333
- const mode = params.config?.features?.mailboxBridge ?? "auto";
7397
+ const mode = params.config?.features?.mailboxBridge ?? "off";
7334
7398
  if (mode === "off") {
7335
7399
  return null;
7336
7400
  }
@@ -7352,10 +7416,10 @@ async function bootstrapMailboxBridgeAtStartup(params) {
7352
7416
  switch (handle.source) {
7353
7417
  case "joined":
7354
7418
  case "spawned":
7355
- params.logger.debug(
7356
- `mailbox bridge ready via ${handle.source} on ${params.source} boot`,
7357
- { url: handle.url, lockPath: handle.lockPath }
7358
- );
7419
+ params.logger.debug(`mailbox bridge ready via ${handle.source} on ${params.source} boot`, {
7420
+ url: handle.url,
7421
+ lockPath: handle.lockPath
7422
+ });
7359
7423
  break;
7360
7424
  case "unhealthy":
7361
7425
  params.logger.warn(
@@ -7587,7 +7651,8 @@ function registerProviderUtilityTools(input) {
7587
7651
  getConfig: input.getConfig,
7588
7652
  fallbackProfileManager: input.fallbackProfileManager,
7589
7653
  defaultProvider: config.provider,
7590
- defaultModel: config.model
7654
+ defaultModel: config.model,
7655
+ wrapProviderCall: input.wrapProviderCall
7591
7656
  });
7592
7657
  registerOrOverride(input.toolRegistry, "llm", llmTool);
7593
7658
  const councilOrchestrator = new OneShotOrchestrator({
@@ -7600,7 +7665,8 @@ function registerProviderUtilityTools(input) {
7600
7665
  // router those hints were inert: every seat fell through to the session
7601
7666
  // provider/model, so a three-seat panel asked one model three times and
7602
7667
  // reported a distinctness warning on every single call.
7603
- modelRouter: createLiveModelRouter(input.getConfig)
7668
+ modelRouter: createLiveModelRouter(input.getConfig),
7669
+ wrapProviderCall: input.wrapProviderCall
7604
7670
  });
7605
7671
  registerOrOverride(
7606
7672
  input.toolRegistry,
@@ -7616,7 +7682,8 @@ function registerProviderUtilityTools(input) {
7616
7682
  buildProvider: input.buildProvider,
7617
7683
  getConfig: input.getConfig,
7618
7684
  fallbackProfileManager: input.fallbackProfileManager,
7619
- statusTracker: input.statusTracker
7685
+ statusTracker: input.statusTracker,
7686
+ wrapProviderCall: input.wrapProviderCall
7620
7687
  });
7621
7688
  input.toolRegistry.override(
7622
7689
  "context_manager",
@@ -7801,7 +7868,7 @@ async function setupLifecycleAndPlugins(deps) {
7801
7868
  toolRegistry,
7802
7869
  events,
7803
7870
  log: logger,
7804
- lazyMode: normalizeTokenSavingTier2(config.features.tokenSavingMode) !== "off",
7871
+ lazyMode: normalizeTokenSavingTier3(config.features.tokenSavingMode) !== "off",
7805
7872
  cacheDir: wpaths.cacheDir,
7806
7873
  authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
7807
7874
  store: mcpTokenStore
@@ -7837,7 +7904,15 @@ async function setupLifecycleAndPlugins(deps) {
7837
7904
  fallbackProfileManager,
7838
7905
  statusTracker,
7839
7906
  modelRouter: createLiveModelRouter(getLiveConfig),
7840
- logger
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)
7841
7916
  });
7842
7917
  const pluginCouncil = new CouncilOrchestrator({
7843
7918
  caller: pluginOneShot,
@@ -7907,12 +7982,16 @@ async function setupLifecycleAndPlugins(deps) {
7907
7982
 
7908
7983
  // src/wiring/management-tools.ts
7909
7984
  import * as fs5 from "node:fs/promises";
7910
- import { createFallbackManageTools, createPluginManagerTool } from "@wrongstack/core/tools";
7985
+ import {
7986
+ createFallbackManageTools,
7987
+ createPluginManagerTool
7988
+ } from "@wrongstack/core/tools";
7911
7989
  function registerCliManagementTools({
7912
7990
  toolRegistry,
7913
7991
  configStore,
7914
7992
  profileConfigPath,
7915
- stdinInteractive
7993
+ stdinInteractive,
7994
+ getHookRunner
7916
7995
  }) {
7917
7996
  const fallbackManageTools = createFallbackManageTools({
7918
7997
  getConfig: () => configStore.get(),
@@ -7942,6 +8021,7 @@ ${prompt}
7942
8021
  for (const tool of fallbackManageTools) toolRegistry.register(tool);
7943
8022
  toolRegistry.register(
7944
8023
  createPluginManagerTool({
8024
+ ...getHookRunner ? { getHookRunner } : {},
7945
8025
  getConfig: () => configStore.get(),
7946
8026
  catalog: PLUGIN_AUDIT_ENTRIES.map((entry) => {
7947
8027
  const aliases = [
@@ -8158,19 +8238,34 @@ function setupProviderRuntime(deps) {
8158
8238
  if (process.env["WRONGSTACK_DISABLE_CONFIG_WATCH"] !== "1") {
8159
8239
  const activeProfile = typeof cfg.activeProfile === "string" && cfg.activeProfile ? cfg.activeProfile : "default";
8160
8240
  const configLayers = [
8161
- { path: wpaths.profileConfig(activeProfile), priority: 1 },
8162
- { path: wpaths.projectLocalConfig, priority: 2 },
8163
- { 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 }
8164
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
+ };
8165
8259
  const readMergedSnapshot = async () => {
8166
8260
  let merged;
8167
8261
  for (const layer of configLayers.sort((a, b) => a.priority - b.priority)) {
8168
- const snap = await readProviderSnapshot(
8262
+ const raw = await readProviderSnapshot(
8169
8263
  layer.path,
8170
8264
  vault,
8171
8265
  (msg) => logger.warn(`Config watcher (${layer.path}): ${msg}`)
8172
8266
  );
8173
- if (!snap) continue;
8267
+ if (!raw) continue;
8268
+ const snap = layer.trusted ? raw : sanitizeUntrustedSnapshot(raw, layer.path);
8174
8269
  if (!merged) {
8175
8270
  merged = {
8176
8271
  ...snap,
@@ -10836,15 +10931,16 @@ ${color13.dim("YOLO enabled; tool calls run without approval unless an explicit
10836
10931
  // src/slash-commands/brain.ts
10837
10932
  import { randomUUID as randomUUID5 } from "node:crypto";
10838
10933
  import { readFile as readFile7 } from "node:fs/promises";
10934
+ import { parseModelRef } from "@wrongstack/core/agent";
10839
10935
  import { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
10840
10936
  import { color as color14 } from "@wrongstack/core/utils";
10841
- import { parseModelRef } from "@wrongstack/core/agent";
10842
10937
  var RISK_LEVELS = /* @__PURE__ */ new Set(["off", "low", "medium", "high", "all"]);
10843
10938
  var COUNCIL_RISKS = /* @__PURE__ */ new Set(["medium", "high", "critical"]);
10844
10939
  var COUNCIL_DISTINCTNESS = /* @__PURE__ */ new Set(["none", "model", "provider"]);
10845
10940
  var COUNCIL_NUMERIC_OPS = {
10846
10941
  timeout: "perCallTimeoutMs",
10847
10942
  concurrency: "maxConcurrency",
10943
+ votertokens: "voterMaxTokens",
10848
10944
  judgetokens: "judgeMaxTokens"
10849
10945
  };
10850
10946
  var PERSONA_SHORTHANDS = new Set(BUILTIN_COUNCIL_PERSONA_IDS);
@@ -10859,7 +10955,14 @@ var DENY_TERMINAL_VALUES = ["never", "when-decided", "always"];
10859
10955
  var TRACE_CONTENT_VALUES = ["none", "redacted", "full"];
10860
10956
  var TERMINAL_POLICY_VALUES = ["conservative", "deny-all", "continue-on-recommended"];
10861
10957
  var MONITOR_POLICY_VALUES = ["llm", "steer", "observe"];
10862
- 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
+ ];
10863
10966
  function parseRefEntry(ref) {
10864
10967
  const parsed = parseModelRef(ref);
10865
10968
  const model = parsed.model?.trim();
@@ -10927,7 +11030,7 @@ function buildBrainCommand(opts) {
10927
11030
  " /brain council personas List the built-in decision lenses",
10928
11031
  " /brain council judge <ref|auto> | quorum <0..1> | approval <0..1>",
10929
11032
  " /brain council distinctness <none|model|provider> warn on a non-diverse panel",
10930
- " /brain council timeout|concurrency|judgetokens <n|default>",
11033
+ " /brain council timeout|concurrency|votertokens|judgetokens <n|default>",
10931
11034
  " /brain ledger [n] Show the last n rows (default 15) of the persistent decision ledger",
10932
11035
  " /brain ledger on|off | autodeny <n>",
10933
11036
  " /brain stats Per-tier decision counts: how often the Brain actually calls a model",
@@ -11039,7 +11142,10 @@ function buildBrainCommand(opts) {
11039
11142
  return { message: msg2 };
11040
11143
  }
11041
11144
  if (ref.toLowerCase() === "session") {
11042
- 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
+ );
11043
11149
  }
11044
11150
  return applyPatch(
11045
11151
  { models: [ref] },
@@ -11055,7 +11161,10 @@ function buildBrainCommand(opts) {
11055
11161
  return { message: msg2 };
11056
11162
  }
11057
11163
  if (op === "clear") {
11058
- 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
+ );
11059
11168
  }
11060
11169
  if (op === "add") {
11061
11170
  const ref = rest[1];
@@ -11065,7 +11174,10 @@ function buildBrainCommand(opts) {
11065
11174
  return { message: msg2 };
11066
11175
  }
11067
11176
  const current = (snapshot?.models ?? []).map(compactEntry);
11068
- 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
+ );
11069
11181
  }
11070
11182
  if (op === "remove") {
11071
11183
  const target = rest[1];
@@ -11075,16 +11187,24 @@ function buildBrainCommand(opts) {
11075
11187
  return { message: msg2 };
11076
11188
  }
11077
11189
  const current = snapshot.models.map(compactEntry);
11078
- 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
+ );
11079
11193
  if (idx < 0 || idx >= current.length) {
11080
11194
  const msg2 = `No pool entry matches "${target}". Current pool: ${current.join(", ") || "(empty)"}`;
11081
11195
  opts.renderer.writeWarning(msg2);
11082
11196
  return { message: msg2 };
11083
11197
  }
11084
11198
  const next = current.filter((_, i) => i !== idx);
11085
- 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
+ );
11086
11203
  }
11087
- 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
+ );
11088
11208
  }
11089
11209
  if (subcommand === "strategy") {
11090
11210
  const strategy = (rest[0] ?? "").toLowerCase();
@@ -11134,7 +11254,10 @@ function buildBrainCommand(opts) {
11134
11254
  return { message: msg3 };
11135
11255
  }
11136
11256
  if (op === "on" || op === "off") {
11137
- 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
+ );
11138
11261
  }
11139
11262
  if (op === "minrisk") {
11140
11263
  const level = (rest[1] ?? "").toLowerCase();
@@ -11143,7 +11266,10 @@ function buildBrainCommand(opts) {
11143
11266
  opts.renderer.writeWarning(msg3);
11144
11267
  return { message: msg3 };
11145
11268
  }
11146
- 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
+ );
11147
11273
  }
11148
11274
  if (op === "voters") {
11149
11275
  const seats = rest.slice(1).map(parseSeat);
@@ -11152,7 +11278,10 @@ function buildBrainCommand(opts) {
11152
11278
  opts.renderer.writeWarning(msg3);
11153
11279
  return { message: msg3 };
11154
11280
  }
11155
- return applyPatch({ council: { voters: seats } }, councilSummary);
11281
+ return applyPatch(
11282
+ { council: { voters: seats } },
11283
+ councilSummary
11284
+ );
11156
11285
  }
11157
11286
  if (op === "personas") {
11158
11287
  const lines = [
@@ -11173,7 +11302,10 @@ function buildBrainCommand(opts) {
11173
11302
  opts.renderer.writeWarning(msg3);
11174
11303
  return { message: msg3 };
11175
11304
  }
11176
- 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
+ );
11177
11309
  }
11178
11310
  if (op === "quorum" || op === "approval") {
11179
11311
  const value = Number(rest[1]);
@@ -11208,7 +11340,7 @@ function buildBrainCommand(opts) {
11208
11340
  return `Brain council ${op} set to ${color14.cyan(applied === void 0 ? "default" : String(applied))}`;
11209
11341
  });
11210
11342
  }
11211
- 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.`;
11212
11344
  opts.renderer.writeWarning(msg2);
11213
11345
  return { message: msg2 };
11214
11346
  }
@@ -11329,7 +11461,9 @@ ${color14.dim(decision.rationale)}` : ""}`;
11329
11461
  lines.push(` decision timeout: ${color14.cyan(`${snapshot.decisionTimeoutMs}ms`)}`);
11330
11462
  }
11331
11463
  if (snapshot?.humanTimeoutMs) {
11332
- 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
+ );
11333
11467
  }
11334
11468
  const councilSeats = opts.brainSettings?.councilLabels ?? [];
11335
11469
  if (councilSeats.length > 0) {
@@ -11548,7 +11682,9 @@ ${color14.dim(decision.rationale)}` : ""}`;
11548
11682
  ` enabled ${snapshot.trace.enabled ? color14.cyan("yes") : color14.dim("no")}`,
11549
11683
  ` content ${color14.cyan(snapshot.trace.content)}`,
11550
11684
  snapshot.trace.path ? ` path ${color14.dim(snapshot.trace.path)}` : "",
11551
- 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
+ )
11552
11688
  ].filter(Boolean).join("\n");
11553
11689
  opts.renderer.write(msg2);
11554
11690
  return { message: msg2 };
@@ -11640,7 +11776,9 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11640
11776
  ` enabled ${m.enabled === false ? color14.dim("no") : color14.cyan("yes")}`,
11641
11777
  ` policy ${color14.cyan(m.policy ?? "llm")}`,
11642
11778
  color14.dim(" Signals: tool-failure streak, error storm, agent stall, file churn."),
11643
- 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
+ )
11644
11782
  ].join("\n");
11645
11783
  opts.renderer.write(msg2);
11646
11784
  return { message: msg2 };
@@ -11678,7 +11816,10 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11678
11816
  }
11679
11817
  const snapshot = opts.brainRuntime?.getSnapshot();
11680
11818
  if (snapshot?.cache.enabled) {
11681
- 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
+ );
11682
11823
  }
11683
11824
  if (snapshot?.circuit && snapshot.circuit.state !== "closed") {
11684
11825
  lines.push(color14.dim(` circuit: ${snapshot.circuit.state}`));
@@ -11689,7 +11830,10 @@ Usage: /brain escalation <conservative|deny-all|continue-on-recommended>`;
11689
11830
  lines.push(color14.dim(` ${fmtAge(w.at).padEnd(8)} ${w.outcome}`));
11690
11831
  }
11691
11832
  }
11692
- 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
+ );
11693
11837
  const msg2 = lines.join("\n");
11694
11838
  opts.renderer.write(msg2);
11695
11839
  return { message: msg2 };
@@ -11826,6 +11970,7 @@ function buildClearCommand(opts) {
11826
11970
  ctx.contextEvidence = createContextEvidenceState();
11827
11971
  ctx.toolAdjacencyDirty = false;
11828
11972
  ctx.pendingPostToolContext = void 0;
11973
+ ctx.clearMemoryEvidence?.();
11829
11974
  ctx.lastRequestTokens = void 0;
11830
11975
  ctx.lastRealInputTokens = void 0;
11831
11976
  for (const key of Object.keys(ctx.meta)) ctx.state.deleteMeta(key);
@@ -12113,8 +12258,17 @@ function buildCompactCommand(opts) {
12113
12258
 
12114
12259
  // src/slash-commands/context.ts
12115
12260
  import * as fs7 from "node:fs/promises";
12116
- import { atomicWrite as atomicWrite3, color as color18, getContextBreakdown, repairToolUseAdjacency } from "@wrongstack/core/utils";
12117
- 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";
12118
12272
  function buildContextCommand(opts) {
12119
12273
  return {
12120
12274
  name: "context",
@@ -12386,7 +12540,14 @@ function safeBreakdown(ctx) {
12386
12540
  return getContextBreakdown(ctx);
12387
12541
  } catch (err) {
12388
12542
  if (process.env["DEBUG_WS"]) {
12389
- 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
+ );
12390
12551
  }
12391
12552
  return null;
12392
12553
  }
@@ -12414,7 +12575,7 @@ function renderBreakdown(bd, detailed) {
12414
12575
  `${color18.bold("Breakdown")} (${headerSuffix})`,
12415
12576
  ` system: ${K(bd.system.total)} (${PCT(bd.system.total, limit)})`,
12416
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`,
12417
- ` 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)}`,
12418
12579
  ` volatile: ${K(bd.volatile.total)} (${PCT(bd.volatile.total, limit)}) \xB7 ledger ${K(bd.volatile.ledger)}, nextsteps ${K(bd.volatile.nextsteps)}`,
12419
12580
  ` ${color18.bold("total")}: ${K(bd.total)}${overBy > 0 ? "" : ` (${PCT(bd.total, limit)})`}`
12420
12581
  ];
@@ -12427,7 +12588,9 @@ function renderBreakdown(bd, detailed) {
12427
12588
  if (sources.length > 0) {
12428
12589
  const top = detailed ? sources : sources.slice(0, 3);
12429
12590
  out.push(
12430
- ` ${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(", ")}`
12431
12594
  );
12432
12595
  if (detailed && bd.tools.total > 0 && (bd.system.bySource["tool-usage"] ?? 0) > 0) {
12433
12596
  out.push(
@@ -13685,7 +13848,7 @@ function buildDoctorCommand(opts) {
13685
13848
  ].join("\n");
13686
13849
  async function loadPluginSchemas() {
13687
13850
  try {
13688
- const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-56CMWMPR.js");
13851
+ const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-K3BAUP4D.js");
13689
13852
  const settled = await Promise.allSettled(BUILTIN_PLUGIN_FACTORIES.map((f) => f()));
13690
13853
  const loaded = [];
13691
13854
  for (const s of settled) {
@@ -20778,6 +20941,7 @@ ${targetMode.description}`
20778
20941
 
20779
20942
  // src/slash-commands/modelcaps.ts
20780
20943
  import * as fs14 from "node:fs/promises";
20944
+ import { hasProviderCredential } from "@wrongstack/core/models";
20781
20945
  import { color as color36 } from "@wrongstack/core/utils";
20782
20946
  function fmtTokens(n) {
20783
20947
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
@@ -20885,13 +21049,7 @@ function buildModelCapsCommand(opts) {
20885
21049
  }
20886
21050
  const config = opts.configStore.get();
20887
21051
  const configProviders = config?.providers ?? {};
20888
- function hasKey(providerId) {
20889
- const pc = configProviders[providerId];
20890
- if (!pc) return false;
20891
- if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
20892
- if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
20893
- return false;
20894
- }
21052
+ const hasKey = (provider) => hasProviderCredential(provider, { providers: configProviders });
20895
21053
  const lines = [
20896
21054
  `${color36.bold("Available Models")} ${color36.dim("\u2014 capacities + pricing")}`,
20897
21055
  ""
@@ -20901,7 +21059,7 @@ function buildModelCapsCommand(opts) {
20901
21059
  if (trimmed && !trimmed.includes("/") && !prov.id.toLowerCase().includes(trimmed) && !prov.name.toLowerCase().includes(trimmed)) {
20902
21060
  continue;
20903
21061
  }
20904
- const keyed = hasKey(prov.id);
21062
+ const keyed = hasKey(prov);
20905
21063
  const marker = keyed ? color36.green("\u25CF") : color36.dim("\u25CB");
20906
21064
  lines.push(` ${marker} ${color36.bold(prov.id.padEnd(16))} ${color36.dim(`(${prov.name})`)}`);
20907
21065
  const models = prov.models ?? [];
@@ -25417,8 +25575,13 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
25417
25575
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25418
25576
  cwd: resolved,
25419
25577
  stdio: "inherit",
25420
- detached: false,
25421
- 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.
25422
25585
  });
25423
25586
  child.on("error", (err) => {
25424
25587
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25554,8 +25717,13 @@ async function spawnInProject(opts, _ctx, root, projectName) {
25554
25717
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25555
25718
  cwd: root,
25556
25719
  stdio: "inherit",
25557
- detached: false,
25558
- 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.
25559
25727
  });
25560
25728
  child.on("error", (err) => {
25561
25729
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25591,8 +25759,13 @@ async function handleNewSession(_opts, _ctx) {
25591
25759
  const child = spawn7(nodeExe, [cliPath, "--no-interactive"], {
25592
25760
  cwd: process.cwd(),
25593
25761
  stdio: "inherit",
25594
- detached: false,
25595
- 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.
25596
25769
  });
25597
25770
  child.on("error", (err) => {
25598
25771
  console.error(color47.red(`Failed to spawn wstack: ${err.message}`));
@@ -25639,6 +25812,7 @@ async function handlePrevSessions(opts, _ctx) {
25639
25812
  import { spawn as spawn8 } from "node:child_process";
25640
25813
  import * as fsp3 from "node:fs/promises";
25641
25814
  import * as path27 from "node:path";
25815
+ import { emitReviewIfChanged } from "@wrongstack/core/plugin";
25642
25816
  async function runGit2(args, cwd) {
25643
25817
  return new Promise((resolve8) => {
25644
25818
  const child = spawn8("git", args, {
@@ -25732,16 +25906,29 @@ function buildReviewCommand(opts) {
25732
25906
  enabled: true,
25733
25907
  provider: ctx.provider.id,
25734
25908
  model: ctx.model,
25735
- maxFiles: limit
25909
+ maxFiles: limit,
25910
+ autoFix: "off",
25911
+ cascadeOn: "off",
25912
+ maxCascadeDepth: 0
25736
25913
  },
25737
25914
  cwd,
25738
25915
  files: filesWithContent
25739
25916
  };
25740
- 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;
25741
25927
  const note = truncated ? `
25742
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.` : "";
25743
25930
  return {
25744
- 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}
25745
25932
  The review report will appear in chat history shortly.${note}`
25746
25933
  };
25747
25934
  }
@@ -25863,6 +26050,7 @@ function buildSettingsCommand(opts) {
25863
26050
  " /settings context-strategy hybrid|intelligent|selective Compactor strategy",
25864
26051
  " /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
25865
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)",
25866
26054
  " /settings mcp on|off Load MCP servers declared in config",
25867
26055
  " /settings plugins on|off Load npm plugins declared in config",
25868
26056
  " /settings memory on|off Register remember/forget tools",
@@ -25912,6 +26100,7 @@ function buildSettingsCommand(opts) {
25912
26100
  const contextAutoCompact = context?.autoCompact !== false;
25913
26101
  const features = opts.configStore.get().features;
25914
26102
  const tokenSavingTier = features?.tokenSavingMode ?? "off";
26103
+ const nextStepsToolEnabled = opts.configStore.get().tools?.nextsteps?.enabled === true;
25915
26104
  const maxConcurrent = opts.configStore.get().maxConcurrent ?? 4;
25916
26105
  const titleAnimation = autonomy?.terminalTitleAnimation !== false;
25917
26106
  const modelRuntime = opts.configStore.get().modelRuntime;
@@ -25954,6 +26143,7 @@ function buildSettingsCommand(opts) {
25954
26143
  ` context strategy: ${color48.cyan(contextStrategy)} ${color48.dim("change: /settings context-strategy hybrid|intelligent|selective")}`,
25955
26144
  ` context auto-compact: ${contextAutoCompact ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings context-auto-compact on|off")}`,
25956
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")}`,
25957
26147
  ` MCP features: ${feats?.mcp !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings mcp on|off")}`,
25958
26148
  ` plugin features: ${feats?.plugins !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings plugins on|off")}`,
25959
26149
  ` memory features: ${feats?.memory !== false ? color48.cyan("on") : color48.dim("off")} ${color48.dim("change: /settings memory on|off")}`,
@@ -26383,6 +26573,21 @@ function buildSettingsCommand(opts) {
26383
26573
  message: `${color48.green("\u2713")} context auto-compact \u2192 ${on ? color48.cyan("on") : color48.dim("off")} ${color48.dim("auto-compact context when thresholds crossed")}`
26384
26574
  };
26385
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
+ }
26386
26591
  if (sub === "token-saving") {
26387
26592
  const raw = (rest[0] ?? "").toLowerCase();
26388
26593
  const tiers = ["off", "minimal", "light", "medium", "aggressive"];
@@ -30130,7 +30335,8 @@ async function runInteractive(cliCtx) {
30130
30335
  events,
30131
30336
  container,
30132
30337
  configStore,
30133
- updateInfo
30338
+ updateInfo,
30339
+ webuiSessionChild
30134
30340
  } = cliCtx;
30135
30341
  const profileConfigPath = activeProfileConfigPath(wpaths, config);
30136
30342
  const modeStore = container.resolve(TOKENS9.ModeStore);
@@ -30193,11 +30399,13 @@ async function runInteractive(cliCtx) {
30193
30399
  wpaths
30194
30400
  });
30195
30401
  const stdinInteractive = process.stdin.isTTY;
30402
+ const hookRunnerRef = { current: null };
30196
30403
  registerCliManagementTools({
30197
30404
  toolRegistry,
30198
30405
  configStore,
30199
30406
  profileConfigPath,
30200
- stdinInteractive
30407
+ stdinInteractive,
30408
+ getHookRunner: () => hookRunnerRef.current
30201
30409
  });
30202
30410
  const { metricsSink, healthRegistry, metricsStatus } = (() => {
30203
30411
  const ms = setupMetrics({
@@ -30231,7 +30439,8 @@ async function runInteractive(cliCtx) {
30231
30439
  const systemPrompt = await promptBuilder.build({
30232
30440
  cwd,
30233
30441
  projectRoot,
30234
- tools: toolRegistry.list(),
30442
+ tools: toolRegistry.listForProvider(),
30443
+ catalogTools: toolRegistry.list(),
30235
30444
  provider: config.provider,
30236
30445
  model: config.model,
30237
30446
  onlineAgents
@@ -30345,7 +30554,8 @@ async function runInteractive(cliCtx) {
30345
30554
  slashRegistry,
30346
30555
  hqPublisherRef,
30347
30556
  brainMailbox,
30348
- pluginHost
30557
+ pluginHost,
30558
+ hookRunner
30349
30559
  } = await setupLifecycleAndPlugins({
30350
30560
  flags,
30351
30561
  config,
@@ -30382,6 +30592,7 @@ async function runInteractive(cliCtx) {
30382
30592
  logger,
30383
30593
  teardownHandlers
30384
30594
  });
30595
+ hookRunnerRef.current = hookRunner;
30385
30596
  const fallbackProfileManager = container.resolve(TOKENS9.FallbackProfileManager);
30386
30597
  const statusTracker = await setupProviderStatus({
30387
30598
  events,
@@ -30426,6 +30637,13 @@ async function runInteractive(cliCtx) {
30426
30637
  getConfig: () => config,
30427
30638
  fallbackProfileManager,
30428
30639
  statusTracker,
30640
+ wrapProviderCall: (request, inner) => agent.extensions.wrapProviderRunner(
30641
+ (_ctx, wrappedRequest) => inner(wrappedRequest),
30642
+ // OneShotOrchestrator owns the selected provider and fallback chain.
30643
+ // The agent-loop fallback wrapper routes from and mutates agent.ctx,
30644
+ // which may describe a different provider/model than this utility call.
30645
+ { exclude: ["fallback-model"] }
30646
+ )(agent.ctx, request),
30429
30647
  compactor: container.resolve(TOKENS9.Compactor)
30430
30648
  });
30431
30649
  let {
@@ -30746,7 +30964,25 @@ async function runInteractive(cliCtx) {
30746
30964
  effectiveMaxContext: effectiveMaxContextRef.current,
30747
30965
  configRef,
30748
30966
  autonomyModeRef,
30749
- logger
30967
+ logger,
30968
+ // WITHOUT this the helper primed an engine and dropped it: the only live
30969
+ // holder is the `eternalEngine` binding below, written solely by the slash
30970
+ // handler. `--eternal "<mission>"` therefore forced YOLO on and set the
30971
+ // mode to 'eternal', then the REPL found no engine, printed "no engine
30972
+ // wired — falling back to off" on EVERY loop iteration without actually
30973
+ // flipping the mode back or `continue`-ing, and left the user staring at
30974
+ // unconfirmed tool calls. `cli-eternal-flag.test.ts` passes this ref, so
30975
+ // the test exercised a wiring production never had.
30976
+ // The live holder is `EternalAutonomyEngine | null`; the ref contract is
30977
+ // `| undefined`. Bridge rather than widen either side.
30978
+ eternalEngineRef: {
30979
+ get current() {
30980
+ return eternalEngine ?? void 0;
30981
+ },
30982
+ set current(engine) {
30983
+ eternalEngine = engine ?? null;
30984
+ }
30985
+ }
30750
30986
  });
30751
30987
  config = configRef.current;
30752
30988
  if (eternalFlag.length > 0) {
@@ -30776,7 +31012,7 @@ async function runInteractive(cliCtx) {
30776
31012
  onEvent: evOn
30777
31013
  });
30778
31014
  const savedProviderCfg = config.providers?.[config.provider];
30779
- const { execute } = await import("./execution-SHGM7BVC.js");
31015
+ const { execute } = await import("./execution-CQ772VPS.js");
30780
31016
  const stopHeapWatchdog = startSharedHeapWatchdog({
30781
31017
  collectStats: () => {
30782
31018
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -30834,6 +31070,7 @@ async function runInteractive(cliCtx) {
30834
31070
  projectRoot,
30835
31071
  flags,
30836
31072
  positional,
31073
+ webuiSessionChild,
30837
31074
  // Forward preflight's update-info so the TUI banner can render
30838
31075
  // the "(update available: v…)" indicator next to the version
30839
31076
  // chip. May be undefined (e.g. when the registry check was
@@ -31008,4 +31245,4 @@ export {
31008
31245
  CLI_VERSION,
31009
31246
  runInteractive
31010
31247
  };
31011
- //# sourceMappingURL=cli-main-7B3G2YSG.js.map
31248
+ //# sourceMappingURL=cli-main-EOJ74CI4.js.map