@wrongstack/cli 1.0.26 → 1.0.28

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 (52) hide show
  1. package/dist/{acp-LI665LO2.js → acp-M4565YUW.js} +30 -17
  2. package/dist/boot/dispatch-singleshot.d.ts +8 -0
  3. package/dist/boot/dispatch-webui.d.ts +2 -0
  4. package/dist/boot/goal-tui-default.d.ts +10 -0
  5. package/dist/{chunk-BEHGRNOQ.js → chunk-3ZTLPNEQ.js} +10 -10
  6. package/dist/chunk-7IQWQCBH.js +160 -0
  7. package/dist/{chunk-OAVBM55Q.js → chunk-ENRYW73H.js} +525 -379
  8. package/dist/{chunk-WQV2NP57.js → chunk-L5TD7UIG.js} +2 -2
  9. package/dist/{chunk-2EBVTYJR.js → chunk-NRXWOMK7.js} +17 -3
  10. package/dist/{chunk-5SEYGOCH.js → chunk-RDGGWMTI.js} +2 -2
  11. package/dist/{chunk-KJS5543T.js → chunk-RGFWGUAB.js} +26 -14
  12. package/dist/{chunk-S4FFHDJG.js → chunk-SDJCLEK6.js} +6 -2
  13. package/dist/{cli-context-UVGAN3NO.js → cli-context-O2SHBDFH.js} +33 -6
  14. package/dist/{cli-main-TN3KZ2P6.js → cli-main-BKG4ZVLN.js} +1079 -616
  15. package/dist/{execution-7QIP6GRY.js → execution-THXWTHYT.js} +87 -33
  16. package/dist/execution-chimera-review.d.ts +18 -1
  17. package/dist/fleet/host-memory-companion.d.ts +43 -0
  18. package/dist/fleet/host-subagent-factory.d.ts +1 -1
  19. package/dist/fleet/host.d.ts +1 -0
  20. package/dist/fleet/memory-companion-evidence.d.ts +12 -0
  21. package/dist/fleet/memory-companion-policy.d.ts +9 -0
  22. package/dist/goal-commands.d.ts +10 -0
  23. package/dist/{hq-GLZ7QESM.js → hq-44F4OQH2.js} +5 -4
  24. package/dist/hq-fleet-control.d.ts +36 -0
  25. package/dist/hq-live-status.d.ts +22 -0
  26. package/dist/hq-publisher.d.ts +18 -0
  27. package/dist/hq-server/routes/command-handlers.d.ts +12 -1
  28. package/dist/hq-server/routes/data-handlers.d.ts +1 -1
  29. package/dist/hq-server/socket-credentials.d.ts +63 -0
  30. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  31. package/dist/hq-server/ws.d.ts +24 -0
  32. package/dist/{hq-server-YTKDVQPY.js → hq-server-UIB24LU6.js} +2 -2
  33. package/dist/{import-claude-code-32PCSIQI.js → import-claude-code-AYG24RG2.js} +36 -4
  34. package/dist/index.js +3 -3
  35. package/dist/live-settings-input.d.ts +1 -0
  36. package/dist/{mailbox-serve-NW5VZG7R.js → mailbox-serve-GNGXEQ2R.js} +2 -2
  37. package/dist/{modeldiag-36VODVIH.js → modeldiag-W7XAE625.js} +2 -2
  38. package/dist/{remote-O5WXHLWV.js → remote-GB5VXCCT.js} +38 -23
  39. package/dist/services/run-git.d.ts +3 -1
  40. package/dist/{sessions-config-SXTTLNXA.js → sessions-config-XHLXJ5C3.js} +75 -6
  41. package/dist/{short-circuit-flags-74Y5N6CF.js → short-circuit-flags-BSIPTAPD.js} +2 -2
  42. package/dist/slash-commands/theme.d.ts +15 -1
  43. package/dist/subcommands/handlers/import-claude-code.d.ts +10 -0
  44. package/dist/subcommands/handlers/sessions-fleet.d.ts +0 -12
  45. package/dist/{subcommands-Q6ORSB2E.js → subcommands-SRRB3RAS.js} +2 -2
  46. package/dist/{typesafe-U7YB2RRV.js → typesafe-WA5O3KVS.js} +10 -2
  47. package/dist/{webui-server-YTHLRFD2.js → webui-server-GCXIO3JY.js} +37 -30
  48. package/dist/webui-server-options.d.ts +10 -0
  49. package/dist/wiring/command-host-state.d.ts +2 -0
  50. package/dist/wiring/hq-telemetry.d.ts +12 -0
  51. package/package.json +29 -28
  52. package/dist/chunk-OWNC7LMD.js +0 -78
