@wrongstack/cli 1.0.24 → 1.0.27

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 (146) hide show
  1. package/data/providers.json +44 -0
  2. package/dist/{acp-AJWSKWE5.js → acp-M4565YUW.js} +33 -20
  3. package/dist/ascii-mode-SG4PRNLP.js +7 -0
  4. package/dist/{auth-GRBZ7Y3D.js → auth-TE55JF4Z.js} +14 -14
  5. package/dist/{bench-N5K7Z5O3.js → bench-GTLPM22B.js} +2 -2
  6. package/dist/boot/append-system-prompt.d.ts +10 -0
  7. package/dist/boot/ascii-mode.d.ts +12 -0
  8. package/dist/boot/container-wiring.d.ts +4 -2
  9. package/dist/boot/dispatch-singleshot.d.ts +12 -1
  10. package/dist/boot/dispatch-webui.d.ts +2 -0
  11. package/dist/boot/goal-tui-default.d.ts +10 -0
  12. package/dist/boot/mcp-config-flag.d.ts +22 -0
  13. package/dist/boot/piped-stdin.d.ts +40 -0
  14. package/dist/boot/restricted-mode.d.ts +15 -0
  15. package/dist/boot/safe-mode.d.ts +30 -0
  16. package/dist/boot/single-shot-budget.d.ts +30 -0
  17. package/dist/boot/stream-json.d.ts +36 -0
  18. package/dist/boot/structured-output.d.ts +16 -0
  19. package/dist/boot/system-prompt-builder.d.ts +2 -0
  20. package/dist/boot/tool-restriction-flags.d.ts +36 -0
  21. package/dist/boot/worktree-sessions.d.ts +21 -0
  22. package/dist/boot-provider-selection.d.ts +67 -0
  23. package/dist/boot.d.ts +1 -38
  24. package/dist/{chunk-LFMVHT3R.js → chunk-2ERX3RPN.js} +11 -13
  25. package/dist/chunk-4TVYGG6G.js +62 -0
  26. package/dist/chunk-5JYDKG5B.js +154 -0
  27. package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
  28. package/dist/chunk-CZTXF6HY.js +140 -0
  29. package/dist/{chunk-DRM7PSW7.js → chunk-ENRYW73H.js} +527 -381
  30. package/dist/{chunk-SKPDE62W.js → chunk-EPR42SWM.js} +36 -6
  31. package/dist/{chunk-YBUCXXYZ.js → chunk-F5DCFAKI.js} +1 -1
  32. package/dist/chunk-HDMFKTT3.js +157 -0
  33. package/dist/{chunk-PGKJ2OQP.js → chunk-HFZH2XJN.js} +5 -5
  34. package/dist/chunk-HLSHFXTO.js +97 -0
  35. package/dist/{chunk-QFPQXKCG.js → chunk-KOEO4CJG.js} +21 -6
  36. package/dist/chunk-LBPLTTZ6.js +105 -0
  37. package/dist/{chunk-3OCUS3VN.js → chunk-LDCGZJTC.js} +4 -4
  38. package/dist/chunk-LKPEBZBE.js +143 -0
  39. package/dist/chunk-N57IU5FG.js +58 -0
  40. package/dist/{chunk-2EBVTYJR.js → chunk-NRXWOMK7.js} +17 -3
  41. package/dist/chunk-PZQBGUVZ.js +18 -0
  42. package/dist/{chunk-MHEBWVXL.js → chunk-Q4INH2DG.js} +46 -2
  43. package/dist/{chunk-5SEYGOCH.js → chunk-RDGGWMTI.js} +2 -2
  44. package/dist/{chunk-BLD5DSVA.js → chunk-RGFWGUAB.js} +27 -15
  45. package/dist/{chunk-ZZRSG7VK.js → chunk-RZ4ZDRYA.js} +3 -3
  46. package/dist/{chunk-S4FFHDJG.js → chunk-SDJCLEK6.js} +6 -2
  47. package/dist/{chunk-U46QR4WR.js → chunk-TI7KLHYX.js} +132 -3
  48. package/dist/chunk-X756ZS72.js +329 -0
  49. package/dist/{chunk-JF2MULWF.js → chunk-YHYINL47.js} +47 -15
  50. package/dist/{cli-context-34Q5IJRA.js → cli-context-C7RBSWHF.js} +434 -327
  51. package/dist/{cli-main-AELJDBGU.js → cli-main-TQTH73ZK.js} +2948 -2315
  52. package/dist/cli-update-notice.d.ts +2 -1
  53. package/dist/config-doctor.d.ts +1 -1
  54. package/dist/{diag-doctor-WIFJH3XH.js → diag-doctor-BIZAFIAT.js} +2 -2
  55. package/dist/{execution-PYC5FCS4.js → execution-3STBD2U7.js} +2167 -1831
  56. package/dist/execution-chimera-review.d.ts +18 -1
  57. package/dist/fleet/host-types.d.ts +3 -1
  58. package/dist/goal-commands.d.ts +47 -0
  59. package/dist/goal-host.d.ts +1 -14
  60. package/dist/{hq-JTUTFI4L.js → hq-44F4OQH2.js} +9 -8
  61. package/dist/hq-fleet-control.d.ts +36 -0
  62. package/dist/hq-live-status.d.ts +22 -0
  63. package/dist/hq-publisher.d.ts +18 -0
  64. package/dist/hq-server/routes/command-handlers.d.ts +12 -1
  65. package/dist/hq-server/routes/data-handlers.d.ts +1 -1
  66. package/dist/hq-server/socket-credentials.d.ts +63 -0
  67. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  68. package/dist/hq-server/ws.d.ts +24 -0
  69. package/dist/{hq-server-PC7GZMPT.js → hq-server-UIB24LU6.js} +2 -2
  70. package/dist/{hq-service-NW56MUP2.js → hq-service-BMMJAHRT.js} +4 -4
  71. package/dist/import-claude-code-AYG24RG2.js +268 -0
  72. package/dist/index.js +12 -8
  73. package/dist/live-settings-input.d.ts +1 -0
  74. package/dist/{mailbox-serve-WDOCH6KR.js → mailbox-serve-GNGXEQ2R.js} +3 -3
  75. package/dist/{modeldiag-36OGAL5C.js → modeldiag-W7XAE625.js} +9 -9
  76. package/dist/{per-subcommand-help-XE77MTGJ.js → per-subcommand-help-QDAQASPZ.js} +2 -2
  77. package/dist/permission-prompt.d.ts +14 -0
  78. package/dist/permission-rules-view.d.ts +18 -0
  79. package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
  80. package/dist/{plugin-usage-S5LVGMBW.js → plugin-usage-CIGOOTX3.js} +2 -2
  81. package/dist/{plugins-RHBIVLI7.js → plugins-HQTXUD6A.js} +4 -2
  82. package/dist/preflight.d.ts +1 -1
  83. package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
  84. package/dist/provider-helpers.d.ts +7 -11
  85. package/dist/{providers-models-2CFG46RD.js → providers-models-5Z2PJTEW.js} +5 -5
  86. package/dist/release-asset-download.d.ts +10 -0
  87. package/dist/release-attestation.d.ts +28 -0
  88. package/dist/remote/remote-session.d.ts +64 -0
  89. package/dist/remote/remote-target.d.ts +69 -0
  90. package/dist/remote-GB5VXCCT.js +576 -0
  91. package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
  92. package/dist/services/commit-message.d.ts +2 -1
  93. package/dist/session-move-P2EC2COA.js +44 -0
  94. package/dist/session-move.d.ts +20 -0
  95. package/dist/session-stats.d.ts +10 -0
  96. package/dist/{sessions-config-IQUH2GXT.js → sessions-config-XHLXJ5C3.js} +109 -10
  97. package/dist/short-circuit-flags-5TNTJPQR.js +10 -0
  98. package/dist/slash-commands/command-context.d.ts +15 -5
  99. package/dist/slash-commands/goal-refiner.d.ts +4 -49
  100. package/dist/slash-commands/mcp.d.ts +2 -1
  101. package/dist/slash-commands/permissions.d.ts +4 -0
  102. package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
  103. package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
  104. package/dist/slash-commands/theme.d.ts +15 -1
  105. package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
  106. package/dist/standalone-auto-update.d.ts +65 -0
  107. package/dist/standalone-update-LZO3MNLH.js +24 -0
  108. package/dist/standalone-update.d.ts +37 -1
  109. package/dist/subcommands/handlers/import-claude-code.d.ts +52 -0
  110. package/dist/subcommands/handlers/permissions.d.ts +9 -3
  111. package/dist/subcommands/handlers/remote.d.ts +6 -0
  112. package/dist/subcommands/handlers/sessions-fleet.d.ts +0 -12
  113. package/dist/subcommands/handlers/version-help.d.ts +5 -0
  114. package/dist/{subcommands-ZTSIN7LG.js → subcommands-BHQYLDSD.js} +2 -2
  115. package/dist/tui-resume-callback.d.ts +18 -0
  116. package/dist/{typesafe-HVEVPA6R.js → typesafe-U7YB2RRV.js} +5 -5
  117. package/dist/{typesafe-topic-replay-4W73BOLV.js → typesafe-topic-replay-ZILIU3WS.js} +18 -2
  118. package/dist/{update-7XIOEXTD.js → update-TDZX4IL2.js} +4 -4
  119. package/dist/{update-check-2POMBHFB.js → update-check-J5ZVKS73.js} +3 -3
  120. package/dist/version-JB2UAV2A.js +11 -0
  121. package/dist/version-help-XJJQ7YSH.js +10 -0
  122. package/dist/webui-server/route-contexts.d.ts +1 -1
  123. package/dist/webui-server/session-start-payload.d.ts +5 -1
  124. package/dist/{webui-server-L2SICPTB.js → webui-server-GCXIO3JY.js} +515 -365
  125. package/dist/webui-server-lifecycle.d.ts +57 -0
  126. package/dist/webui-server-options.d.ts +10 -0
  127. package/dist/webui-server.d.ts +0 -14
  128. package/dist/wiring/brain-and-orchestration.d.ts +3 -1
  129. package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
  130. package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
  131. package/dist/wiring/command-host-state.d.ts +2 -0
  132. package/dist/wiring/hq-telemetry.d.ts +12 -0
  133. package/dist/wiring/lifecycle-plugins.d.ts +4 -2
  134. package/dist/wiring/metrics.d.ts +9 -1
  135. package/dist/wiring/pipeline.d.ts +2 -7
  136. package/dist/wiring/plugins.d.ts +3 -0
  137. package/dist/wiring/provider-status.d.ts +1 -0
  138. package/dist/wiring/provider-utility-tools.d.ts +3 -1
  139. package/package.json +29 -28
  140. package/dist/chunk-2BKFWIER.js +0 -98
  141. package/dist/chunk-424ORSE7.js +0 -56
  142. package/dist/chunk-OWNC7LMD.js +0 -78
  143. package/dist/chunk-QBBF7GIK.js +0 -65
  144. package/dist/short-circuit-flags-SDT53OML.js +0 -10
  145. package/dist/standalone-update-MW47J3AK.js +0 -164
  146. package/dist/version-help-WZ623NWM.js +0 -10
