@wrongstack/cli 1.0.19 → 1.0.26

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 (195) hide show
  1. package/data/providers.json +44 -0
  2. package/dist/{acp-INCNOH42.js → acp-LI665LO2.js} +18 -7
  3. package/dist/arg-parser.d.ts +1 -0
  4. package/dist/ascii-mode-SG4PRNLP.js +7 -0
  5. package/dist/{auth-YXK3EDUL.js → auth-TE55JF4Z.js} +68 -22
  6. package/dist/auth-menu/direct.d.ts +1 -0
  7. package/dist/auth-menu/local.d.ts +2 -0
  8. package/dist/auth-menu/oauth-menu.d.ts +3 -1
  9. package/dist/auth-menu/panel-service.d.ts +20 -0
  10. package/dist/{bench-YBU6BVQP.js → bench-GTLPM22B.js} +2 -2
  11. package/dist/boot/append-system-prompt.d.ts +10 -0
  12. package/dist/boot/ascii-mode.d.ts +12 -0
  13. package/dist/boot/config-backup-recovery.d.ts +2 -1
  14. package/dist/boot/container-wiring.d.ts +4 -2
  15. package/dist/boot/dispatch-singleshot.d.ts +4 -1
  16. package/dist/boot/dispatch-webui.d.ts +11 -0
  17. package/dist/boot/mcp-config-flag.d.ts +22 -0
  18. package/dist/boot/piped-stdin.d.ts +40 -0
  19. package/dist/boot/restricted-mode.d.ts +15 -0
  20. package/dist/boot/safe-mode.d.ts +30 -0
  21. package/dist/boot/short-circuit-desktop.d.ts +1 -0
  22. package/dist/boot/single-shot-budget.d.ts +30 -0
  23. package/dist/boot/stream-json.d.ts +36 -0
  24. package/dist/boot/structured-output.d.ts +16 -0
  25. package/dist/boot/system-prompt-builder.d.ts +2 -0
  26. package/dist/boot/tool-restriction-flags.d.ts +36 -0
  27. package/dist/boot/tui-project-picker-callback.d.ts +1 -1
  28. package/dist/boot/tui-settings-adapter.d.ts +15 -2
  29. package/dist/boot/worktree-sessions.d.ts +21 -0
  30. package/dist/{boot-auto-thin-MANHOYHE.js → boot-auto-thin-6Q56FY3F.js} +3 -2
  31. package/dist/boot-provider-selection.d.ts +67 -0
  32. package/dist/boot.d.ts +1 -38
  33. package/dist/{chunk-JY52HWL6.js → chunk-2EBVTYJR.js} +2 -2
  34. package/dist/{chunk-RDHFXOJM.js → chunk-2ERX3RPN.js} +112 -65
  35. package/dist/chunk-4TVYGG6G.js +62 -0
  36. package/dist/chunk-5JYDKG5B.js +154 -0
  37. package/dist/{chunk-KEPABQYT.js → chunk-5SEYGOCH.js} +8 -82
  38. package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
  39. package/dist/chunk-BBHWW6LR.js +549 -0
  40. package/dist/chunk-BEHGRNOQ.js +58 -0
  41. package/dist/chunk-CZTXF6HY.js +140 -0
  42. package/dist/{chunk-JX6KL7OF.js → chunk-EPR42SWM.js} +39 -8
  43. package/dist/chunk-F5DCFAKI.js +36 -0
  44. package/dist/chunk-HLSHFXTO.js +97 -0
  45. package/dist/{chunk-CJTBLTS4.js → chunk-KJS5543T.js} +4 -3
  46. package/dist/{chunk-DE7BZDNK.js → chunk-KOEO4CJG.js} +27 -4
  47. package/dist/{chunk-SIS37L3W.js → chunk-L2RVEQWO.js} +211 -193
  48. package/dist/chunk-LBPLTTZ6.js +105 -0
  49. package/dist/chunk-LDCGZJTC.js +46 -0
  50. package/dist/chunk-LKPEBZBE.js +143 -0
  51. package/dist/chunk-LUMETJFO.js +171 -0
  52. package/dist/{chunk-KK2LB3ZS.js → chunk-OAVBM55Q.js} +9 -5
  53. package/dist/chunk-PZQBGUVZ.js +18 -0
  54. package/dist/{chunk-C3Z4N6A6.js → chunk-Q4INH2DG.js} +51 -3
  55. package/dist/chunk-R4QGL6GB.js +10 -0
  56. package/dist/{chunk-YLQEGYV4.js → chunk-RZ4ZDRYA.js} +14 -4
  57. package/dist/chunk-S4FFHDJG.js +93 -0
  58. package/dist/chunk-SFBFYIOW.js +271 -0
  59. package/dist/{chunk-RMQNCHGO.js → chunk-TI7KLHYX.js} +136 -11
  60. package/dist/{chunk-2ENZU66X.js → chunk-WQV2NP57.js} +5 -5
  61. package/dist/{chunk-B4JLSXZB.js → chunk-WSTVGC2C.js} +73 -45
  62. package/dist/chunk-X756ZS72.js +329 -0
  63. package/dist/{chunk-XJ7K67PP.js → chunk-YHYINL47.js} +63 -24
  64. package/dist/{chunk-2VMJ3E5Z.js → chunk-YTEOAWOJ.js} +6 -2
  65. package/dist/{chunk-GPPRNAKW.js → chunk-ZMX5HRLI.js} +2 -2
  66. package/dist/{cli-context-33F74CSZ.js → cli-context-UVGAN3NO.js} +496 -384
  67. package/dist/cli-entry-point.d.ts +2 -0
  68. package/dist/{cli-main-FNGMKPBA.js → cli-main-TN3KZ2P6.js} +3561 -2724
  69. package/dist/cli-update-notice.d.ts +2 -1
  70. package/dist/config-doctor.d.ts +10 -2
  71. package/dist/{config-transfer-2YF3CRTN.js → config-transfer-RUWLDCNK.js} +34 -13
  72. package/dist/{diag-doctor-ANPRUYFM.js → diag-doctor-BIZAFIAT.js} +2 -2
  73. package/dist/execute-deps.d.ts +2 -1
  74. package/dist/{execution-A6WJLSOC.js → execution-7QIP6GRY.js} +2384 -2055
  75. package/dist/fleet/host-types.d.ts +3 -1
  76. package/dist/goal-commands.d.ts +47 -0
  77. package/dist/goal-host.d.ts +1 -14
  78. package/dist/{hq-XQWQTRDA.js → hq-GLZ7QESM.js} +277 -261
  79. package/dist/hq-command-controller.d.ts +1 -1
  80. package/dist/{hq-server-IIBZQZU4.js → hq-server-YTKDVQPY.js} +2 -2
  81. package/dist/{hq-service-L5INQFM4.js → hq-service-BMMJAHRT.js} +11 -4
  82. package/dist/import-claude-code-32PCSIQI.js +236 -0
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.js +19 -10
  85. package/dist/kanban-supervisor-67ECABTD.js +7 -0
  86. package/dist/live-settings-input.d.ts +7 -0
  87. package/dist/{mailbox-serve-3GFOK2I7.js → mailbox-serve-NW5VZG7R.js} +3 -3
  88. package/dist/{mcp-DC5WSBOO.js → mcp-LBS45HWZ.js} +4 -4
  89. package/dist/modeldiag-36VODVIH.js +828 -0
  90. package/dist/{per-subcommand-help-DHTYU524.js → per-subcommand-help-QDAQASPZ.js} +3 -3
  91. package/dist/permission-prompt-mirror.d.ts +1 -1
  92. package/dist/permission-prompt.d.ts +16 -2
  93. package/dist/permission-rules-view.d.ts +18 -0
  94. package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
  95. package/dist/plugin-config.d.ts +12 -0
  96. package/dist/plugin-management-types.d.ts +31 -0
  97. package/dist/plugin-management.d.ts +2 -30
  98. package/dist/plugin-package-install.d.ts +3 -0
  99. package/dist/{plugin-usage-M6KET4OZ.js → plugin-usage-CIGOOTX3.js} +6 -5
  100. package/dist/{plugins-WJLW6ULV.js → plugins-HQTXUD6A.js} +6 -3
  101. package/dist/preflight.d.ts +1 -1
  102. package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
  103. package/dist/provider-config-utils.d.ts +1 -6
  104. package/dist/provider-helpers.d.ts +7 -11
  105. package/dist/{providers-models-LFNZ5WI7.js → providers-models-5Z2PJTEW.js} +44 -34
  106. package/dist/release-asset-download.d.ts +13 -0
  107. package/dist/release-attestation.d.ts +28 -0
  108. package/dist/remote/remote-session.d.ts +64 -0
  109. package/dist/remote/remote-target.d.ts +69 -0
  110. package/dist/remote-O5WXHLWV.js +561 -0
  111. package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
  112. package/dist/services/commit-message.d.ts +2 -1
  113. package/dist/services/dispatch-classifier.d.ts +39 -5
  114. package/dist/session-move-P2EC2COA.js +44 -0
  115. package/dist/session-move.d.ts +20 -0
  116. package/dist/session-stats.d.ts +10 -0
  117. package/dist/{sessions-config-UL45XKX7.js → sessions-config-SXTTLNXA.js} +47 -9
  118. package/dist/settings-menu.d.ts +2 -0
  119. package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
  120. package/dist/{simpleui-dist-CXSIDAXZ.js → simpleui-dist-7BQISSEP.js} +7 -3
  121. package/dist/skill-suggest-L2EVWB6R.js +290 -0
  122. package/dist/slash-commands/command-context.d.ts +15 -5
  123. package/dist/slash-commands/effort.d.ts +1 -1
  124. package/dist/slash-commands/fallback.d.ts +1 -1
  125. package/dist/slash-commands/gitid.d.ts +1 -1
  126. package/dist/slash-commands/goal-refiner.d.ts +4 -49
  127. package/dist/slash-commands/jev.d.ts +4 -0
  128. package/dist/slash-commands/mcp.d.ts +2 -1
  129. package/dist/slash-commands/openai-quota.d.ts +15 -0
  130. package/dist/slash-commands/permissions.d.ts +4 -0
  131. package/dist/slash-commands/sdd-task-mutations.d.ts +4 -0
  132. package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
  133. package/dist/slash-commands/settings-keys.d.ts +2 -0
  134. package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
  135. package/dist/slash-commands/settings-reset.d.ts +6 -0
  136. package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
  137. package/dist/standalone-auto-update.d.ts +65 -0
  138. package/dist/standalone-update-LZO3MNLH.js +24 -0
  139. package/dist/standalone-update.d.ts +55 -0
  140. package/dist/subcommands/handlers/hq-alerts.d.ts +3 -0
  141. package/dist/subcommands/handlers/hq-audit.d.ts +46 -0
  142. package/dist/subcommands/handlers/hq-data-dir.d.ts +13 -0
  143. package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
  144. package/dist/subcommands/handlers/permissions.d.ts +9 -3
  145. package/dist/subcommands/handlers/remote.d.ts +6 -0
  146. package/dist/subcommands/handlers/skill-suggest.d.ts +21 -0
  147. package/dist/subcommands/handlers/typesafe-brain-replay.d.ts +26 -0
  148. package/dist/subcommands/handlers/typesafe-judgment-check.d.ts +3 -0
  149. package/dist/subcommands/handlers/typesafe-topic-replay.d.ts +19 -0
  150. package/dist/subcommands/handlers/typesafe-triage-replay.d.ts +18 -0
  151. package/dist/subcommands/handlers/typesafe.d.ts +23 -0
  152. package/dist/subcommands/handlers/version-help.d.ts +5 -0
  153. package/dist/{subcommands-XSLXMIDP.js → subcommands-Q6ORSB2E.js} +2 -2
  154. package/dist/tui-resume-callback.d.ts +18 -0
  155. package/dist/typesafe-U7YB2RRV.js +316 -0
  156. package/dist/typesafe-brain-replay-C5276FVR.js +222 -0
  157. package/dist/typesafe-judgment-check-GGYYV5UF.js +29 -0
  158. package/dist/typesafe-topic-replay-ZILIU3WS.js +234 -0
  159. package/dist/typesafe-triage-replay-FGAW7QM7.js +215 -0
  160. package/dist/{update-5DPB4YD4.js → update-TDZX4IL2.js} +4 -3
  161. package/dist/{update-check-BS3DHTSI.js → update-check-J5ZVKS73.js} +3 -2
  162. package/dist/update-check.d.ts +1 -1
  163. package/dist/version-JB2UAV2A.js +11 -0
  164. package/dist/version-help-XJJQ7YSH.js +10 -0
  165. package/dist/version.d.ts +3 -2
  166. package/dist/webui-client-observability.d.ts +7 -0
  167. package/dist/webui-server/route-contexts.d.ts +1 -1
  168. package/dist/webui-server/session-start-payload.d.ts +5 -1
  169. package/dist/{webui-server-MRPGTTKL.js → webui-server-YTHLRFD2.js} +577 -370
  170. package/dist/webui-server-lifecycle.d.ts +57 -0
  171. package/dist/webui-server-options.d.ts +11 -0
  172. package/dist/webui-server.d.ts +1 -1
  173. package/dist/wiring/brain-and-orchestration.d.ts +3 -1
  174. package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
  175. package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
  176. package/dist/wiring/domain-glossary.d.ts +5 -4
  177. package/dist/wiring/lifecycle-plugins.d.ts +4 -2
  178. package/dist/wiring/metrics.d.ts +9 -1
  179. package/dist/wiring/pipeline.d.ts +2 -7
  180. package/dist/wiring/plugins.d.ts +4 -1
  181. package/dist/wiring/provider-persisters.d.ts +24 -0
  182. package/dist/wiring/provider-runtime.d.ts +9 -1
  183. package/dist/wiring/provider-status.d.ts +1 -0
  184. package/dist/wiring/provider-utility-tools.d.ts +3 -1
  185. package/dist/wiring/runtime-dispatch-state.d.ts +3 -1
  186. package/package.json +28 -28
  187. package/dist/chunk-2CZEORLV.js +0 -54
  188. package/dist/chunk-672UXYL3.js +0 -65
  189. package/dist/chunk-6WRKAACA.js +0 -96
  190. package/dist/chunk-DR2OXU46.js +0 -174
  191. package/dist/chunk-JACOMTIH.js +0 -275
  192. package/dist/chunk-XJXDOF63.js +0 -28
  193. package/dist/modeldiag-CJSFHZ3J.js +0 -1356
  194. package/dist/short-circuit-flags-7GQIGMVD.js +0 -10
  195. package/dist/version-help-WXI42PZ3.js +0 -10