@@ -1,10 +1,12 @@
1
1
  import {
2
+ createHqFleetControl,
2
3
  getSuggestions,
3
4
  normalizeTuiThinkingWord,
5
+ resolveExecutionMode,
4
6
  runGit,
5
7
  setAutoSuggestions,
6
8
  setSuggestions
7
- } from "./chunk-OWNC7LMD.js";
9
+ } from "./chunk-7IQWQCBH.js";
8
10
  import {
9
11
  deriveFsAccessPair,
10
12
  persistConfigSetting
@@ -48,7 +50,7 @@ import {
48
50
  } from "./chunk-HXJYHAR4.js";
49
51
  import {
50
52
  startCliHqConnection
51
- } from "./chunk-KJS5543T.js";
53
+ } from "./chunk-RGFWGUAB.js";
52
54
  import {
53
55
  CLI_VERSION
54
56
  } from "./chunk-F5DCFAKI.js";
@@ -155,6 +157,15 @@ async function runSingleShotDispatch(ctx) {
155
157
  const ctrl = new AbortController();
156
158
  const onSigint = () => ctrl.abort();
157
159
  process.on("SIGINT", onSigint);
160
+ const interrupt = ctx.interruptController;
161
+ const previousAbortLeader = interrupt?.abortLeader;
162
+ if (interrupt !== void 0) {
163
+ interrupt.abortLeader = () => {
164
+ if (ctrl.signal.aborted) return false;
165
+ ctrl.abort();
166
+ return true;
167
+ };
168
+ }
158
169
  let budget;
159
170
  if (limitUsd !== void 0 && ctx.events) {
160
171
  budget = watchSingleShotBudget({
@@ -207,6 +218,9 @@ async function runSingleShotDispatch(ctx) {
207
218
  stopStream?.();
208
219
  budget?.dispose();
209
220
  process.off("SIGINT", onSigint);
221
+ if (interrupt !== void 0 && previousAbortLeader !== void 0) {
222
+ interrupt.abortLeader = previousAbortLeader;
223
+ }
210
224
  const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
211
225
  getProcessRegistry2().killAll({ preserveBackground: true });
212
226
  }
@@ -330,6 +344,8 @@ async function runWebUIDispatch(ctx) {
330
344
  onKanbanDispatch,
331
345
  getFleetBudget,
332
346
  stopSessionFleet,
347
+ interruptController,
348
+ hqFleetControl,
333
349
  onSessionRetired,
334
350
  leaderAutoWake,
335
351
  webuiSessionChild
@@ -338,7 +354,7 @@ async function runWebUIDispatch(ctx) {
338
354
  const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
339
355
  agent.disableInteractiveConfirmation();
340
356
  renderer.setSilent(true);
341
- const { runWebUI } = await import("./webui-server-YTHLRFD2.js");
357
+ const { runWebUI } = await import("./webui-server-GCXIO3JY.js");
342
358
  const flagValue = (names) => {
343
359
  for (const name of names) {
344
360
  if (!Object.hasOwn(flags, name)) continue;
@@ -440,6 +456,8 @@ async function runWebUIDispatch(ctx) {
440
456
  sessionStore,
441
457
  ...getFleetBudget ? { getFleetBudget } : {},
442
458
  ...stopSessionFleet ? { stopSessionFleet } : {},
459
+ ...interruptController ? { interruptController } : {},
460
+ ...hqFleetControl ? { hqFleetControl } : {},
443
461
  ...onSessionRetired ? { onSessionRetired } : {},
444
462
  ...leaderAutoWake ? { leaderAutoWake } : {},
445
463
  sessionsDir: projectSessionsDir,
@@ -579,16 +597,6 @@ async function runWebUIDispatch(ctx) {
579
597
  return webuiExit;
580
598
  }
581
599
 
582
- // src/boot/execution-mode.ts
583
- function resolveExecutionMode(positional, flags) {
584
- const prompt = typeof flags["prompt"] === "string" ? flags["prompt"] : "";
585
- if (positional.length > 0 || prompt.length > 0) return "single-shot";
586
- if (flags.tui && flags["no-tui"] !== true && !flags.webui && !flags["webui-session-child"])
587
- return "tui";
588
- if (flags.webui || flags["webui-session-child"]) return "webui";
589
- return "repl";
590
- }
591
-
592
600
  // src/boot/piped-stdin.ts
593
601
  var PIPED_STDIN_FIRST_BYTE_TIMEOUT_MS = 3e3;
594
602
  var PIPED_STDIN_MAX_BYTES = 2 * 1024 * 1024;
@@ -1206,6 +1214,7 @@ import {
1206
1214
  import { resetSessionSubagentPolicy } from "@wrongstack/core/coordination";
1207
1215
  import { TOKENS } from "@wrongstack/core/kernel";
1208
1216
  import { restoreSessionPermissionOverrides } from "@wrongstack/core/security";
1217
+ import { restoreRequiredSkillsFromEvents } from "@wrongstack/core/skills";
1209
1218
  import { DefaultSessionStore } from "@wrongstack/core/storage";
1210
1219
  import {
1211
1220
  activateProjectStateGuard,
@@ -1315,9 +1324,11 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
1315
1324
  context.projectRoot = resolved;
1316
1325
  context.workingDir = resolved;
1317
1326
  context.session = nextWriter;
1327
+ if (state.sessionRef) state.sessionRef.current = nextWriter;
1318
1328
  context.state.replaceMessages([]);
1319
1329
  resetSessionSubagentPolicy(context);
1320
1330
  restoreSessionPermissionOverrides(context.meta, {});
1331
+ restoreRequiredSkillsFromEvents(context, []);
1321
1332
  context.state.replaceTodos([]);
1322
1333
  context.clearFileTracking();
1323
1334
  context.clearMemoryEvidence?.();
@@ -1663,6 +1674,7 @@ function createSettingsAdapter(ctx) {
1663
1674
  featureMemory: cfg.features?.memory !== false,
1664
1675
  featureSkills: cfg.features?.skills !== false,
1665
1676
  featureModelsRegistry: cfg.features?.modelsRegistry !== false,
1677
+ featureToolCoach: cfg.features?.toolCoach !== false,
1666
1678
  // Preserve the 'auto' sentinel for the picker DISPLAY (normalize would
1667
1679
  // collapse it to 'off', which would then overwrite 'auto' on save);
1668
1680
  // everything else normalizes to a concrete tier.
@@ -1733,7 +1745,7 @@ function createSettingsAdapter(ctx) {
1733
1745
  }
1734
1746
  async function saveSettings(s) {
1735
1747
  try {
1736
- if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.toolResultViewMode !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.lastSettingsField !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
1748
+ if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.toolResultViewMode !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.lastSettingsField !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
1737
1749
  // either key being present in the live patch. Without this, a
1738
1750
  // picker toggle round-trip would silently no-op the persistence
1739
1751
  // layer (the runtime probe would never read the change).
@@ -1796,7 +1808,7 @@ function createSettingsAdapter(ctx) {
1796
1808
  if (s.nextPrediction !== void 0) decrypted.nextPrediction = s.nextPrediction;
1797
1809
  if (s.yolo !== void 0) decrypted.yolo = s.yolo;
1798
1810
  fsAccess = deriveFsAccess(s);
1799
- if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0) {
1811
+ if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0) {
1800
1812
  const feats = decrypted.features ?? {};
1801
1813
  if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
1802
1814
  if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
@@ -1804,6 +1816,7 @@ function createSettingsAdapter(ctx) {
1804
1816
  if (s.featureSkills !== void 0) feats.skills = s.featureSkills;
1805
1817
  if (s.featureModelsRegistry !== void 0)
1806
1818
  feats.modelsRegistry = s.featureModelsRegistry;
1819
+ if (s.featureToolCoach !== void 0) feats.toolCoach = s.featureToolCoach;
1807
1820
  if (s.featureTokenSaving !== void 0) feats.tokenSavingMode = s.featureTokenSaving;
1808
1821
  if (fsAccess !== void 0)
1809
1822
  feats.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
@@ -1942,7 +1955,7 @@ function createSettingsAdapter(ctx) {
1942
1955
  configStore.update({
1943
1956
  ...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
1944
1957
  ...s.yolo !== void 0 ? { yolo: s.yolo } : {},
1945
- ...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0 ? {
1958
+ ...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0 ? {
1946
1959
  features: {
1947
1960
  ...currentConfig.features,
1948
1961
  ...decrypted.features ?? {}
@@ -2044,15 +2057,18 @@ import * as path8 from "node:path";
2044
2057
  import { THEME_PRESET_IDS } from "@wrongstack/core/types";
2045
2058
  import { updateJsonObjectFile } from "@wrongstack/core/utils";
2046
2059
  var VALID_PRESETS = new Set(THEME_PRESET_IDS);
2060
+ function isThemePresetId(candidate) {
2061
+ return VALID_PRESETS.has(candidate);
2062
+ }
2047
2063
  function createThemeAdapter({ configStore, wpaths }) {
2048
2064
  return {
2049
2065
  getThemePreset: () => {
2050
2066
  const live = configStore.get();
2051
2067
  const candidate = live.themePreset;
2052
- return candidate && VALID_PRESETS.has(candidate) ? candidate : void 0;
2068
+ return candidate && isThemePresetId(candidate) ? candidate : void 0;
2053
2069
  },
2054
2070
  saveThemePreset: async (preset) => {
2055
- if (!VALID_PRESETS.has(preset)) {
2071
+ if (!isThemePresetId(preset)) {
2056
2072
  throw new Error(`Unknown theme preset: ${preset}`);
2057
2073
  }
2058
2074
  const configPath = activeProfileConfigPath(wpaths, configStore.get());
@@ -3094,6 +3110,7 @@ import {
3094
3110
  } from "@wrongstack/core/coordination";
3095
3111
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
3096
3112
  import { restoreSessionPermissionOverrides as restoreSessionPermissionOverrides2 } from "@wrongstack/core/security";
3113
+ import { restoreRequiredSkillsFromEvents as restoreRequiredSkillsFromEvents2 } from "@wrongstack/core/skills";
3097
3114
  import { attachTodosCheckpoint, loadTodosCheckpoint } from "@wrongstack/core/storage";
3098
3115
  import { projectLastRequestTokens } from "@wrongstack/core/types/session-timeline";
3099
3116
  import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
@@ -3270,6 +3287,7 @@ async function resumeSession(ctx, sessionId) {
3270
3287
  restoreSessionSubagentPolicy(agent.ctx, resumed.data.events, resumed.data.subagentsAllowed);
3271
3288
  restoreSessionSubagentModelPlan(agent.ctx, resumed.data.events);
3272
3289
  restoreSessionPermissionOverrides2(agent.ctx.meta, resumed.data);
3290
+ restoreRequiredSkillsFromEvents2(agent.ctx, resumed.data.events);
3273
3291
  } catch (err) {
3274
3292
  agent.ctx.session = oldWriter;
3275
3293
  agent.ctx.state.replaceMessages(oldMessages);
@@ -3610,6 +3628,11 @@ import { emitReviewIfChanged } from "@wrongstack/core/plugin";
3610
3628
 
3611
3629
  // src/chimera-cascade-evidence.ts
3612
3630
  import { spawn as spawn2 } from "node:child_process";
3631
+ import {
3632
+ buildWin32CmdShimInvocation,
3633
+ isWinCmdShim,
3634
+ resolveWin32Command
3635
+ } from "@wrongstack/tools/win32";
3613
3636
  var JSON_BLOCK_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
3614
3637
  function tryParseJson(raw) {
3615
3638
  try {
@@ -3692,8 +3715,23 @@ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
3692
3715
  return Promise.resolve({ exitCode: CASCADE_EVIDENCE_UNSAFE_EXIT });
3693
3716
  }
3694
3717
  const tokens = command.trim().split(/\s+/);
3695
- const executable = tokens[0];
3696
- const args = tokens.slice(1);
3718
+ let executable = tokens[0];
3719
+ let args = tokens.slice(1);
3720
+ let windowsVerbatimArguments = false;
3721
+ if (process.platform === "win32") {
3722
+ const resolved = resolveWin32Command(executable);
3723
+ if (isWinCmdShim(resolved)) {
3724
+ let shim;
3725
+ try {
3726
+ shim = buildWin32CmdShimInvocation(resolved, args);
3727
+ } catch {
3728
+ return Promise.resolve({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
3729
+ }
3730
+ executable = shim.command;
3731
+ args = shim.args;
3732
+ windowsVerbatimArguments = true;
3733
+ }
3734
+ }
3697
3735
  return new Promise((resolve5) => {
3698
3736
  let child;
3699
3737
  try {
@@ -3701,7 +3739,8 @@ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
3701
3739
  cwd,
3702
3740
  stdio: "ignore",
3703
3741
  windowsHide: true,
3704
- signal: AbortSignal.timeout(timeoutMs)
3742
+ signal: AbortSignal.timeout(timeoutMs),
3743
+ ...windowsVerbatimArguments ? { windowsVerbatimArguments } : {}
3705
3744
  });
3706
3745
  } catch {
3707
3746
  resolve5({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
@@ -4325,6 +4364,10 @@ function normalizeFileKeyForCitation(raw, cwd) {
4325
4364
  const relative = isAbsolute ? pathMod.relative(cwd, forward).replace(/\\/g, "/").replace(/^\.\//, "") : forward;
4326
4365
  return process.platform === "win32" ? relative.toLowerCase() : relative;
4327
4366
  }
4367
+ function readPositiveNumber(config, key) {
4368
+ const raw = config.extensions?.["wstack-chimera"]?.[key];
4369
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : void 0;
4370
+ }
4328
4371
  function installChimeraReviewHandler({
4329
4372
  events,
4330
4373
  director,
@@ -4374,7 +4417,12 @@ function installChimeraReviewHandler({
4374
4417
  };
4375
4418
  const persistAndEmitCompletion = async (completion) => {
4376
4419
  try {
4377
- await persistReview(completion, projectDir);
4420
+ await persistReview(completion, projectDir, {
4421
+ // Read per completion, not once at install time, so an operator
4422
+ // editing either key takes effect without a restart.
4423
+ openRetentionDays: readPositiveNumber(config, "openRetentionDays"),
4424
+ activeRetentionDays: readPositiveNumber(config, "activeRetentionDays")
4425
+ });
4378
4426
  events.emitCustom("chimera.review_persisted", {
4379
4427
  reportId: completion.reportId,
4380
4428
  sessionId: completion.sessionId,
@@ -4710,13 +4758,13 @@ ${reviewBody}`;
4710
4758
  );
4711
4759
  if (off) teardownHandlers?.push(off);
4712
4760
  }
4713
- async function persistChimeraReview(payload, projectDir) {
4761
+ async function persistChimeraReview(payload, projectDir, maintenance = {}) {
4714
4762
  const reportId = payload.reportId;
4715
4763
  if (!reportId) throw new Error("Chimera completion is missing reportId");
4716
4764
  await persistReviewReport(payload, reportId, projectDir);
4717
4765
  await integrateFindings(payload, projectDir, reportId);
4718
4766
  try {
4719
- await maybeCompactReviewStores(projectDir);
4767
+ await maybeCompactReviewStores(projectDir, maintenance);
4720
4768
  } catch (error) {
4721
4769
  console.error(
4722
4770
  JSON.stringify({
@@ -5133,7 +5181,9 @@ function registerReplClient(opts) {
5133
5181
  projectRoot: replProjectRoot,
5134
5182
  projectName: path13.basename(replProjectRoot),
5135
5183
  appConfig: opts.appConfig,
5136
- capabilities: ["telemetry.publish", "mailbox.summary"]
5184
+ capabilities: ["telemetry.publish", "mailbox.summary"],
5185
+ // The CLI host's own connection in this process syncs the boards.
5186
+ ownKanbanSync: false
5137
5187
  });
5138
5188
  const clientMailbox = getSharedProjectMailbox(
5139
5189
  projectDir,
@@ -5157,6 +5207,7 @@ function registerReplClient(opts) {
5157
5207
  clientMailbox.clientHeartbeat({ clientId, sessionId: opts.getSessionId?.() ?? replProjectRoot }).catch(() => {
5158
5208
  });
5159
5209
  }, 15e3);
5210
+ clientHeartbeat.unref?.();
5160
5211
  }).catch(() => {
5161
5212
  });
5162
5213
  return {
@@ -6711,9 +6762,6 @@ async function execute(deps) {
6711
6762
  }
6712
6763
  const goalFlag = typeof flags["goal"] === "string" ? flags["goal"] : void 0;
6713
6764
  const askFlag = typeof flags["ask"] === "string" ? flags["ask"] : void 0;
6714
- if ((goalFlag || askFlag) && positional.length === 0 && !promptFlag) {
6715
- flags.tui = true;
6716
- }
6717
6765
  const executionMode = resolveExecutionMode(positional, flags);
6718
6766
  if (projectRoot && executionMode !== "webui") {
6719
6767
  backgroundKanbanSupervisor = (await import("./kanban-supervisor-67ECABTD.js")).createKanbanSupervisor({
@@ -6756,7 +6804,8 @@ async function execute(deps) {
6756
6804
  flags,
6757
6805
  tokenCounter,
6758
6806
  renderer,
6759
- events
6807
+ events,
6808
+ interruptController
6760
6809
  });
6761
6810
  } else if (executionMode === "tui") {
6762
6811
  agent.disableInteractiveConfirmation();
@@ -7126,6 +7175,7 @@ async function execute(deps) {
7126
7175
  try {
7127
7176
  code = await runWebUIDispatch({
7128
7177
  leaderAutoWake: webuiLeaderAutoWake,
7178
+ interruptController,
7129
7179
  agent,
7130
7180
  events,
7131
7181
  session,
@@ -7168,9 +7218,13 @@ async function execute(deps) {
7168
7218
  // workers it is BLOCKED on; anything started with `spawn_subagent` +
7169
7219
  // `assign_task` keeps going unless asked to stop. Scoped to the
7170
7220
  // session so one tab's Stop never reaches another tab's fleet.
7171
- stopSessionFleet: async (sessionId) => {
7172
- await getDirector?.()?.terminateSession(sessionId);
7173
- },
7221
+ stopSessionFleet: (sessionId) => getDirector?.()?.terminateSession(sessionId),
7222
+ // HQ's per-tab abort fleet / abort agent / spawn, same scoping.
7223
+ hqFleetControl: createHqFleetControl(
7224
+ () => getDirector?.() ?? null,
7225
+ () => "",
7226
+ { multiConversation: true }
7227
+ ),
7174
7228
  // Closing a tab is not stopping it. The run keeps going and keeps its
7175
7229
  // fleet; what goes is the background help pinned to that conversation
7176
7230
  // — the explore companion's poll timer and the shadow reviewer's
@@ -7288,4 +7342,4 @@ export {
7288
7342
  execute,
7289
7343
  resolveReviewerFallbackModels
7290
7344
  };
7291
- //# sourceMappingURL=execution-7QIP6GRY.js.map
7345
+ //# sourceMappingURL=execution-THXWTHYT.js.map
@@ -19,6 +19,18 @@ type Config = ExecuteDeps['core']['config'];
19
19
  * direct test.
20
20
  */
21
21
  export declare function normalizeFileKeyForCitation(raw: string, cwd: string): string;
22
+ /**
23
+ * Operator-facing store-maintenance knobs, read live from `config` so a hot
24
+ * config edit is picked up on the next review completion. Deliberately narrower
25
+ * than core's `ReviewStoreMaintenanceOptions`: the CLI owns *reading config*,
26
+ * core owns *interpreting it* (day→ms, `0` = disabled, garbage = default).
27
+ */
28
+ type ChimeraStoreMaintenanceOptions = {
29
+ /** Cap for non-terminal (`open`/`actioned`) review rows, in days. */
30
+ openRetentionDays?: number | undefined;
31
+ /** Cap for non-terminal (`active`/`triaged`/`in_progress`) findings, in days. */
32
+ activeRetentionDays?: number | undefined;
33
+ };
22
34
  type InstallChimeraReviewHandlerOptions = {
23
35
  events: Events;
24
36
  director: Director | null | undefined;
@@ -36,7 +48,12 @@ type InstallChimeraReviewHandlerOptions = {
36
48
  * `packages/core/src/coordination/provider-status-tracker.ts`.
37
49
  */
38
50
  statusTracker?: ProviderModelStatusTracker | undefined;
39
- persistReview?: ((payload: ChimeraReviewCompletePayload, projectDir: string) => Promise<void>) | undefined;
51
+ /**
52
+ * Persistence seam, injectable for tests. The third argument carries the
53
+ * operator's store-maintenance knobs resolved from `config` at call time, so
54
+ * a hot config edit applies to the next completion. Test doubles may ignore it.
55
+ */
56
+ persistReview?: ((payload: ChimeraReviewCompletePayload, projectDir: string, maintenance?: ChimeraStoreMaintenanceOptions) => Promise<void>) | undefined;
40
57
  trackWork: (work: Promise<void>) => void;
41
58
  /** Optional session-teardown chain — the wildcard listener disposer is
42
59
  * pushed here so the registration is released when the session ends. */
@@ -0,0 +1,43 @@
1
+ import type { Director } from '@wrongstack/core/coordination';
2
+ import type { EventBus } from '@wrongstack/core/kernel';
3
+ import type { SubagentConfig } from '@wrongstack/core/types';
4
+ import { type SageSurface } from '@wrongstack/sage';
5
+ import { snapshotMemoryEvidence } from './memory-companion-evidence.js';
6
+ interface Input {
7
+ director: Director;
8
+ events: EventBus;
9
+ projectRoot: string;
10
+ roster: Record<string, SubagentConfig>;
11
+ memory: () => SageSurface | undefined;
12
+ enabled: (sessionId: string) => boolean;
13
+ scrub: (text: string) => string;
14
+ note: (sessionId: string, subject: string, body: string) => void;
15
+ /** Test seams; production uses bounded file snapshots and a 75-second watchdog. */
16
+ snapshot?: typeof snapshotMemoryEvidence;
17
+ timeoutMs?: number;
18
+ ioTimeoutMs?: number;
19
+ }
20
+ /** One bounded worker at a time; one lazily spawned resident per live conversation. */
21
+ export declare class HostMemoryCompanion {
22
+ private readonly input;
23
+ private sessions;
24
+ private pending;
25
+ private cooldown;
26
+ private draining;
27
+ private stopped;
28
+ private active;
29
+ private readonly off;
30
+ constructor(input: Input);
31
+ ensure(sessionId: string): void;
32
+ release(sessionId: string): void;
33
+ stop(): void;
34
+ private allowed;
35
+ private enqueue;
36
+ private drain;
37
+ private review;
38
+ private send;
39
+ private withinIoBudget;
40
+ private spawnBounded;
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=host-memory-companion.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { type AgentFactory, type DirectorSessionFactory } from '@wrongstack/core/coordination';
2
- import type { ToolRegistry } from '@wrongstack/core/registry';
2
+ import { ToolRegistry } from '@wrongstack/core/registry';
3
3
  import type { Config, SubagentConfig, Tool } from '@wrongstack/core/types';
4
4
  import type { MultiAgentDeps, MultiAgentHostOptions } from './host-types.js';
5
5
  interface HostSubagentFactoryContext {
@@ -60,6 +60,7 @@ export declare class MultiAgentHost {
60
60
  * fleet.exploreCompanion.enabled=false. Probes are assigned to a
61
61
  * lazily-spawned resident `explore-companion` subagent per session. */
62
62
  private exploreCompanions;
63
+ private memoryCompanion;
63
64
  /** `agent.run.started` subscription that opens a companion for a new tab. */
64
65
  private exploreCompanionOff;
65
66
  /** Built-ins plus lazily-resolved project-created roles. */
@@ -0,0 +1,12 @@
1
+ import type { MemoryEvidenceSnapshot } from '@wrongstack/sage';
2
+ export { type MemoryEvidenceSnapshot, snapshotMemoryEvidence } from '@wrongstack/sage';
3
+ export interface MemoryCompanionVerdict {
4
+ verdict: 'supported' | 'outdated' | 'contradicted' | 'unverifiable' | 'irrelevant';
5
+ summary: string;
6
+ evidence: Array<{
7
+ path: string;
8
+ quote: string;
9
+ }>;
10
+ }
11
+ export declare function parseMemoryCompanionVerdict(raw: string, snapshot: MemoryEvidenceSnapshot): MemoryCompanionVerdict | undefined;
12
+ //# sourceMappingURL=memory-companion-evidence.d.ts.map
@@ -0,0 +1,9 @@
1
+ import type { SubagentConfig, Tool } from '@wrongstack/core/types';
2
+ export declare const MEMORY_COMPANION_READ_TOOLS: string[];
3
+ /** This predicate only narrows privileges; copying the prefix cannot grant any. */
4
+ export declare function isMemoryCompanion(config: SubagentConfig): boolean;
5
+ /** Reapply the host's read-only boundary AFTER project role/model overrides. */
6
+ export declare function constrainMemoryCompanion(config: SubagentConfig): SubagentConfig;
7
+ /** Do not auto-add session_note: all conclusions must pass the host's evidence gate. */
8
+ export declare function memoryCompanionTools(available: readonly Tool[]): Tool[];
9
+ //# sourceMappingURL=memory-companion-policy.d.ts.map
@@ -8,6 +8,16 @@ export declare function pathExists(p: string): Promise<boolean>;
8
8
  * test run); gitText keeps the head (its commands are tiny).
9
9
  */
10
10
  export declare const MAX_CMD_OUTPUT = 200000;
11
+ /**
12
+ * The last `cap` characters of a stream, trimmed WHILE it grows (at 2x the
13
+ * cap, so amortized linear). Collecting every chunk and slicing once at the
14
+ * end held the whole transcript: +146 MB retained for 150 MB of output.
15
+ */
16
+ export declare function createTailBuffer(cap: number): {
17
+ push(chunk: string): void;
18
+ value(): string;
19
+ retained(): number;
20
+ };
11
21
  /** Run a git command, returning trimmed stdout (empty string on failure). */
12
22
  export declare function gitText(args: string[], cwd: string): Promise<{
13
23
  code: number;
@@ -329,7 +329,7 @@ var hqCmd = async (args, deps) => {
329
329
  return 1;
330
330
  };
331
331
  async function startServer(deps) {
332
- const { startHqServer } = await import("./hq-server-YTKDVQPY.js");
332
+ const { startHqServer } = await import("./hq-server-UIB24LU6.js");
333
333
  const dataDir = resolveDataDir(deps);
334
334
  const flags = deps.flags ?? {};
335
335
  const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
@@ -549,8 +549,9 @@ async function hqTokenCmd(args, deps) {
549
549
  }
550
550
  var TOKEN_CAPABILITIES = {
551
551
  // `control.approve` is grantable but never default — see
552
- // DEFAULT_TOKEN_CAPABILITIES below.
553
- browser: ["control.enqueue", "control.approve"],
552
+ // DEFAULT_TOKEN_CAPABILITIES below. `control.approve.once` is the narrower
553
+ // grant: yes/no on a single prompt, no persistent `always`/`deny` policy.
554
+ browser: ["control.enqueue", "control.approve", "control.approve.once"],
554
555
  client: ["telemetry.publish", "control.execute"]
555
556
  };
556
557
  var DEFAULT_TOKEN_CAPABILITIES = {
@@ -1003,4 +1004,4 @@ export {
1003
1004
  hqCmd,
1004
1005
  resolveAuditActor
1005
1006
  };
1006
- //# sourceMappingURL=hq-GLZ7QESM.js.map
1007
+ //# sourceMappingURL=hq-44F4OQH2.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * HQ's fleet commands (`abort fleet`, `abort <agent>`) against a Director.
3
+ *
4
+ * Shared by the CLI host's own HQ connection and the embedded WebUI's per-tab
5
+ * presence, so both apply the same scoping rule.
6
+ *
7
+ * @module hq-fleet-control
8
+ */
9
+ import { type Director } from '@wrongstack/core/coordination';
10
+ export interface HqFleetScope {
11
+ /**
12
+ * `true` when several conversations share this Director (the WebUI host,
13
+ * one per tab). Then `abort fleet` may only ever reach the named session's
14
+ * workers — stopping another tab's fleet is worse than stopping nothing.
15
+ *
16
+ * A single-conversation host (TUI, REPL, one-shot) keeps the process-wide
17
+ * sweep as its fallback: after an in-place `/resume` the leader's older
18
+ * workers still carry the boot session as their origin, so a strictly
19
+ * scoped sweep would find none and silently leave them running.
20
+ */
21
+ multiConversation: boolean;
22
+ }
23
+ /** Stop the named session's workers; returns how many were stopped. */
24
+ export declare function killHqSessionFleet(director: Director | null, sessionId: string | undefined, scope: HqFleetScope): Promise<number>;
25
+ /** Terminate one subagent by id. Ids are process-unique, so no session check. */
26
+ export declare function terminateHqAgent(director: Director | null, subagentId: string): Promise<boolean>;
27
+ /** Spawn a roster (or ad-hoc) subagent owned by `sessionId`. */
28
+ export declare function spawnHqAgent(director: Director | null, sessionId: string, role: string, task?: string, maxIterations?: number): Promise<string>;
29
+ /** The three HQ fleet hooks, bound to a Director that may appear later. */
30
+ export interface HqFleetControl {
31
+ killFleet(sessionId?: string): Promise<number>;
32
+ terminateAgent(subagentId: string): Promise<boolean>;
33
+ spawnAgent(role: string, task?: string, maxIterations?: number, sessionId?: string): Promise<string>;
34
+ }
35
+ export declare function createHqFleetControl(getDirector: () => Director | null, defaultSessionId: () => string, scope: HqFleetScope): HqFleetControl;
36
+ //# sourceMappingURL=hq-fleet-control.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * What THIS process's HQ connection is doing right now.
3
+ *
4
+ * `/hq status` used to describe only the configuration, which answers "where
5
+ * would a new session connect", not "is this terminal on HQ". The CLI host's
6
+ * telemetry registers a probe here; one host per process, so a module slot is
7
+ * the honest shape.
8
+ *
9
+ * @module hq-live-status
10
+ */
11
+ export interface HqLiveConnectionStatus {
12
+ clientId: string;
13
+ projectId: string;
14
+ connected: boolean;
15
+ queuedFrames: number;
16
+ sessionId?: string | undefined;
17
+ }
18
+ /** Install the probe; returns an uninstall that only removes THIS probe. */
19
+ export declare function setHqLiveStatusProbe(next: () => HqLiveConnectionStatus | undefined): () => void;
20
+ /** `undefined` when no host registered, or HQ is off for this process. */
21
+ export declare function readHqLiveStatus(): HqLiveConnectionStatus | undefined;
22
+ //# sourceMappingURL=hq-live-status.d.ts.map
@@ -10,7 +10,25 @@ export interface CliHqConnection {
10
10
  }
11
11
  interface CliHqConnectionOptions extends CliHqPublisherOptions {
12
12
  onConnect?: ((publisher: HqPublisher) => void) | undefined;
13
+ /**
14
+ * Called when a publisher is retired without an immediate replacement —
15
+ * HQ was turned off (or its endpoint removed) in the live config. Hosts
16
+ * stop the bridges they bound in `onConnect`.
17
+ */
18
+ onDisconnect?: (() => void) | undefined;
13
19
  retryIntervalMs?: number | undefined;
20
+ /**
21
+ * The LIVE app config. `appConfig` is a snapshot taken at boot; with this
22
+ * set, `/hq set|token|on|off|clear` take effect on the running connection
23
+ * within one retry interval instead of at the next session start.
24
+ */
25
+ getAppConfig?: (() => CliHqPublisherOptions['appConfig']) | undefined;
26
+ /**
27
+ * `false` for an AUXILIARY connection in a process whose host connection
28
+ * already syncs the project's Kanban boards. Two syncs in one process are
29
+ * two writers applying the same remote snapshots to the same board files.
30
+ */
31
+ ownKanbanSync?: boolean | undefined;
14
32
  }
15
33
  export declare function createCliHqPublisher(options: CliHqPublisherOptions): HqPublisher | undefined;
16
34
  export declare function startCliHqConnection(options: CliHqConnectionOptions): CliHqConnection;
@@ -1,7 +1,18 @@
1
1
  import type * as http from 'node:http';
2
- import type { HqCommandAuditLog } from '@wrongstack/core/hq';
2
+ import type { HqCommand, HqCommandAuditLog } from '@wrongstack/core/hq';
3
3
  import type { TrustBoundary } from '@wrongstack/core/security';
4
4
  import type { WebSocket } from 'ws';
5
5
  import type { ConnectedClient, HqRouterMutableAuth, HqSessionEntry } from '../types.js';
6
6
  export declare function handleApiCommand(req: http.IncomingMessage, res: http.ServerResponse, url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, clients: Map<WebSocket, ConnectedClient>, browsers: Set<WebSocket>, auditLog: HqCommandAuditLog, trustBoundary: TrustBoundary): Promise<void>;
7
+ /**
8
+ * Whether a credential may answer a prompt.
9
+ *
10
+ * `control.approve` answers anything. `control.approve.once` — what the mobile
11
+ * login carries — may release or refuse THIS call, or answer an ask_user
12
+ * question, but never send `always`/`deny`, which write persistent policy onto
13
+ * the machine: that is not a decision to hand to a phone's cookie. Before this
14
+ * the mobile session had no answer rights at all while its Attention view
15
+ * offered Allow/Deny buttons, so every tap came back 403.
16
+ */
17
+ export declare function credentialMayAnswer(capabilities: readonly string[] | undefined, command: HqCommand): boolean;
7
18
  //# sourceMappingURL=command-handlers.d.ts.map
@@ -3,6 +3,6 @@ import type { createHqPersistence } from '@wrongstack/core/hq';
3
3
  import type { WebSocket } from 'ws';
4
4
  import type { ConnectedClient } from '../types.js';
5
5
  export declare function handleApiProjectDetail(_req: http.IncomingMessage, res: http.ServerResponse, url: URL, clients: Map<WebSocket, ConnectedClient>): Promise<void>;
6
- export declare function handleApiEvents(req: http.IncomingMessage, res: http.ServerResponse, persistence: ReturnType<typeof createHqPersistence>): Promise<void>;
6
+ export declare function handleApiEvents(req: http.IncomingMessage, res: http.ServerResponse, persistence: ReturnType<typeof createHqPersistence>, resolveMachineClientIds?: (machineId: string) => ReadonlySet<string>): Promise<void>;
7
7
  export declare function handleApiTrendsCost(req: http.IncomingMessage, res: http.ServerResponse, persistence: ReturnType<typeof createHqPersistence>): Promise<void>;
8
8
  //# sourceMappingURL=data-handlers.d.ts.map