@wrongstack/cli 1.0.10 → 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-BU5DEHDO.js → cli-main-WC5JNIWY.js} +32 -27
  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
@@ -6,7 +6,7 @@ import {
6
6
  removeSuggestions,
7
7
  runGit,
8
8
  setSuggestions
9
- } from "./chunk-SXJTVNWF.js";
9
+ } from "./chunk-OWNC7LMD.js";
10
10
  import {
11
11
  setupPlugins
12
12
  } from "./chunk-XJ7K67PP.js";
@@ -102,7 +102,7 @@ import {
102
102
  runAuthLocal,
103
103
  runProviderAuthLogin,
104
104
  validateFamily
105
- } from "./chunk-FSZ5QU3M.js";
105
+ } from "./chunk-RDHFXOJM.js";
106
106
  import "./chunk-2VMJ3E5Z.js";
107
107
  import {
108
108
  appendHistory
@@ -116,7 +116,7 @@ import {
116
116
  } from "./chunk-YMXXOOFN.js";
117
117
  import {
118
118
  parseSpawnFlags
119
- } from "./chunk-EK2P53GL.js";
119
+ } from "./chunk-C3Z4N6A6.js";
120
120
  import "./chunk-SIGUWXAA.js";
121
121
  import "./chunk-DAKVBMD4.js";
122
122
  import {
@@ -6231,7 +6231,7 @@ async function runCliExecution(params) {
6231
6231
  governanceHandle,
6232
6232
  setConfig
6233
6233
  } = params;
6234
- const { execute } = await import("./execution-4RXQLDAF.js");
6234
+ const { execute } = await import("./execution-EXDRBS4F.js");
6235
6235
  return execute(
6236
6236
  toExecuteDeps({
6237
6237
  core: {
@@ -8419,6 +8419,7 @@ ${diff}`;
8419
8419
  }
8420
8420
 
8421
8421
  // src/services/mcp-management.ts
8422
+ import { resolveMcpServerConfig } from "@wrongstack/core/infrastructure";
8422
8423
  import {
8423
8424
  color as color7,
8424
8425
  expectDefined as expectDefined4,
@@ -8534,7 +8535,7 @@ async function runAdd(name, enable, configured, configPath, mcpRegistry, all) {
8534
8535
  }
8535
8536
  try {
8536
8537
  if (mcpRegistry.list().some((server) => server.name === name)) {
8537
- await mcpRegistry.restart(name);
8538
+ await mcpRegistry.restart(name, { ...nextCfg, name });
8538
8539
  } else {
8539
8540
  await mcpRegistry.start(nextCfg);
8540
8541
  }
@@ -8572,24 +8573,21 @@ async function runRemove(name, configured, configPath, mcpRegistry) {
8572
8573
  async function runEnable(name, configured, configPath, mcpRegistry) {
8573
8574
  const cfg = configured[name];
8574
8575
  if (!cfg) return `Server "${name}" is not in config. Run \`/mcp add ${name} --enable\` first.`;
8576
+ const resolved = resolveMcpServerConfig(name, cfg);
8577
+ if (!resolved) return `Server "${name}" has no transport and matches no preset.`;
8578
+ const startCfg = { ...resolved, enabled: true };
8579
+ const registered = mcpRegistry.list().some((server) => server.name === name);
8575
8580
  if (cfg.enabled !== false) {
8576
- try {
8577
- await mcpRegistry.restart(name);
8578
- return `${color7.green("\u25CF")} "${name}" is already enabled and running.`;
8579
- } catch {
8580
- await mcpRegistry.start({ ...cfg, enabled: true });
8581
- return `${color7.green("Enabled")} "${name}" and started.`;
8582
- }
8581
+ if (registered) await mcpRegistry.restart(name, startCfg);
8582
+ else await mcpRegistry.start(startCfg);
8583
+ return `${color7.green("\u25CF")} "${name}" is already enabled and running.`;
8583
8584
  }
8584
8585
  await updateJsonObjectFile(configPath, (full) => {
8585
8586
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
8586
8587
  setJsonPath(full, ["mcpServers", name], { ...cfg, ...current[name], enabled: true });
8587
8588
  });
8588
- try {
8589
- await mcpRegistry.restart(name);
8590
- } catch {
8591
- await mcpRegistry.start({ ...cfg, enabled: true });
8592
- }
8589
+ if (registered) await mcpRegistry.restart(name, startCfg);
8590
+ else await mcpRegistry.start(startCfg);
8593
8591
  return `${color7.green("Enabled")} "${name}" and started.`;
8594
8592
  }
8595
8593
  async function runDisable(name, configured, configPath, mcpRegistry) {
@@ -8642,6 +8640,8 @@ function stateBadge(state) {
8642
8640
  return color7.dim("\u25CB disconnected");
8643
8641
  case "failed":
8644
8642
  return color7.red("\u2717 failed");
8643
+ case "dormant":
8644
+ return color7.dim("\u25CC dormant (spawns on first call)");
8645
8645
  default:
8646
8646
  return color7.dim(state);
8647
8647
  }
@@ -8781,7 +8781,7 @@ async function checkUncommittedChanges(projectRoot) {
8781
8781
  stdout += data;
8782
8782
  });
8783
8783
  child.on("error", reject);
8784
- child.on("close", (code) => resolve9({ stdout, code: code ?? 0 }));
8784
+ child.on("close", (code) => resolve9({ stdout, code: code ?? 1 }));
8785
8785
  });
8786
8786
  if (!result.stdout.trim()) return void 0;
8787
8787
  const count = result.stdout.split("\n").filter(Boolean).length;
@@ -28231,7 +28231,7 @@ async function runGit2(args, cwd) {
28231
28231
  stdout += d;
28232
28232
  });
28233
28233
  child.on("error", () => resolve9({ stdout, code: 1 }));
28234
- child.on("close", (code) => resolve9({ stdout, code: code ?? 0 }));
28234
+ child.on("close", (code) => resolve9({ stdout, code: code ?? 1 }));
28235
28235
  });