@@ -1,108 +1,30 @@
1
1
  import {
2
- createKanbanRunMirror
3
- } from "./chunk-VCYZ3WZV.js";
2
+ createKanbanSupervisor
3
+ } from "./chunk-R4QGL6GB.js";
4
4
  import {
5
5
  WEBUI_SESSION_CHILD_CAPABILITIES
6
- } from "./chunk-GPPRNAKW.js";
6
+ } from "./chunk-ZMX5HRLI.js";
7
+ import {
8
+ createKanbanRunMirror
9
+ } from "./chunk-VCYZ3WZV.js";
7
10
  import {
8
11
  createHqCommandDispatcher,
9
12
  createProjectKanbanAssignHandler,
10
13
  createProjectKanbanTransitionHandler
11
- } from "./chunk-JY52HWL6.js";
14
+ } from "./chunk-2EBVTYJR.js";
12
15
  import {
13
16
  startCliHqConnection
14
- } from "./chunk-CJTBLTS4.js";
17
+ } from "./chunk-KJS5543T.js";
15
18
  import {
16
19
  CLI_VERSION
17
- } from "./chunk-XJXDOF63.js";
20
+ } from "./chunk-F5DCFAKI.js";
18
21
  import {
19
22
  loadConfigProviders,
20
23
  mutateConfigProviders
21
- } from "./chunk-DR2OXU46.js";
24
+ } from "./chunk-LUMETJFO.js";
22
25
 
