@wrongstack/cli 1.1.4 → 1.1.5

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 (100) hide show
  1. package/dist/{acp-MZ5MQBFN.js → acp-NKUEYJDZ.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 +2 -0
  5. package/dist/acp-leader/acp-leader-picker.d.ts +6 -3
  6. package/dist/acp-leader/acp-leader-stream.d.ts +1 -0
  7. package/dist/{auth-IHQS6AFW.js → auth-2ZG5DBHS.js} +4 -4
  8. package/dist/boot/launch-menu-arrow.d.ts +20 -0
  9. package/dist/boot/launch-menu-types.d.ts +36 -0
  10. package/dist/boot/launch-menu.d.ts +1 -57
  11. package/dist/boot/tui-session-resume-rollback.d.ts +31 -0
  12. package/dist/{boot-auto-thin-7D4OVEWA.js → boot-auto-thin-N2CIGTXE.js} +3 -3
  13. package/dist/{chunk-TZPKOS5G.js → chunk-2XQ6SRKG.js} +2 -2
  14. package/dist/{chunk-JKTJSLUT.js → chunk-5SGMUDP5.js} +242 -18
  15. package/dist/{chunk-3EXRPTAU.js → chunk-5TQ65PXQ.js} +17 -17
  16. package/dist/{chunk-2LTHDKZX.js → chunk-7BM4LGKQ.js} +18 -18
  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-AWRZWPJ3.js → chunk-JFYQGMMM.js} +99 -75
  20. package/dist/{chunk-BIGSVBPI.js → chunk-KNYIEQAY.js} +64 -61
  21. package/dist/{chunk-GKGWPFW2.js → chunk-LUEO5HLZ.js} +1 -1
  22. package/dist/chunk-LYWOV4OX.js +252 -0
  23. package/dist/{chunk-COGI7EQU.js → chunk-M7FNHCE3.js} +621 -9
  24. package/dist/{chunk-75OLD563.js → chunk-M7PSENGF.js} +3 -1
  25. package/dist/{chunk-TAE3EGBM.js → chunk-MEBXWI7U.js} +5 -5
  26. package/dist/{chunk-I5LJLKA3.js → chunk-MKMM5RIW.js} +12 -5
  27. package/dist/{chunk-ZF4HRR5F.js → chunk-P7MEEUGT.js} +275 -250
  28. package/dist/chunk-QLYPIJ7X.js +258 -0
  29. package/dist/chunk-RN5CFAVQ.js +292 -0
  30. package/dist/{chunk-WDXNBYGD.js → chunk-TDP7CZGE.js} +6 -1
  31. package/dist/{chunk-6VPWQ4TG.js → chunk-UROB7MK5.js} +2 -1
  32. package/dist/chunk-VEESMPTX.js +254 -0
  33. package/dist/{chunk-IJLIAPCW.js → chunk-VPSAKGLK.js} +47 -47
  34. package/dist/{cli-context-E44HQ4ZO.js → cli-context-CQYGIWOU.js} +247 -236
  35. package/dist/{cli-main-OT63I2FX.js → cli-main-VRUJNUBX.js} +1743 -1491
  36. package/dist/cli-main-boot.d.ts +1 -1
  37. package/dist/config-doctor-known-keys.d.ts +33 -0
  38. package/dist/config-doctor.d.ts +2 -47
  39. package/dist/config-history-support.d.ts +36 -0
  40. package/dist/config-history.d.ts +1 -1
  41. package/dist/{config-transfer-Y7XJMJRN.js → config-transfer-B4GOZTFC.js} +2 -2
  42. package/dist/{execution-OK34CJKJ.js → execution-IV72RUSM.js} +5883 -5792
  43. package/dist/execution-tui.d.ts +30 -0
  44. package/dist/fleet/acp-wrongstack-bridge.d.ts +11 -3
  45. package/dist/fleet/host-acp.d.ts +3 -0
  46. package/dist/fleet/wrongforge-client.d.ts +116 -0
  47. package/dist/fleet/wrongforge-gateway.d.ts +20 -0
  48. package/dist/fleet/wrongforge-info.d.ts +32 -0
  49. package/dist/fleet/wrongforge-quota.d.ts +10 -0
  50. package/dist/fleet/wrongforge-runner.d.ts +37 -0
  51. package/dist/fleet/wrongforge-runtime.d.ts +10 -0
  52. package/dist/{hq-JD5AZVMG.js → hq-54PQYR5O.js} +311 -307
  53. package/dist/hq-server/sweeps.d.ts +28 -0
  54. package/dist/{hq-server-BI23EPM7.js → hq-server-YK7FBLL6.js} +3 -3
  55. package/dist/index.js +5 -5
  56. package/dist/mcp-22CFBX4H.js +236 -0
  57. package/dist/mcp-serve.d.ts +0 -63
  58. package/dist/{modeldiag-HDIKG37X.js → modeldiag-5PBB777J.js} +4 -4
  59. package/dist/{per-subcommand-help-4GYSCMLU.js → per-subcommand-help-MQGERD4E.js} +2 -2
  60. package/dist/plugin-llm-command.d.ts +9 -0
  61. package/dist/{plugin-usage-TYF5HY4K.js → plugin-usage-UWKYMNQU.js} +3 -3
  62. package/dist/{plugins-XGAXDLPT.js → plugins-VP7JQH47.js} +4 -4
  63. package/dist/{sdd-runtime-6MFUXWJK.js → sdd-runtime-HIJ4Z5TF.js} +2 -2
  64. package/dist/services/sdd/state.d.ts +2 -0
  65. package/dist/session-event-wiring-types.d.ts +62 -0
  66. package/dist/session-event-wiring.d.ts +2 -52
  67. package/dist/{sessions-config-SJVQXCIE.js → sessions-config-7MK742OW.js} +2 -2
  68. package/dist/short-circuit-flags-AGHURHMQ.js +11 -0
  69. package/dist/slash-commands/command-context-fleet.d.ts +187 -0
  70. package/dist/slash-commands/command-context-sdd-goal.d.ts +102 -0
  71. package/dist/slash-commands/command-context.d.ts +3 -282
  72. package/dist/slash-commands/project-switch-interactive.d.ts +11 -0
  73. package/dist/slash-commands/setmodel-helpers.d.ts +36 -0
  74. package/dist/subcommands/handlers/hq-token.d.ts +3 -0
  75. package/dist/subcommands/handlers/hq.d.ts +0 -21
  76. package/dist/subcommands/handlers/per-subcommand-help-operations.d.ts +8 -0
  77. package/dist/subcommands/handlers/tools-cli-args.d.ts +57 -0
  78. package/dist/subcommands/handlers/tools-cli.d.ts +5 -0
  79. package/dist/subcommands/handlers/tools-serve.d.ts +4 -0
  80. package/dist/{subcommands-YADUV7IJ.js → subcommands-OCJNO57E.js} +2 -2
  81. package/dist/tool-gateway-http.d.ts +30 -0
  82. package/dist/tool-gateway.d.ts +185 -0
  83. package/dist/tools-cli-44MLIUQO.js +509 -0
  84. package/dist/tools-serve-TRCET2M7.js +102 -0
  85. package/dist/{tools-skills-55QVTMSK.js → tools-skills-WDTMCE46.js} +7 -2
  86. package/dist/tuneup-runtime-checks.d.ts +7 -0
  87. package/dist/tuneup-types.d.ts +118 -0
  88. package/dist/tuneup.d.ts +3 -122
  89. package/dist/{typesafe-YBEADNAH.js → typesafe-QLSMFU7J.js} +1 -1
  90. package/dist/{version-help-RBZC5GZZ.js → version-help-X6CC4CFY.js} +2 -2
  91. package/dist/{webui-server-CB64CFDG.js → webui-server-V2R5WQUK.js} +44 -6
  92. package/dist/wiring/provider-runtime-setup.d.ts +3 -1
  93. package/dist/wiring/session-runtime.d.ts +1 -1
  94. package/dist/wrongforge-gateway-UOFBK56J.js +92 -0
  95. package/dist/wrongforge-leader/wrongforge-leader-gateway.d.ts +9 -0
  96. package/dist/wrongforge-leader/wrongforge-leader-picker.d.ts +12 -0
  97. package/dist/wrongforge-leader/wrongforge-leader-provider.d.ts +49 -0
  98. package/package.json +1 -1
  99. package/dist/mcp-W7A5HUXV.js +0 -560
  100. package/dist/short-circuit-flags-QEXUS3BY.js +0 -11
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backupCurrent
3
- } from "./chunk-BIGSVBPI.js";
3
+ } from "./chunk-KNYIEQAY.js";
4
4
 