28236
28236
  }
28237
28237
  async function getChangedFiles(cwd) {
@@ -34711,7 +34711,7 @@ import * as path29 from "node:path";
34711
34711
  import { getSharedProjectMailbox as getSharedProjectMailbox10 } from "@wrongstack/core/coordination";
34712
34712
  import { CouncilOrchestrator, OneShotOrchestrator as OneShotOrchestrator3 } from "@wrongstack/core/execution";
34713
34713
  import { countShellHooks, HookRegistry as HookRegistry2, HookRunner as HookRunner2, shellHooksEqual } from "@wrongstack/core/hooks";
34714
- import { allServers as allServers3 } from "@wrongstack/core/infrastructure";
34714
+ import { resolveMcpServerConfig as resolveMcpServerConfig2 } from "@wrongstack/core/infrastructure";
34715
34715
  import { TOKENS as TOKENS11 } from "@wrongstack/core/kernel";
34716
34716
  import { NotifierImpl } from "@wrongstack/core/notifications";
34717
34717
  import {
@@ -35781,18 +35781,23 @@ async function setupLifecycleAndPlugins(deps) {
35781
35781
  log: logger,
35782
35782
  lazyMode: normalizeTokenSavingTier5(config.features.tokenSavingMode) !== "off",
35783
35783
  cacheDir: wpaths.cacheDir,
35784
+ // Presets say `--project-root .`; resolve it against the project, not
35785
+ // wherever this process happens to be running.
35786
+ cwd: wpaths.projectRoot,
35784
35787
  authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
35785
35788
  store: mcpTokenStore
35786
35789
  }),
35787
35790
  authorizationManager: mcpAuthorizationManager
35788
35791
  });
35789
35792
  if (config.features.mcp) {
35790
- const presets = allServers3();
35791
- for (const cfg of Object.values(config.mcpServers ?? {})) {
35792
- const preset = presets[cfg.name];
35793
- const merged = preset ? { ...preset, ...cfg } : cfg;
35794
- void mcpRegistry.start(merged).catch((err) => {
35795
- logger.warn(`MCP server "${cfg.name}" failed to start`, err);
35793
+ for (const [name, entry] of Object.entries(config.mcpServers ?? {})) {
35794
+ const resolved = resolveMcpServerConfig2(name, entry);
35795
+ if (!resolved) {
35796
+ logger.warn(`MCP server "${name}" has no transport and matches no preset \u2014 skipped`);
35797
+ continue;
35798
+ }
35799
+ void mcpRegistry.start(resolved).catch((err) => {
35800
+ logger.warn(`MCP server "${name}" failed to start`, err);
35796
35801
  });
35797
35802
  }
35798
35803
  }
@@ -38950,4 +38955,4 @@ export {
38950
38955
  CLI_VERSION,
38951
38956
  runInteractive
38952
38957
  };
38953
- //# sourceMappingURL=cli-main-BU5DEHDO.js.map
38958
+ //# sourceMappingURL=cli-main-WC5JNIWY.js.map
@@ -13,6 +13,7 @@ import {
13
13
  import * as fs2 from "node:fs/promises";
14
14
  import * as os from "node:os";
15
15
  import * as path2 from "node:path";
16
+ import { resolveMcpServerConfig } from "@wrongstack/core/infrastructure";
16
17
  import { color, toErrorMessage } from "@wrongstack/core/utils";
17
18
  import {
18
19
  applyProxyConfig,
@@ -328,8 +329,17 @@ var doctorCmd = async (args, deps) => {
328
329
  });
329
330
  }
330
331
  const mcpEntries = Object.entries(cfg.mcpServers ?? {});
331
- for (const [name, srv] of mcpEntries) {
332
- if (!srv.enabled) continue;
332
+ for (const [name, entry] of mcpEntries) {
333
+ if (entry.enabled === false) continue;
334
+ const srv = resolveMcpServerConfig(name, entry);
335
+ if (!srv) {
336
+ checks.push({
337
+ name: `mcp:${name}`,
338
+ status: "fail",
339
+ detail: "no transport and no built-in preset of this name"
340
+ });
341
+ continue;
342
+ }
333
343
  if ((srv.transport === "sse" || srv.transport === "streamable-http") && !srv.url)
334
344
  checks.push({ name: `mcp:${name}`, status: "fail", detail: "transport requires url" });
335
345
  else if (srv.transport === "stdio" && !srv.command)
@@ -382,4 +392,4 @@ export {
382
392
  doctorCmd,
383
393
  proxyCmd
384
394
  };
385
- //# sourceMappingURL=diag-doctor-PCER6KHH.js.map
395
+ //# sourceMappingURL=diag-doctor-ANPRUYFM.js.map
@@ -4,7 +4,7 @@ import {
4
4
  runGit,
5
5
  setAutoSuggestions,
6
6
  setSuggestions
7
- } from "./chunk-SXJTVNWF.js";
7
+ } from "./chunk-OWNC7LMD.js";
8
8
  import {
9
9
  deriveFsAccessPair,
10
10
  filterSafeForProject,
@@ -6953,4 +6953,4 @@ export {
6953
6953
  execute,
6954
6954
  resolveReviewerFallbackModels
6955
6955
  };
6956
- //# sourceMappingURL=execution-4RXQLDAF.js.map
6956
+ //# sourceMappingURL=execution-EXDRBS4F.js.map