@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.
- package/data/providers.json +44 -0
- package/dist/{acp-AJWSKWE5.js → acp-M4565YUW.js} +33 -20
- package/dist/ascii-mode-SG4PRNLP.js +7 -0
- package/dist/{auth-GRBZ7Y3D.js → auth-TE55JF4Z.js} +14 -14
- package/dist/{bench-N5K7Z5O3.js → bench-GTLPM22B.js} +2 -2
- package/dist/boot/append-system-prompt.d.ts +10 -0
- package/dist/boot/ascii-mode.d.ts +12 -0
- package/dist/boot/container-wiring.d.ts +4 -2
- package/dist/boot/dispatch-singleshot.d.ts +12 -1
- package/dist/boot/dispatch-webui.d.ts +2 -0
- package/dist/boot/goal-tui-default.d.ts +10 -0
- package/dist/boot/mcp-config-flag.d.ts +22 -0
- package/dist/boot/piped-stdin.d.ts +40 -0
- package/dist/boot/restricted-mode.d.ts +15 -0
- package/dist/boot/safe-mode.d.ts +30 -0
- package/dist/boot/single-shot-budget.d.ts +30 -0
- package/dist/boot/stream-json.d.ts +36 -0
- package/dist/boot/structured-output.d.ts +16 -0
- package/dist/boot/system-prompt-builder.d.ts +2 -0
- package/dist/boot/tool-restriction-flags.d.ts +36 -0
- package/dist/boot/worktree-sessions.d.ts +21 -0
- package/dist/boot-provider-selection.d.ts +67 -0
- package/dist/boot.d.ts +1 -38
- package/dist/{chunk-LFMVHT3R.js → chunk-2ERX3RPN.js} +11 -13
- package/dist/chunk-4TVYGG6G.js +62 -0
- package/dist/chunk-5JYDKG5B.js +154 -0
- package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
- package/dist/chunk-CZTXF6HY.js +140 -0
- package/dist/{chunk-DRM7PSW7.js → chunk-ENRYW73H.js} +527 -381
- package/dist/{chunk-SKPDE62W.js → chunk-EPR42SWM.js} +36 -6
- package/dist/{chunk-YBUCXXYZ.js → chunk-F5DCFAKI.js} +1 -1
- package/dist/chunk-HDMFKTT3.js +157 -0
- package/dist/{chunk-PGKJ2OQP.js → chunk-HFZH2XJN.js} +5 -5
- package/dist/chunk-HLSHFXTO.js +97 -0
- package/dist/{chunk-QFPQXKCG.js → chunk-KOEO4CJG.js} +21 -6
- package/dist/chunk-LBPLTTZ6.js +105 -0
- package/dist/{chunk-3OCUS3VN.js → chunk-LDCGZJTC.js} +4 -4
- package/dist/chunk-LKPEBZBE.js +143 -0
- package/dist/chunk-N57IU5FG.js +58 -0
- package/dist/{chunk-2EBVTYJR.js → chunk-NRXWOMK7.js} +17 -3
- package/dist/chunk-PZQBGUVZ.js +18 -0
- package/dist/{chunk-MHEBWVXL.js → chunk-Q4INH2DG.js} +46 -2
- package/dist/{chunk-5SEYGOCH.js → chunk-RDGGWMTI.js} +2 -2
- package/dist/{chunk-BLD5DSVA.js → chunk-RGFWGUAB.js} +27 -15
- package/dist/{chunk-ZZRSG7VK.js → chunk-RZ4ZDRYA.js} +3 -3
- package/dist/{chunk-S4FFHDJG.js → chunk-SDJCLEK6.js} +6 -2
- package/dist/{chunk-U46QR4WR.js → chunk-TI7KLHYX.js} +132 -3
- package/dist/chunk-X756ZS72.js +329 -0
- package/dist/{chunk-JF2MULWF.js → chunk-YHYINL47.js} +47 -15
- package/dist/{cli-context-34Q5IJRA.js → cli-context-C7RBSWHF.js} +434 -327
- package/dist/{cli-main-AELJDBGU.js → cli-main-TQTH73ZK.js} +2948 -2315
- package/dist/cli-update-notice.d.ts +2 -1
- package/dist/config-doctor.d.ts +1 -1
- package/dist/{diag-doctor-WIFJH3XH.js → diag-doctor-BIZAFIAT.js} +2 -2
- package/dist/{execution-PYC5FCS4.js → execution-3STBD2U7.js} +2167 -1831
- package/dist/execution-chimera-review.d.ts +18 -1
- package/dist/fleet/host-types.d.ts +3 -1
- package/dist/goal-commands.d.ts +47 -0
- package/dist/goal-host.d.ts +1 -14
- package/dist/{hq-JTUTFI4L.js → hq-44F4OQH2.js} +9 -8
- package/dist/hq-fleet-control.d.ts +36 -0
- package/dist/hq-live-status.d.ts +22 -0
- package/dist/hq-publisher.d.ts +18 -0
- package/dist/hq-server/routes/command-handlers.d.ts +12 -1
- package/dist/hq-server/routes/data-handlers.d.ts +1 -1
- package/dist/hq-server/socket-credentials.d.ts +63 -0
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/ws.d.ts +24 -0
- package/dist/{hq-server-PC7GZMPT.js → hq-server-UIB24LU6.js} +2 -2
- package/dist/{hq-service-NW56MUP2.js → hq-service-BMMJAHRT.js} +4 -4
- package/dist/import-claude-code-AYG24RG2.js +268 -0
- package/dist/index.js +12 -8
- package/dist/live-settings-input.d.ts +1 -0
- package/dist/{mailbox-serve-WDOCH6KR.js → mailbox-serve-GNGXEQ2R.js} +3 -3
- package/dist/{modeldiag-36OGAL5C.js → modeldiag-W7XAE625.js} +9 -9
- package/dist/{per-subcommand-help-XE77MTGJ.js → per-subcommand-help-QDAQASPZ.js} +2 -2
- package/dist/permission-prompt.d.ts +14 -0
- package/dist/permission-rules-view.d.ts +18 -0
- package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
- package/dist/{plugin-usage-S5LVGMBW.js → plugin-usage-CIGOOTX3.js} +2 -2
- package/dist/{plugins-RHBIVLI7.js → plugins-HQTXUD6A.js} +4 -2
- package/dist/preflight.d.ts +1 -1
- package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
- package/dist/provider-helpers.d.ts +7 -11
- package/dist/{providers-models-2CFG46RD.js → providers-models-5Z2PJTEW.js} +5 -5
- package/dist/release-asset-download.d.ts +10 -0
- package/dist/release-attestation.d.ts +28 -0
- package/dist/remote/remote-session.d.ts +64 -0
- package/dist/remote/remote-target.d.ts +69 -0
- package/dist/remote-GB5VXCCT.js +576 -0
- package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
- package/dist/services/commit-message.d.ts +2 -1
- package/dist/session-move-P2EC2COA.js +44 -0
- package/dist/session-move.d.ts +20 -0
- package/dist/session-stats.d.ts +10 -0
- package/dist/{sessions-config-IQUH2GXT.js → sessions-config-XHLXJ5C3.js} +109 -10
- package/dist/short-circuit-flags-5TNTJPQR.js +10 -0
- package/dist/slash-commands/command-context.d.ts +15 -5
- package/dist/slash-commands/goal-refiner.d.ts +4 -49
- package/dist/slash-commands/mcp.d.ts +2 -1
- package/dist/slash-commands/permissions.d.ts +4 -0
- package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
- package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
- package/dist/slash-commands/theme.d.ts +15 -1
- package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
- package/dist/standalone-auto-update.d.ts +65 -0
- package/dist/standalone-update-LZO3MNLH.js +24 -0
- package/dist/standalone-update.d.ts +37 -1
- package/dist/subcommands/handlers/import-claude-code.d.ts +52 -0
- package/dist/subcommands/handlers/permissions.d.ts +9 -3
- package/dist/subcommands/handlers/remote.d.ts +6 -0
- package/dist/subcommands/handlers/sessions-fleet.d.ts +0 -12
- package/dist/subcommands/handlers/version-help.d.ts +5 -0
- package/dist/{subcommands-ZTSIN7LG.js → subcommands-BHQYLDSD.js} +2 -2
- package/dist/tui-resume-callback.d.ts +18 -0
- package/dist/{typesafe-HVEVPA6R.js → typesafe-U7YB2RRV.js} +5 -5
- package/dist/{typesafe-topic-replay-4W73BOLV.js → typesafe-topic-replay-ZILIU3WS.js} +18 -2
- package/dist/{update-7XIOEXTD.js → update-TDZX4IL2.js} +4 -4
- package/dist/{update-check-2POMBHFB.js → update-check-J5ZVKS73.js} +3 -3
- package/dist/version-JB2UAV2A.js +11 -0
- package/dist/version-help-XJJQ7YSH.js +10 -0
- package/dist/webui-server/route-contexts.d.ts +1 -1
- package/dist/webui-server/session-start-payload.d.ts +5 -1
- package/dist/{webui-server-L2SICPTB.js → webui-server-GCXIO3JY.js} +515 -365
- package/dist/webui-server-lifecycle.d.ts +57 -0
- package/dist/webui-server-options.d.ts +10 -0
- package/dist/webui-server.d.ts +0 -14
- package/dist/wiring/brain-and-orchestration.d.ts +3 -1
- package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/command-host-state.d.ts +2 -0
- package/dist/wiring/hq-telemetry.d.ts +12 -0
- package/dist/wiring/lifecycle-plugins.d.ts +4 -2
- package/dist/wiring/metrics.d.ts +9 -1
- package/dist/wiring/pipeline.d.ts +2 -7
- package/dist/wiring/plugins.d.ts +3 -0
- package/dist/wiring/provider-status.d.ts +1 -0
- package/dist/wiring/provider-utility-tools.d.ts +3 -1
- package/package.json +29 -28
- package/dist/chunk-2BKFWIER.js +0 -98
- package/dist/chunk-424ORSE7.js +0 -56
- package/dist/chunk-OWNC7LMD.js +0 -78
- package/dist/chunk-QBBF7GIK.js +0 -65
- package/dist/short-circuit-flags-SDT53OML.js +0 -10
- package/dist/standalone-update-MW47J3AK.js +0 -164
- 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-
|
|
14
|
+
} from "./chunk-NRXWOMK7.js";
|
|
15
15
|
import {
|
|
16
16
|
startCliHqConnection
|
|
17
|
-
} from "./chunk-
|
|
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
|
|
63
|
-
import {
|
|
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
|
-
|
|
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}: ${
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
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
|
-
|
|
2049
|
-
|
|
2050
|
-
clients.clear();
|
|
2198
|
+
set signalShutdown(value) {
|
|
2199
|
+
signalShutdown = value;
|
|
2051
2200
|
},
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
httpServer?.server.close();
|
|
2201
|
+
get embeddedAutoHealDispose() {
|
|
2202
|
+
return embeddedAutoHealDispose;
|
|
2055
2203
|
},
|
|
2056
|
-
|
|
2057
|
-
|
|
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
|
-
|
|
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-
|
|
2247
|
+
//# sourceMappingURL=webui-server-GCXIO3JY.js.map
|