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