@@ -1,194 +1,30 @@
1
- import {
2
- createKanbanRunMirror
3
- } from "./chunk-VCYZ3WZV.js";
4
1
  import {
5
2
  createKanbanSupervisor
6
3
  } from "./chunk-R4QGL6GB.js";
7
4
  import {
8
5
  WEBUI_SESSION_CHILD_CAPABILITIES
9
6
  } from "./chunk-ZMX5HRLI.js";
7
+ import {
8
+ createKanbanRunMirror
9
+ } from "./chunk-VCYZ3WZV.js";
10
10
  import {
11
11
  createHqCommandDispatcher,
12
12
  createProjectKanbanAssignHandler,
13
13
  createProjectKanbanTransitionHandler
14
- } from "./chunk-2EBVTYJR.js";
14
+ } from "./chunk-NRXWOMK7.js";
15
15
  import {
16
16
  startCliHqConnection
17
- } from "./chunk-BLD5DSVA.js";
17
+ } from "./chunk-RGFWGUAB.js";
18
+ import {
19
+ CLI_VERSION
20
+ } from "./chunk-F5DCFAKI.js";
18
21
  import {
19
22
  loadConfigProviders,
20
23
  mutateConfigProviders
21
24
  } from "./chunk-LUMETJFO.js";
22
- import {
23
- CLI_VERSION
24
- } from "./chunk-YBUCXXYZ.js";
25
-
26
- // src/webui-server.ts
27
- import * as path7 from "node:path";
28
- import { createCompatibilityTrustBoundary, DefaultSecretScrubber } from "@wrongstack/core/security";
29
- import {
30
- addFatalSalvageHook,
31
- startSharedHeapWatchdog,
32
- wstackGlobalRoot
33
- } from "@wrongstack/core/utils";
34
- import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils/error";
35
- import {
36
- buildWebUIAccessUrl,
37
- clientWantsSession,
38
- collectDisplayedSessionIds,
39
- createCustomModeStore,
40
- createEmbeddedMessageRouter,
41
- createEmbeddedProviderOperations,
42
- createSessionAgentRegistry,
43
- createWebuiLeaderAutoWakeHost,
44
- envFlag,
45
- findFreePort,
46
- findInstalledPackageJson,
47
- isStrictPort,
48
- resolveAuthToken,
49
- resolvePendingConfirmsForSession,
50
- scheduleOwnerlessEmptySessionCleanup,
51
- sendSerialized,
52
- stampDispatchSession,
53
- startTerminalDashboard,
54
- toSessionHistoryEntries
55
- } from "@wrongstack/webui-server";
56
- import { verifyClient as verifyWsClient2 } from "@wrongstack/webui-server/server/ws-auth";
57
- import { WebSocketServer } from "ws";
58
-
59
- // src/webui-client-observability.ts
60
- import { startWebUILiveStatusLogger } from "@wrongstack/webui-server";
61
25
 
