@wrongstack/cli 0.299.0 → 0.300.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.
- package/dist/{acp-S4LZQB66.js → acp-IDNS2YVE.js} +3 -3
- package/dist/{auth-G5YKKSRH.js → auth-6T6ZOT2R.js} +5 -5
- package/dist/auth-menu/loopback-server.d.ts +41 -55
- package/dist/auth-menu/openai-codex-oauth.d.ts +36 -123
- package/dist/auto-discover-providers-DRW4OZHG.js +8 -0
- package/dist/boot/dispatch-webui.d.ts +3 -0
- package/dist/{chunk-PEKUKUCH.js → chunk-3MWUZMOL.js} +19 -12
- package/dist/{chunk-TGDHN4LM.js → chunk-4GVH7V3S.js} +4 -24
- package/dist/{chunk-4U3DMA2Y.js → chunk-4WJKVQJY.js} +15 -20
- package/dist/{chunk-2SLSUDYS.js → chunk-GYDQABMA.js} +269 -180
- package/dist/{chunk-4HH3YGG4.js → chunk-MRLYDHQ7.js} +8 -5
- package/dist/{chunk-66T43Q4Y.js → chunk-N7ULWBO5.js} +4 -3
- package/dist/{chunk-ZNZJ34RF.js → chunk-PEMN4T5O.js} +2 -2
- package/dist/chunk-PYTFS4MC.js +238 -0
- package/dist/{chunk-L5CE52XW.js → chunk-V76R7DC5.js} +25 -19
- package/dist/{cli-main-3MQDFSYK.js → cli-main-7B3G2YSG.js} +528 -156
- package/dist/{execution-ANOYMWPO.js → execution-SHGM7BVC.js} +50 -5
- package/dist/fleet/budget-source.d.ts +18 -0
- package/dist/fleet/host-status.d.ts +31 -0
- package/dist/fleet/host-types.d.ts +8 -0
- package/dist/fleet/host.d.ts +5 -0
- package/dist/{hq-BDO56CX6.js → hq-DVDD4XPV.js} +22 -3
- package/dist/hq-server/auth-state.d.ts +29 -1
- package/dist/hq-server/auth.d.ts +16 -0
- package/dist/hq-server/client-address.d.ts +56 -0
- package/dist/hq-server/login-attempt-store.d.ts +30 -10
- package/dist/hq-server/routes/auth-handlers.d.ts +66 -26
- package/dist/hq-server/routes.d.ts +13 -0
- package/dist/hq-server/types.d.ts +10 -0
- package/dist/{hq-server-RJLJJZU7.js → hq-server-DQHLNHDL.js} +2 -2
- package/dist/hq-server.d.ts +19 -0
- package/dist/index.js +20 -20
- package/dist/{modeldiag-HENYJ4N7.js → modeldiag-YOQZHBQV.js} +2 -2
- package/dist/{plugin-usage-VTL7ZTVC.js → plugin-usage-EOG4ET4S.js} +2 -2
- package/dist/{plugins-ETY6W3DC.js → plugins-56CMWMPR.js} +3 -3
- package/dist/{providers-models-GDELBHZK.js → providers-models-K6RT7ABI.js} +34 -9
- package/dist/slash-commands/command-context.d.ts +19 -0
- package/dist/webui-server/setup-events.d.ts +17 -0
- package/dist/webui-server/terminal-log-view.d.ts +29 -12
- package/dist/{webui-server-PCLSXX6L.js → webui-server-G3EKFXLL.js} +109 -38
- package/dist/webui-server-options.d.ts +16 -0
- package/dist/wiring/brain-and-orchestration.d.ts +3 -0
- package/dist/wiring/director-setup.d.ts +4 -0
- package/dist/wiring/fallback-gate.d.ts +31 -0
- package/dist/wiring/fleet-command-handlers.d.ts +1 -1
- package/dist/wiring/plugins.d.ts +2 -8
- package/dist/wiring/provider-utility-tools.d.ts +26 -1
- package/package.json +23 -23
- package/dist/chunk-GO3TJICK.js +0 -489
|
@@ -182,12 +182,13 @@ async function runWebUIDispatch(ctx) {
|
|
|
182
182
|
rebindTodosCheckpoint,
|
|
183
183
|
agentTranscripts,
|
|
184
184
|
sddSubagentFactory,
|
|
185
|
-
onKanbanDispatch
|
|
185
|
+
onKanbanDispatch,
|
|
186
|
+
getFleetBudget
|
|
186
187
|
} = ctx;
|
|
187
188
|
const isSimpleUi = flags["simpleui"] === true;
|
|
188
189
|
agent.disableInteractiveConfirmation();
|
|
189
190
|
renderer.setSilent(true);
|
|
190
|
-
const { runWebUI } = await import("./webui-server-
|
|
191
|
+
const { runWebUI } = await import("./webui-server-G3EKFXLL.js");
|
|
191
192
|
const flagValue = (names) => {
|
|
192
193
|
for (const name of names) {
|
|
193
194
|
if (!Object.hasOwn(flags, name)) continue;
|
|
@@ -274,6 +275,7 @@ async function runWebUIDispatch(ctx) {
|
|
|
274
275
|
mcpRegistry,
|
|
275
276
|
subscribeEternalIteration,
|
|
276
277
|
sessionStore,
|
|
278
|
+
...getFleetBudget ? { getFleetBudget } : {},
|
|
277
279
|
sessionsDir: projectSessionsDir,
|
|
278
280
|
claimSession: activateSessionIdentity ? async (sessionId) => {
|
|
279
281
|
const previousSessionId = agent.ctx.session?.id ?? session.id;
|
|
@@ -336,7 +338,11 @@ async function runWebUIDispatch(ctx) {
|
|
|
336
338
|
resolve3(0);
|
|
337
339
|
}).catch((err) => {
|
|
338
340
|
renderer.setSilent(false);
|
|
339
|
-
|
|
341
|
+
renderer.writeInfo(
|
|
342
|
+
color2.red(
|
|
343
|
+
` \u2717 ${isSimpleUi ? "SimpleUI" : "WebUI"} failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
344
|
+
)
|
|
345
|
+
);
|
|
340
346
|
resolve3(1);
|
|
341
347
|
});
|
|
342
348
|
});
|
|
@@ -1982,6 +1988,19 @@ async function finalizeExecutionCleanup(input) {
|
|
|
1982
1988
|
} catch (_err) {
|
|
1983
1989
|
}
|
|
1984
1990
|
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
1991
|
+
try {
|
|
1992
|
+
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
1993
|
+
getProcessRegistry2().killAll({ force: true, includeProtected: true });
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
console.warn(
|
|
1996
|
+
JSON.stringify({
|
|
1997
|
+
level: "warn",
|
|
1998
|
+
event: "shutdown.process_kill_all_failed",
|
|
1999
|
+
message: `Process registry killAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2000
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2001
|
+
})
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
1985
2004
|
await mcpRegistry.stopAll().catch((err) => {
|
|
1986
2005
|
console.warn(
|
|
1987
2006
|
JSON.stringify({
|
|
@@ -2719,6 +2738,9 @@ function installChimeraCascadeHandler({
|
|
|
2719
2738
|
maxIterations: 40,
|
|
2720
2739
|
maxToolCalls: 200,
|
|
2721
2740
|
timeoutMs,
|
|
2741
|
+
// Cascade agents are ephemeral infrastructure: like reviewers,
|
|
2742
|
+
// they must not consume the leader's lifetime maxSpawns budget.
|
|
2743
|
+
spawnBudgetExempt: true,
|
|
2722
2744
|
// Rung 0 stays unpinned so the role model matrix still decides;
|
|
2723
2745
|
// later rungs pin a model precisely because it just failed.
|
|
2724
2746
|
...attempt.tier === "inherit" ? {} : {
|
|
@@ -2977,7 +2999,10 @@ function installChimeraReviewHandler({
|
|
|
2977
2999
|
timeoutMs,
|
|
2978
3000
|
provider: attempt.provider,
|
|
2979
3001
|
model: attempt.model,
|
|
2980
|
-
fallbackModels: attempt.fallbackModels
|
|
3002
|
+
fallbackModels: attempt.fallbackModels,
|
|
3003
|
+
// Reviewers are ephemeral background infrastructure: they must
|
|
3004
|
+
// not consume the leader's lifetime maxSpawns budget.
|
|
3005
|
+
spawnBudgetExempt: true
|
|
2981
3006
|
}),
|
|
2982
3007
|
// The reviewer is read-only, so a retry re-runs the task verbatim.
|
|
2983
3008
|
buildTask: () => taskDesc,
|
|
@@ -4885,6 +4910,26 @@ async function execute(deps) {
|
|
|
4885
4910
|
sddSubagentFactory,
|
|
4886
4911
|
statusTracker,
|
|
4887
4912
|
updateInfo: bootUpdateInfo,
|
|
4913
|
+
getFleetBudget: () => {
|
|
4914
|
+
const d = getDirector?.() ?? null;
|
|
4915
|
+
if (!d) return null;
|
|
4916
|
+
const snap = d.fleetManager?.budgetSnapshot?.();
|
|
4917
|
+
const maxSpawns = snap?.maxSpawns ?? d.maxSpawns;
|
|
4918
|
+
const usedSpawns = snap?.usedSpawns ?? d.spawnCount;
|
|
4919
|
+
const remainingSpawns = snap?.remainingSpawns ?? Math.max(
|
|
4920
|
+
0,
|
|
4921
|
+
(Number.isFinite(maxSpawns) ? maxSpawns : Number.POSITIVE_INFINITY) - usedSpawns
|
|
4922
|
+
);
|
|
4923
|
+
const activeAgents = d.status().subagents.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
4924
|
+
return {
|
|
4925
|
+
maxSpawns,
|
|
4926
|
+
usedSpawns,
|
|
4927
|
+
remainingSpawns,
|
|
4928
|
+
activeAgents,
|
|
4929
|
+
...snap?.checkpointMaxSpawns !== void 0 ? { checkpointMaxSpawns: snap.checkpointMaxSpawns } : {},
|
|
4930
|
+
...snap?.ceilingMismatch ? { ceilingMismatch: true } : {}
|
|
4931
|
+
};
|
|
4932
|
+
},
|
|
4888
4933
|
...createKanbanDispatchHandler({ config, events, skillLoader, sddSubagentFactory })
|
|
4889
4934
|
});
|
|
4890
4935
|
} else {
|
|
@@ -4965,4 +5010,4 @@ export {
|
|
|
4965
5010
|
execute,
|
|
4966
5011
|
resolveReviewerFallbackModels
|
|
4967
5012
|
};
|
|
4968
|
-
//# sourceMappingURL=execution-
|
|
5013
|
+
//# sourceMappingURL=execution-SHGM7BVC.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Config } from '@wrongstack/core/types';
|
|
2
|
+
export type FleetBudgetSource = 'cli-flag' | 'env' | 'profile' | 'default';
|
|
3
|
+
export interface ResolvedFleetBudget {
|
|
4
|
+
maxConcurrent: number;
|
|
5
|
+
maxConcurrentSource: FleetBudgetSource;
|
|
6
|
+
maxSpawns: number;
|
|
7
|
+
maxSpawnsSource: FleetBudgetSource;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveFleetBudgetSources(input: {
|
|
10
|
+
flags?: Record<string, string | boolean> | undefined;
|
|
11
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
12
|
+
config?: Config | undefined;
|
|
13
|
+
/** Built-in maxConcurrent when nothing else wins. */
|
|
14
|
+
defaultMaxConcurrent?: number | undefined;
|
|
15
|
+
/** Built-in maxSpawns when nothing else wins. */
|
|
16
|
+
defaultMaxSpawns?: number | undefined;
|
|
17
|
+
}): ResolvedFleetBudget;
|
|
18
|
+
//# sourceMappingURL=budget-source.d.ts.map
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
import type { TaskResult } from '@wrongstack/core/types';
|
|
2
|
+
/** Read-only fleet concurrency + lifetime spawn budget snapshot for operators. */
|
|
3
|
+
export interface FleetBudgetView {
|
|
4
|
+
maxConcurrent: number;
|
|
5
|
+
activeAgents: number;
|
|
6
|
+
maxSpawns: number;
|
|
7
|
+
usedSpawns: number;
|
|
8
|
+
remainingSpawns: number;
|
|
9
|
+
maxTokens?: number | undefined;
|
|
10
|
+
usedTokens?: number | undefined;
|
|
11
|
+
remainingTokens?: number | undefined;
|
|
12
|
+
maxCostUsd?: number | undefined;
|
|
13
|
+
usedCostUsd?: number | undefined;
|
|
14
|
+
remainingCostUsd?: number | undefined;
|
|
15
|
+
/** Historical ceiling from checkpoint metadata when it differed from live. */
|
|
16
|
+
checkpointMaxSpawns?: number | undefined;
|
|
17
|
+
ceilingMismatch?: boolean | undefined;
|
|
18
|
+
/** Winning source for maxConcurrent: cli-flag | env | profile | default. */
|
|
19
|
+
maxConcurrentSource?: string | undefined;
|
|
20
|
+
/** Winning source for maxSpawns: cli-flag | env | profile | default. */
|
|
21
|
+
maxSpawnsSource?: string | undefined;
|
|
22
|
+
/** Compact effective-source label for status lines. */
|
|
23
|
+
effectiveSource?: string | undefined;
|
|
24
|
+
}
|
|
2
25
|
export interface FleetHostStatus {
|
|
3
26
|
pending: {
|
|
4
27
|
taskId: string;
|
|
@@ -12,6 +35,7 @@ export interface FleetHostStatus {
|
|
|
12
35
|
task?: string | undefined;
|
|
13
36
|
}[];
|
|
14
37
|
summary: string;
|
|
38
|
+
budget?: FleetBudgetView | undefined;
|
|
15
39
|
}
|
|
16
40
|
export interface FleetHostUsage {
|
|
17
41
|
rows: Array<{
|
|
@@ -42,7 +66,14 @@ export interface FleetHostStatusInputs {
|
|
|
42
66
|
} | null | undefined;
|
|
43
67
|
completedResults?: readonly TaskResult[] | null | undefined;
|
|
44
68
|
shadowTaskIds: ReadonlySet<string>;
|
|
69
|
+
budget?: FleetBudgetView | undefined;
|
|
45
70
|
}
|
|
46
71
|
export declare function buildFleetHostStatus(input: FleetHostStatusInputs): FleetHostStatus;
|
|
72
|
+
/** Format a compact multi-line budget block for `/fleet status` and resume logs. */
|
|
73
|
+
export declare function formatFleetBudgetLines(budget: FleetBudgetView, colorize?: {
|
|
74
|
+
bold: (s: string) => string;
|
|
75
|
+
dim: (s: string) => string;
|
|
76
|
+
amber: (s: string) => string;
|
|
77
|
+
}): string[];
|
|
47
78
|
export declare function aggregateFleetUsage(completed: readonly TaskResult[]): FleetHostUsage;
|
|
48
79
|
//# sourceMappingURL=host-status.d.ts.map
|
|
@@ -58,6 +58,14 @@ export interface MultiAgentHostOptions {
|
|
|
58
58
|
};
|
|
59
59
|
/** Lifetime spawn cap for this Director. CLI default: 64. */
|
|
60
60
|
maxSpawns?: number | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Winning configuration sources for concurrency / lifetime spawn ceilings
|
|
63
|
+
* (issue #323). Surfaced in `/fleet status` and WebUI budget views.
|
|
64
|
+
*/
|
|
65
|
+
budgetSources?: {
|
|
66
|
+
maxConcurrent?: import('./budget-source.js').FleetBudgetSource | undefined;
|
|
67
|
+
maxSpawns?: import('./budget-source.js').FleetBudgetSource | undefined;
|
|
68
|
+
} | undefined;
|
|
61
69
|
/** Maximum auto-extensions per subagent per budget kind. */
|
|
62
70
|
maxBudgetExtensions?: number | undefined;
|
|
63
71
|
/** Optional global Brain arbiter for director-level policy decisions. */
|
package/dist/fleet/host.d.ts
CHANGED
|
@@ -203,6 +203,11 @@ export declare class MultiAgentHost {
|
|
|
203
203
|
private recordLearningRole;
|
|
204
204
|
private captureCompletedTaskLearning;
|
|
205
205
|
status(): FleetHostStatus;
|
|
206
|
+
/**
|
|
207
|
+
* Read-only concurrency + lifetime spawn budget for `/fleet status` and
|
|
208
|
+
* resume diagnostics. Safe when no director/fleet is active yet.
|
|
209
|
+
*/
|
|
210
|
+
budgetView(): import('./host-status.js').FleetBudgetView;
|
|
206
211
|
usage(): FleetHostUsage;
|
|
207
212
|
manifest(): Promise<string | null>;
|
|
208
213
|
promoteToDirector(): Promise<Director | null>;
|
|
@@ -36,7 +36,7 @@ var hqCmd = async (args, deps) => {
|
|
|
36
36
|
return 1;
|
|
37
37
|
};
|
|
38
38
|
async function startServer(deps) {
|
|
39
|
-
const { startHqServer } = await import("./hq-server-
|
|
39
|
+
const { startHqServer } = await import("./hq-server-DQHLNHDL.js");
|
|
40
40
|
const dataDir = resolveDataDir(deps);
|
|
41
41
|
const flags = deps.flags ?? {};
|
|
42
42
|
const host = typeof flags["host"] === "string" ? flags["host"] : HQ_CLI_DEFAULT_HOST;
|
|
@@ -56,6 +56,18 @@ async function startServer(deps) {
|
|
|
56
56
|
}
|
|
57
57
|
tokenTtlMs = parsed.value;
|
|
58
58
|
}
|
|
59
|
+
const rawHops = typeof flags["hq-trusted-proxy-hops"] === "string" ? flags["hq-trusted-proxy-hops"] : void 0;
|
|
60
|
+
let trustedProxyHops;
|
|
61
|
+
if (rawHops !== void 0 && rawHops.length > 0) {
|
|
62
|
+
const parsed = Number.parseInt(rawHops, 10);
|
|
63
|
+
if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== rawHops.trim()) {
|
|
64
|
+
deps.renderer.writeError(
|
|
65
|
+
"--hq-trusted-proxy-hops must be a non-negative integer (0 = ignore X-Forwarded-For).\n"
|
|
66
|
+
);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
trustedProxyHops = parsed;
|
|
70
|
+
}
|
|
59
71
|
if (password !== void 0 && password.length < 8) {
|
|
60
72
|
deps.renderer.writeError("HQ password must be at least 8 characters.\n");
|
|
61
73
|
return 1;
|
|
@@ -69,7 +81,8 @@ async function startServer(deps) {
|
|
|
69
81
|
dataDir,
|
|
70
82
|
allowInsecureOpen,
|
|
71
83
|
...password !== void 0 ? { password } : {},
|
|
72
|
-
...tokenTtlMs !== void 0 ? { tokenTtlMs } : {}
|
|
84
|
+
...tokenTtlMs !== void 0 ? { tokenTtlMs } : {},
|
|
85
|
+
...trustedProxyHops !== void 0 ? { trustedProxyHops } : {}
|
|
73
86
|
});
|
|
74
87
|
} catch (err) {
|
|
75
88
|
if (err instanceof HqInsecureExposureError) {
|
|
@@ -528,6 +541,12 @@ function printHelp(deps) {
|
|
|
528
541
|
`);
|
|
529
542
|
deps.renderer.write(` --hq-token-ttl <dur> Stamp an expiresAt on first-run tokens (e.g. 1h, 7d, 3600s).
|
|
530
543
|
`);
|
|
544
|
+
deps.renderer.write(
|
|
545
|
+
` --hq-trusted-proxy-hops <n> Trust the rightmost n X-Forwarded-For entries when rate-limiting logins.
|
|
546
|
+
Default 0 (ignore the header). Set to the real hop count behind a tunnel,
|
|
547
|
+
otherwise every user shares one backoff bucket. Never guess high.
|
|
548
|
+
`
|
|
549
|
+
);
|
|
531
550
|
deps.renderer.write(` --open Open the dashboard in the default browser.
|
|
532
551
|
`);
|
|
533
552
|
deps.renderer.write(` --client, -c Operate on client tokens instead of browser tokens.
|
|
@@ -544,4 +563,4 @@ export {
|
|
|
544
563
|
hqCmd,
|
|
545
564
|
resolveAuditActor
|
|
546
565
|
};
|
|
547
|
-
//# sourceMappingURL=hq-
|
|
566
|
+
//# sourceMappingURL=hq-DVDD4XPV.js.map
|
|
@@ -5,6 +5,34 @@ export interface HqAuthState {
|
|
|
5
5
|
apply(next: HqAuthFile): void;
|
|
6
6
|
tokenStats(): NonNullable<HqSnapshot['totals']['tokenStats']>;
|
|
7
7
|
}
|
|
8
|
+
export interface HqAuthStateOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Invoked after every {@link HqAuthState.apply}, with the freshly projected
|
|
11
|
+
* `mutableAuth`. The server wires this to the WS-010 exposure re-assessment
|
|
12
|
+
* so `requireAuthFloor` is re-latched by EVERY path that changes the live
|
|
13
|
+
* credential set — not just the `fs.watch` reload.
|
|
14
|
+
*
|
|
15
|
+
* WS-101: the in-process mutation routes (`DELETE /api/auth/password`, the
|
|
16
|
+
* TOTP endpoints, recovery-code consumption) used to run their own copy of
|
|
17
|
+
* this projection and never touched the floor, so removing the last
|
|
18
|
+
* credential on a non-loopback bind dropped HQ into open mode until the
|
|
19
|
+
* watcher happened to fire. Routing every mutation through `apply` is what
|
|
20
|
+
* makes the latch unconditional.
|
|
21
|
+
*/
|
|
22
|
+
onApplied?: ((mutableAuth: HqRouterMutableAuth) => void) | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Project an `auth.json` document onto the live `mutableAuth` used by every
|
|
26
|
+
* HQ gate. THE single projection — `HqAuthState.apply` is its only caller and
|
|
27
|
+
* every mutation path goes through that, so there is exactly one place where
|
|
28
|
+
* "which tokens are live" is decided.
|
|
29
|
+
*
|
|
30
|
+
* Expired tokens are dropped from BOTH scopes here. The browser scope is also
|
|
31
|
+
* re-checked at the request boundary (`authenticateBrowserRequest`), but the
|
|
32
|
+
* `/ws/client` upgrade gate is a bare set-membership test, so for client
|
|
33
|
+
* tokens this filter is the only expiry enforcement there is.
|
|
34
|
+
*/
|
|
35
|
+
export declare function projectAuthFile(mutableAuth: HqRouterMutableAuth, next: HqAuthFile): void;
|
|
8
36
|
/** Owns the live auth projection, expiry statistics, and reload audit diff. */
|
|
9
|
-
export declare function createHqAuthState(authFile: HqAuthFile, dataDir: string): HqAuthState;
|
|
37
|
+
export declare function createHqAuthState(authFile: HqAuthFile, dataDir: string, opts?: HqAuthStateOptions): HqAuthState;
|
|
10
38
|
//# sourceMappingURL=auth-state.d.ts.map
|
package/dist/hq-server/auth.d.ts
CHANGED
|
@@ -42,6 +42,22 @@ export declare function hasTrustedBrowserOrigin(req: http.IncomingMessage, bound
|
|
|
42
42
|
export declare function serializeHqSessionCookie(sessionId: string, secret: string): string;
|
|
43
43
|
export declare function parseHqSessionCookie(value: string, secret: string): string | undefined;
|
|
44
44
|
export declare function parseCookieHeader(cookieHeader: string | undefined): Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* Read the HQ session cookie, preferring the `__Host-`-prefixed name.
|
|
47
|
+
*
|
|
48
|
+
* WS-103: every reader open-coded `cookies[HQ_SESSION_COOKIE] ??
|
|
49
|
+
* cookies['__Host-hq.session']`, which prefers the UNPREFIXED name. The whole
|
|
50
|
+
* point of the `__Host-` prefix is that a browser refuses to accept such a
|
|
51
|
+
* cookie unless it is Secure, Path=/, and Domain-less — guarantees a plain
|
|
52
|
+
* `hq.session` set by a sibling subdomain does not carry. Reading the weak name
|
|
53
|
+
* first hands that injected value precedence over the hardened one on exactly
|
|
54
|
+
* the deployments (`secure`) where the prefix was doing work.
|
|
55
|
+
*
|
|
56
|
+
* Signature verification still stands behind this, so a forged cookie needs
|
|
57
|
+
* `cookieSecret` — but preference order is the layer whose only job is to stop
|
|
58
|
+
* the attacker-controlled name from winning, so it should not be inverted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readHqSessionCookie(cookieHeader: string | undefined): string | undefined;
|
|
45
61
|
export declare function setHqSessionCookie(res: http.ServerResponse, value: string, secure?: boolean): void;
|
|
46
62
|
export declare function clearHqSessionCookie(res: http.ServerResponse, secure?: boolean): void;
|
|
47
63
|
export declare function isTokenAuth(auth: HqBrowserAuthResult): auth is HqBrowserAuthContext;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-address resolution for rate limiting behind a reverse proxy.
|
|
3
|
+
*
|
|
4
|
+
* WS-106: HQ's login backoff keys on `req.socket.remoteAddress`. On a direct
|
|
5
|
+
* bind that is exactly right. Behind the documented public-relay / tunnel
|
|
6
|
+
* deployment (`--hq-public-url`, `requireBrowserAuth`) it is exactly wrong:
|
|
7
|
+
* every request arrives from the tunnel's own address, so all users share one
|
|
8
|
+
* bucket and a single attacker's backoff locks out everyone — while a
|
|
9
|
+
* distributed attacker gets no per-source limiting at all.
|
|
10
|
+
*
|
|
11
|
+
* The fix is NOT to read `X-Forwarded-For` whenever it is present. That header
|
|
12
|
+
* is client-supplied; trusting it unconditionally would let an attacker mint a
|
|
13
|
+
* fresh rate-limit identity per request and remove the limiter entirely, which
|
|
14
|
+
* is strictly worse than the shared bucket. It is only meaningful when the
|
|
15
|
+
* operator states how many proxies actually sit in front of HQ.
|
|
16
|
+
*
|
|
17
|
+
* So resolution is opt-in and counted, following the well-understood
|
|
18
|
+
* `trust proxy: <n>` model:
|
|
19
|
+
*
|
|
20
|
+
* chain = [socket.remoteAddress, ...reverse(X-Forwarded-For)]
|
|
21
|
+
* client = chain[hops]
|
|
22
|
+
*
|
|
23
|
+
* `hops` is how many trusted proxies stand between the client and this server.
|
|
24
|
+
* The default, 0, ignores the header completely and returns the socket peer —
|
|
25
|
+
* byte-for-byte the previous behaviour.
|
|
26
|
+
*
|
|
27
|
+
* Only the rightmost `hops` entries are ever consulted, because those are the
|
|
28
|
+
* ones trusted infrastructure appended. Everything further left is whatever
|
|
29
|
+
* the client claimed and is never read. If the chain is shorter than `hops`
|
|
30
|
+
* (a client that stripped or under-filled the header), resolution falls back
|
|
31
|
+
* to the socket address rather than reaching into attacker-controlled entries:
|
|
32
|
+
* such requests then share the proxy's bucket, which throttles them rather
|
|
33
|
+
* than freeing them.
|
|
34
|
+
*
|
|
35
|
+
* @module hq-server/client-address
|
|
36
|
+
*/
|
|
37
|
+
import type * as http from 'node:http';
|
|
38
|
+
/**
|
|
39
|
+
* Parse one `X-Forwarded-For` element into a bare address, or `undefined` when
|
|
40
|
+
* it is not one. Handles `1.2.3.4`, `1.2.3.4:5678`, `[::1]`, and `[::1]:5678`.
|
|
41
|
+
* Anything that does not parse as an IP is dropped — a forwarded chain with a
|
|
42
|
+
* junk entry must not silently become a rate-limit identity of its own.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseForwardedEntry(entry: string): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the address a rate limiter should key on.
|
|
47
|
+
*
|
|
48
|
+
* @param req - the incoming request.
|
|
49
|
+
* @param trustedProxyHops - how many trusted reverse proxies sit in front of
|
|
50
|
+
* this server. `0` (the default) ignores `X-Forwarded-For` entirely.
|
|
51
|
+
* @returns a bare IP address, or `'unknown'` when the socket has none (a
|
|
52
|
+
* destroyed socket). `'unknown'` is a single shared bucket by design — it
|
|
53
|
+
* should throttle, not exempt.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveClientAddress(req: http.IncomingMessage, trustedProxyHops?: number): string;
|
|
56
|
+
//# sourceMappingURL=client-address.d.ts.map
|
|
@@ -38,27 +38,47 @@ export declare class LoginAttemptStore {
|
|
|
38
38
|
set(key: string, entry: LoginAttemptEntry): void;
|
|
39
39
|
delete(key: string): void;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* Rate-limit key for a *candidate password*, independent of source IP.
|
|
42
|
+
*
|
|
43
|
+
* WS-104: this used to be `cred:${ip}:${hash}`. Embedding the IP made the
|
|
44
|
+
* documented purpose — "limit how often the SAME password can be tried from
|
|
45
|
+
* different IPs" — structurally impossible, because a rotating-IP attacker
|
|
46
|
+
* got a fresh counter with every hop, exactly the case the key exists for.
|
|
47
|
+
* The IP-scoped counter is already kept separately under the bare `ip` key.
|
|
48
|
+
*
|
|
49
|
+
* Backoff caps at 16s (see {@link recordFailure}), so a global per-password
|
|
50
|
+
* counter throttles guessing without becoming a lockout an attacker could
|
|
51
|
+
* aim at the operator.
|
|
44
52
|
*/
|
|
45
|
-
static credentialKey(
|
|
53
|
+
static credentialKey(password: string): string;
|
|
46
54
|
/**
|
|
47
|
-
* Check
|
|
55
|
+
* Check the IP key and — when a candidate password is supplied — the
|
|
56
|
+
* credential key too, returning the stricter (later `blockedUntil`).
|
|
57
|
+
*
|
|
58
|
+
* WS-104: `handleApiLogin` called this with `''` before parsing the request
|
|
59
|
+
* body ("we don't have the password yet") and never called it again, so the
|
|
60
|
+
* credential entries {@link recordFailure} wrote were only ever *written*.
|
|
61
|
+
* Callers now re-check once the password is known; passing no password (or
|
|
62
|
+
* an empty one) means "IP scope only".
|
|
48
63
|
*/
|
|
49
|
-
checkBlocked(ip: string, password
|
|
64
|
+
checkBlocked(ip: string, password?: string): {
|
|
50
65
|
blocked: boolean;
|
|
51
66
|
retryAfter: number;
|
|
52
67
|
};
|
|
53
68
|
/**
|
|
54
|
-
* Record a failed attempt on
|
|
69
|
+
* Record a failed attempt on the IP key, and on the credential key when a
|
|
70
|
+
* candidate password is supplied. Omit the password for flows that have no
|
|
71
|
+
* one (2FA verification, TOTP disable) so they do not accumulate a
|
|
72
|
+
* meaningless `cred:sha256("")` counter shared by every such flow.
|
|
73
|
+
*
|
|
55
74
|
* Returns the updated IP entry (for the response Retry-After header).
|
|
56
75
|
*/
|
|
57
|
-
recordFailure(ip: string, password
|
|
76
|
+
recordFailure(ip: string, password?: string, maxBackoffMs?: number): LoginAttemptEntry;
|
|
58
77
|
/**
|
|
59
|
-
* Clear
|
|
78
|
+
* Clear the IP entry — and the credential entry when a password is given —
|
|
79
|
+
* on successful login.
|
|
60
80
|
*/
|
|
61
|
-
clearOnSuccess(ip: string, password
|
|
81
|
+
clearOnSuccess(ip: string, password?: string): void;
|
|
62
82
|
/** Number of tracked entries (for diagnostics). */
|
|
63
83
|
get size(): number;
|
|
64
84
|
/**
|
|
@@ -1,27 +1,59 @@
|
|
|
1
1
|
import type * as http from 'node:http';
|
|
2
|
-
import { type
|
|
2
|
+
import { type HqAuthFile } from '@wrongstack/core/hq';
|
|
3
|
+
import { type HqBrowserAuthResult } from '../auth.js';
|
|
3
4
|
import type { LoginAttemptStore } from '../login-attempt-store.js';
|
|
4
5
|
import type { HqRouterMutableAuth, HqSessionEntry } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Capability required to operate the account-security surface: enrolling or
|
|
8
|
+
* removing 2FA, changing the password without knowing it, listing and revoking
|
|
9
|
+
* browser sessions, and reading the auth audit log.
|
|
10
|
+
*
|
|
11
|
+
* WS-102: these routes used to gate on `if (!auth)` alone, so ANY authenticated
|
|
12
|
+
* principal could reach them — including the least-privileged token HQ mints on
|
|
13
|
+
* first run (`capabilities: ['control.enqueue']`). That token could
|
|
14
|
+
* `POST /api/auth/totp/setup` → `/enable`, receive the recovery codes, and take
|
|
15
|
+
* the `sessions.clear()` that enrollment performs. The operator was then held
|
|
16
|
+
* at `totpRequired: true` on every password login with no authenticator and no
|
|
17
|
+
* recovery codes, and `/totp/disable` demands auth they could no longer obtain:
|
|
18
|
+
* a one-request, unrecoverable lockout from a token scoped to enqueueing
|
|
19
|
+
* commands. Editing `auth.json` by hand was the only way back.
|
|
20
|
+
*/
|
|
21
|
+
export declare const HQ_AUTH_ADMIN_CAPABILITY = "auth.admin";
|
|
22
|
+
/**
|
|
23
|
+
* True when `auth` may operate the account-security surface.
|
|
24
|
+
*
|
|
25
|
+
* - Password-origin cookie sessions (`kind: 'cookie'` with no `tokenId`) are
|
|
26
|
+
* the operator themselves — always allowed.
|
|
27
|
+
* - A token with no `capabilities` field is unrestricted by the documented
|
|
28
|
+
* contract in `HqToken.capabilities`, so it is allowed.
|
|
29
|
+
* - A capability-scoped token must list {@link HQ_AUTH_ADMIN_CAPABILITY}.
|
|
30
|
+
*
|
|
31
|
+
* Token-origin cookie sessions resolve their capabilities from the LIVE token
|
|
32
|
+
* record on every request (see `authenticateBrowserRequest`), so upgrading a
|
|
33
|
+
* token to a cookie never widens what it can do here.
|
|
34
|
+
*/
|
|
35
|
+
export declare function callerCanAdministerAuth(auth: HqBrowserAuthResult): boolean;
|
|
5
36
|
export declare function handleApiAuthStatus(req: http.IncomingMessage, res: http.ServerResponse, url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, requireBrowserAuth: boolean | undefined, trustedPublicOrigins: Set<string>, secureCookies: boolean | undefined): Promise<void>;
|
|
6
|
-
export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined): Promise<void>;
|
|
37
|
+
export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number): Promise<void>;
|
|
7
38
|
export declare function handleApiLogout(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, secureCookies: boolean | undefined): Promise<void>;
|
|
8
|
-
export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined): Promise<void>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Applies a freshly-persisted `auth.json` to the live server state.
|
|
42
|
+
*
|
|
43
|
+
* WS-101: this used to be a second, hand-rolled copy of the projection in
|
|
44
|
+
* `hq-server/auth-state.ts`. The two drifted in exactly the ways duplicated
|
|
45
|
+
* security code drifts: this copy skipped the expired-token filter on the
|
|
46
|
+
* CLIENT scope (re-admitting expired `/ws/client` tokens, where the upgrade
|
|
47
|
+
* gate does no expiry check of its own), never refreshed the raw token lists
|
|
48
|
+
* behind `tokenStats()`, and — worst — never re-ran the WS-010 exposure
|
|
49
|
+
* assessment, so `DELETE /api/auth/password` on a non-loopback bind dropped
|
|
50
|
+
* every gate into open mode until the `fs.watch` debounce happened to fire.
|
|
51
|
+
*
|
|
52
|
+
* It is now a function type supplied by the server, bound to
|
|
53
|
+
* `HqAuthState.apply`, so the mutation routes and the reload watcher share one
|
|
54
|
+
* projection and one floor evaluation.
|
|
55
|
+
*/
|
|
56
|
+
export type ApplyHqAuthFile = (next: HqAuthFile) => void;
|
|
25
57
|
export declare function handleApiBootstrap(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, secureCookies: boolean | undefined, bootstrapStore: import('@wrongstack/core/hq').HqBootstrapCodeStore): Promise<void>;
|
|
26
58
|
/**
|
|
27
59
|
* WS-065 — exchange a live browser token for an HttpOnly session cookie.
|
|
@@ -50,7 +82,7 @@ export declare function handleApiTokenUpgrade(req: http.IncomingMessage, res: ht
|
|
|
50
82
|
* session is upgraded to a full session (pending2fa cleared). Recovery codes
|
|
51
83
|
* are single-use: the matched hash is removed from `auth.json` on success.
|
|
52
84
|
*/
|
|
53
|
-
export declare function handleApiLoginVerify(req: http.IncomingMessage, res: http.ServerResponse, _url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, dataDir: string, secureCookies: boolean | undefined): Promise<void>;
|
|
85
|
+
export declare function handleApiLoginVerify(req: http.IncomingMessage, res: http.ServerResponse, _url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, dataDir: string, secureCookies: boolean | undefined, applyAuthFile: ApplyHqAuthFile, trustedProxyHops: number): Promise<void>;
|
|
54
86
|
/**
|
|
55
87
|
* POST `/api/auth/totp/setup` — generate a new TOTP secret (not yet active).
|
|
56
88
|
* Returns the base32 secret + otpauth URI for QR display. The secret is
|
|
@@ -60,24 +92,32 @@ export declare function handleApiLoginVerify(req: http.IncomingMessage, res: htt
|
|
|
60
92
|
*
|
|
61
93
|
* Requires existing auth (cookie or token) — only the operator can set up 2FA.
|
|
62
94
|
*/
|
|
63
|
-
export declare function handleApiTotpSetup(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string): Promise<void>;
|
|
95
|
+
export declare function handleApiTotpSetup(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, applyAuthFile: ApplyHqAuthFile): Promise<void>;
|
|
64
96
|
/**
|
|
65
97
|
* POST `/api/auth/totp/enable` — confirm 2FA enrollment by providing a valid
|
|
66
98
|
* TOTP code. Promotes `totpPendingSecret` → `totpSecret` (active), generates
|
|
67
99
|
* and returns recovery codes (shown once). After this, 2FA is required for
|
|
68
100
|
* password login.
|
|
69
101
|
*/
|
|
70
|
-
export declare function handleApiTotpEnable(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string): Promise<void>;
|
|
102
|
+
export declare function handleApiTotpEnable(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, applyAuthFile: ApplyHqAuthFile): Promise<void>;
|
|
71
103
|
/**
|
|
72
104
|
* POST `/api/auth/totp/disable` — remove TOTP 2FA entirely. Requires the
|
|
73
105
|
* current password (or a valid TOTP code) as confirmation.
|
|
74
106
|
*/
|
|
75
|
-
export declare function handleApiTotpDisable(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, dataDir: string): Promise<void>;
|
|
107
|
+
export declare function handleApiTotpDisable(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, dataDir: string, applyAuthFile: ApplyHqAuthFile, trustedProxyHops: number): Promise<void>;
|
|
76
108
|
/**
|
|
77
109
|
* GET `/api/auth/sessions` — list all active browser sessions for the
|
|
78
|
-
* session-management UI.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
110
|
+
* session-management UI.
|
|
111
|
+
*
|
|
112
|
+
* The full session ID is returned because the revoke endpoint below is keyed
|
|
113
|
+
* on it. That is safe on its own — the cookie value is `<id>.<HMAC>` and the
|
|
114
|
+
* HMAC needs `cookieSecret` — but it does hand every reader a working revoke
|
|
115
|
+
* handle for every other session, so the route is gated on `auth.admin`
|
|
116
|
+
* (WS-102) rather than on "is authenticated" as it was.
|
|
117
|
+
*
|
|
118
|
+
* The previous docstring claimed the full ID was never returned while the code
|
|
119
|
+
* returned it; the code was right about what the UI needs, the docstring was
|
|
120
|
+
* right about it deserving a gate.
|
|
81
121
|
*/
|
|
82
122
|
export declare function handleApiAuthSessions(_req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>): void;
|
|
83
123
|
/**
|
|
@@ -16,6 +16,7 @@ import type { createMailboxHttpRouter } from '@wrongstack/core/coordination';
|
|
|
16
16
|
import { type Mailbox, type MailboxHttpAccessDecision, type MailboxHttpRateLimiter } from '@wrongstack/core/coordination';
|
|
17
17
|
import * as HqServerAuth from './auth.js';
|
|
18
18
|
import * as HqServerUtils from './utils.js';
|
|
19
|
+
import { type ApplyHqAuthFile } from './routes/auth-handlers.js';
|
|
19
20
|
export declare const setHqSecurityHeaders: typeof HqServerAuth.setHqSecurityHeaders;
|
|
20
21
|
export declare const hasTrustedBrowserOrigin: typeof HqServerAuth.hasTrustedBrowserOrigin;
|
|
21
22
|
export declare const authenticateBrowserRequest: typeof HqServerAuth.authenticateBrowserRequest;
|
|
@@ -77,6 +78,18 @@ export interface HqRouterDeps {
|
|
|
77
78
|
getTokenStats?: (() => HqSnapshot['totals']['tokenStats'] | undefined) | undefined;
|
|
78
79
|
/** One-time bootstrap code store for token-to-cookie exchange. */
|
|
79
80
|
bootstrapStore?: import('@wrongstack/core/hq').HqBootstrapCodeStore | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Applies a freshly-persisted `auth.json` to live server state. Bound to
|
|
83
|
+
* `HqAuthState.apply` so the in-process mutation routes share one projection
|
|
84
|
+
* — and one WS-010 exposure re-assessment — with the reload watcher.
|
|
85
|
+
*/
|
|
86
|
+
applyAuthFile: ApplyHqAuthFile;
|
|
87
|
+
/**
|
|
88
|
+
* How many trusted reverse proxies sit in front of this server. Only used to
|
|
89
|
+
* resolve the address the login backoff keys on — see `client-address.ts`.
|
|
90
|
+
* Defaults to 0, which ignores `X-Forwarded-For` entirely.
|
|
91
|
+
*/
|
|
92
|
+
trustedProxyHops?: number | undefined;
|
|
80
93
|
}
|
|
81
94
|
/**
|
|
82
95
|
* Create the HTTP request listener that handles all `/api/*` routes as well
|
|
@@ -83,6 +83,16 @@ export interface HqRouterMutableAuth {
|
|
|
83
83
|
totpPendingSecret?: string | undefined;
|
|
84
84
|
/** SHA-256 hashes of single-use recovery codes (live-reloaded from auth.json). */
|
|
85
85
|
totpRecoveryCodes?: string[] | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Highest TOTP time-step counter already spent on a successful login
|
|
88
|
+
* (RFC 6238 §5.2 single-use). The ±1-step validation window keeps a code
|
|
89
|
+
* arithmetically valid for ~90s, so without this a code captured in transit
|
|
90
|
+
* — shoulder-surfed, read off a notification, replayed from a proxy log —
|
|
91
|
+
* authenticates a second time. Deliberately process-local and NOT persisted
|
|
92
|
+
* to auth.json: a code cannot outlive its own window, so a restart losing
|
|
93
|
+
* the marker costs at most the tail of one step.
|
|
94
|
+
*/
|
|
95
|
+
totpLastUsedCounter?: number | undefined;
|
|
86
96
|
alertRules: HqAlertRuleConfig | undefined;
|
|
87
97
|
/**
|
|
88
98
|
* WS-010: set when the live bind would become unauthenticated and
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
readLocalSubagentTranscript,
|
|
16
16
|
sanitizeApiError,
|
|
17
17
|
startHqServer
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-GYDQABMA.js";
|
|
19
19
|
import "./chunk-Q5GTM25S.js";
|
|
20
20
|
import "./chunk-7OCVIDC7.js";
|
|
21
21
|
export {
|
|
@@ -36,4 +36,4 @@ export {
|
|
|
36
36
|
sanitizeApiError,
|
|
37
37
|
startHqServer
|
|
38
38
|
};
|
|
39
|
-
//# sourceMappingURL=hq-server-
|
|
39
|
+
//# sourceMappingURL=hq-server-DQHLNHDL.js.map
|