@wrongstack/cli 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/{acp-CXLANMEZ.js → acp-IJ55NBMJ.js} +5 -3
  2. package/dist/acp-mcp-servers.d.ts +2 -0
  3. package/dist/arg-parser.d.ts +6 -0
  4. package/dist/{auth-3K6EN5JP.js → auth-YXK3EDUL.js} +9 -3
  5. package/dist/{chunk-EK2P53GL.js → chunk-C3Z4N6A6.js} +23 -3
  6. package/dist/{chunk-7K3IJGWC.js → chunk-OJR7K3OI.js} +8 -8
  7. package/dist/{chunk-SXJTVNWF.js → chunk-OWNC7LMD.js} +2 -2
  8. package/dist/{chunk-FSZ5QU3M.js → chunk-RDHFXOJM.js} +153 -69
  9. package/dist/{chunk-5VPDKQDH.js → chunk-TNZBQG45.js} +3 -3
  10. package/dist/{chunk-7FPN3QTH.js → chunk-YRC3I3LE.js} +172 -15
  11. package/dist/{cli-context-6GMXUOXD.js → cli-context-5IF7ZYEY.js} +7 -7
  12. package/dist/{cli-main-4E4YMCNS.js → cli-main-WC5JNIWY.js} +67 -48
  13. package/dist/{diag-doctor-PCER6KHH.js → diag-doctor-ANPRUYFM.js} +13 -3
  14. package/dist/{execution-4RXQLDAF.js → execution-EXDRBS4F.js} +2 -2
  15. package/dist/{hq-DQTUE7RC.js → hq-NO2NWGU4.js} +153 -5
  16. package/dist/hq-server/auth-state.d.ts +11 -0
  17. package/dist/hq-server/mailbox-gateway-health.d.ts +67 -0
  18. package/dist/hq-server/mailbox-gateway-manager.d.ts +38 -0
  19. package/dist/hq-server/routes/system-handlers.d.ts +27 -0
  20. package/dist/hq-server/routes.d.ts +11 -0
  21. package/dist/hq-server/snapshot.d.ts +14 -1
  22. package/dist/hq-server/upgrade-handler.d.ts +7 -0
  23. package/dist/{hq-server-JJFGFWI5.js → hq-server-KE5YDNGG.js} +2 -2
  24. package/dist/index.js +4 -4
  25. package/dist/{mcp-FX7TKPTV.js → mcp-DC5WSBOO.js} +28 -10
  26. package/dist/mcp-serve.d.ts +9 -0
  27. package/dist/services/mcp-management.d.ts +0 -6
  28. package/dist/{short-circuit-flags-LHUNLCZY.js → short-circuit-flags-72E77TJI.js} +3 -3
  29. package/dist/{subcommands-UXWY3OPC.js → subcommands-EJI7DBQS.js} +2 -2
  30. 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 path6 from "node:path";
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 snapshot = buildSnapshot(clients);
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: events.length }));
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 path5 from "node:path";
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 ${path5.join(startup.dataDir, "auth.json")}
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
- return [...deps.mutableAuth.browserTokenObjs.values()].some(
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 = path6.join(path6.dirname(dataDir), "projects");
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 = path6.join(projectsDir, entry.name);
4589
- const metadataPath = path6.join(projectDir, ".mailbox-server.json");
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-7FPN3QTH.js.map
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-7K3IJGWC.js";
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-5VPDKQDH.js";
29
+ } from "./chunk-TNZBQG45.js";
30
30
  import {
31
31
  DEFAULT_PORT
32
- } from "./chunk-7FPN3QTH.js";
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-FSZ5QU3M.js";
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-EK2P53GL.js";
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-JJFGFWI5.js");
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-6GMXUOXD.js.map
3306
+ //# sourceMappingURL=cli-context-5IF7ZYEY.js.map