62
- // src/webui-server/client-registration.ts
63
- import { mailboxSessionTag } from "@wrongstack/core/coordination";
64
- import { createWebuiClientPresence } from "@wrongstack/webui-server";
65
- function createWebuiClientRegistration(deps) {
66
- const control = deps.hqControl;
67
- return createWebuiClientPresence({
68
- projectRoot: deps.projectRoot,
69
- appConfig: deps.appConfig,
70
- events: deps.events,
71
- hqSessionId: deps.hqSessionId,
72
- getSessionId: deps.getSessionId,
73
- clientVersion: CLI_VERSION,
74
- ...deps.listSessions ? { listSessions: deps.listSessions } : {},
75
- ...deps.isSessionOwnedElsewhere ? { isSessionOwnedElsewhere: deps.isSessionOwnedElsewhere } : {},
76
- ...deps.getSessionWriter ? { getSessionWriter: deps.getSessionWriter } : {},
77
- startHqConnection: (options) => startCliHqConnection({
78
- ...options,
79
- capabilities: options.capabilities
80
- }),
81
- ...control ? {
82
- createCommandHandler: (mailbox, approvals) => createHqCommandDispatcher({
83
- steerMailbox: mailbox,
84
- interruptLeader: control.interruptLeader,
85
- allowRunCommand: control.allowRunCommand,
86
- // Same registry the approval bridge publishes from, so every
87
- // prompt HQ shows is one this handler can actually answer.
88
- resolveApproval: (toolUseId, decision, sessionId) => approvals.resolve(toolUseId, decision, sessionId),
89
- ...deps.projectRoot ? {
90
- kanbanTransition: createProjectKanbanTransitionHandler(deps.projectRoot),
91
- kanbanAssign: createProjectKanbanAssignHandler(deps.projectRoot)
92
- } : {},
93
- // The session HQ actually SHOWS for this client — the boot
94
- // session the telemetry bridge publishes under and the only one
95
- // `interruptLeader` speaks for. `getSessionId` follows whichever
96
- // browser tab is in front, which is a different session HQ has
97
- // no view of and cannot control.
98
- sessionTag: () => mailboxSessionTag(deps.hqSessionId),
99
- // The session HQ falls back to when a command names none — the
100
- // boot tab. A command that DOES name one is honoured (and
101
- // refused when this host no longer has it) via `ownsSession`.
102
- sessionId: () => deps.hqSessionId,
103
- ...control.ownsSession ? { ownsSession: control.ownsSession } : {},
104
- ...control.spawnAgent ? { spawnAgent: control.spawnAgent } : {},
105
- ...control.killFleet ? { killFleet: control.killFleet } : {},
106
- ...control.terminateAgent ? { terminateAgent: control.terminateAgent } : {}
107
- })
108
- } : {}
109
- });
110
- }
111
-
112
- // src/webui-client-observability.ts
113
- function createEmbeddedClientRegistration(opts, clients, abortControllers, getSessionAgents) {
114
- return createWebuiClientRegistration({
115
- projectRoot: opts.projectRoot,
116
- appConfig: opts.appConfig,
117
- events: opts.events,
118
- hqSessionId: opts.session.id,
119
- getSessionId: () => opts.agent.ctx.session?.id ?? opts.session.id,
120
- // One HQ session per open tab. The set is the same one the terminal
121
- // panel lists: every session a connected browser is displaying.
122
- listSessions: () => {
123
- const ids = /* @__PURE__ */ new Set();
124
- for (const client of clients.values()) {
125
- if (client.sessionId) ids.add(client.sessionId);
126
- for (const id of client.sessionIds ?? []) ids.add(id);
127
- }
128
- return [...ids];
129
- },
130
- // `cli-main` already runs a tracker and a bridge for the boot session.
131
- // Announcing it here too would put two trackers on one bus flushing the
132
- // same agent list.
133
- isSessionOwnedElsewhere: (sessionId) => sessionId === opts.session.id,
134
- // The tab's own journal, so HQ takes its turns from the write path
135
- // instead of tailing the file.
136
- getSessionWriter: (sessionId) => getSessionAgents()?.peek(sessionId)?.ctx.session,
137
- hqControl: {
138
- // HQ speaks for the LEADER — the boot session, the one it registered
139
- // itself under (`hqSessionId`) — not for whatever else the browser
140
- // has open. This used to abort every controller in the map and clear
141
- // it, so a remote "interrupt" issued against the leader also killed
142
- // the three other tabs' in-flight runs. Deleting the entries was
143
- // wrong on its own terms too: the run's own `end()` owns removal, and
144
- // clearing early makes `isRunActive` lie to every tab still running.
145
- // Every open tab is its own session with its own abort controller, so
146
- // the command's session is the one that gets stopped. Falling back to
147
- // the boot session keeps a dashboard that sends no session — every one
148
- // before this existed — behaving exactly as before.
149
- interruptLeader: (sessionId) => {
150
- const leaderId = sessionId ?? opts.session.id;
151
- const controller = abortControllers.get(leaderId);
152
- if (!controller) return false;
153
- controller.abort();
154
- try {
155
- void Promise.resolve(opts.stopSessionFleet?.(leaderId)).catch(() => void 0);
156
- } catch {
157
- }
158
- return true;
159
- },
160
- allowRunCommand: () => opts.hqAllowExec === true,
161
- // A command naming a tab this process no longer holds is refused, not
162
- // redirected onto the boot session: the operator picked a terminal,
163
- // and steering a different one is worse than not steering at all.
164
- ownsSession: (sessionId) => sessionId === opts.session.id || getSessionAgents()?.has(sessionId) === true
165
- }
166
- });
167
- }
168
- function startEmbeddedLiveStatusLogger(opts, clients, abortControllers, getSessionAgents, terminalLogView) {
169
- return startWebUILiveStatusLogger({
170
- events: opts.events,
171
- dashboard: terminalLogView,
172
- getSessionList: () => {
173
- const ids = /* @__PURE__ */ new Set();
174
- for (const client of clients.values()) {
175
- if (client.sessionId) ids.add(client.sessionId);
176
- for (const id of client.sessionIds ?? []) ids.add(id);
177
- }
178
- const currentId = opts.agent.ctx.session?.id ?? opts.session.id;
179
- if (ids.size === 0 && currentId) ids.add(currentId);
180
- return Array.from(ids).map((id) => {
181
- const ctx = getSessionAgents()?.peek(id)?.ctx;
182
- return {
183
- id,
184
- model: ctx?.model ?? opts.agent.ctx.model ?? "",
185
- provider: ctx?.provider?.id ?? opts.agent.ctx.provider?.id ?? "",
186
- isRunning: abortControllers.has(id)
187
- };
188
- });
189
- }
190
- });
191
- }
26
+ // src/webui-server-lifecycle.ts
27
+ import { toErrorMessage } from "@wrongstack/core/utils/error";
192
28
 