5
5
  // src/wiring/external-plugins.ts
6
6
  import { isAbsolute, join, resolve } from "node:path";
@@ -345,7 +345,7 @@ import {
345
345
  resolvePluginTarget as resolvePluginTarget2,
346
346
  unpinPluginTrust
347
347
  } from "@wrongstack/core/plugin";
348
- import { atomicWrite } from "@wrongstack/core/utils";
348
+ import { atomicWrite as atomicWrite2 } from "@wrongstack/core/utils";
349
349
  import {
350
350
  PLUGIN_AUDIT_ENTRIES
351
351
  } from "@wrongstack/plugins/plugin-audit-catalog";
@@ -420,6 +420,139 @@ function isRecord(value) {
420
420
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
421
421
  }
422
422
 
423
+ // src/plugin-llm-command.ts
424
+ import { atomicWrite } from "@wrongstack/core/utils";
425
+ var PLUGIN_LLM_USAGE = [
426
+ "Usage:",
427
+ " wstack plugin llm List every per-plugin LLM override.",
428
+ " wstack plugin llm <plugin> Show one plugin's LLM override.",
429
+ " wstack plugin llm <plugin> <provider> [model] Route this plugin through a provider (and model).",
430
+ " wstack plugin llm <plugin> - <model> Keep the session provider, override only the model.",
431
+ " wstack plugin llm <plugin> --clear Remove the override (back to the session default).",
432
+ "",
433
+ "The override is stored as config.extensions.<plugin>.llm and is used by",
434
+ "every api.llm call the plugin makes. Without an override, plugins follow",
435
+ "the active session provider/model (the chat leader)."
436
+ ].join("\n");
437
+ function renderPluginLlmOverviews(config, sessionDefault) {
438
+ const rows = [];
439
+ for (const [pluginName2, ext] of Object.entries(config.extensions ?? {})) {
440
+ const llm = ext?.["llm"];
441
+ if (llm && typeof llm === "object") {
442
+ const o = llm;
443
+ rows.push({
444
+ name: pluginName2,
445
+ provider: o.provider ?? "(session)",
446
+ model: o.model ?? "(session)"
447
+ });
448
+ }
449
+ }
450
+ const lines = [`Per-plugin LLM routing (session default: ${sessionDefault}):`];
451
+ if (rows.length === 0) {
452
+ lines.push(
453
+ " No plugin overrides \u2014 every plugin follows the session default.",
454
+ " Set one with: wstack plugin llm <plugin> <provider> [model]"
455
+ );
456
+ } else {
457
+ rows.sort((a, b) => a.name.localeCompare(b.name));
458
+ for (const r of rows) {
459
+ lines.push(` ${r.name.padEnd(24)} provider=${r.provider.padEnd(16)} model=${r.model}`);
460
+ }
461
+ lines.push("", "Clear one with: wstack plugin llm <plugin> --clear");
462
+ }
463
+ return lines.join("\n");
464
+ }
465
+ async function runPluginLlmCommand(args, deps) {
466
+ const sessionDefault = `${deps.config.provider ?? "?"} / ${deps.config.model ?? "?"}`;
467
+ if (args.length === 0 || args[0] === "list") {
468
+ return {
469
+ code: 0,
470
+ level: "output",
471
+ message: renderPluginLlmOverviews(deps.config, sessionDefault)
472
+ };
473
+ }
474
+ const name = args[0];
475
+ if (!name || name.startsWith("-")) return errorResult(PLUGIN_LLM_USAGE);
476
+ const currentOverride = deps.config.extensions?.[name]?.["llm"];
477
+ if (args.length === 1) {
478
+ const lines = [`LLM routing for plugin "${name}":`];
479
+ if (currentOverride && typeof currentOverride === "object") {
480
+ const o = currentOverride;
481
+ lines.push(
482
+ ` override: provider=${o.provider ?? "(session)"} model=${o.model ?? "(session)"}`,
483
+ ` session default: ${sessionDefault}`
484
+ );
485
+ } else {
486
+ lines.push(
487
+ ` no override \u2014 follows the session default (${sessionDefault}).`,
488
+ ` Set one with: wstack plugin llm ${name} <provider> [model]`
489
+ );
490
+ }
491
+ return { code: 0, level: "output", message: lines.join("\n") };
492
+ }
493
+ const existing = await readConfig(deps.configPath);
494
+ const fileExtensions = isRecord(existing.extensions) ? existing.extensions : {};
495
+ const pluginExt = isRecord(fileExtensions[name]) ? { ...fileExtensions[name] } : {};
496
+ if (args[1] === "--clear" || args[1] === "clear") {
497
+ if (!("llm" in pluginExt) && !currentOverride) {
498
+ return { code: 0, level: "info", message: `Plugin "${name}" has no LLM override.` };
499
+ }
500
+ delete pluginExt["llm"];
501
+ if (Object.keys(pluginExt).length === 0) delete fileExtensions[name];
502
+ else fileExtensions[name] = pluginExt;
503
+ existing.extensions = fileExtensions;
504
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
505
+ const merged2 = mergeExtensionsForPatch(deps.config, fileExtensions);
506
+ const mergedEntry = merged2[name];
507
+ if (mergedEntry) {
508
+ delete mergedEntry["llm"];
509
+ if (Object.keys(mergedEntry).length === 0) delete merged2[name];
510
+ }
511
+ return {
512
+ code: 0,
513
+ level: "info",
514
+ message: `Cleared LLM override for "${name}" \u2014 it now follows the session default (${sessionDefault}). Takes effect immediately for api.llm calls.`,
515
+ patch: { extensions: merged2 }
516
+ };
517
+ }
518
+ const providerArg = args[1];
519
+ const modelArg = args[2];
520
+ if (!providerArg || providerArg.startsWith("--")) return errorResult(PLUGIN_LLM_USAGE);
521
+ const provider = providerArg === "-" ? void 0 : providerArg;
522
+ const model = modelArg && modelArg !== "-" ? modelArg : void 0;
523
+ if (!provider && !model) return errorResult(PLUGIN_LLM_USAGE);
524
+ const llm = {};
525
+ if (provider) llm["provider"] = provider;
526
+ if (model) llm["model"] = model;
527
+ pluginExt["llm"] = llm;
528
+ fileExtensions[name] = pluginExt;
529
+ existing.extensions = fileExtensions;
530
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
531
+ const configured = Object.keys(deps.config.providers ?? {});
532
+ const note = provider && configured.length > 0 && !configured.includes(provider) ? `
533
+ Note: "${provider}" is not in config.providers (configured: ${configured.join(", ")}). Catalog providers (anthropic, openai, \u2026) still resolve if credentials are available.` : "";
534
+ const merged = mergeExtensionsForPatch(deps.config, fileExtensions);
535
+ return {
536
+ code: 0,
537
+ level: "info",
538
+ message: `Plugin "${name}" now routes api.llm calls via provider=${provider ?? "(session)"} model=${model ?? "(session)"}. Session default stays ${sessionDefault}. Takes effect immediately for api.llm calls.${note}`,
539
+ patch: { extensions: merged }
540
+ };
541
+ }
542
+ function mergeExtensionsForPatch(config, fileExtensions) {
543
+ const merged = {};
544
+ for (const [k, v] of Object.entries(config.extensions ?? {})) {
545
+ merged[k] = { ...v };
546
+ }
547
+ for (const [k, v] of Object.entries(fileExtensions)) {
548
+ if (isRecord(v)) merged[k] = { ...merged[k] ?? {}, ...v };
549
+ }
550
+ for (const k of Object.keys(merged)) {
551
+ if (!(k in fileExtensions) && !(k in (config.extensions ?? {}))) delete merged[k];
552
+ }
553
+ return merged;
554
+ }
555
+
423
556
  // src/plugin-package-install.ts
