@wrongstack/cli 1.1.4 → 1.1.6

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 (103) hide show
  1. package/dist/{acp-MZ5MQBFN.js → acp-CZBSFQB7.js} +6 -5
  2. package/dist/acp-leader/acp-leader-confirm.d.ts +2 -0
  3. package/dist/acp-leader/acp-leader-host-binding.d.ts +1 -0
  4. package/dist/acp-leader/acp-leader-host.d.ts +9 -0
  5. package/dist/acp-leader/acp-leader-picker.d.ts +6 -3
  6. package/dist/acp-leader/acp-leader-provider.d.ts +10 -0
  7. package/dist/acp-leader/acp-leader-skills.d.ts +5 -0
  8. package/dist/acp-leader/acp-leader-stream.d.ts +9 -0
  9. package/dist/{auth-IHQS6AFW.js → auth-2ZG5DBHS.js} +4 -4
  10. package/dist/boot/launch-menu-arrow.d.ts +20 -0
  11. package/dist/boot/launch-menu-types.d.ts +36 -0
  12. package/dist/boot/launch-menu.d.ts +1 -57
  13. package/dist/boot/tui-session-resume-rollback.d.ts +31 -0
  14. package/dist/{boot-auto-thin-7D4OVEWA.js → boot-auto-thin-N2CIGTXE.js} +3 -3
  15. package/dist/{chunk-TZPKOS5G.js → chunk-2XQ6SRKG.js} +2 -2
  16. package/dist/{chunk-3EXRPTAU.js → chunk-5TQ65PXQ.js} +17 -17
  17. package/dist/{chunk-VAQXOZKP.js → chunk-AJM4EFCM.js} +139 -136
  18. package/dist/{chunk-C2BBH4S4.js → chunk-E3MJFAFR.js} +2 -2
  19. package/dist/{chunk-COGI7EQU.js → chunk-EAATL6FL.js} +713 -21
  20. package/dist/{chunk-AWRZWPJ3.js → chunk-FWUIKCCO.js} +152 -123
  21. package/dist/{chunk-TAE3EGBM.js → chunk-H5DCQFUI.js} +5 -5
  22. package/dist/{chunk-BIGSVBPI.js → chunk-KNYIEQAY.js} +64 -61
  23. package/dist/chunk-LYWOV4OX.js +252 -0
  24. package/dist/{chunk-75OLD563.js → chunk-M7PSENGF.js} +3 -1
  25. package/dist/{chunk-I5LJLKA3.js → chunk-MKMM5RIW.js} +12 -5
  26. package/dist/{chunk-ZF4HRR5F.js → chunk-P7MEEUGT.js} +275 -250
  27. package/dist/chunk-PGC44T7N.js +255 -0
  28. package/dist/chunk-QLYPIJ7X.js +258 -0
  29. package/dist/{chunk-JKTJSLUT.js → chunk-QRWXHGLB.js} +242 -18
  30. package/dist/chunk-RN5CFAVQ.js +292 -0
  31. package/dist/{chunk-WDXNBYGD.js → chunk-TDP7CZGE.js} +6 -1
  32. package/dist/{chunk-6VPWQ4TG.js → chunk-UROB7MK5.js} +2 -1
  33. package/dist/{chunk-IJLIAPCW.js → chunk-VPSAKGLK.js} +47 -47
  34. package/dist/{chunk-2LTHDKZX.js → chunk-W7X23HI6.js} +18 -18
  35. package/dist/{chunk-GKGWPFW2.js → chunk-X4DEQLAZ.js} +1 -1
  36. package/dist/{cli-context-E44HQ4ZO.js → cli-context-6KS3HV5R.js} +247 -236
  37. package/dist/{cli-main-OT63I2FX.js → cli-main-NF3YGDME.js} +1779 -1494
  38. package/dist/cli-main-boot.d.ts +1 -1
  39. package/dist/config-doctor-known-keys.d.ts +33 -0
  40. package/dist/config-doctor.d.ts +2 -47
  41. package/dist/config-history-support.d.ts +36 -0
  42. package/dist/config-history.d.ts +1 -1
  43. package/dist/{config-transfer-Y7XJMJRN.js → config-transfer-B4GOZTFC.js} +2 -2
  44. package/dist/{execution-OK34CJKJ.js → execution-RVDUXLNT.js} +6522 -6426
  45. package/dist/execution-tui.d.ts +30 -0
  46. package/dist/fleet/acp-wrongstack-bridge.d.ts +11 -3
  47. package/dist/fleet/host-acp.d.ts +3 -0
  48. package/dist/fleet/wrongforge-client.d.ts +116 -0
  49. package/dist/fleet/wrongforge-gateway.d.ts +20 -0
  50. package/dist/fleet/wrongforge-info.d.ts +32 -0
  51. package/dist/fleet/wrongforge-quota.d.ts +10 -0
  52. package/dist/fleet/wrongforge-runner.d.ts +37 -0
  53. package/dist/fleet/wrongforge-runtime.d.ts +10 -0
  54. package/dist/{hq-JD5AZVMG.js → hq-V2M4IYCI.js} +320 -306
  55. package/dist/hq-server/login-attempt-store.d.ts +2 -0
  56. package/dist/hq-server/sweeps.d.ts +28 -0
  57. package/dist/{hq-server-BI23EPM7.js → hq-server-QM7EIVGJ.js} +3 -3
  58. package/dist/index.js +5 -5
  59. package/dist/mcp-22CFBX4H.js +236 -0
  60. package/dist/mcp-serve.d.ts +0 -63
  61. package/dist/{modeldiag-HDIKG37X.js → modeldiag-5PBB777J.js} +4 -4
  62. package/dist/{per-subcommand-help-4GYSCMLU.js → per-subcommand-help-MQGERD4E.js} +2 -2
  63. package/dist/plugin-llm-command.d.ts +9 -0
  64. package/dist/{plugin-usage-TYF5HY4K.js → plugin-usage-UWKYMNQU.js} +3 -3
  65. package/dist/{plugins-XGAXDLPT.js → plugins-VP7JQH47.js} +4 -4
  66. package/dist/{sdd-runtime-6MFUXWJK.js → sdd-runtime-HIJ4Z5TF.js} +2 -2
  67. package/dist/services/sdd/state.d.ts +2 -0
  68. package/dist/session-event-wiring-types.d.ts +62 -0
  69. package/dist/session-event-wiring.d.ts +2 -52
  70. package/dist/{sessions-config-SJVQXCIE.js → sessions-config-7MK742OW.js} +2 -2
  71. package/dist/short-circuit-flags-276OLPIN.js +11 -0
  72. package/dist/slash-commands/command-context-fleet.d.ts +187 -0
  73. package/dist/slash-commands/command-context-sdd-goal.d.ts +102 -0
  74. package/dist/slash-commands/command-context.d.ts +3 -282
  75. package/dist/slash-commands/project-switch-interactive.d.ts +11 -0
  76. package/dist/slash-commands/setmodel-helpers.d.ts +36 -0
  77. package/dist/subcommands/handlers/hq-token.d.ts +3 -0
  78. package/dist/subcommands/handlers/hq.d.ts +0 -21
  79. package/dist/subcommands/handlers/per-subcommand-help-operations.d.ts +8 -0
  80. package/dist/subcommands/handlers/tools-cli-args.d.ts +57 -0
  81. package/dist/subcommands/handlers/tools-cli.d.ts +5 -0
  82. package/dist/subcommands/handlers/tools-serve.d.ts +4 -0
  83. package/dist/{subcommands-YADUV7IJ.js → subcommands-SKJPUJ7N.js} +2 -2
  84. package/dist/tool-gateway-http.d.ts +30 -0
  85. package/dist/tool-gateway.d.ts +185 -0
  86. package/dist/tools-cli-44MLIUQO.js +509 -0
  87. package/dist/tools-serve-TRCET2M7.js +102 -0
  88. package/dist/{tools-skills-55QVTMSK.js → tools-skills-WDTMCE46.js} +7 -2
  89. package/dist/tuneup-runtime-checks.d.ts +7 -0
  90. package/dist/tuneup-types.d.ts +118 -0
  91. package/dist/tuneup.d.ts +3 -122
  92. package/dist/{typesafe-YBEADNAH.js → typesafe-QLSMFU7J.js} +1 -1
  93. package/dist/{version-help-RBZC5GZZ.js → version-help-X6CC4CFY.js} +2 -2
  94. package/dist/{webui-server-CB64CFDG.js → webui-server-6I6QOECV.js} +44 -6
  95. package/dist/wiring/provider-runtime-setup.d.ts +3 -1
  96. package/dist/wiring/session-runtime.d.ts +1 -1
  97. package/dist/wrongforge-gateway-UOFBK56J.js +92 -0
  98. package/dist/wrongforge-leader/wrongforge-leader-gateway.d.ts +9 -0
  99. package/dist/wrongforge-leader/wrongforge-leader-picker.d.ts +12 -0
  100. package/dist/wrongforge-leader/wrongforge-leader-provider.d.ts +56 -0
  101. package/package.json +1 -1
  102. package/dist/mcp-W7A5HUXV.js +0 -560
  103. package/dist/short-circuit-flags-QEXUS3BY.js +0 -11