193
29
  // src/webui-server/connection-handler.ts
194
30
  import { deriveSessionAgents } from "@wrongstack/core/storage";
@@ -304,14 +140,410 @@ function createConnectionHandler(deps) {
304
140
  } catch {
305
141
  console.debug("[WebUI] Failed to load replay");
306
142
  }
307
- try {
308
- const roster = deriveSessionAgents(replayEvents ?? []);
309
- const sessions = roster.length > 0 ? await deps.loadAgentSessions?.(roster.map((agent) => agent.agentId)) : [];
310
- if (sessions?.length) payload["agentSessions"] = sessions;
311
- } catch {
312
- console.debug("[WebUI] Failed to load agent sessions");
143
+ try {
144
+ const roster = deriveSessionAgents(replayEvents ?? []);
145
+ const sessions = roster.length > 0 ? await deps.loadAgentSessions?.(roster.map((agent) => agent.agentId)) : [];
146
+ if (sessions?.length) payload["agentSessions"] = sessions;
147
+ } catch {
148
+ console.debug("[WebUI] Failed to load agent sessions");
149
+ }
150
+ return payload;
151
+ }
152
+ });
153
+ }
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
+ // Embedded in the CLI host, whose own HQ connection already syncs
432
+ // this project's Kanban boards; a second sync is a second writer.
433
+ ownKanbanSync: false
434
+ }),
435
+ ...control ? {
436
+ createCommandHandler: (mailbox, approvals) => createHqCommandDispatcher({
437
+ steerMailbox: mailbox,
438
+ interruptLeader: control.interruptLeader,
439
+ allowRunCommand: control.allowRunCommand,
440
+ // Same registry the approval bridge publishes from, so every
441
+ // prompt HQ shows is one this handler can actually answer.
442
+ resolveApproval: (toolUseId, decision, sessionId) => approvals.resolve(toolUseId, decision, sessionId),
443
+ // Same registry again: it publishes `user_input.requested`, so
444
+ // it has to be the one that answers `answer-input`.
445
+ resolveUserInput: (requestId, response, sessionId) => approvals.resolveUserInput?.(requestId, response, sessionId) ?? false,
446
+ ...deps.projectRoot ? {
447
+ kanbanTransition: createProjectKanbanTransitionHandler(deps.projectRoot),
448
+ kanbanAssign: createProjectKanbanAssignHandler(deps.projectRoot)
449
+ } : {},
450
+ // The session HQ actually SHOWS for this client — the boot
451
+ // session the telemetry bridge publishes under and the only one
452
+ // `interruptLeader` speaks for. `getSessionId` follows whichever
453
+ // browser tab is in front, which is a different session HQ has
454
+ // no view of and cannot control.
455
+ sessionTag: () => mailboxSessionTag(deps.hqSessionId),
456
+ // The session HQ falls back to when a command names none — the
457
+ // boot tab. A command that DOES name one is honoured (and
458
+ // refused when this host no longer has it) via `ownsSession`.
459
+ sessionId: () => deps.hqSessionId,
460
+ ...control.ownsSession ? { ownsSession: control.ownsSession } : {},
461
+ ...control.spawnAgent ? { spawnAgent: control.spawnAgent } : {},
462
+ ...control.killFleet ? { killFleet: control.killFleet } : {},
463
+ ...control.terminateAgent ? { terminateAgent: control.terminateAgent } : {}
464
+ })
465
+ } : {}
466
+ });
467
+ }
468
+
469
+ // src/webui-client-observability.ts
470
+ function createEmbeddedClientRegistration(opts, clients, abortControllers, getSessionAgents) {
471
+ const interruptSession = (sessionId) => {
472
+ const leaderId = sessionId ?? opts.session.id;
473
+ const controller = abortControllers.get(leaderId);
474
+ if (!controller) return false;
475
+ controller.abort();
476
+ try {
477
+ void Promise.resolve(opts.stopSessionFleet?.(leaderId)).catch(() => void 0);
478
+ } catch {
479
+ }
480
+ return true;
481
+ };
482
+ if (opts.interruptController) {
483
+ opts.interruptController.abortLeader = () => interruptSession(opts.agent.ctx.session?.id ?? opts.session.id);
484
+ }
485
+ return createWebuiClientRegistration({
486
+ projectRoot: opts.projectRoot,
487
+ appConfig: opts.appConfig,
488
+ events: opts.events,
489
+ hqSessionId: opts.session.id,
490
+ getSessionId: () => opts.agent.ctx.session?.id ?? opts.session.id,
491
+ // One HQ session per open tab. The set is the same one the terminal
492
+ // panel lists: every session a connected browser is displaying.
493
+ listSessions: () => {
494
+ const ids = /* @__PURE__ */ new Set();
495
+ for (const client of clients.values()) {
496
+ if (client.sessionId) ids.add(client.sessionId);
497
+ for (const id of client.sessionIds ?? []) ids.add(id);
498
+ }
499
+ return [...ids];
500
+ },
501
+ // `cli-main` already runs a tracker and a bridge for the root
502
+ // conversation; announcing it here too would put two trackers on one bus
503
+ // flushing the same agent list. Compared against the LIVE root writer:
504
+ // the CLI host's telemetry follows `ctx.session`, so once the root moves
505
+ // (session.new / resume) the old boot id is an ordinary tab this client
506
+ // must publish, and the new root id must not be published twice.
507
+ isSessionOwnedElsewhere: (sessionId) => sessionId === (opts.agent.ctx.session?.id ?? opts.session.id),
508
+ // The tab's own journal, so HQ takes its turns from the write path
509
+ // instead of tailing the file.
510
+ getSessionWriter: (sessionId) => getSessionAgents()?.peek(sessionId)?.ctx.session,
511
+ hqControl: {
512
+ interruptLeader: interruptSession,
513
+ ...opts.hqFleetControl ? {
514
+ killFleet: opts.hqFleetControl.killFleet,
515
+ terminateAgent: opts.hqFleetControl.terminateAgent,
516
+ spawnAgent: opts.hqFleetControl.spawnAgent
517
+ } : {},
518
+ allowRunCommand: () => opts.hqAllowExec === true,
519
+ // A command naming a tab this process no longer holds is refused, not
520
+ // redirected onto the boot session: the operator picked a terminal,
521
+ // and steering a different one is worse than not steering at all.
522
+ ownsSession: (sessionId) => sessionId === opts.session.id || getSessionAgents()?.has(sessionId) === true
523
+ }
524
+ });
525
+ }
526
+ function startEmbeddedLiveStatusLogger(opts, clients, abortControllers, getSessionAgents, terminalLogView) {
527
+ return startWebUILiveStatusLogger({
528
+ events: opts.events,
529
+ dashboard: terminalLogView,
530
+ getSessionList: () => {
531
+ const ids = /* @__PURE__ */ new Set();
532
+ for (const client of clients.values()) {
533
+ if (client.sessionId) ids.add(client.sessionId);
534
+ for (const id of client.sessionIds ?? []) ids.add(id);
313
535
  }
314
- return payload;
536
+ const currentId = opts.agent.ctx.session?.id ?? opts.session.id;
537
+ if (ids.size === 0 && currentId) ids.add(currentId);
538
+ return Array.from(ids).map((id) => {
539
+ const ctx = getSessionAgents()?.peek(id)?.ctx;
540
+ return {
541
+ id,
542
+ model: ctx?.model ?? opts.agent.ctx.model ?? "",
543
+ provider: ctx?.provider?.id ?? opts.agent.ctx.provider?.id ?? "",
544
+ isRunning: abortControllers.has(id)
545
+ };
546
+ });
315
547
  }
316
548
  });
317
549
  }