424
557
  import { execFile } from "node:child_process";
425
558
  import * as fs2 from "node:fs/promises";
@@ -710,7 +843,7 @@ ${locked.map((name2) => ` ${name2}`).join("\n")}`
710
843
  const next = action === "lock" ? current.some((entry) => canonicalPolicyName(entry) === canonical) ? current : [...current, name] : current.filter((entry) => canonicalPolicyName(entry) !== canonical);
711
844
  const pluginManager = { locked: next };
712
845
  existing.pluginManager = { ...filePolicy ?? {}, ...pluginManager };
713
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
846
+ await atomicWrite2(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
714
847
  return {
715
848
  code: 0,
716
849
  level: "info",
@@ -722,136 +855,6 @@ function canonicalPolicyName(name) {
722
855
  if (name === "*") return name;
723
856
  return resolvePluginToggleSpecifier(name).trim().toLowerCase();
724
857
  }
725
- var PLUGIN_LLM_USAGE = [
726
- "Usage:",
727
- " wstack plugin llm List every per-plugin LLM override.",
728
- " wstack plugin llm <plugin> Show one plugin's LLM override.",
729
- " wstack plugin llm <plugin> <provider> [model] Route this plugin through a provider (and model).",
730
- " wstack plugin llm <plugin> - <model> Keep the session provider, override only the model.",
731
- " wstack plugin llm <plugin> --clear Remove the override (back to the session default).",
732
- "",
733
- "The override is stored as config.extensions.<plugin>.llm and is used by",
734
- "every api.llm call the plugin makes. Without an override, plugins follow",
735
- "the active session provider/model (the chat leader)."
736
- ].join("\n");
737
- function renderPluginLlmOverviews(config, sessionDefault) {
738
- const rows = [];
739
- for (const [pluginName2, ext] of Object.entries(config.extensions ?? {})) {
740
- const llm = ext?.["llm"];
741
- if (llm && typeof llm === "object") {
742
- const o = llm;
743
- rows.push({
744
- name: pluginName2,
745
- provider: o.provider ?? "(session)",
746
- model: o.model ?? "(session)"
747
- });
748
- }
749
- }
750
- const lines = [`Per-plugin LLM routing (session default: ${sessionDefault}):`];
751
- if (rows.length === 0) {
752
- lines.push(
753
- " No plugin overrides \u2014 every plugin follows the session default.",
754
- " Set one with: wstack plugin llm <plugin> <provider> [model]"
755
- );
756
- } else {
757
- rows.sort((a, b) => a.name.localeCompare(b.name));
758
- for (const r of rows) {
759
- lines.push(` ${r.name.padEnd(24)} provider=${r.provider.padEnd(16)} model=${r.model}`);
760
- }
761
- lines.push("", "Clear one with: wstack plugin llm <plugin> --clear");
762
- }
763
- return lines.join("\n");
764
- }
765
- async function runPluginLlmCommand(args, deps) {
766
- const sessionDefault = `${deps.config.provider ?? "?"} / ${deps.config.model ?? "?"}`;
767
- if (args.length === 0 || args[0] === "list") {
768
- return {
769
- code: 0,
770
- level: "output",
771
- message: renderPluginLlmOverviews(deps.config, sessionDefault)
772
- };
773
- }
774
- const name = args[0];
775
- if (!name || name.startsWith("-")) return errorResult(PLUGIN_LLM_USAGE);
776
- const currentOverride = deps.config.extensions?.[name]?.["llm"];
777
- if (args.length === 1) {
778
- const lines = [`LLM routing for plugin "${name}":`];
779
- if (currentOverride && typeof currentOverride === "object") {
780
- const o = currentOverride;
781
- lines.push(
782
- ` override: provider=${o.provider ?? "(session)"} model=${o.model ?? "(session)"}`,
783
- ` session default: ${sessionDefault}`
784
- );
785
- } else {
786
- lines.push(
787
- ` no override \u2014 follows the session default (${sessionDefault}).`,
788
- ` Set one with: wstack plugin llm ${name} <provider> [model]`
789
- );
790
- }
791
- return { code: 0, level: "output", message: lines.join("\n") };
792
- }
793
- const existing = await readConfig(deps.configPath);
794
- const fileExtensions = isRecord(existing.extensions) ? existing.extensions : {};
795
- const pluginExt = isRecord(fileExtensions[name]) ? { ...fileExtensions[name] } : {};
796
- if (args[1] === "--clear" || args[1] === "clear") {
797
- if (!("llm" in pluginExt) && !currentOverride) {
798
- return { code: 0, level: "info", message: `Plugin "${name}" has no LLM override.` };
799
- }
800
- delete pluginExt["llm"];
801
- if (Object.keys(pluginExt).length === 0) delete fileExtensions[name];
802
- else fileExtensions[name] = pluginExt;
803
- existing.extensions = fileExtensions;
804
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
805
- const merged2 = mergeExtensionsForPatch(deps.config, fileExtensions);
806
- const mergedEntry = merged2[name];
807
- if (mergedEntry) {
808
- delete mergedEntry["llm"];
809
- if (Object.keys(mergedEntry).length === 0) delete merged2[name];
810
- }
811
- return {
812
- code: 0,
813
- level: "info",
814
- message: `Cleared LLM override for "${name}" \u2014 it now follows the session default (${sessionDefault}). Takes effect immediately for api.llm calls.`,
815
- patch: { extensions: merged2 }
816
- };
817
- }
818
- const providerArg = args[1];
819
- const modelArg = args[2];
820
- if (!providerArg || providerArg.startsWith("--")) return errorResult(PLUGIN_LLM_USAGE);
821
- const provider = providerArg === "-" ? void 0 : providerArg;
822
- const model = modelArg && modelArg !== "-" ? modelArg : void 0;
823
- if (!provider && !model) return errorResult(PLUGIN_LLM_USAGE);
824
- const llm = {};
825
- if (provider) llm["provider"] = provider;
826
- if (model) llm["model"] = model;
827
- pluginExt["llm"] = llm;
828
- fileExtensions[name] = pluginExt;
829
- existing.extensions = fileExtensions;
830
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
831
- const configured = Object.keys(deps.config.providers ?? {});
832
- const note = provider && configured.length > 0 && !configured.includes(provider) ? `
833
- Note: "${provider}" is not in config.providers (configured: ${configured.join(", ")}). Catalog providers (anthropic, openai, \u2026) still resolve if credentials are available.` : "";
834
- const merged = mergeExtensionsForPatch(deps.config, fileExtensions);
835
- return {
836
- code: 0,
837
- level: "info",
838
- message: `Plugin "${name}" now routes api.llm calls via provider=${provider ?? "(session)"} model=${model ?? "(session)"}. Session default stays ${sessionDefault}. Takes effect immediately for api.llm calls.${note}`,
839
- patch: { extensions: merged }
840
- };
841
- }
842
- function mergeExtensionsForPatch(config, fileExtensions) {
843
- const merged = {};
844
- for (const [k, v] of Object.entries(config.extensions ?? {})) {
845
- merged[k] = { ...v };
846
- }
847
- for (const [k, v] of Object.entries(fileExtensions)) {
848
- if (isRecord(v)) merged[k] = { ...merged[k] ?? {}, ...v };
849
- }
850
- for (const k of Object.keys(merged)) {
851
- if (!(k in fileExtensions) && !(k in (config.extensions ?? {}))) delete merged[k];
852
- }
853
- return merged;
854
- }
855
858
  function resolvePluginSpecifier(input) {
856
859
  return OFFICIAL_ALIASES.get(input.toLowerCase()) ?? input;
857
860
  }
@@ -1014,7 +1017,7 @@ async function togglePlugin(spec, deps) {
1014
1017
  };
1015
1018
  existing.plugins = nextPlugins;
1016
1019
  existing.features = features;
1017
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1020
+ await atomicWrite2(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1018
1021
  const restartRequired = spec === "branch-guard" && !nextEnabled ? void 0 : true;
1019
1022
  return {
1020
1023
  code: 0,
@@ -1032,7 +1035,7 @@ async function removePlugin(spec, deps) {
1032
1035
  return errorResult(`Plugin "${spec}" not in config.`);
1033
1036
  }
1034
1037
  existing.plugins = next;
1035
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1038
+ await atomicWrite2(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1036
1039
  return {
1037
1040
  code: 0,
1038
1041
  level: "info",
@@ -1136,4 +1139,4 @@ export {
1136
1139
  PLUGIN_AUDIT_ENTRIES,
1137
1140
  runPluginManagementCommand
1138
1141
  };
1139
- //# sourceMappingURL=chunk-VAQXOZKP.js.map
1142
+ //# sourceMappingURL=chunk-AJM4EFCM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  appendHistory,
3
3
  backupCurrent
4
- } from "./chunk-BIGSVBPI.js";
4
+ } from "./chunk-KNYIEQAY.js";
5
5
 
6
6
  // src/settings-menu.ts
7
7
  import * as fs from "node:fs/promises";
@@ -246,4 +246,4 @@ export {
246
246
  persistTelegramConfig,
247
247
  deriveFsAccessPair
248
248
  };
249
- //# sourceMappingURL=chunk-C2BBH4S4.js.map
249
+ //# sourceMappingURL=chunk-E3MJFAFR.js.map
@@ -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";
@@ -3839,9 +3839,6 @@ function createHqSocketCredentialEnforcer(state) {
3839
3839
  };
3840
3840
  }
3841
3841
 
3842
- // src/hq-server/upgrade-handler.ts
3843
- import { hqTokenVerifier as hqTokenVerifier2, isLoopbackHost as isLoopbackHost3, isTokenExpired as isTokenExpired5 } from "@wrongstack/core/hq";
3844
-
3845
3842
  // src/hq-server/ws.ts
3846
3843
  import { randomUUID as randomUUID5 } from "node:crypto";
3847
3844
  import {
@@ -4706,7 +4703,91 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
4706
4703
  });
4707
4704
  }
4708
4705
 
4706
+ // src/hq-server/sweeps.ts
4707
+ var HQ_SESSION_MAX_AGE_MS2 = 7 * 24 * 60 * 60 * 1e3;
4708
+ var HQ_SESSION_IDLE_TIMEOUT_MS2 = 30 * 6e4;
4709
+ var PENDING_2FA_TTL_MS2 = 5 * 6e4;
4710
+ var HQ_SESSION_CLEANUP_INTERVAL_MS = 6e4;
4711
+ function sweepHqBrowserSessions(sessions, loginAttempts, socketCredentials) {
4712
+ const now = Date.now();
4713
+ const maxAgeCutoff = now - HQ_SESSION_MAX_AGE_MS2;
4714
+ const idleCutoff = now - HQ_SESSION_IDLE_TIMEOUT_MS2;
4715
+ const pending2faCutoff = now - PENDING_2FA_TTL_MS2;
4716
+ for (const [id, session] of sessions) {
4717
+ if (session.createdAt < maxAgeCutoff) {
4718
+ sessions.delete(id);
4719
+ socketCredentials.closeSessionSockets(id, "Browser session expired");
4720
+ continue;
4721
+ }
4722
+ if (session.pending2fa) {
4723
+ if (session.createdAt < pending2faCutoff) sessions.delete(id);
4724
+ continue;
4725
+ }
4726
+ if (session.lastSeenAt < idleCutoff) {
4727
+ sessions.delete(id);
4728
+ }
4729
+ }
4730
+ for (const [ip, entry] of loginAttempts.entries()) {
4731
+ if (now - entry.lastAttempt > 15 * 6e4) loginAttempts.delete(ip);
4732
+ }
4733
+ }
4734
+ function sweepStaleHqClients({
4735
+ clients,
4736
+ browsers,
4737
+ auditLog,
4738
+ eventLog,
4739
+ persistence,
4740
+ clientTtlMs,
4741
+ sessionSnapshotTtlMs
4742
+ }) {
4743
+ const cutoff = Date.now() - clientTtlMs;
4744
+ const sessionCutoff = Date.now() - sessionSnapshotTtlMs;
4745
+ let changed = false;
4746
+ for (const [ws, client] of clients.entries()) {
4747
+ if (new Date(client.lastSeenAt).getTime() < cutoff) {
4748
+ const lostClient = client;
4749
+ ws.terminate();
4750
+ clients.delete(ws);
4751
+ failUndeliveredCommands(lostClient, browsers, auditLog);
4752
+ detectLeaderLoss(lostClient, clients, browsers, "heartbeat-timeout", {
4753
+ eventLog,
4754
+ persistence
4755
+ });
4756
+ changed = true;
4757
+ continue;
4758
+ }
4759
+ const expiredSessions = /* @__PURE__ */ new Set();
4760
+ for (const [sessionId, tracked] of client.sessions.entries()) {
4761
+ if (tracked.receivedAt < sessionCutoff) {
4762
+ client.sessions.delete(sessionId);
4763
+ client.mcpSnapshots.delete(sessionId);
4764
+ expiredSessions.add(sessionId);
4765
+ changed = true;
4766
+ }
4767
+ }
4768
+ for (const [runId, fleet] of client.fleets) {
4769
+ if (fleet.sessionId !== void 0 && expiredSessions.has(fleet.sessionId)) {
4770
+ client.fleets.delete(runId);
4771
+ changed = true;
4772
+ }
4773
+ }
4774
+ if (client.capabilities.includes("session.summary") && client.sessions.size === 0 && Date.now() - Date.parse(client.connectedAt) > sessionSnapshotTtlMs) {
4775
+ const lostClient = client;
4776
+ ws.terminate();
4777
+ clients.delete(ws);
4778
+ failUndeliveredCommands(lostClient, browsers, auditLog);
4779
+ detectLeaderLoss(lostClient, clients, browsers, "crash", {
4780
+ eventLog,
4781
+ persistence
4782
+ });
4783
+ changed = true;
4784
+ }
4785
+ }
4786
+ return changed;
4787
+ }
4788
+
4709
4789
  // src/hq-server/upgrade-handler.ts
4790
+ import { hqTokenVerifier as hqTokenVerifier2, isLoopbackHost as isLoopbackHost3, isTokenExpired as isTokenExpired5 } from "@wrongstack/core/hq";
4710
4791
  function handleHqUpgrade(req, socket, head, deps) {
4711
4792
  const url = new URL(req.url ?? "/", `http://${deps.host}:${deps.port}`);