23
- // src/webui-server.ts
24
- import * as path7 from "node:path";
25
- import { createCompatibilityTrustBoundary, DefaultSecretScrubber } from "@wrongstack/core/security";
26
- import {
27
- addFatalSalvageHook,
28
- startSharedHeapWatchdog,
29
- wstackGlobalRoot
30
- } from "@wrongstack/core/utils";
31
- import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils/error";
32
- import {
33
- buildWebUIAccessUrl,
34
- clientWantsSession,
35
- collectDisplayedSessionIds,
36
- createCustomModeStore,
37
- createEmbeddedMessageRouter,
38
- createEmbeddedProviderOperations,
39
- createSessionAgentRegistry,
40
- createWebuiLeaderAutoWakeHost,
41
- envFlag,
42
- findFreePort,
43
- findInstalledPackageJson,
44
- isStrictPort,
45
- resolveAuthToken,
46
- resolvePendingConfirmsForSession,
47
- scheduleOwnerlessEmptySessionCleanup,
48
- sendSerialized,
49
- stampDispatchSession,
50
- startTerminalDashboard,
51
- startWebUILiveStatusLogger,
52
- toSessionHistoryEntries
53
- } from "@wrongstack/webui-server";
54
- import { verifyClient as verifyWsClient2 } from "@wrongstack/webui-server/server/ws-auth";
55
- import { WebSocketServer } from "ws";
56
-
57
- // src/webui-server/client-registration.ts
58
- import { mailboxSessionTag } from "@wrongstack/core/coordination";
59
- import { createWebuiClientPresence } from "@wrongstack/webui-server";
60
- function createWebuiClientRegistration(deps) {
61
- const control = deps.hqControl;
62
- return createWebuiClientPresence({
63
- projectRoot: deps.projectRoot,
64
- appConfig: deps.appConfig,
65
- events: deps.events,
66
- hqSessionId: deps.hqSessionId,
67
- getSessionId: deps.getSessionId,
68
- clientVersion: CLI_VERSION,
69
- ...deps.listSessions ? { listSessions: deps.listSessions } : {},
70
- ...deps.isSessionOwnedElsewhere ? { isSessionOwnedElsewhere: deps.isSessionOwnedElsewhere } : {},
71
- ...deps.getSessionWriter ? { getSessionWriter: deps.getSessionWriter } : {},
72
- startHqConnection: (options) => startCliHqConnection({
73
- ...options,
74
- capabilities: options.capabilities
75
- }),
76
- ...control ? {
77
- createCommandHandler: (mailbox, approvals) => createHqCommandDispatcher({
78
- steerMailbox: mailbox,
79
- interruptLeader: control.interruptLeader,
80
- allowRunCommand: control.allowRunCommand,
81
- // Same registry the approval bridge publishes from, so every
82
- // prompt HQ shows is one this handler can actually answer.
83
- resolveApproval: (toolUseId, decision, sessionId) => approvals.resolve(toolUseId, decision, sessionId),
84
- ...deps.projectRoot ? {
85
- kanbanTransition: createProjectKanbanTransitionHandler(deps.projectRoot),
86
- kanbanAssign: createProjectKanbanAssignHandler(deps.projectRoot)
87
- } : {},
88
- // The session HQ actually SHOWS for this client — the boot
89
- // session the telemetry bridge publishes under and the only one
90
- // `interruptLeader` speaks for. `getSessionId` follows whichever
91
- // browser tab is in front, which is a different session HQ has
92
- // no view of and cannot control.
93
- sessionTag: () => mailboxSessionTag(deps.hqSessionId),
94
- // The session HQ falls back to when a command names none — the
95
- // boot tab. A command that DOES name one is honoured (and
96
- // refused when this host no longer has it) via `ownsSession`.
97
- sessionId: () => deps.hqSessionId,
98
- ...control.ownsSession ? { ownsSession: control.ownsSession } : {},
99
- ...control.spawnAgent ? { spawnAgent: control.spawnAgent } : {},
100
- ...control.killFleet ? { killFleet: control.killFleet } : {},
101
- ...control.terminateAgent ? { terminateAgent: control.terminateAgent } : {}
102
- })
103
- } : {}
104
- });
105
- }
26
+ // src/webui-server-lifecycle.ts
27
+ import { toErrorMessage } from "@wrongstack/core/utils/error";
106
28
 
107
29
  // src/webui-server/connection-handler.ts
108
30
  import { deriveSessionAgents } from "@wrongstack/core/storage";
@@ -230,15 +152,406 @@ function createConnectionHandler(deps) {
230
152
  });
231
153
  }
232
154
 