@@ -319,7 +551,7 @@ function createConnectionHandler(deps) {
319
551
  // src/webui-server/credential-watcher.ts
320
552
  import { TOKENS } from "@wrongstack/core/kernel";
321
553
  import { watchProviderConfig } from "@wrongstack/core/storage";
322
- import { toErrorMessage } from "@wrongstack/core/utils/error";
554
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils/error";
323
555
  import { makeProviderFromConfig } from "@wrongstack/providers";
324
556
 
325
557
  // src/webui-server/provider-config.ts
@@ -474,7 +706,7 @@ function startWebuiCredentialWatcher({
474
706
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
475
707
  } catch (err) {
476
708
  console.warn(
477
- `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage(err)}`
709
+ `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage2(err)}`
478
710
  );
479
711
  }
480
712
  },
@@ -506,35 +738,6 @@ import {
506
738
  WorktreeWebSocketHandler
507
739
  } from "@wrongstack/webui-server";
508
740
 
509
- // src/webui-server/logger-shim.ts
510
- var structuredLine = (level, message) => JSON.stringify({
511
- level,
512
- event: "webui.goal",
513
- message,
514
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
515
- });
516
- var consoleLogger = {
517
- level: "debug",
518
- error(msg, _ctx) {
519
- console.error(structuredLine("error", msg));
520
- },
521
- warn(msg, _ctx) {
522
- console.warn(structuredLine("warn", msg));
523
- },
524
- info(msg, _ctx) {
525
- console.log(structuredLine("info", msg));
526
- },
527
- debug(msg, _ctx) {
528
- console.debug(structuredLine("debug", msg));
529
- },
530
- trace(msg, _ctx) {
531
- console.debug(structuredLine("trace", msg));
532
- },
533
- child(_bindings) {
534
- return this;
535
- }
536
- };
537
-
538
741
  // src/webui-server/node-pty-loader.ts
539
742
  import { existsSync } from "node:fs";
540
743
  import { createRequire } from "node:module";
@@ -602,7 +805,7 @@ function createNodePtyLoader(importMetaUrl, logger) {
602
805
 
603
806
  // src/webui-server/domain-handlers.ts
604
807
  function createWebuiDomainHandlers(opts, trustBoundary, kanbanRunMirror) {
605
- const goalStoreDir = opts.projectRoot ? path3.join(opts.projectRoot, ".wrongstack", "goal") : path3.join(os.tmpdir(), ".wrongstack", "goal");
808
+ const goalStoreDir = opts.projectRoot ? resolveWstackPaths({ projectRoot: opts.projectRoot }).projectAutophase : path3.join(os.tmpdir(), ".wrongstack", "goal");
606
809
  const goalHandler = new GoalWebSocketHandler(
607
810
  opts.agent,
608
811
  opts.agent.ctx,
@@ -610,7 +813,8 @@ function createWebuiDomainHandlers(opts, trustBoundary, kanbanRunMirror) {
610
813
  goalStoreDir,
611
814
  opts.events,
612
815
  opts.projectRoot,
613
- kanbanRunMirror ? (graphId, state) => kanbanRunMirror.onGoalState(graphId, state) : void 0
816
+ kanbanRunMirror ? (graphId, state) => kanbanRunMirror.onGoalState(graphId, state) : void 0,
817
+ opts.sddSubagentFactory
614
818
  );
615
819
  const worktreeHandler = new WorktreeWebSocketHandler(opts.events, consoleLogger);
616
820
  const loadNodePtyViaWebui = createNodePtyLoader(import.meta.url, consoleLogger);
@@ -837,14 +1041,6 @@ function createCliKanbanHostRoutes(deps) {
837
1041
  };
838
1042
  }
839
1043
 
840
- // src/webui-server/lifecycle.ts
841
- import {
842
- announceWebuiReady,
843
- createWebuiShutdown,
844
- registerWebuiInstance,
845
- registerWebuiSignalHandlers
846
- } from "@wrongstack/webui-server";
847
-
848
1044
  // src/webui-server/listen-helpers.ts
849
1045
  import * as http from "node:http";
850
1046
  import { listenWithRetry } from "@wrongstack/webui-server";
@@ -1008,6 +1204,7 @@ import {
1008
1204
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
1009
1205
  import { SkillInstaller } from "@wrongstack/core/skills";
1010
1206
  import { PromptUsageStore } from "@wrongstack/core/storage";
1207
+ import { resolveTokenSavingTier } from "@wrongstack/core/types";
1011
1208
  import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
1012
1209
  import {
1013
1210
  createChimeraRouteHandlers,
@@ -1142,6 +1339,16 @@ function createWebuiRouteContexts({
1142
1339
  pendingConfirms,
1143
1340
  configStore: opts.agent.container?.safeResolve?.(TOKENS2.ConfigStore),
1144
1341
  systemPrompt: {
1342
+ previewContext: (sessionId) => {
1343
+ const target = sessionId ? getSessionAgent(sessionId).ctx : opts.agent.ctx;
1344
+ return {
1345
+ toolNames: target.tools.map((tool) => tool.name),
1346
+ tier: resolveTokenSavingTier(
1347
+ opts.appConfig?.features?.tokenSavingMode,
1348
+ target.provider?.capabilities?.maxContext
1349
+ )
1350
+ };
1351
+ },
1145
1352
  paths: () => {
1146
1353
  const wpaths = resolveWstackPaths3({ projectRoot: promptProjectRoot(), globalRoot });
1147
1354
  return {
@@ -1291,6 +1498,7 @@ function createWebuiRouteContexts({
1291
1498
  // src/webui-server/session-start-payload.ts
1292
1499
  import * as path6 from "node:path";
1293
1500
  import { DEFAULT_CONTEXT_WINDOW_MODE_ID } from "@wrongstack/core/types";
1501
+ import { catalogProviderIdFor } from "@wrongstack/providers";
1294
1502
  import { protocolAdvertisement } from "@wrongstack/webui-protocol";
1295
1503
 
1296
1504
  // src/webui-server/cost-helpers.ts
@@ -1312,7 +1520,11 @@ function createSessionStartPayloadBuilder(deps) {
1312
1520
  const updateInfo = deps.updateInfo;
1313
1521
  try {
1314
1522
  if (deps.modelsRegistry) {
1315
- const m = await deps.modelsRegistry.getModel(ctx.provider.id, ctx.model);
1523
+ const catalogId = catalogProviderIdFor(
1524
+ ctx.provider.id,
1525
+ deps.appConfig?.providers?.[ctx.provider.id]?.type
1526
+ );
1527
+ const m = await deps.modelsRegistry.getModel(catalogId, ctx.model);
1316
1528
  const registryMax = m?.capabilities.maxContext;
1317
1529
  maxContext = registryMax ?? ctx.provider.capabilities?.maxContext ?? 0;
1318
1530
  const rates = getCostRates(m);
@@ -1562,6 +1774,9 @@ async function runWebUI(opts) {
1562
1774
  get session() {
1563
1775
  return opts.session;
1564
1776
  },
1777
+ get appConfig() {
1778
+ return opts.appConfig;
1779
+ },
1565
1780
  // `peek`, never `get`: building a payload must not materialise an agent
1566
1781
  // for a session id that arrived from a stale browser tab.
1567
1782
  getSessionContext: (sessionId) => sessionAgentsRef?.peek(sessionId)?.ctx
@@ -1768,7 +1983,7 @@ async function runWebUI(opts) {
1768
1983
  log: (m) => console.log(m)
1769
1984
  };
1770
1985
  const embeddedProviderOperations = createEmbeddedProviderOperations(wsHandlerCtx);
1771
- let credentialWatcherClose = startWebuiCredentialWatcher({
1986
+ const credentialWatcherClose = startWebuiCredentialWatcher({
1772
1987
  opts,
1773
1988
  profileConfigPath,
1774
1989
  broadcast,
@@ -1842,7 +2057,15 @@ async function runWebUI(opts) {
1842
2057
  void sessionAgents.endAndClose(sessionId);
1843
2058
  }
1844
2059
  };
1845
- const stopEmptySessionCleanup = opts.sessionStore ? scheduleOwnerlessEmptySessionCleanup({
2060
+ const refreshSessionHistory = async () => {
2061
+ const list = await opts.sessionStore?.list(200);
2062
+ if (!list) return;
2063
+ broadcastEveryone({
2064
+ type: "sessions.list",
2065
+ payload: { sessions: toSessionHistoryEntries(list, foregroundSession.id) }
2066
+ });
2067
+ };
2068
+ const emptySessionCleanup = opts.sessionStore ? scheduleOwnerlessEmptySessionCleanup({
1846
2069
  getSessionStore: () => opts.sessionStore,
1847
2070
  getActiveSessionId: () => foregroundSession.id,
1848
2071
  getActiveSessionIds: () => collectDisplayedSessionIds({ getSession: () => foregroundSession, clients }),
@@ -1850,16 +2073,19 @@ async function runWebUI(opts) {
1850
2073
  // written its first record yet looks exactly like a session nobody
1851
2074
  // ever used.
1852
2075
  hasParticipants: (sessionId) => abortControllers.has(sessionId),
1853
- refreshSessions: async () => {
1854
- const list = await opts.sessionStore?.list(200);
1855
- if (!list) return;
1856
- broadcastEveryone({
1857
- type: "sessions.list",
1858
- payload: { sessions: toSessionHistoryEntries(list, foregroundSession.id) }
1859
- });
1860
- },
2076
+ refreshSessions: refreshSessionHistory,
1861
2077
  logger: consoleLogger
1862
2078
  }) : null;
2079
+ const offSessionRenamed = opts.sessionStore ? opts.events.on("session.renamed", () => {
2080
+ void refreshSessionHistory().catch(() => void 0);
2081
+ }) : void 0;
2082
+ const stopEmptySessionCleanup = emptySessionCleanup && {
2083
+ runNow: emptySessionCleanup.runNow,
2084
+ dispose: async () => {
2085
+ offSessionRenamed?.();
2086
+ await emptySessionCleanup.dispose();
2087
+ }
2088
+ };
1863
2089
  const routeContexts = createWebuiRouteContexts({
1864
2090
  opts,
1865
2091
  profileConfigPath,
@@ -1936,148 +2162,72 @@ async function runWebUI(opts) {
1936
2162
  kanbanHostRoutes,
1937
2163
  statusTracker: opts.statusTracker
1938
2164
  });
1939
- const stopped = new Promise((resolve) => {
1940
- let listeningAnnounced = false;
1941
- const announceListening = () => {
1942
- if (listeningAnnounced) return;
1943
- listeningAnnounced = true;
1944
- console.log(`[WebUI] WebSocket server running on ws://${host}:${httpPort}`);
1945
- try {
1946
- setupEvents();
1947
- opts.onListening?.({
1948
- httpPort,
1949
- wsPort,
1950
- host,
1951
- url: accessUrl,
1952
- authToken: wsToken,
1953
- webuiInstanceRegistered
1954
- });
1955
- } catch (err) {
1956
- consoleLogger.error("setup_events_failed", { message: toErrorMessage2(err) });
1957
- }
1958
- };
1959
- wss.on("listening", announceListening);
1960
- if (httpServer?.server.listening || wss.address()) queueMicrotask(announceListening);
1961
- wss.on(
1962
- "connection",
1963
- createConnectionHandler({
1964
- host,
1965
- wsToken,
1966
- requireToken,
1967
- publicHostnames,
1968
- publicWsUrl,
1969
- clients,
1970
- currentSessionId,
1971
- goalHandler,
1972
- specsHandler,
1973
- sddBoardHandler,
1974
- sddWizardHandler,
1975
- worktreeHandler,
1976
- terminalHandler,
1977
- rateLimitMax,
1978
- send,
1979
- sessionPayload,
1980
- handleMessage,
1981
- pendingConfirms,
1982
- buildSessionStartPayload,
1983
- loadReplay: async () => {
1984
- const activeSession = opts.agent.ctx.session ?? opts.session;
1985
- await activeSession.flush();
1986
- if (opts.sessionStore) {
1987
- const data = await opts.sessionStore.load(activeSession.id);
1988
- return { messages: data.messages, events: data.events, usage: data.usage };
1989
- }
1990
- const usage = opts.agent.ctx.tokenCounter.total();
1991
- return { messages: opts.agent.ctx.messages, usage };
1992
- },
1993
- loadAgentSessions: async (subagentIds) => await opts.agentTranscripts?.loadSessionsFromDisk(subagentIds) ?? [],
1994
- // What this process is actually holding right now. The browser
1995
- // reconciles its persisted tab strip against it, so a restarted server
1996
- // is not dressed in the previous run's tabs.
1997
- openSessionIds: () => sessionAgents.ids(),
1998
- needsSetup: opts.needsSetup ?? false
1999
- })
2000
- );
2001
- signalShutdown = createWebuiShutdown({
2002
- abortInFlight: () => {
2003
- void embeddedAutoHealDispose?.();
2004
- for (const c of abortControllers.values()) c.abort();
2005
- abortControllers.clear();
2006
- },
2007
- unsubscribeEvents: () => {
2008
- flushAllStreamBuffers();
2009
- for (const unsub of eventUnsubscribers) unsub();
2010
- },
2011
- stopOwnedChildren: async () => {
2012
- try {
2013
- const { getProcessRegistry } = await import("@wrongstack/tools");
2014
- getProcessRegistry().killAll({ force: true, includeProtected: true });
2015
- } catch (err) {
2016
- console.debug(
2017
- `[webui-server] process-registry killAll failed: ${err instanceof Error ? err.message : String(err)}`
2018
- );
2019
- }
2020
- if (opts.mcpRegistry) {
2021
- try {
2022
- await opts.mcpRegistry.stopAll();
2023
- } catch (err) {
2024
- console.debug(
2025
- `[webui-server] mcpRegistry.stopAll failed: ${err instanceof Error ? err.message : String(err)}`
2026
- );
2027
- }
2028
- }
2029
- },
2030
- disposeResources: async () => {
2031
- releaseSessionSalvage();
2032
- autoWakeHost?.dispose();
2033
- await stopEmptySessionCleanup?.dispose();
2034
- await sessionAgents.closeAll().catch(() => void 0);
2035
- credentialWatcherClose?.();
2036
- credentialWatcherClose = void 0;
2037
- goalHandler.dispose();
2038
- sddBoardHandler.dispose();
2039
- worktreeHandler.dispose();
2040
- terminalHandler.dispose();
2041
- kanbanRunMirror?.dispose();
2042
- kanbanSupervisor?.dispose();
2043
- void stopKanbanSupervisorMemoryStats?.();
2044
- await embeddedAutoHealDispose?.();
2045
- embeddedAutoHealDispose = null;
2046
- unregisterWebuiClient();
2165
+ const { stopped } = runWebuiServerLifecycle({
2166
+ host,
2167
+ httpPort,
2168
+ setupEvents,
2169
+ opts,
2170
+ wsPort,
2171
+ accessUrl,
2172
+ wsToken,
2173
+ webuiInstanceRegistered,
2174
+ wss,
2175
+ httpServer,
2176
+ requireToken,
2177
+ publicHostnames,
2178
+ publicWsUrl,
2179
+ clients,
2180
+ currentSessionId,
2181
+ goalHandler,
2182
+ specsHandler,
2183
+ sddBoardHandler,
2184
+ sddWizardHandler,
2185
+ worktreeHandler,
2186
+ terminalHandler,
2187
+ rateLimitMax,
2188
+ send,
2189
+ sessionPayload,
2190
+ handleMessage,
2191
+ pendingConfirms,
2192
+ buildSessionStartPayload,
2193
+ sessionAgents,
2194
+ lifecycleState: {
2195
+ get signalShutdown() {
2196
+ return signalShutdown;
2047
2197
  },
2048
- closeClients: () => {
2049
- for (const [ws] of clients) ws.close();
2050
- clients.clear();
2198
+ set signalShutdown(value) {
2199
+ signalShutdown = value;
2051
2200
  },
2052
- closeHttpServer: () => {
2053
- ipv6LoopbackServer?.close();
2054
- httpServer?.server.close();
2201
+ get embeddedAutoHealDispose() {
2202
+ return embeddedAutoHealDispose;
2055
2203
  },
2056
- wss,
2057
- pid: process.pid,
2058
- registryBaseDir,
2059
- onStopped: () => {
2060
- stopLiveStatusLogger();
2061
- const muted = terminalLogView.mutedCount;
2062
- terminalLogView.stop();
2063
- if (muted > 0) {
2064
- console.log(
2065
- `[WebUI] ${muted} progress line(s) kept out of this terminal \u2014 set WEBUI_LOGS=1 to stream them.`
2066
- );
2067
- }
2068
- opts.onExit?.();
2069
- resolve();
2204
+ set embeddedAutoHealDispose(value) {
2205
+ embeddedAutoHealDispose = value;
2070
2206
  }
2071
- });
2072
- registerWebuiSignalHandlers(signalShutdown);
2207
+ },
2208
+ abortControllers,
2209
+ flushAllStreamBuffers,
2210
+ eventUnsubscribers,
2211
+ releaseSessionSalvage,
2212
+ autoWakeHost,
2213
+ stopEmptySessionCleanup,
2214
+ credentialWatcherClose,
2215
+ kanbanRunMirror,
2216
+ kanbanSupervisor,
2217
+ stopKanbanSupervisorMemoryStats,
2218
+ unregisterWebuiClient,
2219
+ ipv6LoopbackServer,
2220
+ registryBaseDir,
2221
+ stopLiveStatusLogger,
2222
+ terminalLogView
2073
2223
  });
2074
2224
  function send(ws, msg) {
2075
2225
  sendSerialized(ws, JSON.stringify(stampDispatchSession(msg)));
2076
2226
  }
2077
2227
  function broadcast(msg, targetSessionId) {
2078
- const data = JSON.stringify(msg);
2079
2228
  const payload = msg.payload;
2080
2229
  const sessionId = targetSessionId ?? (payload && typeof payload === "object" && typeof payload.sessionId === "string" ? payload.sessionId : void 0);
2230
+ const data = sessionId ? webuiSessionFrameLog().sequence(sessionId, msg) : JSON.stringify(msg);
2081
2231
  for (const [ws, client] of clients) {
2082
2232
  if (clientWantsSession(client, sessionId)) sendSerialized(ws, data);
2083
2233
  }
@@ -2094,4 +2244,4 @@ async function runWebUI(opts) {
2094
2244
  export {
2095
2245
  runWebUI
2096
2246
  };
2097
- //# sourceMappingURL=webui-server-L2SICPTB.js.map
2247
+ //# sourceMappingURL=webui-server-GCXIO3JY.js.map