@@ -24,7 +24,7 @@ import {
24
24
  sanitizeApiError,
25
25
  truncateHqSummary,
26
26
  writeInvalidBody
27
- } from "./chunk-3EXRPTAU.js";
27
+ } from "./chunk-5TQ65PXQ.js";
28
28
  import {
29
29
  createHqIpAllowlist
30
30
  } from "./chunk-OGVSKS4Q.js";
@@ -201,6 +201,7 @@ var LoginAttemptStore = class _LoginAttemptStore {
201
201
  store = /* @__PURE__ */ new Map();
202
202
  writeTimer;
203
203
  dirty = false;
204
+ writeTail = Promise.resolve();
204
205
  filePath;
205
206
  debounceMs;
206
207
  retentionMs;
@@ -339,9 +340,7 @@ var LoginAttemptStore = class _LoginAttemptStore {
339
340
  clearTimeout(this.writeTimer);
340
341
  this.writeTimer = void 0;
341
342
  }
342
- if (this.dirty) {
343
- await this.writeToDisk();
344
- }
343
+ await this.writeToDisk();
345
344
  }
346
345
  scheduleWrite() {
347
346
  this.dirty = true;
@@ -352,7 +351,14 @@ var LoginAttemptStore = class _LoginAttemptStore {
352
351
  }, this.debounceMs);
353
352
  this.writeTimer.unref?.();