155
+ // src/webui-server/lifecycle.ts
156
+ import {
157
+ announceWebuiReady,
158
+ createWebuiShutdown,
159
+ registerWebuiInstance,
160
+ registerWebuiSignalHandlers
161
+ } from "@wrongstack/webui-server";
162
+
163
+ // src/webui-server/logger-shim.ts
164
+ var structuredLine = (level, message) => JSON.stringify({
165
+ level,
166
+ event: "webui.goal",
167
+ message,
168
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
169
+ });
170
+ var consoleLogger = {
171
+ level: "debug",
172
+ error(msg, _ctx) {
173
+ console.error(structuredLine("error", msg));
174
+ },
175
+ warn(msg, _ctx) {
176
+ console.warn(structuredLine("warn", msg));
177
+ },
178
+ info(msg, _ctx) {
179
+ console.log(structuredLine("info", msg));
180
+ },
181
+ debug(msg, _ctx) {
182
+ console.debug(structuredLine("debug", msg));
183
+ },
184
+ trace(msg, _ctx) {
185
+ console.debug(structuredLine("trace", msg));
186
+ },
187
+ child(_bindings) {
188
+ return this;
189
+ }
190
+ };
191
+
192
+ // src/webui-server-lifecycle.ts
193
+ function runWebuiServerLifecycle({
194
+ host,
195
+ httpPort,
196
+ setupEvents,
197
+ opts,
198
+ wsPort,
199
+ accessUrl,
200
+ wsToken,
201
+ webuiInstanceRegistered,
202
+ wss,
203
+ httpServer,
204
+ requireToken,
205
+ publicHostnames,
206
+ publicWsUrl,
207
+ clients,
208
+ currentSessionId,
209
+ goalHandler,
210
+ specsHandler,
211
+ sddBoardHandler,
212
+ sddWizardHandler,
213
+ worktreeHandler,
214
+ terminalHandler,
215
+ rateLimitMax,
216
+ send,
217
+ sessionPayload,
218
+ handleMessage,
219
+ pendingConfirms,
220
+ buildSessionStartPayload,
221
+ sessionAgents,
222
+ lifecycleState,
223
+ abortControllers,
224
+ flushAllStreamBuffers,
225
+ eventUnsubscribers,
226
+ releaseSessionSalvage,
227
+ autoWakeHost,
228
+ stopEmptySessionCleanup,
229
+ credentialWatcherClose,
230
+ kanbanRunMirror,
231
+ kanbanSupervisor,
232
+ stopKanbanSupervisorMemoryStats,
233
+ unregisterWebuiClient,
234
+ ipv6LoopbackServer,
235
+ registryBaseDir,
236
+ stopLiveStatusLogger,
237
+ terminalLogView
238
+ }) {
239
+ const stopped = new Promise((resolve) => {
240
+ let listeningAnnounced = false;
241
+ const announceListening = () => {
242
+ if (listeningAnnounced) return;
243
+ listeningAnnounced = true;
244
+ console.log(`[WebUI] WebSocket server running on ws://${host}:${httpPort}`);
245
+ try {
246
+ setupEvents();
247
+ opts.onListening?.({
248
+ httpPort,
249
+ wsPort,
250
+ host,
251
+ url: accessUrl,
252
+ authToken: wsToken,
253
+ webuiInstanceRegistered
254
+ });
255
+ } catch (err) {
256
+ consoleLogger.error("setup_events_failed", { message: toErrorMessage(err) });
257
+ }
258
+ };
259
+ wss.on("listening", announceListening);
260
+ if (httpServer?.server.listening || wss.address()) queueMicrotask(announceListening);
261
+ wss.on(
262
+ "connection",
263
+ createConnectionHandler({
264
+ host,
265
+ wsToken,
266
+ requireToken,
267
+ publicHostnames,
268
+ publicWsUrl,
269
+ clients,
270
+ currentSessionId,
271
+ goalHandler,
272
+ specsHandler,
273
+ sddBoardHandler,
274
+ sddWizardHandler,
275
+ worktreeHandler,
276
+ terminalHandler,
277
+ rateLimitMax,
278
+ send,
279
+ sessionPayload,
280
+ handleMessage,
281
+ pendingConfirms,
282
+ buildSessionStartPayload,
283
+ loadReplay: async () => {
284
+ const activeSession = opts.agent.ctx.session ?? opts.session;
285
+ await activeSession.flush();
286
+ if (opts.sessionStore) {
287
+ const data = await opts.sessionStore.load(activeSession.id);
288
+ return { messages: data.messages, events: data.events, usage: data.usage };
289
+ }
290
+ const usage = opts.agent.ctx.tokenCounter.total();
291
+ return { messages: opts.agent.ctx.messages, usage };
292
+ },
293
+ loadAgentSessions: async (subagentIds) => await opts.agentTranscripts?.loadSessionsFromDisk(subagentIds) ?? [],
294
+ // What this process is actually holding right now. The browser
295
+ // reconciles its persisted tab strip against it, so a restarted server
296
+ // is not dressed in the previous run's tabs.
297
+ openSessionIds: () => sessionAgents.ids(),
298
+ needsSetup: opts.needsSetup ?? false
299
+ })
300
+ );
301
+ lifecycleState.signalShutdown = createWebuiShutdown({
302
+ abortInFlight: () => {
303
+ void lifecycleState.embeddedAutoHealDispose?.();
304
+ for (const c of abortControllers.values()) c.abort();
305
+ abortControllers.clear();
306
+ },
307
+ unsubscribeEvents: () => {
308
+ flushAllStreamBuffers();
309
+ for (const unsub of eventUnsubscribers) unsub();
310
+ },
311
+ stopOwnedChildren: async () => {
312
+ try {
313
+ const { getProcessRegistry } = await import("@wrongstack/tools");
314
+ getProcessRegistry().killAll({ force: true, includeProtected: true });
315
+ } catch (err) {
316
+ console.debug(
317
+ `[webui-server] process-registry killAll failed: ${err instanceof Error ? err.message : String(err)}`
318
+ );
319
+ }
320
+ if (opts.mcpRegistry) {
321
+ try {
322
+ await opts.mcpRegistry.stopAll();
323
+ } catch (err) {
324
+ console.debug(
325
+ `[webui-server] mcpRegistry.stopAll failed: ${err instanceof Error ? err.message : String(err)}`
326
+ );
327
+ }
328
+ }
329
+ },
330
+ disposeResources: async () => {
331
+ releaseSessionSalvage();
332
+ autoWakeHost?.dispose();
333
+ await stopEmptySessionCleanup?.dispose();
334
+ await sessionAgents.closeAll().catch(() => void 0);
335
+ credentialWatcherClose?.();
336
+ credentialWatcherClose = void 0;
337
+ goalHandler.dispose();
338
+ sddBoardHandler.dispose();
339
+ worktreeHandler.dispose();
340
+ terminalHandler.dispose();
341
+ kanbanRunMirror?.dispose();
342
+ kanbanSupervisor?.dispose();
343
+ void stopKanbanSupervisorMemoryStats?.();
344
+ await lifecycleState.embeddedAutoHealDispose?.();
345
+ lifecycleState.embeddedAutoHealDispose = null;
346
+ unregisterWebuiClient();
347
+ },
348
+ closeClients: () => {
349
+ for (const [ws] of clients) ws.close();
350
+ clients.clear();
351
+ },
352
+ closeHttpServer: () => {
353
+ ipv6LoopbackServer?.close();
354
+ httpServer?.server.close();
355
+ },
356
+ wss,
357
+ pid: process.pid,
358
+ registryBaseDir,
359
+ onStopped: () => {
360
+ stopLiveStatusLogger();
361
+ const muted = terminalLogView.mutedCount;
362
+ terminalLogView.stop();
363
+ if (muted > 0) {
364
+ console.log(
365
+ `[WebUI] ${muted} progress line(s) kept out of this terminal \u2014 set WEBUI_LOGS=1 to stream them.`
366
+ );
367
+ }
368
+ opts.onExit?.();
369
+ resolve();
370
+ }
371
+ });
372
+ registerWebuiSignalHandlers(lifecycleState.signalShutdown);
373
+ });
374
+ return { stopped };
375
+ }
376
+
377
+ // src/webui-server.ts
378
+ import * as path7 from "node:path";
379
+ import { createCompatibilityTrustBoundary, DefaultSecretScrubber } from "@wrongstack/core/security";
380
+ import {
381
+ addFatalSalvageHook,
382
+ startSharedHeapWatchdog,
383
+ wstackGlobalRoot
384
+ } from "@wrongstack/core/utils";
385
+ import {
386
+ buildWebUIAccessUrl,
387
+ clientWantsSession,
388
+ collectDisplayedSessionIds,
389
+ createCustomModeStore,
390
+ createEmbeddedMessageRouter,
391
+ createEmbeddedProviderOperations,
392
+ createSessionAgentRegistry,
393
+ createWebuiLeaderAutoWakeHost,
394
+ envFlag,
395
+ findFreePort,
396
+ findInstalledPackageJson,
397
+ isStrictPort,
398
+ resolveAuthToken,
399
+ resolvePendingConfirmsForSession,
400
+ scheduleOwnerlessEmptySessionCleanup,
401
+ sendSerialized,
402
+ stampDispatchSession,
403
+ startTerminalDashboard,
404
+ toSessionHistoryEntries,
405
+ webuiSessionFrameLog
406
+ } from "@wrongstack/webui-server";
407
+ import { verifyClient as verifyWsClient2 } from "@wrongstack/webui-server/server/ws-auth";
408
+ import { WebSocketServer } from "ws";
409
+
410
+ // src/webui-client-observability.ts
411
+ import { startWebUILiveStatusLogger } from "@wrongstack/webui-server";
412
+
413
+ // src/webui-server/client-registration.ts
414
+ import { mailboxSessionTag } from "@wrongstack/core/coordination";
415
+ import { createWebuiClientPresence } from "@wrongstack/webui-server";
416
+ function createWebuiClientRegistration(deps) {
417
+ const control = deps.hqControl;
418
+ return createWebuiClientPresence({
419
+ projectRoot: deps.projectRoot,
420
+ appConfig: deps.appConfig,
421
+ events: deps.events,
422
+ hqSessionId: deps.hqSessionId,
423
+ getSessionId: deps.getSessionId,
424
+ clientVersion: CLI_VERSION,
425
+ ...deps.listSessions ? { listSessions: deps.listSessions } : {},
426
+ ...deps.isSessionOwnedElsewhere ? { isSessionOwnedElsewhere: deps.isSessionOwnedElsewhere } : {},
427
+ ...deps.getSessionWriter ? { getSessionWriter: deps.getSessionWriter } : {},
428
+ startHqConnection: (options) => startCliHqConnection({
429
+ ...options,
430
+ capabilities: options.capabilities
431
+ }),
432
+ ...control ? {
433
+ createCommandHandler: (mailbox, approvals) => createHqCommandDispatcher({
434
+ steerMailbox: mailbox,
435
+ interruptLeader: control.interruptLeader,
436
+ allowRunCommand: control.allowRunCommand,
437
+ // Same registry the approval bridge publishes from, so every
438
+ // prompt HQ shows is one this handler can actually answer.
439
+ resolveApproval: (toolUseId, decision, sessionId) => approvals.resolve(toolUseId, decision, sessionId),
440
+ ...deps.projectRoot ? {
441
+ kanbanTransition: createProjectKanbanTransitionHandler(deps.projectRoot),
442
+ kanbanAssign: createProjectKanbanAssignHandler(deps.projectRoot)
443
+ } : {},
444
+ // The session HQ actually SHOWS for this client — the boot
445
+ // session the telemetry bridge publishes under and the only one
446
+ // `interruptLeader` speaks for. `getSessionId` follows whichever
447
+ // browser tab is in front, which is a different session HQ has
448
+ // no view of and cannot control.
449
+ sessionTag: () => mailboxSessionTag(deps.hqSessionId),
450
+ // The session HQ falls back to when a command names none — the
451
+ // boot tab. A command that DOES name one is honoured (and
452
+ // refused when this host no longer has it) via `ownsSession`.
453
+ sessionId: () => deps.hqSessionId,
454
+ ...control.ownsSession ? { ownsSession: control.ownsSession } : {},
455
+ ...control.spawnAgent ? { spawnAgent: control.spawnAgent } : {},
456
+ ...control.killFleet ? { killFleet: control.killFleet } : {},
457
+ ...control.terminateAgent ? { terminateAgent: control.terminateAgent } : {}
458
+ })
459
+ } : {}
460
+ });
461
+ }
462
+
463
+ // src/webui-client-observability.ts
464
+ function createEmbeddedClientRegistration(opts, clients, abortControllers, getSessionAgents) {
465
+ return createWebuiClientRegistration({
466
+ projectRoot: opts.projectRoot,
467
+ appConfig: opts.appConfig,
468
+ events: opts.events,
469
+ hqSessionId: opts.session.id,
470
+ getSessionId: () => opts.agent.ctx.session?.id ?? opts.session.id,
471
+ // One HQ session per open tab. The set is the same one the terminal
472
+ // panel lists: every session a connected browser is displaying.
473
+ listSessions: () => {
474
+ const ids = /* @__PURE__ */ new Set();
475
+ for (const client of clients.values()) {
476
+ if (client.sessionId) ids.add(client.sessionId);
477
+ for (const id of client.sessionIds ?? []) ids.add(id);
478
+ }
479
+ return [...ids];
480
+ },
481
+ // `cli-main` already runs a tracker and a bridge for the boot session.
482
+ // Announcing it here too would put two trackers on one bus flushing the
483
+ // same agent list.
484
+ isSessionOwnedElsewhere: (sessionId) => sessionId === opts.session.id,
485
+ // The tab's own journal, so HQ takes its turns from the write path
486
+ // instead of tailing the file.
487
+ getSessionWriter: (sessionId) => getSessionAgents()?.peek(sessionId)?.ctx.session,
488
+ hqControl: {
489
+ // HQ speaks for the LEADER — the boot session, the one it registered
490
+ // itself under (`hqSessionId`) — not for whatever else the browser
491
+ // has open. This used to abort every controller in the map and clear
492
+ // it, so a remote "interrupt" issued against the leader also killed
493
+ // the three other tabs' in-flight runs. Deleting the entries was
494
+ // wrong on its own terms too: the run's own `end()` owns removal, and
495
+ // clearing early makes `isRunActive` lie to every tab still running.
496
+ // Every open tab is its own session with its own abort controller, so
497
+ // the command's session is the one that gets stopped. Falling back to
498
+ // the boot session keeps a dashboard that sends no session — every one
499
+ // before this existed — behaving exactly as before.
500
+ interruptLeader: (sessionId) => {
501
+ const leaderId = sessionId ?? opts.session.id;
502
+ const controller = abortControllers.get(leaderId);
503
+ if (!controller) return false;
504
+ controller.abort();
505
+ try {
506
+ void Promise.resolve(opts.stopSessionFleet?.(leaderId)).catch(() => void 0);
507
+ } catch {
508
+ }
509
+ return true;
510
+ },
511
+ allowRunCommand: () => opts.hqAllowExec === true,
512
+ // A command naming a tab this process no longer holds is refused, not
513
+ // redirected onto the boot session: the operator picked a terminal,
514
+ // and steering a different one is worse than not steering at all.
515
+ ownsSession: (sessionId) => sessionId === opts.session.id || getSessionAgents()?.has(sessionId) === true
516
+ }
517
+ });
518
+ }
519
+ function startEmbeddedLiveStatusLogger(opts, clients, abortControllers, getSessionAgents, terminalLogView) {
520
+ return startWebUILiveStatusLogger({
521
+ events: opts.events,
522
+ dashboard: terminalLogView,
523
+ getSessionList: () => {
524
+ const ids = /* @__PURE__ */ new Set();
525
+ for (const client of clients.values()) {
526
+ if (client.sessionId) ids.add(client.sessionId);
527
+ for (const id of client.sessionIds ?? []) ids.add(id);
528
+ }
529
+ const currentId = opts.agent.ctx.session?.id ?? opts.session.id;
530
+ if (ids.size === 0 && currentId) ids.add(currentId);
531
+ return Array.from(ids).map((id) => {
532
+ const ctx = getSessionAgents()?.peek(id)?.ctx;
533
+ return {
534
+ id,
535
+ model: ctx?.model ?? opts.agent.ctx.model ?? "",
536
+ provider: ctx?.provider?.id ?? opts.agent.ctx.provider?.id ?? "",
537
+ isRunning: abortControllers.has(id)
538
+ };
539
+ });
540
+ }
541
+ });
542
+ }
543
+
233
544
  // src/webui-server/credential-watcher.ts