4712
4793
  const pathname = url.pathname;
@@ -4964,33 +5045,10 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4964
5045
  mutableAuth,
4965
5046
  sessions
4966
5047
  });
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);
5048
+ const sessionCleanupTimer = setInterval(
5049
+ () => sweepHqBrowserSessions(sessions, loginAttempts, socketCredentials),
5050
+ HQ_SESSION_CLEANUP_INTERVAL_MS
5051
+ );
4994
5052
  sessionCleanupTimer.unref?.();
4995
5053
  const persistence = createHqPersistence(dataDir);
4996
5054
  const auditLog = new HqCommandAuditLog(1e3, (entry) => persistence.commandLog.append(entry));
@@ -5065,49 +5123,15 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
5065
5123
  const sessionSnapshotTtlMs = options.sessionSnapshotTtlMs ?? SESSION_SNAPSHOT_TTL_MS;
5066
5124
  const cleanupTimer = setInterval(() => {
5067
5125
  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
- }
5126
+ const changed = sweepStaleHqClients({
5127
+ clients,
5128
+ browsers,
5129
+ auditLog,
5130
+ eventLog,
5131
+ persistence,
5132
+ clientTtlMs,
5133
+ sessionSnapshotTtlMs
5134
+ });
5111
5135
  if (changed) snapshotBroadcaster.broadcast();
5112
5136
  }, options.clientCleanupIntervalMs ?? CLIENT_CLEANUP_INTERVAL_MS);
5113
5137
  cleanupTimer.unref?.();
@@ -5361,4 +5385,4 @@ export {
5361
5385
  startHqServer,
5362
5386
  HqInsecureExposureError2 as HqInsecureExposureError
5363
5387
  };
5364
- //# sourceMappingURL=chunk-AWRZWPJ3.js.map
5388
+ //# sourceMappingURL=chunk-JFYQGMMM.js.map