@wrongstack/cli 1.0.10 → 1.0.12
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-CXLANMEZ.js → acp-IJ55NBMJ.js} +5 -3
- package/dist/acp-mcp-servers.d.ts +2 -0
- package/dist/arg-parser.d.ts +6 -0
- package/dist/{auth-3K6EN5JP.js → auth-YXK3EDUL.js} +9 -3
- package/dist/{chunk-EK2P53GL.js → chunk-C3Z4N6A6.js} +23 -3
- package/dist/{chunk-7K3IJGWC.js → chunk-OJR7K3OI.js} +8 -8
- package/dist/{chunk-SXJTVNWF.js → chunk-OWNC7LMD.js} +2 -2
- package/dist/{chunk-FSZ5QU3M.js → chunk-RDHFXOJM.js} +153 -69
- package/dist/{chunk-5VPDKQDH.js → chunk-TNZBQG45.js} +3 -3
- package/dist/{chunk-7FPN3QTH.js → chunk-YRC3I3LE.js} +172 -15
- package/dist/{cli-context-6GMXUOXD.js → cli-context-5IF7ZYEY.js} +7 -7
- package/dist/{cli-main-BU5DEHDO.js → cli-main-6LMQSS66.js} +84 -42
- package/dist/{diag-doctor-PCER6KHH.js → diag-doctor-ANPRUYFM.js} +13 -3
- package/dist/{execution-4RXQLDAF.js → execution-EXDRBS4F.js} +2 -2
- package/dist/{hq-DQTUE7RC.js → hq-NO2NWGU4.js} +153 -5
- package/dist/hq-server/auth-state.d.ts +11 -0
- package/dist/hq-server/mailbox-gateway-health.d.ts +67 -0
- package/dist/hq-server/mailbox-gateway-manager.d.ts +38 -0
- package/dist/hq-server/routes/system-handlers.d.ts +27 -0
- package/dist/hq-server/routes.d.ts +11 -0
- package/dist/hq-server/snapshot.d.ts +14 -1
- package/dist/hq-server/upgrade-handler.d.ts +7 -0
- package/dist/{hq-server-JJFGFWI5.js → hq-server-KE5YDNGG.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{mcp-FX7TKPTV.js → mcp-DC5WSBOO.js} +28 -10
- package/dist/mcp-serve.d.ts +9 -0
- package/dist/services/mcp-management.d.ts +0 -6
- package/dist/{short-circuit-flags-LHUNLCZY.js → short-circuit-flags-72E77TJI.js} +3 -3
- package/dist/{subcommands-UXWY3OPC.js → subcommands-EJI7DBQS.js} +2 -2
- package/dist/wiring/codebase-index.d.ts +8 -0
- package/package.json +28 -28
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
34
34
|
import * as fs4 from "node:fs/promises";
|
|
35
35
|
import * as http from "node:http";
|
|
36
|
-
import * as
|
|
36
|
+
import * as path7 from "node:path";
|
|
37
37
|
import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
|
|
38
38
|
import {
|
|
39
39
|
assessHqExposure as assessHqExposure2,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
isTokenExpired as isTokenExpired4,
|
|
46
46
|
mintHqCookieSecret as mintHqCookieSecret2,
|
|
47
47
|
mutateHqAuthFile as mutateHqAuthFile4,
|
|
48
|
+
shouldCloseBrowserSocket,
|
|
48
49
|
toAlertMessage,
|
|
49
50
|
watchHqAuthFile
|
|
50
51
|
} from "@wrongstack/core/hq";
|
|
@@ -330,6 +331,7 @@ function authenticateBrowserRequest(req, url, mutableAuth, sessions) {
|
|
|
330
331
|
// src/hq-server/auth-state.ts
|
|
331
332
|
import {
|
|
332
333
|
DEFAULT_HQ_REDACTION_POLICY,
|
|
334
|
+
findRevokedTokenKeys,
|
|
333
335
|
hqAuthContentHash,
|
|
334
336
|
hqTokenKey,
|
|
335
337
|
isTokenExpired as isTokenExpired2,
|
|
@@ -395,10 +397,16 @@ function createHqAuthState(authFile, dataDir, opts = {}) {
|
|
|
395
397
|
const newClient = next.clientTokens ?? [];
|
|
396
398
|
auditPrunedTokens("browser", rawBrowserTokens, newBrowser, next, dataDir);
|
|
397
399
|
auditPrunedTokens("client", rawClientTokens, newClient, next, dataDir);
|
|
400
|
+
const revokedKeys = findRevokedTokenKeys(
|
|
401
|
+
mutableAuth.browserTokens,
|
|
402
|
+
new Set(liveTokens(newBrowser).map(hqTokenKey))
|
|
403
|
+
);
|
|
404
|
+
const revokedIds = revokedKeys.length === 0 ? [] : revokedKeys.map((key) => mutableAuth.browserTokenObjs.get(key)?.id).filter((id) => id !== void 0 && id.length > 0);
|
|
398
405
|
rawBrowserTokens = newBrowser;
|
|
399
406
|
rawClientTokens = newClient;
|
|
400
407
|
projectAuthFile(mutableAuth, next);
|
|
401
408
|
opts.onApplied?.(mutableAuth);
|
|
409
|
+
if (revokedKeys.length > 0) opts.onTokensRevoked?.(revokedKeys, revokedIds);
|
|
402
410
|
}
|
|
403
411
|
};
|
|
404
412
|
}
|
|
@@ -627,6 +635,7 @@ import {
|
|
|
627
635
|
MailboxEventEmitter,
|
|
628
636
|
MailboxHttpRateLimiter
|
|
629
637
|
} from "@wrongstack/core/coordination";
|
|
638
|
+
import * as path5 from "node:path";
|
|
630
639
|
|
|
631
640
|
// src/hq-static-serve.ts
|
|
632
641
|
import * as fs2 from "node:fs";
|
|
@@ -1667,6 +1676,7 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
1667
1676
|
import { tokenHasCapability, validateHqCommand } from "@wrongstack/core/hq";
|
|
1668
1677
|
|
|
1669
1678
|
// src/hq-server/snapshot.ts
|
|
1679
|
+
import { summarizeCommandLatency } from "@wrongstack/core/hq";
|
|
1670
1680
|
import { WebSocket } from "ws";
|
|
1671
1681
|
var HQ_SNAPSHOT_BROADCAST_DEBOUNCE_MS = 100;
|
|
1672
1682
|
var HQ_SNAPSHOT_PERSIST_MIN_INTERVAL_MS = 3e3;
|
|
@@ -1942,6 +1952,7 @@ function buildSnapshot(clients, options) {
|
|
|
1942
1952
|
}
|
|
1943
1953
|
}
|
|
1944
1954
|
}
|
|
1955
|
+
const commandLatency = summarizeCommandLatency(options?.commandAudit ?? []);
|
|
1945
1956
|
return {
|
|
1946
1957
|
generatedAt: now,
|
|
1947
1958
|
clients: clientRecords,
|
|
@@ -1952,6 +1963,7 @@ function buildSnapshot(clients, options) {
|
|
|
1952
1963
|
machines,
|
|
1953
1964
|
liveSessions,
|
|
1954
1965
|
mcpServers,
|
|
1966
|
+
...commandLatency.sampleCount > 0 ? { commandLatency } : {},
|
|
1955
1967
|
totals: {
|
|
1956
1968
|
activeProjects: projects.length,
|
|
1957
1969
|
activeClients: new Set(clientRecords.map(processKeyOf)).size,
|
|
@@ -1966,7 +1978,7 @@ function buildSnapshot(clients, options) {
|
|
|
1966
1978
|
}
|
|
1967
1979
|
};
|
|
1968
1980
|
}
|
|
1969
|
-
function createSnapshotBroadcaster(clients, browsers, persistence) {
|
|
1981
|
+
function createSnapshotBroadcaster(clients, browsers, persistence, options) {
|
|
1970
1982
|
let cached = "";
|
|
1971
1983
|
let dirty = true;
|
|
1972
1984
|
let timer = null;
|
|
@@ -1981,7 +1993,8 @@ function createSnapshotBroadcaster(clients, browsers, persistence) {
|
|
|
1981
1993
|
};
|
|
1982
1994
|
const serialize = () => {
|
|
1983
1995
|
if (!dirty && cached.length > 0) return cached;
|
|
1984
|
-
const
|
|
1996
|
+
const commandAudit = options?.commandAudit?.();
|
|
1997
|
+
const snapshot = buildSnapshot(clients, commandAudit !== void 0 ? { commandAudit } : {});
|
|
1985
1998
|
const msg = { type: "hq.snapshot", snapshot };
|
|
1986
1999
|
cached = JSON.stringify(msg);
|
|
1987
2000
|
dirty = false;
|
|
@@ -2594,9 +2607,31 @@ async function handleApiEvents(req, res, persistence) {
|
|
|
2594
2607
|
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
|
|
2595
2608
|
const limit = Math.min(5e3, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 200));
|
|
2596
2609
|
const typeFilter = url.searchParams.get("type") ?? void 0;
|
|
2610
|
+
const clientIdFilter = url.searchParams.get("clientId") ?? void 0;
|
|
2611
|
+
const machineIdFilter = url.searchParams.get("machineId") ?? void 0;
|
|
2612
|
+
const sinceMsRaw = url.searchParams.get("since");
|
|
2613
|
+
const untilMsRaw = url.searchParams.get("until");
|
|
2614
|
+
const sinceMs = sinceMsRaw !== null && sinceMsRaw.length > 0 && Number.isFinite(Number(sinceMsRaw)) ? Number(sinceMsRaw) : void 0;
|
|
2615
|
+
const untilMs = untilMsRaw !== null && untilMsRaw.length > 0 && Number.isFinite(Number(untilMsRaw)) ? Number(untilMsRaw) : void 0;
|
|
2597
2616
|
const events = await persistence.eventLog.recent(limit, typeFilter);
|
|
2617
|
+
const filtered = events.filter((event) => {
|
|
2618
|
+
const payload = event.payload;
|
|
2619
|
+
if (clientIdFilter !== void 0 && payload?.clientId !== clientIdFilter) {
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
if (machineIdFilter !== void 0 && payload?.machineId !== machineIdFilter) {
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
if (sinceMs !== void 0 || untilMs !== void 0) {
|
|
2626
|
+
const ts = event.timestamp !== void 0 ? Date.parse(event.timestamp) : Number.NaN;
|
|
2627
|
+
if (!Number.isFinite(ts)) return true;
|
|
2628
|
+
if (sinceMs !== void 0 && ts < sinceMs) return false;
|
|
2629
|
+
if (untilMs !== void 0 && ts > untilMs) return false;
|
|
2630
|
+
}
|
|
2631
|
+
return true;
|
|
2632
|
+
});
|
|
2598
2633
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2599
|
-
res.end(JSON.stringify({ events, total:
|
|
2634
|
+
res.end(JSON.stringify({ events: filtered, total: filtered.length }));
|
|
2600
2635
|
}
|
|
2601
2636
|
async function handleApiTrendsCost(req, res, persistence) {
|
|
2602
2637
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -2802,6 +2837,20 @@ async function handleApiSystemHealth(res, clients, persistence, eventLog) {
|
|
|
2802
2837
|
const connectedClients = [...clients.values()].filter(
|
|
2803
2838
|
(c) => Date.now() - new Date(c.lastSeenAt).getTime() < 6e4
|
|
2804
2839
|
).length;
|
|
2840
|
+
const clientHealth = [...clients.values()].map((c) => {
|
|
2841
|
+
const lastSeenMs = new Date(c.lastSeenAt).getTime();
|
|
2842
|
+
const ageMs = Number.isFinite(lastSeenMs) ? now - lastSeenMs : Number.POSITIVE_INFINITY;
|
|
2843
|
+
return {
|
|
2844
|
+
clientId: c.clientId,
|
|
2845
|
+
hostname: c.hostname ?? null,
|
|
2846
|
+
machineId: c.machineId ?? null,
|
|
2847
|
+
lastSeenAt: c.lastSeenAt,
|
|
2848
|
+
ageMs,
|
|
2849
|
+
// Bucket the staleness the same way the cockpit tile renders it: fresh
|
|
2850
|
+
// (<5s), quiet (<60s), stale (>60s).
|
|
2851
|
+
staleness: ageMs < 5e3 ? "fresh" : ageMs < 6e4 ? "quiet" : "stale"
|
|
2852
|
+
};
|
|
2853
|
+
});
|
|
2805
2854
|
res.writeHead(200, {
|
|
2806
2855
|
"Content-Type": "application/json",
|
|
2807
2856
|
"Cache-Control": "no-store"
|
|
@@ -2822,7 +2871,9 @@ async function handleApiSystemHealth(res, clients, persistence, eventLog) {
|
|
|
2822
2871
|
total: clientCount,
|
|
2823
2872
|
active: connectedClients,
|
|
2824
2873
|
stale: clientCount - connectedClients
|
|
2825
|
-
}
|
|
2874
|
+
},
|
|
2875
|
+
// New: per-client publisher health (W1 #18).
|
|
2876
|
+
publisherHealth: clientHealth
|
|
2826
2877
|
})
|
|
2827
2878
|
);
|
|
2828
2879
|
}
|
|
@@ -2845,6 +2896,14 @@ async function handleApiAlerts(req, res, alertEngine) {
|
|
|
2845
2896
|
})
|
|
2846
2897
|
);
|
|
2847
2898
|
}
|
|
2899
|
+
function handleApiMailboxHealth(res, mailboxManager) {
|
|
2900
|
+
const health = mailboxManager.getHealth();
|
|
2901
|
+
res.writeHead(200, {
|
|
2902
|
+
"Content-Type": "application/json",
|
|
2903
|
+
"Cache-Control": "no-store"
|
|
2904
|
+
});
|
|
2905
|
+
res.end(JSON.stringify(health));
|
|
2906
|
+
}
|
|
2848
2907
|
|
|
2849
2908
|
// src/hq-server/routes.ts
|
|
2850
2909
|
var hasTrustedBrowserOrigin2 = hasTrustedBrowserOrigin;
|
|
@@ -2883,6 +2942,7 @@ function createHqRouter(deps) {
|
|
|
2883
2942
|
secureCookies,
|
|
2884
2943
|
authorizeMailboxGateway,
|
|
2885
2944
|
getMailboxGateway,
|
|
2945
|
+
mailboxManager,
|
|
2886
2946
|
getTokenStats,
|
|
2887
2947
|
trustBoundary,
|
|
2888
2948
|
bootstrapStore,
|
|
@@ -3095,6 +3155,10 @@ function createHqRouter(deps) {
|
|
|
3095
3155
|
await handleApiSystemHealth(res, clients, persistence, eventLog);
|
|
3096
3156
|
return;
|
|
3097
3157
|
}
|
|
3158
|
+
if (url.pathname === "/api/health/mailbox" && req.method === "GET") {
|
|
3159
|
+
handleApiMailboxHealth(res, mailboxManager);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3098
3162
|
const mailboxGatewayMatch = url.pathname.match(
|
|
3099
3163
|
/^\/api\/projects\/([^/]+)\/mailbox(?:\/(.*))?$/
|
|
3100
3164
|
);
|
|
@@ -3319,6 +3383,69 @@ var MailboxGatewayManager = class {
|
|
|
3319
3383
|
void gateway.mailbox.close().catch(() => void 0);
|
|
3320
3384
|
}
|
|
3321
3385
|
}
|
|
3386
|
+
/**
|
|
3387
|
+
* W2 #14 (RFC hq-improvements-2026-09.md): health snapshot for the cockpit
|
|
3388
|
+
* "Mailbox gateway" card.
|
|
3389
|
+
*
|
|
3390
|
+
* Per-gateway entry surfaces the three diagnostic facts an operator
|
|
3391
|
+
* needs:
|
|
3392
|
+
*
|
|
3393
|
+
* - `projectId`: the **basename** of the project directory, matching
|
|
3394
|
+
* the credential contract used by `mailbox-serve.ts:192` (where
|
|
3395
|
+
* `path.basename(projectDir)` is the `projectId` key for mailbox
|
|
3396
|
+
* data). HQ binds the gateway with the full filesystem path
|
|
3397
|
+
* (`resolveHqProjectRoot(...)` from `mailbox-handlers.ts:90`), so
|
|
3398
|
+
* reporting the basename here means the dashboard's projectId
|
|
3399
|
+
* matches what other mailbox surfaces see.
|
|
3400
|
+
* - `projectRoot`: the full filesystem path the manager actually
|
|
3401
|
+
* binds against. Surfaced separately so operators can debug
|
|
3402
|
+
* path-mismatch issues without grepping the source.
|
|
3403
|
+
* - `hasActiveStreams`: whether the gateway has any open HTTP streams
|
|
3404
|
+
* right now. A gateway with `false` is a candidate for the next
|
|
3405
|
+
* idle eviction; one with `true` is being watched.
|
|
3406
|
+
* - `lastUsedAt`: epoch ms of the most recent `getMailboxGateway()`
|
|
3407
|
+
* call (or the last `authorizeMailboxGateway` hit that resolved to
|
|
3408
|
+
* this gateway). Surfaced so the dashboard can render the same
|
|
3409
|
+
* "fresh / quiet / stale" staleness buckets the publisher health
|
|
3410
|
+
* tile uses.
|
|
3411
|
+
* - `idleForMs`: convenience — `Date.now() - lastUsedAt`. `Infinity`
|
|
3412
|
+
* when the gateway was never used (shouldn't happen, since
|
|
3413
|
+
* `getMailboxGateway` always stamps `mailboxGatewayLastUsed`).
|
|
3414
|
+
*
|
|
3415
|
+
* The aggregate `actor` field is **explicitly absent** on the HQ mount:
|
|
3416
|
+
* `authorizeMailboxGateway` (L57-96) never attaches a `MailboxActorContext`
|
|
3417
|
+
* to its decision, because HQ is a read-mostly operator dashboard, not
|
|
3418
|
+
* a producer-side mailbox client. We surface this truth with a stable
|
|
3419
|
+
* `actorAttached: false` so the dashboard can render "no actor
|
|
3420
|
+
* context" honestly rather than papering over it.
|
|
3421
|
+
*/
|
|
3422
|
+
getHealth() {
|
|
3423
|
+
const now = Date.now();
|
|
3424
|
+
const gateways = [];
|
|
3425
|
+
for (const [projectDir, gateway] of this.mailboxGateways) {
|
|
3426
|
+
const lastUsed = this.mailboxGatewayLastUsed.get(projectDir);
|
|
3427
|
+
gateways.push({
|
|
3428
|
+
// projectId = basename(projectDir) — matches the credential contract.
|
|
3429
|
+
projectId: path5.basename(projectDir),
|
|
3430
|
+
projectRoot: projectDir,
|
|
3431
|
+
hasActiveStreams: gateway.router.hasActiveStreams(),
|
|
3432
|
+
lastUsedAt: typeof lastUsed === "number" ? lastUsed : null,
|
|
3433
|
+
idleForMs: typeof lastUsed === "number" ? Math.max(0, now - lastUsed) : null
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
gateways.sort((a, b) => a.projectId.localeCompare(b.projectId));
|
|
3437
|
+
return {
|
|
3438
|
+
gatewayCount: gateways.length,
|
|
3439
|
+
rateLimiterConfigured: true,
|
|
3440
|
+
// HQ's gateway manager never attaches a MailboxActorContext (see
|
|
3441
|
+
// authorizeMailboxGateway above). The dashboard renders this as
|
|
3442
|
+
// "no actor context" — that is the truthful HQ state, not a bug.
|
|
3443
|
+
actorAttached: false,
|
|
3444
|
+
gateways,
|
|
3445
|
+
sweepIntervalMs: this.MAILBOX_GATEWAY_SWEEP_INTERVAL_MS,
|
|
3446
|
+
idleTtlMs: this.MAILBOX_GATEWAY_IDLE_TTL_MS
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3322
3449
|
close() {
|
|
3323
3450
|
clearInterval(this.rateLimitCleanupTimer);
|
|
3324
3451
|
clearInterval(this.sweepTimer);
|
|
@@ -3389,7 +3516,7 @@ async function prepareHqServerStart(options, defaults) {
|
|
|
3389
3516
|
|
|
3390
3517
|
// src/hq-server/startup.ts
|
|
3391
3518
|
import * as fs3 from "node:fs/promises";
|
|
3392
|
-
import * as
|
|
3519
|
+
import * as path6 from "node:path";
|
|
3393
3520
|
import { writeHqRuntimeFile } from "@wrongstack/core/hq";
|
|
3394
3521
|
var hqRuntimeMarkerPath2 = hqRuntimeMarkerPath;
|
|
3395
3522
|
async function writeHqRuntimeMarker(dataDir, url) {
|
|
@@ -3470,7 +3597,7 @@ HQ auth loaded from ${startup.dataDir}
|
|
|
3470
3597
|
if (startup.clientEnv.WRONGSTACK_HQ_TOKEN) {
|
|
3471
3598
|
write(` WRONGSTACK_HQ_TOKEN=${startup.clientEnv.WRONGSTACK_HQ_TOKEN}
|
|
3472
3599
|
`);
|
|
3473
|
-
write(`Credentials are stored in ${
|
|
3600
|
+
write(`Credentials are stored in ${path6.join(startup.dataDir, "auth.json")}
|
|
3474
3601
|
`);
|
|
3475
3602
|
}
|
|
3476
3603
|
writeHqLanEndpoints(write, handle, void 0);
|
|
@@ -4263,8 +4390,9 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
|
|
|
4263
4390
|
if (toSend.length > 0) {
|
|
4264
4391
|
const batch = JSON.stringify({ type: "hq.command_batch", commands: toSend });
|
|
4265
4392
|
if (ws.readyState === WebSocket2.OPEN) ws.send(batch);
|
|
4393
|
+
const dispatchedAt = Date.now();
|
|
4266
4394
|
for (const cmd of toSend) {
|
|
4267
|
-
auditLog?.update(cmd.commandId, { status: "delivered" });
|
|
4395
|
+
auditLog?.update(cmd.commandId, { status: "delivered", dispatchedAt });
|
|
4268
4396
|
const updated = auditLog?.get(cmd.commandId);
|
|
4269
4397
|
if (updated !== void 0) broadcastCommandStatus(updated, browsers);
|
|
4270
4398
|
}
|
|
@@ -4284,6 +4412,7 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
|
|
|
4284
4412
|
if (client) {
|
|
4285
4413
|
const updated = auditLog?.updateForClient(frame.commandId, client.clientId, {
|
|
4286
4414
|
status: "acked",
|
|
4415
|
+
acknowledgedAt: Date.now(),
|
|
4287
4416
|
ackStatus: frame.status,
|
|
4288
4417
|
...frame.message !== void 0 ? { ackMessage: frame.message } : {},
|
|
4289
4418
|
ackedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4401,10 +4530,12 @@ function handleHqUpgrade(req, socket, head, deps) {
|
|
|
4401
4530
|
session.lastSeenAt = now2;
|
|
4402
4531
|
if (session.pending2fa) return false;
|
|
4403
4532
|
if (session.kind === "token" && session.tokenId !== void 0) {
|
|
4404
|
-
|
|
4533
|
+
const stillAuthorized = [...deps.mutableAuth.browserTokenObjs.values()].some(
|
|
4405
4534
|
(obj) => obj.id === session.tokenId
|
|
4406
4535
|
);
|
|
4536
|
+
if (!stillAuthorized) return false;
|
|
4407
4537
|
}
|
|
4538
|
+
req.hqBrowserSessionId = sessionId;
|
|
4408
4539
|
return true;
|
|
4409
4540
|
})();
|
|
4410
4541
|
if (!tokenValid && !cookieValid) {
|
|
@@ -4426,6 +4557,11 @@ function handleHqUpgrade(req, socket, head, deps) {
|
|
|
4426
4557
|
}
|
|
4427
4558
|
function handleHqConnection(ws, req, pathname, deps) {
|
|
4428
4559
|
if (pathname === "/ws/browser") {
|
|
4560
|
+
const sessionId = req.hqBrowserSessionId;
|
|
4561
|
+
if (sessionId !== void 0) {
|
|
4562
|
+
deps.browserSocketSessions.set(ws, sessionId);
|
|
4563
|
+
ws.once("close", () => deps.browserSocketSessions.delete(ws));
|
|
4564
|
+
}
|
|
4429
4565
|
handleBrowser(ws, deps.snapshotBroadcaster, deps.browsers, deps.eventLog);
|
|
4430
4566
|
} else {
|
|
4431
4567
|
const token = new URL(req.url ?? "/", `http://${deps.host}:${deps.port}`).searchParams.get(
|
|
@@ -4494,8 +4630,10 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4494
4630
|
);
|
|
4495
4631
|
}
|
|
4496
4632
|
};
|
|
4633
|
+
let revokeBrowserSessions;
|
|
4497
4634
|
const authState = createHqAuthState(authFile, dataDir, {
|
|
4498
4635
|
onApplied: (live) => reassessExposureFloor(live),
|
|
4636
|
+
onTokensRevoked: (keys) => revokeBrowserSessions?.(keys),
|
|
4499
4637
|
requireBrowserAuth: options.requireBrowserAuth
|
|
4500
4638
|
});
|
|
4501
4639
|
const { mutableAuth } = authState;
|
|
@@ -4540,8 +4678,16 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4540
4678
|
let listeningPort = port;
|
|
4541
4679
|
const clients = /* @__PURE__ */ new Map();
|
|
4542
4680
|
const clientSocketTokens = /* @__PURE__ */ new Map();
|
|
4681
|
+
const browserSocketSessions = /* @__PURE__ */ new Map();
|
|
4543
4682
|
const browsers = /* @__PURE__ */ new Set();
|
|
4544
4683
|
const sessions = /* @__PURE__ */ new Map();
|
|
4684
|
+
revokeBrowserSessions = (keys) => {
|
|
4685
|
+
const frame = JSON.stringify({ type: "hq.auth_revoked", revokedTokenKeys: keys });
|
|
4686
|
+
for (const ws of browsers) {
|
|
4687
|
+
if (ws.readyState !== WebSocket3.OPEN) continue;
|
|
4688
|
+
ws.send(frame);
|
|
4689
|
+
}
|
|
4690
|
+
};
|
|
4545
4691
|
const eventLog = [];
|
|
4546
4692
|
const transcripts = /* @__PURE__ */ new Map();
|
|
4547
4693
|
const agentMessages = /* @__PURE__ */ new Map();
|
|
@@ -4581,12 +4727,12 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4581
4727
|
const persistence = createHqPersistence(dataDir);
|
|
4582
4728
|
const auditLog = new HqCommandAuditLog(1e3, (entry) => persistence.commandLog.append(entry));
|
|
4583
4729
|
void (async () => {
|
|
4584
|
-
const projectsDir =
|
|
4730
|
+
const projectsDir = path7.join(path7.dirname(dataDir), "projects");
|
|
4585
4731
|
const entries = await fs4.readdir(projectsDir, { withFileTypes: true }).catch(() => []);
|
|
4586
4732
|
await Promise.all(
|
|
4587
4733
|
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
4588
|
-
const projectDir =
|
|
4589
|
-
const metadataPath =
|
|
4734
|
+
const projectDir = path7.join(projectsDir, entry.name);
|
|
4735
|
+
const metadataPath = path7.join(projectDir, ".mailbox-server.json");
|
|
4590
4736
|
const ownerPid = await fs4.readFile(metadataPath, "utf8").then((raw) => JSON.parse(raw).pid).catch(() => void 0);
|
|
4591
4737
|
if (ownerPid === void 0) return;
|
|
4592
4738
|
let ownerAlive = false;
|
|
@@ -4644,7 +4790,10 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4644
4790
|
const snapshotBroadcaster = createSnapshotBroadcaster(
|
|
4645
4791
|
clients,
|
|
4646
4792
|
browsers,
|
|
4647
|
-
persistence
|
|
4793
|
+
persistence,
|
|
4794
|
+
// W4 #7: the ring is the read path for `/api/commands`, so the browser
|
|
4795
|
+
// snapshot's latency roll-up is computed from the same window.
|
|
4796
|
+
{ commandAudit: () => auditLog.recent(200) }
|
|
4648
4797
|
);
|
|
4649
4798
|
snapshotBroadcaster.currentSerialized();
|
|
4650
4799
|
const stopAlertEngine = alertEngine.startPeriodic(
|
|
@@ -4714,6 +4863,7 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4714
4863
|
agentMessages,
|
|
4715
4864
|
mailboxGateways: mailboxManager.mailboxGateways,
|
|
4716
4865
|
mailboxGatewayRateLimiter: mailboxManager.mailboxGatewayRateLimiter,
|
|
4866
|
+
mailboxManager,
|
|
4717
4867
|
alertEngine,
|
|
4718
4868
|
auditLog,
|
|
4719
4869
|
persistence,
|
|
@@ -4755,6 +4905,7 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4755
4905
|
sessions,
|
|
4756
4906
|
clients,
|
|
4757
4907
|
clientSocketTokens,
|
|
4908
|
+
browserSocketSessions,
|
|
4758
4909
|
browsers,
|
|
4759
4910
|
eventLog,
|
|
4760
4911
|
transcripts,
|
|
@@ -4785,12 +4936,18 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4785
4936
|
const liveBrowserTokenIds = new Set(
|
|
4786
4937
|
[...mutableAuth.browserTokenObjs.values()].map((token) => token.id)
|
|
4787
4938
|
);
|
|
4939
|
+
const revokedSessionIds = /* @__PURE__ */ new Set();
|
|
4788
4940
|
for (const [sessionId, session] of sessions) {
|
|
4789
4941
|
if (passwordChanged || cookieSecretChanged || session.kind === "token" && (session.tokenId === void 0 || !liveBrowserTokenIds.has(session.tokenId))) {
|
|
4790
4942
|
sessions.delete(sessionId);
|
|
4943
|
+
revokedSessionIds.add(sessionId);
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
for (const browser of browsers) {
|
|
4947
|
+
if (shouldCloseBrowserSocket(browserSocketSessions.get(browser), revokedSessionIds)) {
|
|
4948
|
+
browser.close(1008, "Browser authentication changed");
|
|
4791
4949
|
}
|
|
4792
4950
|
}
|
|
4793
|
-
for (const browser of browsers) browser.close(1008, "Browser authentication changed");
|
|
4794
4951
|
}
|
|
4795
4952
|
const clientAuthRequired = hqClientAuthRequired(mutableAuth);
|
|
4796
4953
|
for (const [clientSocket, authToken] of clientSocketTokens) {
|
|
@@ -4932,4 +5089,4 @@ export {
|
|
|
4932
5089
|
startHqServer,
|
|
4933
5090
|
HqInsecureExposureError2 as HqInsecureExposureError
|
|
4934
5091
|
};
|
|
4935
|
-
//# sourceMappingURL=chunk-
|
|
5092
|
+
//# sourceMappingURL=chunk-YRC3I3LE.js.map
|
|
@@ -19,17 +19,17 @@ import {
|
|
|
19
19
|
} from "./chunk-GPPRNAKW.js";
|
|
20
20
|
import {
|
|
21
21
|
subcommands
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-OJR7K3OI.js";
|
|
23
23
|
import {
|
|
24
24
|
renderDeepHelp,
|
|
25
25
|
renderFocusedHelp
|
|
26
26
|
} from "./chunk-JX6KL7OF.js";
|
|
27
27
|
import {
|
|
28
28
|
handleHelpVersionShortCircuit
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-TNZBQG45.js";
|
|
30
30
|
import {
|
|
31
31
|
DEFAULT_PORT
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-YRC3I3LE.js";
|
|
33
33
|
import "./chunk-HXJYHAR4.js";
|
|
34
34
|
import {
|
|
35
35
|
resolveHqPasswordInput
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
runPicker,
|
|
64
64
|
saveToGlobalConfig,
|
|
65
65
|
theme
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-RDHFXOJM.js";
|
|
67
67
|
import "./chunk-2VMJ3E5Z.js";
|
|
68
68
|
import "./chunk-B4JLSXZB.js";
|
|
69
69
|
import {
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
} from "./chunk-YMXXOOFN.js";
|
|
76
76
|
import {
|
|
77
77
|
parseArgs
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-C3Z4N6A6.js";
|
|
79
79
|
import "./chunk-DAKVBMD4.js";
|
|
80
80
|
import {
|
|
81
81
|
mutateConfigProviders,
|
|
@@ -866,7 +866,7 @@ async function isPortInUse(host, port) {
|
|
|
866
866
|
}
|
|
867
867
|
async function handleHqShortCircuit(flags) {
|
|
868
868
|
if (flags["hq"] !== true) return null;
|
|
869
|
-
const { startHqServer } = await import("./hq-server-
|
|
869
|
+
const { startHqServer } = await import("./hq-server-KE5YDNGG.js");
|
|
870
870
|
const tunnelRequested = flags["tunnel"] === true;
|
|
871
871
|
const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
|
|
872
872
|
let publicOrigin;
|
|
@@ -3303,4 +3303,4 @@ async function initializeCli(argv) {
|
|
|
3303
3303
|
export {
|
|
3304
3304
|
initializeCli
|
|
3305
3305
|
};
|
|
3306
|
-
//# sourceMappingURL=cli-context-
|
|
3306
|
+
//# sourceMappingURL=cli-context-5IF7ZYEY.js.map
|