234
545
  import { TOKENS } from "@wrongstack/core/kernel";
235
546
  import { watchProviderConfig } from "@wrongstack/core/storage";
236
- import { toErrorMessage } from "@wrongstack/core/utils/error";
547
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils/error";
237
548
  import { makeProviderFromConfig } from "@wrongstack/providers";
238
549
 
239
550
  // src/webui-server/provider-config.ts
240
551
  import * as path from "node:path";
241
552
  import { DefaultSecretVault } from "@wrongstack/core/security";
553
+ import { ProviderConfigSnapshots } from "@wrongstack/providers";
554
+ var snapshots = new ProviderConfigSnapshots();
242
555
  function getVault(globalConfigPath) {
243
556
  const configDir = path.dirname(globalConfigPath ?? "");
244
557
  const parentDir = path.dirname(configDir);
@@ -248,7 +561,7 @@ function getVault(globalConfigPath) {
248
561
  }
249
562
  async function loadSavedProviders(globalConfigPath) {
250
563
  if (!globalConfigPath) return {};
251
- return loadConfigProviders(globalConfigPath, getVault(globalConfigPath));
564
+ return snapshots.track(await loadConfigProviders(globalConfigPath, getVault(globalConfigPath)));
252
565
  }
253
566
  async function saveProviders(globalConfigPath, providers) {
254
567
  if (!globalConfigPath) return;
@@ -256,14 +569,56 @@ async function saveProviders(globalConfigPath, providers) {
256
569
  globalConfigPath,
257
570
  getVault(globalConfigPath),
258
571
  (existing) => {
572
+ const merged = snapshots.merge(existing, providers);
259
573
  for (const key of Object.keys(existing)) delete existing[key];
260
- Object.assign(existing, providers);
574
+ Object.assign(existing, merged);
575
+ for (const key of Object.keys(providers)) delete providers[key];
576
+ Object.assign(providers, merged);
261
577
  }
262
578
  );
579
+ snapshots.track(providers);
263
580
  }
264
581
  function createProviderConfigStore(globalConfigPath, configProvidersRef) {
582
+ if (configProvidersRef && globalConfigPath) {
583
+ const views = /* @__PURE__ */ new WeakMap();
584
+ const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
585
+ return {
586
+ load: async () => {
587
+ const disk = await loadSavedProviders(globalConfigPath);
588
+ const providers = structuredClone(configProvidersRef());
589
+ views.set(providers, { view: structuredClone(providers), disk });
590
+ return providers;
591
+ },
592
+ save: async (providers) => {
593
+ const baseline = views.get(providers);
594
+ if (!baseline) {
595
+ throw new Error("Load the provider list before saving. Refresh and try again.");
596
+ }
597
+ const pending = structuredClone(baseline.disk);
598
+ snapshots.track(pending);
599
+ for (const id of /* @__PURE__ */ new Set([...Object.keys(baseline.view), ...Object.keys(providers)])) {
600
+ if (equal(baseline.view[id], providers[id])) continue;
601
+ if (!equal(baseline.view[id], baseline.disk[id])) {
602
+ throw new Error(
603
+ `Provider "${id}" comes from another config file. Edit its source config or create a new auth profile alias.`
604
+ );
605
+ }
606
+ if (Object.hasOwn(providers, id)) pending[id] = structuredClone(providers[id]);
607
+ else delete pending[id];
608
+ }
609
+ await saveProviders(globalConfigPath, pending);
610
+ for (const id of Object.keys(providers)) {
611
+ if (equal(baseline.view[id], baseline.disk[id])) delete providers[id];
612
+ }
613
+ for (const [id, record] of Object.entries(pending)) {
614
+ if (equal(baseline.view[id], baseline.disk[id])) providers[id] = record;
615
+ }
616
+ views.set(providers, { view: structuredClone(providers), disk: structuredClone(pending) });
617
+ }
618
+ };
619
+ }
265
620
  return {
266
- load: () => configProvidersRef ? Promise.resolve(configProvidersRef()) : loadSavedProviders(globalConfigPath),
621
+ load: () => configProvidersRef ? Promise.resolve(snapshots.track(structuredClone(configProvidersRef()))) : loadSavedProviders(globalConfigPath),
267
622
  save: (providers) => saveProviders(globalConfigPath, providers)
268
623
  };
269
624
  }
@@ -344,7 +699,7 @@ function startWebuiCredentialWatcher({
344
699
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
345
700
  } catch (err) {
346
701
  console.warn(
347
- `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage(err)}`
702
+ `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage2(err)}`
348
703
  );
349
704
  }
350
705
  },
@@ -364,52 +719,24 @@ function startWebuiCredentialWatcher({
364
719
 
365
720
  // src/webui-server/domain-handlers.ts
366
721
  import * as os from "node:os";
367
- import * as path3 from "node:path";
368
- import { resolveWstackPaths } from "@wrongstack/core/utils";
369
- import {
370
- buildSddWizardDeps,
371
- GoalWebSocketHandler,
372
- SddBoardWebSocketHandler,
373
- SddWizardWebSocketHandler,
374
- SpecsWebSocketHandler,
375
- TerminalWebSocketHandler,
376
- WorktreeWebSocketHandler
377
- } from "@wrongstack/webui-server";
378
-
379
- // src/webui-server/logger-shim.ts
380
- var structuredLine = (level, message) => JSON.stringify({
381
- level,
382
- event: "webui.goal",
383
- message,
384
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
385
- });
386
- var consoleLogger = {
387
- level: "debug",
388
- error(msg, _ctx) {
389
- console.error(structuredLine("error", msg));
390
- },
391
- warn(msg, _ctx) {
392
- console.warn(structuredLine("warn", msg));
393
- },
394
- info(msg, _ctx) {
395
- console.log(structuredLine("info", msg));
396
- },
397
- debug(msg, _ctx) {
398
- console.debug(structuredLine("debug", msg));
399
- },
400
- trace(msg, _ctx) {
401
- console.debug(structuredLine("trace", msg));
402
- },
403
- child(_bindings) {
404
- return this;
405
- }
406
- };
722
+ import * as path3 from "node:path";
723
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
724
+ import {
725
+ buildSddWizardDeps,
726
+ GoalWebSocketHandler,
727
+ SddBoardWebSocketHandler,
728
+ SddWizardWebSocketHandler,
729
+ SpecsWebSocketHandler,
730
+ TerminalWebSocketHandler,
731
+ WorktreeWebSocketHandler
732
+ } from "@wrongstack/webui-server";
407
733
 
408
734
  // src/webui-server/node-pty-loader.ts
409
735
  import { existsSync } from "node:fs";
410
736
  import { createRequire } from "node:module";
411
737
  import * as path2 from "node:path";
412
738
  import { fileURLToPath } from "node:url";
739
+ import { isStandaloneBinary } from "@wrongstack/core/utils";
413
740
  import { repairNodePtySpawnHelper } from "@wrongstack/webui-server";
414
741
  function createNodePtyLoader(importMetaUrl, logger) {
415
742
  const requireFromCli = createRequire(importMetaUrl);
@@ -421,6 +748,10 @@ function createNodePtyLoader(importMetaUrl, logger) {
421
748
  };
422
749
  return () => {
423
750
  if (cachedNodePty !== void 0) return cachedNodePty;
751
+ if (isStandaloneBinary()) {
752
+ cachedNodePty = null;
753
+ return null;
754
+ }
424
755
  try {
425
756
  const webuiEntry = requireFromCli.resolve("@wrongstack/webui");
426
757
  const webuiDir = webuiEntry.replace(/[\\/]dist.*$/, "");
@@ -467,7 +798,7 @@ function createNodePtyLoader(importMetaUrl, logger) {
467
798
 
468
799
  // src/webui-server/domain-handlers.ts
469
800
  function createWebuiDomainHandlers(opts, trustBoundary, kanbanRunMirror) {
470
- const goalStoreDir = opts.projectRoot ? path3.join(opts.projectRoot, ".wrongstack", "goal") : path3.join(os.tmpdir(), ".wrongstack", "goal");
801
+ const goalStoreDir = opts.projectRoot ? resolveWstackPaths({ projectRoot: opts.projectRoot }).projectAutophase : path3.join(os.tmpdir(), ".wrongstack", "goal");
471
802
  const goalHandler = new GoalWebSocketHandler(
472
803
  opts.agent,
473
804
  opts.agent.ctx,
@@ -475,7 +806,8 @@ function createWebuiDomainHandlers(opts, trustBoundary, kanbanRunMirror) {
475
806
  goalStoreDir,
476
807
  opts.events,
477
808
  opts.projectRoot,
478
- kanbanRunMirror ? (graphId, state) => kanbanRunMirror.onGoalState(graphId, state) : void 0
809
+ kanbanRunMirror ? (graphId, state) => kanbanRunMirror.onGoalState(graphId, state) : void 0,
810
+ opts.sddSubagentFactory
479
811
  );
480
812
  const worktreeHandler = new WorktreeWebSocketHandler(opts.events, consoleLogger);
481
813
  const loadNodePtyViaWebui = createNodePtyLoader(import.meta.url, consoleLogger);
@@ -607,10 +939,10 @@ function createCliKanbanHostRoutes(deps) {
607
939
  });
608
940
  return;
609
941
  }
610
- const snapshots = await deps.kanbanSupervisor.auditNow(boardId);
942
+ const snapshots2 = await deps.kanbanSupervisor.auditNow(boardId);
611
943
  send(ws, {
612
944
  type: "kanban.supervisor.audit",
613
- payload: { success: true, data: boardId ? snapshots[0] ?? null : snapshots }
945
+ payload: { success: true, data: boardId ? snapshots2[0] ?? null : snapshots2 }
614
946
  });
615
947
  },
616
948
  runStart: async (ws, msg) => {
@@ -702,19 +1034,6 @@ function createCliKanbanHostRoutes(deps) {
702
1034
  };
703
1035
  }
704
1036
 
705
- // src/webui-server/kanban-supervisor.ts
706
- import {
707
- createKanbanSupervisor
708
- } from "@wrongstack/webui-server";
709
-
710
- // src/webui-server/lifecycle.ts
711
- import {
712
- announceWebuiReady,
713
- createWebuiShutdown,
714
- registerWebuiInstance,
715
- registerWebuiSignalHandlers
716
- } from "@wrongstack/webui-server";
717
-
718
1037
  // src/webui-server/listen-helpers.ts
719
1038
  import * as http from "node:http";
720
1039
  import { listenWithRetry } from "@wrongstack/webui-server";
@@ -878,6 +1197,7 @@ import {
878
1197
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
879
1198
  import { SkillInstaller } from "@wrongstack/core/skills";
880
1199
  import { PromptUsageStore } from "@wrongstack/core/storage";
1200
+ import { resolveTokenSavingTier } from "@wrongstack/core/types";
881
1201
  import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
882
1202
  import {
883
1203
  createChimeraRouteHandlers,
@@ -1012,6 +1332,16 @@ function createWebuiRouteContexts({
1012
1332
  pendingConfirms,
1013
1333
  configStore: opts.agent.container?.safeResolve?.(TOKENS2.ConfigStore),
1014
1334
  systemPrompt: {
1335
+ previewContext: (sessionId) => {
1336
+ const target = sessionId ? getSessionAgent(sessionId).ctx : opts.agent.ctx;
1337
+ return {
1338
+ toolNames: target.tools.map((tool) => tool.name),
1339
+ tier: resolveTokenSavingTier(
1340
+ opts.appConfig?.features?.tokenSavingMode,
1341
+ target.provider?.capabilities?.maxContext
1342
+ )
1343
+ };
1344
+ },
1015
1345
  paths: () => {
1016
1346
  const wpaths = resolveWstackPaths3({ projectRoot: promptProjectRoot(), globalRoot });
1017
1347
  return {
@@ -1161,6 +1491,7 @@ function createWebuiRouteContexts({
1161
1491
  // src/webui-server/session-start-payload.ts
1162
1492
  import * as path6 from "node:path";
1163
1493
  import { DEFAULT_CONTEXT_WINDOW_MODE_ID } from "@wrongstack/core/types";
1494
+ import { catalogProviderIdFor } from "@wrongstack/providers";
1164
1495
  import { protocolAdvertisement } from "@wrongstack/webui-protocol";
1165
1496
 
1166
1497
  // src/webui-server/cost-helpers.ts
@@ -1182,7 +1513,11 @@ function createSessionStartPayloadBuilder(deps) {
1182
1513
  const updateInfo = deps.updateInfo;
1183
1514
  try {
1184
1515
  if (deps.modelsRegistry) {
1185
- const m = await deps.modelsRegistry.getModel(ctx.provider.id, ctx.model);
1516
+ const catalogId = catalogProviderIdFor(
1517
+ ctx.provider.id,
1518
+ deps.appConfig?.providers?.[ctx.provider.id]?.type
1519
+ );
1520
+ const m = await deps.modelsRegistry.getModel(catalogId, ctx.model);
1186
1521
  const registryMax = m?.capabilities.maxContext;
1187
1522
  maxContext = registryMax ?? ctx.provider.capabilities?.maxContext ?? 0;
1188
1523
  const rates = getCostRates(m);
@@ -1432,63 +1767,14 @@ async function runWebUI(opts) {
1432
1767
  get session() {
1433
1768
  return opts.session;
1434
1769
  },
1770
+ get appConfig() {
1771
+ return opts.appConfig;
1772
+ },
1435
1773
  // `peek`, never `get`: building a payload must not materialise an agent
1436
1774
  // for a session id that arrived from a stale browser tab.
1437
1775
  getSessionContext: (sessionId) => sessionAgentsRef?.peek(sessionId)?.ctx
1438
1776
  });
1439
- const { register: registerWebuiClient, unregister: unregisterWebuiClient } = createWebuiClientRegistration({
1440
- projectRoot: opts.projectRoot,
1441
- appConfig: opts.appConfig,
1442
- events: opts.events,
1443
- hqSessionId: opts.session.id,
1444
- getSessionId: () => opts.agent.ctx.session?.id ?? opts.session.id,
1445
- // One HQ session per open tab. The set is the same one the terminal
1446
- // panel lists: every session a connected browser is displaying.
1447
- listSessions: () => {
1448
- const ids = /* @__PURE__ */ new Set();
1449
- for (const client of clients.values()) {
1450
- if (client.sessionId) ids.add(client.sessionId);
1451
- for (const id of client.sessionIds ?? []) ids.add(id);
1452
- }
1453
- return [...ids];
1454
- },
1455
- // `cli-main` already runs a tracker and a bridge for the boot session.
1456
- // Announcing it here too would put two trackers on one bus flushing the
1457
- // same agent list.
1458
- isSessionOwnedElsewhere: (sessionId) => sessionId === opts.session.id,
1459
- // The tab's own journal, so HQ takes its turns from the write path
1460
- // instead of tailing the file.
1461
- getSessionWriter: (sessionId) => sessionAgentsRef?.peek(sessionId)?.ctx.session,
1462
- hqControl: {
1463
- // HQ speaks for the LEADER — the boot session, the one it registered
1464
- // itself under (`hqSessionId`) — not for whatever else the browser
1465
- // has open. This used to abort every controller in the map and clear
1466
- // it, so a remote "interrupt" issued against the leader also killed
1467
- // the three other tabs' in-flight runs. Deleting the entries was
1468
- // wrong on its own terms too: the run's own `end()` owns removal, and
1469
- // clearing early makes `isRunActive` lie to every tab still running.
1470
- // Every open tab is its own session with its own abort controller, so
1471
- // the command's session is the one that gets stopped. Falling back to
1472
- // the boot session keeps a dashboard that sends no session — every one
1473
- // before this existed — behaving exactly as before.
1474
- interruptLeader: (sessionId) => {
1475
- const leaderId = sessionId ?? opts.session.id;
1476
- const controller = abortControllers.get(leaderId);
1477
- if (!controller) return false;
1478
- controller.abort();
1479
- try {
1480
- void Promise.resolve(opts.stopSessionFleet?.(leaderId)).catch(() => void 0);
1481
- } catch {
1482
- }
1483
- return true;
1484
- },
1485
- allowRunCommand: () => opts.hqAllowExec === true,
1486
- // A command naming a tab this process no longer holds is refused, not
1487
- // redirected onto the boot session: the operator picked a terminal,
1488
- // and steering a different one is worse than not steering at all.
1489
- ownsSession: (sessionId) => sessionId === opts.session.id || sessionAgentsRef?.has(sessionId) === true
1490
- }
1491
- });
1777
+ const { register: registerWebuiClient, unregister: unregisterWebuiClient } = createEmbeddedClientRegistration(opts, clients, abortControllers, () => sessionAgentsRef);
1492
1778
  registerWebuiClient();
1493
1779
  const wsToken = resolveAuthToken(opts.accessToken);
1494
1780
  const publicHostnames = [publicUrl, publicWsUrl].map((value) => {
@@ -1690,7 +1976,7 @@ async function runWebUI(opts) {
1690
1976
  log: (m) => console.log(m)
1691
1977
  };
1692
1978
  const embeddedProviderOperations = createEmbeddedProviderOperations(wsHandlerCtx);
1693
- let credentialWatcherClose = startWebuiCredentialWatcher({
1979
+ const credentialWatcherClose = startWebuiCredentialWatcher({
1694
1980
  opts,
1695
1981
  profileConfigPath,
1696
1982
  broadcast,
@@ -1728,28 +2014,13 @@ async function runWebUI(opts) {
1728
2014
  broadcast,
1729
2015
  logger: { warn: (message) => consoleLogger.warn(message) }
1730
2016
  }) : void 0;
1731
- const stopLiveStatusLogger = startWebUILiveStatusLogger({
1732
- events: opts.events,
1733
- dashboard: terminalLogView,
1734
- getSessionList: () => {
1735
- const ids = /* @__PURE__ */ new Set();
1736
- for (const client of clients.values()) {
1737
- if (client.sessionId) ids.add(client.sessionId);
1738
- for (const id of client.sessionIds ?? []) ids.add(id);
1739
- }
1740
- const currentId = opts.agent.ctx.session?.id ?? opts.session.id;
1741
- if (ids.size === 0 && currentId) ids.add(currentId);
1742
- return Array.from(ids).map((id) => {
1743
- const ctx = sessionAgentsRef?.peek(id)?.ctx;
1744
- return {
1745
- id,
1746
- model: ctx?.model ?? opts.agent.ctx.model ?? "",
1747
- provider: ctx?.provider?.id ?? opts.agent.ctx.provider?.id ?? "",
1748
- isRunning: abortControllers.has(id)
1749
- };
1750
- });
1751
- }
1752
- });
2017
+ const stopLiveStatusLogger = startEmbeddedLiveStatusLogger(
2018
+ opts,
2019
+ clients,
2020
+ abortControllers,
2021
+ () => sessionAgentsRef,
2022
+ terminalLogView
2023
+ );
1753
2024
  const releaseSessionSalvage = addFatalSalvageHook(() => {
1754
2025
  try {
1755
2026
  sessionAgents.flushAllSync();
@@ -1779,7 +2050,15 @@ async function runWebUI(opts) {
1779
2050
  void sessionAgents.endAndClose(sessionId);
1780
2051
  }
1781
2052
  };
1782
- const stopEmptySessionCleanup = opts.sessionStore ? scheduleOwnerlessEmptySessionCleanup({
2053
+ const refreshSessionHistory = async () => {
2054
+ const list = await opts.sessionStore?.list(200);
2055
+ if (!list) return;
2056
+ broadcastEveryone({
2057
+ type: "sessions.list",
2058
+ payload: { sessions: toSessionHistoryEntries(list, foregroundSession.id) }
2059
+ });
2060
+ };
2061
+ const emptySessionCleanup = opts.sessionStore ? scheduleOwnerlessEmptySessionCleanup({
1783
2062
  getSessionStore: () => opts.sessionStore,
1784
2063
  getActiveSessionId: () => foregroundSession.id,
1785
2064
  getActiveSessionIds: () => collectDisplayedSessionIds({ getSession: () => foregroundSession, clients }),
@@ -1787,16 +2066,19 @@ async function runWebUI(opts) {
1787
2066
  // written its first record yet looks exactly like a session nobody
1788
2067
  // ever used.
1789
2068
  hasParticipants: (sessionId) => abortControllers.has(sessionId),
1790
- refreshSessions: async () => {
1791
- const list = await opts.sessionStore?.list(200);
1792
- if (!list) return;
1793
- broadcastEveryone({
1794
- type: "sessions.list",
1795
- payload: { sessions: toSessionHistoryEntries(list, foregroundSession.id) }
1796
- });
1797
- },
2069
+ refreshSessions: refreshSessionHistory,
1798
2070
  logger: consoleLogger
1799
2071
  }) : null;
2072
+ const offSessionRenamed = opts.sessionStore ? opts.events.on("session.renamed", () => {
2073
+ void refreshSessionHistory().catch(() => void 0);
2074
+ }) : void 0;
2075
+ const stopEmptySessionCleanup = emptySessionCleanup && {
2076
+ runNow: emptySessionCleanup.runNow,
2077
+ dispose: async () => {
2078
+ offSessionRenamed?.();
2079
+ await emptySessionCleanup.dispose();
2080
+ }
2081
+ };
1800
2082
  const routeContexts = createWebuiRouteContexts({
1801
2083
  opts,
1802
2084
  profileConfigPath,
@@ -1837,6 +2119,7 @@ async function runWebUI(opts) {
1837
2119
  const shutdown = () => signalShutdown?.();
1838
2120
  let embeddedAutoHealDispose = null;
1839
2121
  const handleMessage = createEmbeddedMessageRouter({
2122
+ jevVault: getVault(opts.globalConfigPath ?? opts.profileConfigPath),
1840
2123
  trustBoundary,
1841
2124
  opts,
1842
2125
  logger: consoleLogger,
@@ -1872,148 +2155,72 @@ async function runWebUI(opts) {
1872
2155
  kanbanHostRoutes,
1873
2156
  statusTracker: opts.statusTracker
1874
2157
  });
1875
- const stopped = new Promise((resolve) => {
1876
- let listeningAnnounced = false;
1877
- const announceListening = () => {
1878
- if (listeningAnnounced) return;
1879
- listeningAnnounced = true;
1880
- console.log(`[WebUI] WebSocket server running on ws://${host}:${httpPort}`);
1881
- try {
1882
- setupEvents();
1883
- opts.onListening?.({
1884
- httpPort,
1885
- wsPort,
1886
- host,
1887
- url: accessUrl,
1888
- authToken: wsToken,
1889
- webuiInstanceRegistered
1890
- });
1891
- } catch (err) {
1892
- consoleLogger.error("setup_events_failed", { message: toErrorMessage2(err) });
1893
- }
1894
- };
1895
- wss.on("listening", announceListening);
1896
- if (httpServer?.server.listening || wss.address()) queueMicrotask(announceListening);
1897
- wss.on(
1898
- "connection",
1899
- createConnectionHandler({
1900
- host,
1901
- wsToken,
1902
- requireToken,
1903
- publicHostnames,
1904
- publicWsUrl,
1905
- clients,
1906
- currentSessionId,
1907
- goalHandler,
1908
- specsHandler,
1909
- sddBoardHandler,
1910
- sddWizardHandler,
1911
- worktreeHandler,
1912
- terminalHandler,
1913
- rateLimitMax,
1914
- send,
1915
- sessionPayload,
1916
- handleMessage,
1917
- pendingConfirms,
1918
- buildSessionStartPayload,
1919
- loadReplay: async () => {
1920
- const activeSession = opts.agent.ctx.session ?? opts.session;
1921
- await activeSession.flush();
1922
- if (opts.sessionStore) {
1923
- const data = await opts.sessionStore.load(activeSession.id);
1924
- return { messages: data.messages, events: data.events, usage: data.usage };
1925
- }
1926
- const usage = opts.agent.ctx.tokenCounter.total();
1927
- return { messages: opts.agent.ctx.messages, usage };
1928
- },
1929
- loadAgentSessions: async (subagentIds) => await opts.agentTranscripts?.loadSessionsFromDisk(subagentIds) ?? [],
1930
- // What this process is actually holding right now. The browser
1931
- // reconciles its persisted tab strip against it, so a restarted server
1932
- // is not dressed in the previous run's tabs.
1933
- openSessionIds: () => sessionAgents.ids(),
1934
- needsSetup: opts.needsSetup ?? false
1935
- })
1936
- );
1937
- signalShutdown = createWebuiShutdown({
1938
- abortInFlight: () => {
1939
- void embeddedAutoHealDispose?.();
1940
- for (const c of abortControllers.values()) c.abort();
1941
- abortControllers.clear();
1942
- },
1943
- unsubscribeEvents: () => {
1944
- flushAllStreamBuffers();
1945
- for (const unsub of eventUnsubscribers) unsub();
1946
- },
1947
- stopOwnedChildren: async () => {
1948
- try {
1949
- const { getProcessRegistry } = await import("@wrongstack/tools");
1950
- getProcessRegistry().killAll({ force: true, includeProtected: true });
1951
- } catch (err) {
1952
- console.debug(
1953
- `[webui-server] process-registry killAll failed: ${err instanceof Error ? err.message : String(err)}`
1954
- );
1955
- }
1956
- if (opts.mcpRegistry) {
1957
- try {
1958
- await opts.mcpRegistry.stopAll();
1959
- } catch (err) {
1960
- console.debug(
1961
- `[webui-server] mcpRegistry.stopAll failed: ${err instanceof Error ? err.message : String(err)}`
1962
- );
1963
- }
1964
- }
1965
- },
1966
- disposeResources: async () => {
1967
- releaseSessionSalvage();
1968
- autoWakeHost?.dispose();
1969
- await stopEmptySessionCleanup?.dispose();
1970
- await sessionAgents.closeAll().catch(() => void 0);
1971
- credentialWatcherClose?.();
1972
- credentialWatcherClose = void 0;
1973
- goalHandler.dispose();
1974
- sddBoardHandler.dispose();
1975
- worktreeHandler.dispose();
1976
- terminalHandler.dispose();
1977
- kanbanRunMirror?.dispose();
1978
- kanbanSupervisor?.dispose();
1979
- void stopKanbanSupervisorMemoryStats?.();
1980
- await embeddedAutoHealDispose?.();
1981
- embeddedAutoHealDispose = null;
1982
- unregisterWebuiClient();
2158
+ const { stopped } = runWebuiServerLifecycle({
2159
+ host,
2160
+ httpPort,
2161
+ setupEvents,
2162
+ opts,
2163
+ wsPort,
2164
+ accessUrl,
2165
+ wsToken,
2166
+ webuiInstanceRegistered,
2167
+ wss,
2168
+ httpServer,
2169
+ requireToken,
2170
+ publicHostnames,
2171
+ publicWsUrl,
2172
+ clients,
2173
+ currentSessionId,
2174
+ goalHandler,
2175
+ specsHandler,
2176
+ sddBoardHandler,
2177
+ sddWizardHandler,
2178
+ worktreeHandler,
2179
+ terminalHandler,
2180
+ rateLimitMax,
2181
+ send,
2182
+ sessionPayload,
2183
+ handleMessage,
2184
+ pendingConfirms,
2185
+ buildSessionStartPayload,
2186
+ sessionAgents,
2187
+ lifecycleState: {
2188
+ get signalShutdown() {
2189
+ return signalShutdown;
1983
2190
  },
1984
- closeClients: () => {
1985
- for (const [ws] of clients) ws.close();
1986
- clients.clear();
2191
+ set signalShutdown(value) {
2192
+ signalShutdown = value;
1987
2193
  },
1988
- closeHttpServer: () => {
1989
- ipv6LoopbackServer?.close();
1990
- httpServer?.server.close();
2194
+ get embeddedAutoHealDispose() {
2195
+ return embeddedAutoHealDispose;
1991
2196
  },
1992
- wss,
1993
- pid: process.pid,
1994
- registryBaseDir,
1995
- onStopped: () => {
1996
- stopLiveStatusLogger();
1997
- const muted = terminalLogView.mutedCount;
1998
- terminalLogView.stop();
1999
- if (muted > 0) {
2000
- console.log(
2001
- `[WebUI] ${muted} progress line(s) kept out of this terminal \u2014 set WEBUI_LOGS=1 to stream them.`
2002
- );
2003
- }
2004
- opts.onExit?.();
2005
- resolve();
2197
+ set embeddedAutoHealDispose(value) {
2198
+ embeddedAutoHealDispose = value;
2006
2199
  }
2007
- });
2008
- registerWebuiSignalHandlers(signalShutdown);
2200
+ },
2201
+ abortControllers,
2202
+ flushAllStreamBuffers,
2203
+ eventUnsubscribers,
2204
+ releaseSessionSalvage,
2205
+ autoWakeHost,
2206
+ stopEmptySessionCleanup,
2207
+ credentialWatcherClose,
2208
+ kanbanRunMirror,
2209
+ kanbanSupervisor,
2210
+ stopKanbanSupervisorMemoryStats,
2211
+ unregisterWebuiClient,
2212
+ ipv6LoopbackServer,
2213
+ registryBaseDir,
2214
+ stopLiveStatusLogger,
2215
+ terminalLogView
2009
2216
  });
2010
2217
  function send(ws, msg) {
2011
2218
  sendSerialized(ws, JSON.stringify(stampDispatchSession(msg)));
2012
2219
  }
2013
2220
  function broadcast(msg, targetSessionId) {
2014
- const data = JSON.stringify(msg);
2015
2221
  const payload = msg.payload;
2016
2222
  const sessionId = targetSessionId ?? (payload && typeof payload === "object" && typeof payload.sessionId === "string" ? payload.sessionId : void 0);
2223
+ const data = sessionId ? webuiSessionFrameLog().sequence(sessionId, msg) : JSON.stringify(msg);
2017
2224
  for (const [ws, client] of clients) {
2018
2225
  if (clientWantsSession(client, sessionId)) sendSerialized(ws, data);
2019
2226
  }
@@ -2030,4 +2237,4 @@ async function runWebUI(opts) {
2030
2237
  export {
2031
2238
  runWebUI
2032
2239
  };
2033
- //# sourceMappingURL=webui-server-MRPGTTKL.js.map
2240
+ //# sourceMappingURL=webui-server-YTHLRFD2.js.map