@wrongstack/cli 1.0.26 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-LI665LO2.js → acp-M4565YUW.js} +30 -17
- package/dist/boot/dispatch-singleshot.d.ts +8 -0
- package/dist/boot/dispatch-webui.d.ts +2 -0
- package/dist/boot/goal-tui-default.d.ts +10 -0
- package/dist/{chunk-OAVBM55Q.js → chunk-ENRYW73H.js} +525 -379
- package/dist/chunk-HDMFKTT3.js +157 -0
- package/dist/{chunk-WQV2NP57.js → chunk-HFZH2XJN.js} +2 -2
- package/dist/{chunk-BEHGRNOQ.js → chunk-N57IU5FG.js} +9 -9
- package/dist/{chunk-2EBVTYJR.js → chunk-NRXWOMK7.js} +17 -3
- package/dist/{chunk-5SEYGOCH.js → chunk-RDGGWMTI.js} +2 -2
- package/dist/{chunk-KJS5543T.js → chunk-RGFWGUAB.js} +26 -14
- package/dist/{chunk-S4FFHDJG.js → chunk-SDJCLEK6.js} +6 -2
- package/dist/{cli-context-UVGAN3NO.js → cli-context-C7RBSWHF.js} +33 -6
- package/dist/{cli-main-TN3KZ2P6.js → cli-main-TQTH73ZK.js} +304 -345
- package/dist/{execution-7QIP6GRY.js → execution-3STBD2U7.js} +87 -33
- package/dist/execution-chimera-review.d.ts +18 -1
- package/dist/{hq-GLZ7QESM.js → hq-44F4OQH2.js} +5 -4
- package/dist/hq-fleet-control.d.ts +36 -0
- package/dist/hq-live-status.d.ts +22 -0
- package/dist/hq-publisher.d.ts +18 -0
- package/dist/hq-server/routes/command-handlers.d.ts +12 -1
- package/dist/hq-server/routes/data-handlers.d.ts +1 -1
- package/dist/hq-server/socket-credentials.d.ts +63 -0
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/ws.d.ts +24 -0
- package/dist/{hq-server-YTKDVQPY.js → hq-server-UIB24LU6.js} +2 -2
- package/dist/{import-claude-code-32PCSIQI.js → import-claude-code-AYG24RG2.js} +36 -4
- package/dist/index.js +3 -3
- package/dist/live-settings-input.d.ts +1 -0
- package/dist/{mailbox-serve-NW5VZG7R.js → mailbox-serve-GNGXEQ2R.js} +2 -2
- package/dist/{modeldiag-36VODVIH.js → modeldiag-W7XAE625.js} +2 -2
- package/dist/{remote-O5WXHLWV.js → remote-GB5VXCCT.js} +38 -23
- package/dist/{sessions-config-SXTTLNXA.js → sessions-config-XHLXJ5C3.js} +75 -6
- package/dist/{short-circuit-flags-74Y5N6CF.js → short-circuit-flags-5TNTJPQR.js} +2 -2
- package/dist/slash-commands/theme.d.ts +15 -1
- package/dist/subcommands/handlers/import-claude-code.d.ts +10 -0
- package/dist/subcommands/handlers/sessions-fleet.d.ts +0 -12
- package/dist/{subcommands-Q6ORSB2E.js → subcommands-BHQYLDSD.js} +2 -2
- package/dist/{webui-server-YTHLRFD2.js → webui-server-GCXIO3JY.js} +37 -30
- package/dist/webui-server-options.d.ts +10 -0
- package/dist/wiring/command-host-state.d.ts +2 -0
- package/dist/wiring/hq-telemetry.d.ts +12 -0
- package/package.json +29 -28
- 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-
|
|
9
|
+
} from "./chunk-HDMFKTT3.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-
|
|
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-
|
|
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 &&
|
|
2068
|
+
return candidate && isThemePresetId(candidate) ? candidate : void 0;
|
|
2053
2069
|
},
|
|
2054
2070
|
saveThemePreset: async (preset) => {
|
|
2055
|
-
if (!
|
|
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
|
-
|
|
3696
|
-
|
|
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:
|
|
7172
|
-
|
|
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-
|
|
7345
|
+
//# sourceMappingURL=execution-3STBD2U7.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
|
-
|
|
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. */
|
|
@@ -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-
|
|
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
|
-
|
|
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-
|
|
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
|
package/dist/hq-publisher.d.ts
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential enforcement for HQ sockets that are ALREADY open.
|
|
3
|
+
*
|
|
4
|
+
* Neither side of a WebSocket re-presents its credential after the upgrade,
|
|
5
|
+
* so every rule here is the only enforcement that credential gets once the
|
|
6
|
+
* socket is live: revocation notices, expiry that happens with the clock
|
|
7
|
+
* rather than with an auth.json write, and client tokens that stopped being
|
|
8
|
+
* live.
|
|
9
|
+
*
|
|
10
|
+
* @module hq-server/socket-credentials
|
|
11
|
+
*/
|
|
12
|
+
import { type HqToken } from '@wrongstack/core/hq';
|
|
13
|
+
import { WebSocket } from 'ws';
|
|
14
|
+
import type { HqRouterMutableAuth, HqSessionEntry } from './types.js';
|
|
15
|
+
export interface HqSocketCredentialState {
|
|
16
|
+
mutableAuth: HqRouterMutableAuth;
|
|
17
|
+
sessions: Map<string, HqSessionEntry>;
|
|
18
|
+
browsers: Set<WebSocket>;
|
|
19
|
+
/** Session that authorized each cookie-authenticated browser socket. */
|
|
20
|
+
browserSocketSessions: Map<WebSocket, string>;
|
|
21
|
+
/** Token each `/ws/client` socket authenticated with (absent in open mode). */
|
|
22
|
+
clientSocketTokens: Map<WebSocket, HqToken | undefined>;
|
|
23
|
+
/**
|
|
24
|
+
* Browser-token verifier each bare loopback `?token=` `/ws/browser` socket
|
|
25
|
+
* authenticated with. Those sockets have no session, so the session sweep
|
|
26
|
+
* never saw them: one kept streaming after its token expired by the clock
|
|
27
|
+
* (H-3, security-check 2026-09-26). Optional for callers that predate it.
|
|
28
|
+
*/
|
|
29
|
+
browserSocketTokenKeys?: Map<WebSocket, string> | undefined;
|
|
30
|
+
}
|
|
31
|
+
export interface HqSocketCredentialEnforcer {
|
|
32
|
+
/**
|
|
33
|
+
* Tell the browsers a token revocation can concern.
|
|
34
|
+
*
|
|
35
|
+
* This ONLY announces; it deliberately does not close. The auth.json
|
|
36
|
+
* watcher owns eviction and does it precisely (it deletes the sessions
|
|
37
|
+
* whose `tokenId` is no longer live, and distinguishes a password change
|
|
38
|
+
* from a token change). Because the revocation hook fires from
|
|
39
|
+
* `authState.apply`, which the watcher calls before its close loop, the
|
|
40
|
+
* browser still learns why it is about to be disconnected.
|
|
41
|
+
*
|
|
42
|
+
* A socket bound to a password (or mobile) session, or to a session minted
|
|
43
|
+
* from a DIFFERENT token, is untouched by the revocation — telling it anyway
|
|
44
|
+
* made every password-signed dashboard re-validate and, having no stored
|
|
45
|
+
* token to re-mint, sign itself out. A socket with no recorded session
|
|
46
|
+
* authenticated with a bare `?token=` the server cannot attribute, so it is
|
|
47
|
+
* told and re-validates on its own (fail closed).
|
|
48
|
+
*/
|
|
49
|
+
announceRevokedTokens(keys: readonly string[], ids: readonly string[]): void;
|
|
50
|
+
/** Close every `/ws/client` socket whose credential is no longer live. */
|
|
51
|
+
closeUnauthorizedClientSockets(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Credentials also die with the clock. The watcher reacts to file changes
|
|
54
|
+
* only, so before this a client token or a token-backed browser session
|
|
55
|
+
* that reached its `expiresAt` kept its open socket (telemetry in, commands
|
|
56
|
+
* out) until the process restarted.
|
|
57
|
+
*/
|
|
58
|
+
sweepExpiredSocketCredentials(now?: number): void;
|
|
59
|
+
/** Close the browser sockets bound to a session that was just removed. */
|
|
60
|
+
closeSessionSockets(sessionId: string, reason: string): void;
|
|
61
|
+
}
|
|
62
|
+
export declare function createHqSocketCredentialEnforcer(state: HqSocketCredentialState): HqSocketCredentialEnforcer;
|
|
63
|
+
//# sourceMappingURL=socket-credentials.d.ts.map
|
|
@@ -28,6 +28,8 @@ interface HqUpgradeHandlerDeps {
|
|
|
28
28
|
* the revocation path treats that absence as "affected" (fail closed).
|
|
29
29
|
*/
|
|
30
30
|
browserSocketSessions: Map<WebSocket, string>;
|
|
31
|
+
/** Browser-token verifier behind each bare `?token=` browser socket (H-3). */
|
|
32
|
+
browserSocketTokenKeys?: Map<WebSocket, string> | undefined;
|
|
31
33
|
browsers: Set<WebSocket>;
|
|
32
34
|
eventLog: import('@wrongstack/core/hq').HqEventEnvelope[];
|
|
33
35
|
transcripts: Map<string, TranscriptRing>;
|
package/dist/hq-server/ws.d.ts
CHANGED
|
@@ -10,6 +10,30 @@ export { fanoutKanbanDelta } from './ws-client-events.js';
|
|
|
10
10
|
export { detectLeaderLoss } from './ws-leader.js';
|
|
11
11
|
export { handleClientResume } from './ws-resume.js';
|
|
12
12
|
export declare function handleBrowser(ws: WebSocket, snapshotBroadcaster: HqSnapshotBroadcaster, browsers: Set<WebSocket>, eventLog: HqEventEnvelope[]): void;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve every command the disconnecting client never picked up.
|
|
15
|
+
*
|
|
16
|
+
* The queue lives on the per-socket `ConnectedClient`, so it dies with the
|
|
17
|
+
* socket: a command enqueued in the window between the operator's dispatch and
|
|
18
|
+
* the next 500 ms `command_poll` is simply gone, and nothing re-queues it on
|
|
19
|
+
* the replacement connection. Left alone, its audit row stayed `queued`
|
|
20
|
+
* forever — the Control rail showed a command as pending that will never run,
|
|
21
|
+
* which is the one thing an operator must not have to guess about.
|
|
22
|
+
*
|
|
23
|
+
* `status: 'queued'` in the audit log is the exact undelivered set: the poll
|
|
24
|
+
* handler flips a command to `delivered` the moment it is put on the wire, so
|
|
25
|
+
* anything still `queued` never reached the client. Delivered-but-unacked
|
|
26
|
+
* commands are deliberately left alone — those DID run, and the ack may simply
|
|
27
|
+
* have been in flight when the socket closed.
|
|
28
|
+
*
|
|
29
|
+
* EVERY path that removes a client from `clients` must call this. The socket's
|
|
30
|
+
* own `close` handler finds the client through `clients.get(ws)`, so a path
|
|
31
|
+
* that deletes the entry first — the heartbeat-timeout eviction, the
|
|
32
|
+
* sessionless-surface eviction, the supersede loop — leaves that handler with
|
|
33
|
+
* nothing to resolve, and the rows it owned stayed `queued` forever. Those are
|
|
34
|
+
* exactly the cases (a hung client, a zombie socket) where commands strand.
|
|
35
|
+
*/
|
|
36
|
+
export declare function failUndeliveredCommands(lostClient: ConnectedClient, browsers: Set<WebSocket>, auditLog: HqCommandAuditLog | undefined): void;
|
|
13
37
|
export declare function handleClient(ws: WebSocket, clients: Map<WebSocket, ConnectedClient>, browsers: Set<WebSocket>, eventLog: HqEventEnvelope[], auth: {
|
|
14
38
|
token?: HqToken | undefined;
|
|
15
39
|
getOperatorPolicy: () => Partial<HqRedactionPolicy> | undefined;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
readLocalSubagentTranscript,
|
|
11
11
|
sanitizeApiError,
|
|
12
12
|
startHqServer
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ENRYW73H.js";
|
|
14
14
|
import "./chunk-HXJYHAR4.js";
|
|
15
15
|
import "./chunk-5EA6OTLJ.js";
|
|
16
16
|
import "./chunk-Q7E3BPDU.js";
|
|
@@ -27,4 +27,4 @@ export {
|
|
|
27
27
|
sanitizeApiError,
|
|
28
28
|
startHqServer
|
|
29
29
|
};
|
|
30
|
-
//# sourceMappingURL=hq-server-
|
|
30
|
+
//# sourceMappingURL=hq-server-UIB24LU6.js.map
|