354
353
  }
355
- async writeToDisk() {
354
+ writeToDisk() {
355
+ const pending = this.writeTail.then(() => {
356
+ return this.dirty ? this.writeSnapshotToDisk() : void 0;
357
+ });
358
+ this.writeTail = pending.catch(() => void 0);
359
+ return pending;
360
+ }
361
+ async writeSnapshotToDisk() {
356
362
  this.dirty = false;
357
363
  const now = Date.now();
358
364
  const cutoff = now - this.retentionMs;
@@ -3733,52 +3739,51 @@ ${terminalText("Reachable from other machines on your network:", "blue", { bold:
3733
3739
 
3734
3740
  // src/hq-server/server-lifecycle.ts
3735
3741
  function createHqServerShutdown(params) {
3736
- let closed = false;
3737
- return () => new Promise((res) => {
3738
- if (closed) {
3739
- res();
3740
- return;
3741
- }
3742
- closed = true;
3743
- clearInterval(params.cleanupTimer);
3744
- clearInterval(params.browserHeartbeatTimer);
3745
- clearInterval(params.timeseriesFlushTimer);
3746
- clearInterval(params.sessionCleanupTimer);
3747
- params.stopAlertEngine();
3748
- params.authWatcher.close();
3749
- params.snapshotBroadcaster.close();
3750
- params.bootstrapStore.clear();
3751
- params.persistence.timeseries.flush();
3752
- for (const ws of [...params.browsers, ...params.clients.keys()]) {
3753
- try {
3754
- ws.close(1001, "HQ shutting down");
3755
- ws.terminate();
3756
- } catch {
3742
+ let closing;
3743
+ return () => {
3744
+ if (closing) return closing;
3745
+ closing = new Promise((res) => {
3746
+ clearInterval(params.cleanupTimer);
3747
+ clearInterval(params.browserHeartbeatTimer);
3748
+ clearInterval(params.timeseriesFlushTimer);
3749
+ clearInterval(params.sessionCleanupTimer);
3750
+ params.stopAlertEngine();
3751
+ params.authWatcher.close();
3752
+ params.snapshotBroadcaster.close();
3753
+ params.bootstrapStore.clear();
3754
+ params.persistence.timeseries.flush();
3755
+ for (const ws of [...params.browsers, ...params.clients.keys()]) {
3756
+ try {
3757
+ ws.close(1001, "HQ shutting down");
3758
+ ws.terminate();
3759
+ } catch {
3760
+ }
3757
3761
  }
3758
- }
3759
- params.wss.close();
3760
- params.mailboxManager.close();
3761
- params.httpServer.close(() => {
3762
- const mailboxCloses = [...params.mailboxManager.mailboxGateways.values()].map(
3763
- ({ mailbox }) => mailbox.close().catch(() => void 0)
3764
- );
3765
- params.mailboxManager.mailboxGateways.clear();
3766
- params.mailboxManager.mailboxGatewayLastUsed.clear();
3767
- void Promise.all([
3768
- ...mailboxCloses,
3769
- // Chimera/race: flush the login-attempt store INSIDE the awaited
3770
- // shutdown set — the old fire-and-forget `void flush()` let close()
3771
- // resolve before the debounced write landed, so persistence tests
3772
- // had to sleep past it (and restarts could miss lockout state).
3773
- params.loginAttempts.flush(),
3774
- params.persistence.eventLog.drain(),
3775
- params.persistence.snapshotStore.drain(),
3776
- params.persistence.timeseries.drain(),
3777
- params.persistence.kanban.drain(),
3778
- params.persistence.sage?.drain()
3779
- ]).then(() => clearHqRuntimeMarker(params.dataDir, params.hqUrl)).catch(() => void 0).finally(() => res());
3762
+ params.wss.close();
3763
+ params.mailboxManager.close();
3764
+ params.httpServer.close(() => {
3765
+ const mailboxCloses = [...params.mailboxManager.mailboxGateways.values()].map(
3766
+ ({ mailbox }) => mailbox.close().catch(() => void 0)
3767
+ );
3768
+ params.mailboxManager.mailboxGateways.clear();
3769
+ params.mailboxManager.mailboxGatewayLastUsed.clear();
3770
+ void Promise.allSettled([
3771
+ ...mailboxCloses,
3772
+ // Chimera/race: flush the login-attempt store INSIDE the awaited
3773
+ // shutdown set — the old fire-and-forget `void flush()` let close()
3774
+ // resolve before the debounced write landed, so persistence tests
3775
+ // had to sleep past it (and restarts could miss lockout state).
3776
+ params.loginAttempts.flush(),
3777
+ params.persistence.eventLog.drain(),
3778
+ params.persistence.snapshotStore.drain(),
3779
+ params.persistence.timeseries.drain(),
3780
+ params.persistence.kanban.drain(),
3781
+ params.persistence.sage?.drain()
3782
+ ]).then(() => clearHqRuntimeMarker(params.dataDir, params.hqUrl)).catch(() => void 0).finally(() => res());
3783
+ });
3780
3784
  });
3781
- });
3785
+ return closing;
3786
+ };
3782
3787
  }
3783
3788
 
3784
3789
  // src/hq-server/socket-credentials.ts
@@ -3839,9 +3844,6 @@ function createHqSocketCredentialEnforcer(state) {
3839
3844
  };
3840
3845
  }
3841
3846
 
3842
- // src/hq-server/upgrade-handler.ts
3843
- import { hqTokenVerifier as hqTokenVerifier2, isLoopbackHost as isLoopbackHost3, isTokenExpired as isTokenExpired5 } from "@wrongstack/core/hq";
3844
-
3845
3847
  // src/hq-server/ws.ts
3846
3848
  import { randomUUID as randomUUID5 } from "node:crypto";
3847
3849
  import {
@@ -4706,7 +4708,91 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
4706
4708
  });
4707
4709
  }
4708
4710
 
4711
+ // src/hq-server/sweeps.ts
4712
+ var HQ_SESSION_MAX_AGE_MS2 = 7 * 24 * 60 * 60 * 1e3;
4713
+ var HQ_SESSION_IDLE_TIMEOUT_MS2 = 30 * 6e4;
4714
+ var PENDING_2FA_TTL_MS2 = 5 * 6e4;
4715
+ var HQ_SESSION_CLEANUP_INTERVAL_MS = 6e4;
4716
+ function sweepHqBrowserSessions(sessions, loginAttempts, socketCredentials) {
4717
+ const now = Date.now();
4718
+ const maxAgeCutoff = now - HQ_SESSION_MAX_AGE_MS2;
4719
+ const idleCutoff = now - HQ_SESSION_IDLE_TIMEOUT_MS2;
4720
+ const pending2faCutoff = now - PENDING_2FA_TTL_MS2;
4721
+ for (const [id, session] of sessions) {
4722
+ if (session.createdAt < maxAgeCutoff) {
4723
+ sessions.delete(id);
4724
+ socketCredentials.closeSessionSockets(id, "Browser session expired");
4725
+ continue;
4726
+ }
4727
+ if (session.pending2fa) {
4728
+ if (session.createdAt < pending2faCutoff) sessions.delete(id);
4729
+ continue;
4730
+ }
4731
+ if (session.lastSeenAt < idleCutoff) {
4732
+ sessions.delete(id);
4733
+ }
4734
+ }
4735
+ for (const [ip, entry] of loginAttempts.entries()) {
4736
+ if (now - entry.lastAttempt > 15 * 6e4) loginAttempts.delete(ip);
4737
+ }
4738
+ }
4739
+ function sweepStaleHqClients({
4740
+ clients,
4741
+ browsers,
4742
+ auditLog,
4743
+ eventLog,
4744
+ persistence,
4745
+ clientTtlMs,
4746
+ sessionSnapshotTtlMs
4747
+ }) {
4748
+ const cutoff = Date.now() - clientTtlMs;
4749
+ const sessionCutoff = Date.now() - sessionSnapshotTtlMs;
4750
+ let changed = false;
4751
+ for (const [ws, client] of clients.entries()) {
4752
+ if (new Date(client.lastSeenAt).getTime() < cutoff) {
4753
+ const lostClient = client;
4754
+ ws.terminate();
4755
+ clients.delete(ws);
4756
+ failUndeliveredCommands(lostClient, browsers, auditLog);
4757
+ detectLeaderLoss(lostClient, clients, browsers, "heartbeat-timeout", {
4758
+ eventLog,
4759
+ persistence
4760
+ });
4761
+ changed = true;
4762
+ continue;
4763
+ }
4764
+ const expiredSessions = /* @__PURE__ */ new Set();
4765
+ for (const [sessionId, tracked] of client.sessions.entries()) {
4766
+ if (tracked.receivedAt < sessionCutoff) {
4767
+ client.sessions.delete(sessionId);
4768
+ client.mcpSnapshots.delete(sessionId);
4769
+ expiredSessions.add(sessionId);
4770
+ changed = true;
4771
+ }
4772
+ }
4773
+ for (const [runId, fleet] of client.fleets) {
4774
+ if (fleet.sessionId !== void 0 && expiredSessions.has(fleet.sessionId)) {
4775
+ client.fleets.delete(runId);
4776
+ changed = true;
4777
+ }
4778
+ }
4779
+ if (client.capabilities.includes("session.summary") && client.sessions.size === 0 && Date.now() - Date.parse(client.connectedAt) > sessionSnapshotTtlMs) {
4780
+ const lostClient = client;
4781
+ ws.terminate();
4782
+ clients.delete(ws);
4783
+ failUndeliveredCommands(lostClient, browsers, auditLog);
4784
+ detectLeaderLoss(lostClient, clients, browsers, "crash", {
4785
+ eventLog,
4786
+ persistence
4787
+ });
4788
+ changed = true;
4789
+ }
4790
+ }
4791
+ return changed;
4792
+ }
4793
+
4709
4794
  // src/hq-server/upgrade-handler.ts
4795
+ import { hqTokenVerifier as hqTokenVerifier2, isLoopbackHost as isLoopbackHost3, isTokenExpired as isTokenExpired5 } from "@wrongstack/core/hq";
4710
4796
  function handleHqUpgrade(req, socket, head, deps) {
4711
4797
  const url = new URL(req.url ?? "/", `http://${deps.host}:${deps.port}`);
4712
4798
  const pathname = url.pathname;
@@ -4964,33 +5050,10 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4964
5050
  mutableAuth,
4965
5051
  sessions
4966
5052
  });
4967
- const HQ_SESSION_MAX_AGE_MS2 = 7 * 24 * 60 * 60 * 1e3;
4968
- const HQ_SESSION_IDLE_TIMEOUT_MS2 = 30 * 6e4;
4969
- const SESSION_CLEANUP_INTERVAL_MS = 6e4;
4970
- const PENDING_2FA_TTL_MS2 = 5 * 6e4;
4971
- const sessionCleanupTimer = setInterval(() => {
4972
- const now = Date.now();
4973
- const maxAgeCutoff = now - HQ_SESSION_MAX_AGE_MS2;
4974
- const idleCutoff = now - HQ_SESSION_IDLE_TIMEOUT_MS2;
4975
- const pending2faCutoff = now - PENDING_2FA_TTL_MS2;
4976
- for (const [id, session] of sessions) {
4977
- if (session.createdAt < maxAgeCutoff) {
4978
- sessions.delete(id);
4979
- socketCredentials.closeSessionSockets(id, "Browser session expired");
4980
- continue;
4981
- }
4982
- if (session.pending2fa) {
4983
- if (session.createdAt < pending2faCutoff) sessions.delete(id);
4984
- continue;
4985
- }
4986
- if (session.lastSeenAt < idleCutoff) {
4987
- sessions.delete(id);
4988
- }
4989
- }
4990
- for (const [ip, entry] of loginAttempts.entries()) {
4991
- if (now - entry.lastAttempt > 15 * 6e4) loginAttempts.delete(ip);
4992
- }
4993
- }, SESSION_CLEANUP_INTERVAL_MS);
5053
+ const sessionCleanupTimer = setInterval(
5054
+ () => sweepHqBrowserSessions(sessions, loginAttempts, socketCredentials),
5055
+ HQ_SESSION_CLEANUP_INTERVAL_MS
5056
+ );
4994
5057
  sessionCleanupTimer.unref?.();
4995
5058
  const persistence = createHqPersistence(dataDir);
4996
5059
  const auditLog = new HqCommandAuditLog(1e3, (entry) => persistence.commandLog.append(entry));
@@ -5065,49 +5128,15 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
5065
5128
  const sessionSnapshotTtlMs = options.sessionSnapshotTtlMs ?? SESSION_SNAPSHOT_TTL_MS;
5066
5129
  const cleanupTimer = setInterval(() => {
5067
5130
  socketCredentials.sweepExpiredSocketCredentials();
5068
- const cutoff = Date.now() - clientTtlMs;
5069
- const sessionCutoff = Date.now() - sessionSnapshotTtlMs;
5070
- let changed = false;
5071
- for (const [ws, client] of clients.entries()) {
5072
- if (new Date(client.lastSeenAt).getTime() < cutoff) {
5073
- const lostClient = client;
5074
- ws.terminate();
5075
- clients.delete(ws);
5076
- failUndeliveredCommands(lostClient, browsers, auditLog);
5077
- detectLeaderLoss(lostClient, clients, browsers, "heartbeat-timeout", {
5078
- eventLog,
5079
- persistence
5080
- });
5081
- changed = true;
5082
- continue;
5083
- }
5084
- const expiredSessions = /* @__PURE__ */ new Set();
5085
- for (const [sessionId, tracked] of client.sessions.entries()) {
5086
- if (tracked.receivedAt < sessionCutoff) {
5087
- client.sessions.delete(sessionId);
5088
- client.mcpSnapshots.delete(sessionId);
5089
- expiredSessions.add(sessionId);
5090
- changed = true;
5091
- }
5092
- }
5093
- for (const [runId, fleet] of client.fleets) {
5094
- if (fleet.sessionId !== void 0 && expiredSessions.has(fleet.sessionId)) {
5095
- client.fleets.delete(runId);
5096
- changed = true;
5097
- }
5098
- }
5099
- if (client.capabilities.includes("session.summary") && client.sessions.size === 0 && Date.now() - Date.parse(client.connectedAt) > sessionSnapshotTtlMs) {
5100
- const lostClient = client;
5101
- ws.terminate();
5102
- clients.delete(ws);
5103
- failUndeliveredCommands(lostClient, browsers, auditLog);
5104
- detectLeaderLoss(lostClient, clients, browsers, "crash", {
5105
- eventLog,
5106
- persistence
5107
- });
5108
- changed = true;
5109
- }
5110
- }
5131
+ const changed = sweepStaleHqClients({
5132
+ clients,
5133
+ browsers,
5134
+ auditLog,
5135
+ eventLog,
5136
+ persistence,
5137
+ clientTtlMs,
5138
+ sessionSnapshotTtlMs
5139
+ });
5111
5140
  if (changed) snapshotBroadcaster.broadcast();
5112
5141
  }, options.clientCleanupIntervalMs ?? CLIENT_CLEANUP_INTERVAL_MS);
5113
5142
  cleanupTimer.unref?.();
@@ -5361,4 +5390,4 @@ export {
5361
5390
  startHqServer,
5362
5391
  HqInsecureExposureError2 as HqInsecureExposureError
5363
5392
  };
5364
- //# sourceMappingURL=chunk-AWRZWPJ3.js.map
5393
+ //# sourceMappingURL=chunk-FWUIKCCO.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseArgs
3
- } from "./chunk-75OLD563.js";
3
+ } from "./chunk-M7PSENGF.js";
4
4
  import {
5
5
  helpCmd,
6
6
  versionCmd
7
- } from "./chunk-6VPWQ4TG.js";
7
+ } from "./chunk-UROB7MK5.js";
8
8
 
9
9
  // src/boot/short-circuit-flags.ts
10
10
  async function handleHelpVersionShortCircuit(argv) {
@@ -22,11 +22,11 @@ async function handleHelpVersionShortCircuit(argv) {
22
22
  }
23
23
  if (earlyFlags["help"] === true) {
24
24
  if (positional.length > 0) {
25
- const { subcommandNames } = await import("./subcommands-YADUV7IJ.js");
25
+ const { subcommandNames } = await import("./subcommands-SKJPUJ7N.js");
26
26
  if (subcommandNames.includes(positional[0])) {
27
27
  const first = positional[0];
28
28
  const deep = positional[1];
29
- const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-4GYSCMLU.js");
29
+ const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-MQGERD4E.js");
30
30
  if (deep && renderDeepHelp(`${first}:${deep}`, stubRenderer)) {
31
31
  return 0;
32
32
  }
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
43
43
  export {
44
44
  handleHelpVersionShortCircuit
45
45
  };
46
- //# sourceMappingURL=chunk-TAE3EGBM.js.map
46
+ //# sourceMappingURL=chunk-H5DCQFUI.js.map
@@ -1,18 +1,19 @@
1
1
 
2
2
  // src/config-history.ts
3
3
  import { randomUUID } from "node:crypto";
4
+ import * as fs2 from "node:fs/promises";
5
+ import * as path2 from "node:path";
6
+ import { isSecretField as isSecretField2 } from "@wrongstack/core/security";
7
+ import { ERROR_CODES as ERROR_CODES2, FsError as FsError2 } from "@wrongstack/core/types";
8
+ import { atomicWrite, toErrorMessage, withFileLock, writeErr } from "@wrongstack/core/utils";
9
+
10
+ // src/config-history-support.ts
4
11
  import * as fs from "node:fs/promises";
5
12
  import os from "node:os";
6
13
  import * as path from "node:path";
7
14
  import { isSecretField } from "@wrongstack/core/security";
8
15
  import { ERROR_CODES, FsError } from "@wrongstack/core/types";
9
- import {
10
- atomicWrite,
11
- resolveWstackPaths,
12
- toErrorMessage,
13
- withFileLock,
14
- writeErr
15
- } from "@wrongstack/core/utils";
16
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
16
17
  var defaultUidFn = () => os.userInfo().uid;
17
18
  async function getFileUid(filePath) {
18
19
  try {
@@ -32,8 +33,6 @@ async function checkConfigOwnership(homeFn, uidFn = defaultUidFn, targetConfigPa
32
33
  return fileUid === callerUid;
33
34
  }
34
35
  var PROTECTED_BASENAMES = /* @__PURE__ */ new Set(["config.json", ".key", "index.json"]);
35
- var MAX_CONFIG_BACKUPS = 10;
36
- var MAX_CONFIG_HISTORY_ENTRIES = 50;
37
36
  function assertSafeToDelete(filename, parentDir) {
38
37
  if (PROTECTED_BASENAMES.has(filename)) {
39
38
  throw new FsError({
@@ -72,19 +71,6 @@ function assertSafeToDelete(filename, parentDir) {
72
71
  });
73
72
  }
74
73
  }
75
- async function safeDelete(filePath) {
76
- const dir = path.dirname(filePath);
77
- const filename = path.basename(filePath);
78
- try {
79
- assertSafeToDelete(filename, dir);
80
- await fs.unlink(filePath);
81
- } catch (err) {
82
- if (err instanceof Error && err.message.startsWith("Refusing")) {
83
- writeErr(`[config-history] SAFETY: ${err.message}
84
- `);
85
- }
86
- }
87
- }
88
74
  var REDACTED = "[REDACTED]";
89
75
  function maskValue(v) {
90
76
  if (Array.isArray(v)) return v.map(maskValue);
@@ -130,6 +116,31 @@ function reviveSecrets(masked, current) {
130
116
  }
131
117
  return out;
132
118
  }
119
+ var defaultHomeDir = () => os.homedir();
120
+ function activeProfileConfigPath(homeFn) {
121
+ const paths = homeFn === defaultHomeDir ? resolveWstackPaths({ projectRoot: process.cwd() }) : resolveWstackPaths({ projectRoot: process.cwd(), userHome: homeFn() });
122
+ return paths.profileConfig(paths.profileName);
123
+ }
124
+ function configPath(homeFn = defaultHomeDir, targetConfigPath) {
125
+ return targetConfigPath ?? activeProfileConfigPath(homeFn);
126
+ }
127
+
128
+ // src/config-history.ts
129
+ var MAX_CONFIG_BACKUPS = 10;
130
+ var MAX_CONFIG_HISTORY_ENTRIES = 50;
131
+ async function safeDelete(filePath) {
132
+ const dir = path2.dirname(filePath);
133
+ const filename = path2.basename(filePath);
134
+ try {
135
+ assertSafeToDelete(filename, dir);
136
+ await fs2.unlink(filePath);
137
+ } catch (err) {
138
+ if (err instanceof Error && err.message.startsWith("Refusing")) {
139
+ writeErr(`[config-history] SAFETY: ${err.message}
140
+ `);
141
+ }
142
+ }
143
+ }
133
144
  function diffSummary(oldCfg, newCfg) {
134
145
  const changes = [];
135
146
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);
@@ -137,7 +148,7 @@ function diffSummary(oldCfg, newCfg) {
137
148
  const o = JSON.stringify(oldCfg[k]);
138
149
  const n = JSON.stringify(newCfg[k]);
139
150
  if (o !== n) {
140
- if (isSecretField(k)) {
151
+ if (isSecretField2(k)) {
141
152
  changes.push(`${k}: [CHANGED]`);
142
153
  } else if (typeof newCfg[k] !== "object") {
143
154
  changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
@@ -148,24 +159,16 @@ function diffSummary(oldCfg, newCfg) {
148
159
  }
149
160
  return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
150
161
  }
151
- var defaultHomeDir = () => os.homedir();
152
- function activeProfileConfigPath(homeFn) {
153
- const paths = homeFn === defaultHomeDir ? resolveWstackPaths({ projectRoot: process.cwd() }) : resolveWstackPaths({ projectRoot: process.cwd(), userHome: homeFn() });
154
- return paths.profileConfig(paths.profileName);
155
- }
156
162
  function historyDir(homeFn = defaultHomeDir, targetConfigPath) {
157
- return path.join(path.dirname(configPath(homeFn, targetConfigPath)), "config.history", "entries");
163
+ return path2.join(path2.dirname(configPath(homeFn, targetConfigPath)), "config.history", "entries");
158
164
  }
159
165
  function historyIndexPath(homeFn = defaultHomeDir, targetConfigPath) {
160
- return path.join(
161
- path.dirname(configPath(homeFn, targetConfigPath)),
166
+ return path2.join(
167
+ path2.dirname(configPath(homeFn, targetConfigPath)),
162
168
  "config.history",
163
169
  "index.json"
164
170
  );
165
171
  }
166
- function configPath(homeFn = defaultHomeDir, targetConfigPath) {
167
- return targetConfigPath ?? activeProfileConfigPath(homeFn);
168
- }
169
172
  function backupLastPath(homeFn = defaultHomeDir, targetConfigPath) {
170
173
  return `${configPath(homeFn, targetConfigPath)}.last`;
171
174
  }
@@ -174,11 +177,11 @@ function entryId(ts) {
174
177
  }
175
178
  async function ensureHistoryDir(homeFn = defaultHomeDir, targetConfigPath) {
176
179
  try {
177
- await fs.mkdir(historyDir(homeFn, targetConfigPath), { recursive: true });
180
+ await fs2.mkdir(historyDir(homeFn, targetConfigPath), { recursive: true });
178
181
  } catch (err) {
179
- throw new FsError({
182
+ throw new FsError2({
180
183
  message: toErrorMessage(err),
181
- code: ERROR_CODES.FS_MKDIR_FAILED,
184
+ code: ERROR_CODES2.FS_MKDIR_FAILED,
182
185
  path: historyDir(homeFn, targetConfigPath),
183
186
  cause: err
184
187
  });
@@ -187,7 +190,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir, targetConfigPath) {
187
190
  async function readIndex(homeFn = defaultHomeDir, targetConfigPath) {
188
191
  let raw;
189
192
  try {
190
- raw = await fs.readFile(historyIndexPath(homeFn, targetConfigPath), "utf8");
193
+ raw = await fs2.readFile(historyIndexPath(homeFn, targetConfigPath), "utf8");
191
194
  } catch (err) {
192
195
  if (err.code !== "ENOENT") throw err;
193
196
  return rebuildIndexFromEntries(homeFn, targetConfigPath);
@@ -208,7 +211,7 @@ async function rebuildIndexFromEntries(homeFn = defaultHomeDir, targetConfigPath
208
211
  const dir = historyDir(homeFn, targetConfigPath);
209
212
  let files;
210
213
  try {
211
- files = (await fs.readdir(dir)).filter((file) => file.endsWith(".json"));
214
+ files = (await fs2.readdir(dir)).filter((file) => file.endsWith(".json"));
212
215
  } catch (err) {
213
216
  if (err.code === "ENOENT") return { version: 1, entries: [] };
214
217
  throw err;
@@ -218,7 +221,7 @@ async function rebuildIndexFromEntries(homeFn = defaultHomeDir, targetConfigPath
218
221
  const id = file.slice(0, -".json".length);
219
222
  let raw;
220
223
  try {
221
- raw = await fs.readFile(path.join(dir, file), "utf8");
224
+ raw = await fs2.readFile(path2.join(dir, file), "utf8");
222
225
  } catch (err) {
223
226
  if (err.code === "ENOENT") continue;
224
227
  entries.push({ id, timestamp: "", description: "" });
@@ -244,9 +247,9 @@ async function writeIndex(idx, homeFn = defaultHomeDir, targetConfigPath) {
244
247
  try {
245
248
  await atomicWrite(historyIndexPath(homeFn, targetConfigPath), JSON.stringify(idx, null, 2));
246
249
  } catch (err) {
247
- throw new FsError({
250
+ throw new FsError2({
248
251
  message: toErrorMessage(err),
249
- code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
252
+ code: ERROR_CODES2.FS_ATOMIC_WRITE_FAILED,
250
253
  path: historyIndexPath(homeFn, targetConfigPath),
251
254
  cause: err
252
255
  });
@@ -258,16 +261,16 @@ async function pruneHistoryEntries(idx, homeFn = defaultHomeDir, targetConfigPat
258
261
  const dir = historyDir(homeFn, targetConfigPath);
259
262
  for (const entry of removed) {
260
263
  try {
261
- await fs.unlink(path.join(dir, `${entry.id}.json`));
264
+ await fs2.unlink(path2.join(dir, `${entry.id}.json`));
262
265
  } catch {
263
266
  }
264
267
  }
265
268
  try {
266
- const files = await fs.readdir(dir);
269
+ const files = await fs2.readdir(dir);
267
270
  await Promise.all(
268
271
  files.filter((file) => file.endsWith(".json") && !keep.has(file)).map(async (file) => {
269
272
  try {
270
- await fs.unlink(path.join(dir, file));
273
+ await fs2.unlink(path2.join(dir, file));
271
274
  } catch {
272
275
  }
273
276
  })
@@ -281,7 +284,7 @@ async function backupCurrent(homeFn = defaultHomeDir, targetConfigPath) {
281
284
  const ts = Date.now();
282
285
  let content;
283
286
  try {
284
- content = await fs.readFile(cfg, "utf8");
287
+ content = await fs2.readFile(cfg, "utf8");
285
288
  } catch {
286
289
  }
287
290
  if (content !== void 0) {
@@ -300,11 +303,11 @@ async function backupCurrent(homeFn = defaultHomeDir, targetConfigPath) {
300
303
  }
301
304
  }
302
305
  try {
303
- const dir = path.dirname(cfg);
304
- const files = await fs.readdir(dir);
306
+ const dir = path2.dirname(cfg);
307
+ const files = await fs2.readdir(dir);
305
308
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
306
309
  for (const f of baks.slice(MAX_CONFIG_BACKUPS)) {
307
- await safeDelete(path.join(dir, f));
310
+ await safeDelete(path2.join(dir, f));
308
311
  }
309
312
  } catch (err) {
310
313
  writeErr(`[config-history] backup prune failed: ${toErrorMessage(err)}`);
@@ -322,16 +325,16 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
322
325
  diffSummary: diffSummary(oldCfg, newCfg)
323
326
  };
324
327
  try {
325
- await fs.writeFile(
326
- path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
328
+ await fs2.writeFile(
329
+ path2.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
327
330
  JSON.stringify(entry, null, 2),
328
331
  { encoding: "utf8", mode: 384 }
329
332
  );
330
333
  } catch (err) {
331
- throw new FsError({
334
+ throw new FsError2({
332
335
  message: toErrorMessage(err),
333
- code: ERROR_CODES.FS_WRITE_FAILED,
334
- path: path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
336
+ code: ERROR_CODES2.FS_WRITE_FAILED,
337
+ path: path2.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
335
338
  cause: err
336
339
  });
337
340
  }
@@ -350,10 +353,10 @@ async function listHistory(homeFn = defaultHomeDir, targetConfigPath) {
350
353
  }
351
354
  async function getHistoryEntry(id, homeFn = defaultHomeDir, targetConfigPath) {
352
355
  const dir = historyDir(homeFn, targetConfigPath);
353
- const filePath = path.join(dir, `${id}.json`);
354
- if (path.dirname(path.resolve(filePath)) !== path.resolve(dir)) return null;
356
+ const filePath = path2.join(dir, `${id}.json`);
357
+ if (path2.dirname(path2.resolve(filePath)) !== path2.resolve(dir)) return null;
355
358
  try {
356
- const raw = await fs.readFile(filePath, "utf8");
359
+ const raw = await fs2.readFile(filePath, "utf8");
357
360
  return JSON.parse(raw);
358
361
  } catch {
359
362
  return null;
@@ -374,7 +377,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir, targetConfigPath)
374
377
  await backupCurrent(homeFn, targetConfigPath);
375
378
  let oldCfg = {};
376
379
  try {
377
- const raw = await fs.readFile(cfg, "utf8");
380
+ const raw = await fs2.readFile(cfg, "utf8");
378
381
  oldCfg = JSON.parse(raw);
379
382
  } catch {
380
383
  }
@@ -399,7 +402,7 @@ async function restoreLast(homeFn = defaultHomeDir, targetConfigPath) {
399
402
  const cfg = configPath(homeFn, targetConfigPath);
400
403
  let lastCfg = {};
401
404
  try {
402
- const raw = await fs.readFile(last, "utf8");
405
+ const raw = await fs2.readFile(last, "utf8");
403
406
  lastCfg = JSON.parse(raw);
404
407
  } catch {
405
408
  return { ok: false, error: "No prior backup found" };
@@ -410,7 +413,7 @@ async function restoreLast(homeFn = defaultHomeDir, targetConfigPath) {
410
413
  return withFileLock(cfg, async () => {
411
414
  let oldCfg = {};
412
415
  try {
413
- const raw = await fs.readFile(cfg, "utf8");
416
+ const raw = await fs2.readFile(cfg, "utf8");
414
417
  oldCfg = JSON.parse(raw);
415
418
  } catch {
416
419
  }
@@ -439,4 +442,4 @@ export {
439
442
  restoreFromHistory,
440
443
  restoreLast
441
444
  };
442
- //# sourceMappingURL=chunk-BIGSVBPI.js.map
445
+ //# sourceMappingURL=chunk-KNYIEQAY.js.map