@wrongstack/webui 0.255.0 → 0.256.1
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/dist/assets/index-DH7u59XN.js +164 -0
- package/dist/assets/index-nmjg9dOb.css +2 -0
- package/dist/assets/{vendor-DEuauVA6.js → vendor-XlX63RRU.js} +228 -228
- package/dist/index.html +3 -3
- package/dist/index.js +747 -564
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +325 -170
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.d.ts +131 -4
- package/dist/server/index.js +331 -170
- package/dist/server/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/assets/index-DcnrP-gv.js +0 -163
- package/dist/assets/index-u41ruU9Z.css +0 -2
package/dist/server/entry.js
CHANGED
|
@@ -16,13 +16,99 @@ import {
|
|
|
16
16
|
createAutonomyBrain,
|
|
17
17
|
createTieredBrainArbiter
|
|
18
18
|
} from "@wrongstack/core";
|
|
19
|
-
import * as
|
|
20
|
-
import * as
|
|
19
|
+
import * as fs7 from "fs/promises";
|
|
20
|
+
import * as path9 from "path";
|
|
21
21
|
|
|
22
22
|
// src/server/http-server.ts
|
|
23
23
|
import * as fs from "fs/promises";
|
|
24
24
|
import * as http from "http";
|
|
25
25
|
import * as path from "path";
|
|
26
|
+
|
|
27
|
+
// src/server/ws-auth.ts
|
|
28
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
29
|
+
import { timingSafeEqual } from "crypto";
|
|
30
|
+
function isLoopbackHostname(hostname) {
|
|
31
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
32
|
+
}
|
|
33
|
+
function isTrustedLoopbackOrigin(origin) {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(origin);
|
|
36
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
37
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function isLoopbackBind(wsHost) {
|
|
43
|
+
return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
|
|
44
|
+
}
|
|
45
|
+
function tokenMatches(provided, expected) {
|
|
46
|
+
if (!provided) return false;
|
|
47
|
+
const a = Buffer2.from(provided);
|
|
48
|
+
const b = Buffer2.from(expected);
|
|
49
|
+
if (a.length !== b.length) return false;
|
|
50
|
+
return timingSafeEqual(a, b);
|
|
51
|
+
}
|
|
52
|
+
function extractToken(url) {
|
|
53
|
+
const match = url.match(/[?&]token=([^&]+)/);
|
|
54
|
+
return match ? match[1] : void 0;
|
|
55
|
+
}
|
|
56
|
+
function extractTokenFromCookie(cookieHeader) {
|
|
57
|
+
if (!cookieHeader) return void 0;
|
|
58
|
+
const raw = Array.isArray(cookieHeader) ? cookieHeader.join("; ") : cookieHeader;
|
|
59
|
+
for (const part of raw.split(";")) {
|
|
60
|
+
const eq = part.indexOf("=");
|
|
61
|
+
if (eq < 0) continue;
|
|
62
|
+
const name = part.slice(0, eq).trim();
|
|
63
|
+
if (name === "ws_token") {
|
|
64
|
+
try {
|
|
65
|
+
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
66
|
+
} catch {
|
|
67
|
+
return part.slice(eq + 1).trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
function hostHeaderOk(input) {
|
|
74
|
+
if (!isLoopbackBind(input.wsHost)) return true;
|
|
75
|
+
const hostHeader = (input.hostHeader ?? "").trim();
|
|
76
|
+
if (!hostHeader) return false;
|
|
77
|
+
let hostname;
|
|
78
|
+
try {
|
|
79
|
+
hostname = new URL(`http://${hostHeader}`).hostname;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return isLoopbackHostname(hostname);
|
|
84
|
+
}
|
|
85
|
+
function verifyClient(input) {
|
|
86
|
+
const { origin, url, hostHeader, remoteAddress, cookieHeader, wsHost, expectedToken } = input;
|
|
87
|
+
const urlToken = extractToken(url ?? "");
|
|
88
|
+
const cookieToken = extractTokenFromCookie(cookieHeader);
|
|
89
|
+
const tokenOk = tokenMatches(urlToken, expectedToken) || tokenMatches(cookieToken, expectedToken);
|
|
90
|
+
if (!hostHeaderOk({ hostHeader, wsHost })) return false;
|
|
91
|
+
if (!origin) {
|
|
92
|
+
const remoteIp = remoteAddress ?? "";
|
|
93
|
+
const isRemoteLoopback = remoteIp === "127.0.0.1" || remoteIp === "::1";
|
|
94
|
+
if (!isRemoteLoopback && wsHost === "0.0.0.0") return false;
|
|
95
|
+
return tokenOk || isLoopbackBind(wsHost);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const { hostname } = new URL(origin);
|
|
99
|
+
if (isLoopbackHostname(hostname)) {
|
|
100
|
+
if (wsHost === "0.0.0.0" && !isTrustedLoopbackOrigin(origin)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return tokenOk;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/server/http-server.ts
|
|
26
112
|
var MIME_TYPES = {
|
|
27
113
|
".html": "text/html",
|
|
28
114
|
".js": "application/javascript",
|
|
@@ -54,15 +140,47 @@ function createHttpServer(opts) {
|
|
|
54
140
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
55
141
|
const distDir = path.resolve(opts.distDir);
|
|
56
142
|
const wsPort = opts.wsPort;
|
|
143
|
+
const requireApiToken = !isLoopbackBind(opts.host) && Boolean(opts.apiToken);
|
|
57
144
|
return http.createServer(async (req, res) => {
|
|
58
145
|
try {
|
|
59
146
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
147
|
+
if (url.pathname === "/ws-auth" && req.method === "GET" && (opts.enableWsCookie ?? true)) {
|
|
148
|
+
const provided = url.searchParams.get("token") ?? req.headers["x-ws-token"];
|
|
149
|
+
if (!provided || !opts.apiToken || !tokenMatches(provided, opts.apiToken)) {
|
|
150
|
+
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
151
|
+
res.end("Unauthorized");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
res.writeHead(200, {
|
|
155
|
+
"Content-Type": "text/plain",
|
|
156
|
+
"Set-Cookie": `ws_token=${encodeURIComponent(opts.apiToken)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600`,
|
|
157
|
+
// Belt-and-braces: tell any caches the cookie response itself
|
|
158
|
+
// is sensitive.
|
|
159
|
+
"Cache-Control": "no-store"
|
|
160
|
+
});
|
|
161
|
+
res.end("ok");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
60
164
|
if (url.pathname === "/api/sessions" && req.method === "GET") {
|
|
165
|
+
const headerToken = req.headers["x-ws-token"];
|
|
166
|
+
const provided = Array.isArray(headerToken) ? headerToken[0] : headerToken;
|
|
167
|
+
if (requireApiToken && !tokenMatches(provided, opts.apiToken ?? "")) {
|
|
168
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
169
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
61
172
|
await handleApiSessions(res, opts.globalRoot);
|
|
62
173
|
return;
|
|
63
174
|
}
|
|
64
175
|
const agentsMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/agents$/);
|
|
65
176
|
if (agentsMatch && req.method === "GET") {
|
|
177
|
+
const headerToken = req.headers["x-ws-token"];
|
|
178
|
+
const provided = Array.isArray(headerToken) ? headerToken[0] : headerToken;
|
|
179
|
+
if (requireApiToken && !tokenMatches(provided, opts.apiToken ?? "")) {
|
|
180
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
181
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
66
184
|
await handleApiSessionAgents(res, opts.globalRoot, agentsMatch[1]);
|
|
67
185
|
return;
|
|
68
186
|
}
|
|
@@ -1800,71 +1918,6 @@ async function handleMailboxClear(ws, deps) {
|
|
|
1800
1918
|
}
|
|
1801
1919
|
}
|
|
1802
1920
|
|
|
1803
|
-
// src/server/ws-auth.ts
|
|
1804
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
1805
|
-
import { timingSafeEqual } from "crypto";
|
|
1806
|
-
function isLoopbackHostname(hostname) {
|
|
1807
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1808
|
-
}
|
|
1809
|
-
function isTrustedLoopbackOrigin(origin) {
|
|
1810
|
-
try {
|
|
1811
|
-
const url = new URL(origin);
|
|
1812
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
1813
|
-
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
1814
|
-
} catch {
|
|
1815
|
-
return false;
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
function isLoopbackBind(wsHost) {
|
|
1819
|
-
return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
|
|
1820
|
-
}
|
|
1821
|
-
function tokenMatches(provided, expected) {
|
|
1822
|
-
if (!provided) return false;
|
|
1823
|
-
const a = Buffer2.from(provided);
|
|
1824
|
-
const b = Buffer2.from(expected);
|
|
1825
|
-
if (a.length !== b.length) return false;
|
|
1826
|
-
return timingSafeEqual(a, b);
|
|
1827
|
-
}
|
|
1828
|
-
function extractToken(url) {
|
|
1829
|
-
const match = url.match(/[?&]token=([^&]+)/);
|
|
1830
|
-
return match ? match[1] : void 0;
|
|
1831
|
-
}
|
|
1832
|
-
function hostHeaderOk(input) {
|
|
1833
|
-
if (!isLoopbackBind(input.wsHost)) return true;
|
|
1834
|
-
const hostHeader = (input.hostHeader ?? "").trim();
|
|
1835
|
-
if (!hostHeader) return false;
|
|
1836
|
-
let hostname;
|
|
1837
|
-
try {
|
|
1838
|
-
hostname = new URL(`http://${hostHeader}`).hostname;
|
|
1839
|
-
} catch {
|
|
1840
|
-
return false;
|
|
1841
|
-
}
|
|
1842
|
-
return isLoopbackHostname(hostname);
|
|
1843
|
-
}
|
|
1844
|
-
function verifyClient(input) {
|
|
1845
|
-
const { origin, url, hostHeader, remoteAddress, wsHost, expectedToken } = input;
|
|
1846
|
-
const tokenOk = tokenMatches(extractToken(url ?? ""), expectedToken);
|
|
1847
|
-
if (!hostHeaderOk({ hostHeader, wsHost })) return false;
|
|
1848
|
-
if (!origin) {
|
|
1849
|
-
const remoteIp = remoteAddress ?? "";
|
|
1850
|
-
const isRemoteLoopback = remoteIp === "127.0.0.1" || remoteIp === "::1";
|
|
1851
|
-
if (!isRemoteLoopback && wsHost === "0.0.0.0") return false;
|
|
1852
|
-
return tokenOk || isLoopbackBind(wsHost);
|
|
1853
|
-
}
|
|
1854
|
-
try {
|
|
1855
|
-
const { hostname } = new URL(origin);
|
|
1856
|
-
if (isLoopbackHostname(hostname)) {
|
|
1857
|
-
if (wsHost === "0.0.0.0" && !isTrustedLoopbackOrigin(origin)) {
|
|
1858
|
-
return false;
|
|
1859
|
-
}
|
|
1860
|
-
return true;
|
|
1861
|
-
}
|
|
1862
|
-
return tokenOk;
|
|
1863
|
-
} catch {
|
|
1864
|
-
return false;
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
1921
|
// src/server/lifecycle.ts
|
|
1869
1922
|
function createShutdown(res) {
|
|
1870
1923
|
const log = res.log ?? ((m) => console.log(m));
|
|
@@ -1982,16 +2035,16 @@ function formatInstances(instances) {
|
|
|
1982
2035
|
// src/server/port-utils.ts
|
|
1983
2036
|
import * as net from "net";
|
|
1984
2037
|
function isPortFree(host, port) {
|
|
1985
|
-
return new Promise((
|
|
2038
|
+
return new Promise((resolve6) => {
|
|
1986
2039
|
const srv = net.createServer();
|
|
1987
|
-
srv.once("error", () =>
|
|
2040
|
+
srv.once("error", () => resolve6(false));
|
|
1988
2041
|
srv.once("listening", () => {
|
|
1989
|
-
srv.close(() =>
|
|
2042
|
+
srv.close(() => resolve6(true));
|
|
1990
2043
|
});
|
|
1991
2044
|
try {
|
|
1992
2045
|
srv.listen(port, host);
|
|
1993
2046
|
} catch {
|
|
1994
|
-
|
|
2047
|
+
resolve6(false);
|
|
1995
2048
|
}
|
|
1996
2049
|
});
|
|
1997
2050
|
}
|
|
@@ -2722,6 +2775,79 @@ function estimateContextBreakdown(input) {
|
|
|
2722
2775
|
};
|
|
2723
2776
|
}
|
|
2724
2777
|
|
|
2778
|
+
// src/server/eternal-iteration-broadcast.ts
|
|
2779
|
+
function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
2780
|
+
let disposed = false;
|
|
2781
|
+
const dispose = subscribe((entry) => {
|
|
2782
|
+
if (disposed) return;
|
|
2783
|
+
broadcast2(clientsRef(), {
|
|
2784
|
+
type: "eternal.iteration",
|
|
2785
|
+
payload: { entry }
|
|
2786
|
+
});
|
|
2787
|
+
});
|
|
2788
|
+
return {
|
|
2789
|
+
dispose() {
|
|
2790
|
+
if (disposed) return;
|
|
2791
|
+
disposed = true;
|
|
2792
|
+
dispose();
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
// src/server/shell-open.ts
|
|
2798
|
+
import * as fs6 from "fs/promises";
|
|
2799
|
+
import * as path8 from "path";
|
|
2800
|
+
import { spawn as spawn2 } from "child_process";
|
|
2801
|
+
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
2802
|
+
async function handleShellOpen(req, logger) {
|
|
2803
|
+
try {
|
|
2804
|
+
const resolved = path8.resolve(req.path);
|
|
2805
|
+
await fs6.access(resolved);
|
|
2806
|
+
if (METACHAR_REGEX.test(resolved)) {
|
|
2807
|
+
return { success: false, message: "Path contains unsupported characters." };
|
|
2808
|
+
}
|
|
2809
|
+
const platform = process.platform;
|
|
2810
|
+
const launch = (cmd, args, onError) => {
|
|
2811
|
+
const child = spawn2(cmd, args, {
|
|
2812
|
+
detached: true,
|
|
2813
|
+
stdio: "ignore",
|
|
2814
|
+
windowsHide: true
|
|
2815
|
+
});
|
|
2816
|
+
child.on("error", (err) => {
|
|
2817
|
+
logger.warn(`shell.open spawn failed: ${err.message}`);
|
|
2818
|
+
onError?.();
|
|
2819
|
+
});
|
|
2820
|
+
child.unref();
|
|
2821
|
+
};
|
|
2822
|
+
if (req.target === "file-manager") {
|
|
2823
|
+
if (platform === "win32") launch("explorer", [resolved]);
|
|
2824
|
+
else if (platform === "darwin") launch("open", [resolved]);
|
|
2825
|
+
else launch("xdg-open", [resolved]);
|
|
2826
|
+
} else if (req.target === "terminal") {
|
|
2827
|
+
if (platform === "win32") {
|
|
2828
|
+
launch("cmd", ["/c", "start", "cmd", "/k", "cd", "/d", resolved]);
|
|
2829
|
+
} else if (platform === "darwin") {
|
|
2830
|
+
launch("open", ["-a", "Terminal", resolved]);
|
|
2831
|
+
} else {
|
|
2832
|
+
launch(
|
|
2833
|
+
"x-terminal-emulator",
|
|
2834
|
+
[`--working-directory=${resolved}`],
|
|
2835
|
+
() => launch(
|
|
2836
|
+
"gnome-terminal",
|
|
2837
|
+
[`--working-directory=${resolved}`],
|
|
2838
|
+
() => launch("xterm", ["-e", `cd '${resolved}' && ${process.env["SHELL"] ?? "sh"}`])
|
|
2839
|
+
)
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
} else {
|
|
2843
|
+
return { success: false, message: `Unknown shell.open target: ${String(req.target)}` };
|
|
2844
|
+
}
|
|
2845
|
+
return { success: true, message: `Opened ${req.target} at ${resolved}` };
|
|
2846
|
+
} catch (err) {
|
|
2847
|
+
return { success: false, message: err instanceof Error ? err.message : String(err) };
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2725
2851
|
// src/server/index.ts
|
|
2726
2852
|
async function startWebUI(opts = {}) {
|
|
2727
2853
|
const requestedWsPort = opts.wsPort ?? 3457;
|
|
@@ -2756,7 +2882,8 @@ async function startWebUI(opts = {}) {
|
|
|
2756
2882
|
}
|
|
2757
2883
|
console.log("[WebUI] Starting backend services...");
|
|
2758
2884
|
const boot = await bootConfig();
|
|
2759
|
-
const { config: baseConfig,
|
|
2885
|
+
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
2886
|
+
const vault = opts.services?.vault ?? boot.vault;
|
|
2760
2887
|
let config = baseConfig;
|
|
2761
2888
|
let projectRoot = boot.projectRoot;
|
|
2762
2889
|
let workingDir = projectRoot;
|
|
@@ -2768,12 +2895,12 @@ async function startWebUI(opts = {}) {
|
|
|
2768
2895
|
console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
|
|
2769
2896
|
}
|
|
2770
2897
|
const needsProvider = !config.provider || !config.model;
|
|
2771
|
-
const modelsRegistry = new DefaultModelsRegistry({
|
|
2898
|
+
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
2772
2899
|
cacheFile: wpaths.modelsCache,
|
|
2773
2900
|
ttlSeconds: 24 * 3600
|
|
2774
2901
|
});
|
|
2775
2902
|
const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
|
|
2776
|
-
const configStore = container.resolve(TOKENS2.ConfigStore);
|
|
2903
|
+
const configStore = opts.services?.configStore ?? container.resolve(TOKENS2.ConfigStore);
|
|
2777
2904
|
const providerRegistry = new ProviderRegistry();
|
|
2778
2905
|
try {
|
|
2779
2906
|
const factories = await buildProviderFactoriesFromRegistry({
|
|
@@ -2790,8 +2917,11 @@ async function startWebUI(opts = {}) {
|
|
|
2790
2917
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2791
2918
|
}));
|
|
2792
2919
|
}
|
|
2793
|
-
const toolRegistry =
|
|
2794
|
-
|
|
2920
|
+
const toolRegistry = opts.services?.toolRegistry ?? (() => {
|
|
2921
|
+
const r = new ToolRegistry();
|
|
2922
|
+
r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
2923
|
+
return r;
|
|
2924
|
+
})();
|
|
2795
2925
|
const memoryStore = new DefaultMemoryStore2({ paths: wpaths });
|
|
2796
2926
|
if (config.features.memory) {
|
|
2797
2927
|
toolRegistry.register(rememberTool(memoryStore));
|
|
@@ -2799,16 +2929,18 @@ async function startWebUI(opts = {}) {
|
|
|
2799
2929
|
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
2800
2930
|
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
2801
2931
|
}
|
|
2802
|
-
const events = new EventBus();
|
|
2932
|
+
const events = opts.services?.events ?? new EventBus();
|
|
2803
2933
|
events.setLogger(logger);
|
|
2804
2934
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
2805
2935
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
2806
2936
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
2807
2937
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
2808
|
-
let sessionStore = new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2938
|
+
let sessionStore = opts.services?.session ?? new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
2939
|
+
if (!opts.services?.session) {
|
|
2940
|
+
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
2941
|
+
if (count > 0) logger.info(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
|
|
2942
|
+
}).catch(() => void 0);
|
|
2943
|
+
}
|
|
2812
2944
|
const sessionReader = new DefaultSessionReader({ store: sessionStore });
|
|
2813
2945
|
const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions });
|
|
2814
2946
|
let session = await sessionStore.create({
|
|
@@ -2830,7 +2962,7 @@ async function startWebUI(opts = {}) {
|
|
|
2830
2962
|
sessionId: session.id,
|
|
2831
2963
|
projectSlug: wpaths.projectSlug,
|
|
2832
2964
|
projectRoot,
|
|
2833
|
-
projectName:
|
|
2965
|
+
projectName: path9.basename(projectRoot),
|
|
2834
2966
|
workingDir,
|
|
2835
2967
|
pid: process.pid,
|
|
2836
2968
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3025,7 +3157,7 @@ async function startWebUI(opts = {}) {
|
|
|
3025
3157
|
const write = async () => {
|
|
3026
3158
|
let raw;
|
|
3027
3159
|
try {
|
|
3028
|
-
raw = await
|
|
3160
|
+
raw = await fs7.readFile(globalConfigPath, "utf8");
|
|
3029
3161
|
} catch {
|
|
3030
3162
|
raw = "{}";
|
|
3031
3163
|
}
|
|
@@ -3318,12 +3450,11 @@ async function startWebUI(opts = {}) {
|
|
|
3318
3450
|
inputCost,
|
|
3319
3451
|
outputCost,
|
|
3320
3452
|
cacheReadCost,
|
|
3321
|
-
projectName:
|
|
3453
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
3322
3454
|
projectRoot,
|
|
3323
3455
|
cwd: workingDir,
|
|
3324
3456
|
mode: modeId,
|
|
3325
|
-
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID)
|
|
3326
|
-
wsToken
|
|
3457
|
+
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID)
|
|
3327
3458
|
};
|
|
3328
3459
|
}
|
|
3329
3460
|
const wsToken = generateAuthToken();
|
|
@@ -3333,6 +3464,11 @@ async function startWebUI(opts = {}) {
|
|
|
3333
3464
|
url: info.req.url ?? "",
|
|
3334
3465
|
hostHeader: info.req.headers.host,
|
|
3335
3466
|
remoteAddress: info.req.socket.remoteAddress,
|
|
3467
|
+
// C-2 fix: accept the token via the HttpOnly cookie set by
|
|
3468
|
+
// `/ws-auth` (preferred) OR the URL query param (non-browser
|
|
3469
|
+
// fallback). The cookie path closes the C-598 query-string
|
|
3470
|
+
// exposure class.
|
|
3471
|
+
cookieHeader: info.req.headers.cookie,
|
|
3336
3472
|
wsHost,
|
|
3337
3473
|
expectedToken: wsToken
|
|
3338
3474
|
});
|
|
@@ -3357,6 +3493,14 @@ async function startWebUI(opts = {}) {
|
|
|
3357
3493
|
payload: { cwd: newDir, projectRoot }
|
|
3358
3494
|
});
|
|
3359
3495
|
});
|
|
3496
|
+
let eternalSubscription = null;
|
|
3497
|
+
if (opts.subscribeEternalIteration) {
|
|
3498
|
+
eternalSubscription = createEternalSubscription(
|
|
3499
|
+
opts.subscribeEternalIteration,
|
|
3500
|
+
broadcast,
|
|
3501
|
+
() => clients
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3360
3504
|
const RATE_LIMIT_MESSAGES = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
|
|
3361
3505
|
const RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3362
3506
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
@@ -3433,8 +3577,8 @@ async function startWebUI(opts = {}) {
|
|
|
3433
3577
|
clients.delete(ws);
|
|
3434
3578
|
rateLimits.delete(String(ws));
|
|
3435
3579
|
if (pendingConfirms.size > 0) {
|
|
3436
|
-
for (const [id,
|
|
3437
|
-
|
|
3580
|
+
for (const [id, resolve6] of pendingConfirms) {
|
|
3581
|
+
resolve6("no");
|
|
3438
3582
|
pendingConfirms.delete(id);
|
|
3439
3583
|
}
|
|
3440
3584
|
}
|
|
@@ -3498,33 +3642,33 @@ async function startWebUI(opts = {}) {
|
|
|
3498
3642
|
});
|
|
3499
3643
|
}
|
|
3500
3644
|
async function touchProjectEntry(root, workDir) {
|
|
3501
|
-
const resolved =
|
|
3645
|
+
const resolved = path9.resolve(root);
|
|
3502
3646
|
const manifest = await loadManifest(globalConfigPath);
|
|
3503
3647
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3504
|
-
const existing = manifest.projects.find((p) =>
|
|
3648
|
+
const existing = manifest.projects.find((p) => path9.resolve(p.root) === resolved);
|
|
3505
3649
|
if (existing) {
|
|
3506
3650
|
existing.lastSeen = now;
|
|
3507
|
-
if (workDir) existing.lastWorkingDir =
|
|
3651
|
+
if (workDir) existing.lastWorkingDir = path9.resolve(workDir);
|
|
3508
3652
|
} else {
|
|
3509
3653
|
manifest.projects.push({
|
|
3510
|
-
name:
|
|
3654
|
+
name: path9.basename(resolved),
|
|
3511
3655
|
root: resolved,
|
|
3512
3656
|
slug: generateProjectSlug(resolved),
|
|
3513
3657
|
createdAt: now,
|
|
3514
3658
|
lastSeen: now,
|
|
3515
|
-
lastWorkingDir: workDir ?
|
|
3659
|
+
lastWorkingDir: workDir ? path9.resolve(workDir) : void 0
|
|
3516
3660
|
});
|
|
3517
3661
|
}
|
|
3518
3662
|
await saveManifest(manifest, globalConfigPath);
|
|
3519
3663
|
await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
|
|
3520
3664
|
}
|
|
3521
3665
|
function projectsJsonPath(globalConfigPath2) {
|
|
3522
|
-
const base =
|
|
3523
|
-
return
|
|
3666
|
+
const base = path9.dirname(globalConfigPath2);
|
|
3667
|
+
return path9.join(base, "projects.json");
|
|
3524
3668
|
}
|
|
3525
3669
|
async function loadManifest(globalConfigPath2) {
|
|
3526
3670
|
try {
|
|
3527
|
-
const raw = await
|
|
3671
|
+
const raw = await fs7.readFile(projectsJsonPath(globalConfigPath2), "utf8");
|
|
3528
3672
|
const parsed = JSON.parse(raw);
|
|
3529
3673
|
return { projects: parsed.projects ?? [] };
|
|
3530
3674
|
} catch {
|
|
@@ -3533,16 +3677,16 @@ async function startWebUI(opts = {}) {
|
|
|
3533
3677
|
}
|
|
3534
3678
|
async function saveManifest(manifest, globalConfigPath2) {
|
|
3535
3679
|
const file = projectsJsonPath(globalConfigPath2);
|
|
3536
|
-
await
|
|
3537
|
-
await
|
|
3680
|
+
await fs7.mkdir(path9.dirname(file), { recursive: true });
|
|
3681
|
+
await fs7.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
3538
3682
|
}
|
|
3539
3683
|
function generateProjectSlug(rootPath) {
|
|
3540
3684
|
return projectSlug(rootPath);
|
|
3541
3685
|
}
|
|
3542
3686
|
async function ensureProjectDataDir(slug, globalConfigPath2) {
|
|
3543
|
-
const base =
|
|
3544
|
-
const dir =
|
|
3545
|
-
await
|
|
3687
|
+
const base = path9.dirname(globalConfigPath2);
|
|
3688
|
+
const dir = path9.join(base, "projects", slug);
|
|
3689
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
3546
3690
|
return dir;
|
|
3547
3691
|
}
|
|
3548
3692
|
async function handleMessage(ws, _client, msg) {
|
|
@@ -3604,10 +3748,10 @@ async function startWebUI(opts = {}) {
|
|
|
3604
3748
|
}
|
|
3605
3749
|
case "tool.confirm_result": {
|
|
3606
3750
|
const { id, decision } = msg.payload;
|
|
3607
|
-
const
|
|
3608
|
-
if (
|
|
3751
|
+
const resolve6 = pendingConfirms.get(id);
|
|
3752
|
+
if (resolve6) {
|
|
3609
3753
|
pendingConfirms.delete(id);
|
|
3610
|
-
|
|
3754
|
+
resolve6(decision);
|
|
3611
3755
|
}
|
|
3612
3756
|
break;
|
|
3613
3757
|
}
|
|
@@ -3881,7 +4025,7 @@ async function startWebUI(opts = {}) {
|
|
|
3881
4025
|
updateAutoCompactionMaxContext?.(newProv);
|
|
3882
4026
|
try {
|
|
3883
4027
|
configWriteLock = configWriteLock.then(async () => {
|
|
3884
|
-
const raw = await
|
|
4028
|
+
const raw = await fs7.readFile(globalConfigPath, "utf8");
|
|
3885
4029
|
const parsed = JSON.parse(raw);
|
|
3886
4030
|
parsed.provider = newProvider;
|
|
3887
4031
|
parsed.model = newModel;
|
|
@@ -4427,8 +4571,8 @@ async function startWebUI(opts = {}) {
|
|
|
4427
4571
|
}
|
|
4428
4572
|
case "goal.get": {
|
|
4429
4573
|
try {
|
|
4430
|
-
const goalPath =
|
|
4431
|
-
const raw = await
|
|
4574
|
+
const goalPath = path9.join(projectRoot, ".wrongstack", "goal.json");
|
|
4575
|
+
const raw = await fs7.readFile(goalPath, "utf8");
|
|
4432
4576
|
const goal = JSON.parse(raw);
|
|
4433
4577
|
broadcast(clients, { type: "goal.updated", payload: goal });
|
|
4434
4578
|
} catch {
|
|
@@ -4488,7 +4632,7 @@ async function startWebUI(opts = {}) {
|
|
|
4488
4632
|
try {
|
|
4489
4633
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4490
4634
|
const rewinder = new DefaultSessionRewinder(
|
|
4491
|
-
|
|
4635
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
4492
4636
|
projectRoot
|
|
4493
4637
|
);
|
|
4494
4638
|
const checkpoints = await rewinder.listCheckpoints(session.id);
|
|
@@ -4509,7 +4653,7 @@ async function startWebUI(opts = {}) {
|
|
|
4509
4653
|
try {
|
|
4510
4654
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4511
4655
|
const rewinder = new DefaultSessionRewinder(
|
|
4512
|
-
|
|
4656
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
4513
4657
|
projectRoot
|
|
4514
4658
|
);
|
|
4515
4659
|
await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
|
|
@@ -4543,9 +4687,9 @@ async function startWebUI(opts = {}) {
|
|
|
4543
4687
|
case "projects.add": {
|
|
4544
4688
|
const { root: addRoot, name: displayName } = msg.payload;
|
|
4545
4689
|
try {
|
|
4546
|
-
const resolved =
|
|
4547
|
-
await
|
|
4548
|
-
const stat2 = await
|
|
4690
|
+
const resolved = path9.resolve(addRoot);
|
|
4691
|
+
await fs7.access(resolved);
|
|
4692
|
+
const stat2 = await fs7.stat(resolved);
|
|
4549
4693
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
4550
4694
|
const manifest = await loadManifest(globalConfigPath);
|
|
4551
4695
|
const existing = manifest.projects.find((p) => p.root === resolved);
|
|
@@ -4561,7 +4705,7 @@ async function startWebUI(opts = {}) {
|
|
|
4561
4705
|
});
|
|
4562
4706
|
break;
|
|
4563
4707
|
}
|
|
4564
|
-
const name = displayName?.trim() ||
|
|
4708
|
+
const name = displayName?.trim() || path9.basename(resolved);
|
|
4565
4709
|
const slug = generateProjectSlug(resolved);
|
|
4566
4710
|
await ensureProjectDataDir(slug, globalConfigPath);
|
|
4567
4711
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4580,7 +4724,7 @@ async function startWebUI(opts = {}) {
|
|
|
4580
4724
|
send(ws, {
|
|
4581
4725
|
type: "projects.added",
|
|
4582
4726
|
payload: {
|
|
4583
|
-
name:
|
|
4727
|
+
name: path9.basename(addRoot),
|
|
4584
4728
|
root: addRoot,
|
|
4585
4729
|
slug: "",
|
|
4586
4730
|
message: errMessage(err)
|
|
@@ -4592,17 +4736,17 @@ async function startWebUI(opts = {}) {
|
|
|
4592
4736
|
case "projects.select": {
|
|
4593
4737
|
const { root: selRoot, name: selName } = msg.payload;
|
|
4594
4738
|
try {
|
|
4595
|
-
const resolved =
|
|
4739
|
+
const resolved = path9.resolve(selRoot);
|
|
4596
4740
|
try {
|
|
4597
|
-
await
|
|
4598
|
-
const stat2 = await
|
|
4741
|
+
await fs7.access(resolved);
|
|
4742
|
+
const stat2 = await fs7.stat(resolved);
|
|
4599
4743
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
4600
4744
|
} catch (err) {
|
|
4601
4745
|
send(ws, {
|
|
4602
4746
|
type: "projects.selected",
|
|
4603
4747
|
payload: {
|
|
4604
4748
|
root: selRoot,
|
|
4605
|
-
name: selName ||
|
|
4749
|
+
name: selName || path9.basename(selRoot),
|
|
4606
4750
|
message: `Cannot switch: ${errMessage(err)}`
|
|
4607
4751
|
}
|
|
4608
4752
|
});
|
|
@@ -4614,7 +4758,7 @@ async function startWebUI(opts = {}) {
|
|
|
4614
4758
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
4615
4759
|
entry.lastWorkingDir = resolved;
|
|
4616
4760
|
} else {
|
|
4617
|
-
const name = selName?.trim() ||
|
|
4761
|
+
const name = selName?.trim() || path9.basename(resolved);
|
|
4618
4762
|
const slug = generateProjectSlug(resolved);
|
|
4619
4763
|
manifest.projects.push({
|
|
4620
4764
|
name,
|
|
@@ -4635,13 +4779,33 @@ async function startWebUI(opts = {}) {
|
|
|
4635
4779
|
workingDir = resolved;
|
|
4636
4780
|
context.cwd = workingDir;
|
|
4637
4781
|
context.projectRoot = projectRoot;
|
|
4638
|
-
const
|
|
4639
|
-
|
|
4782
|
+
const switchSlug = entry?.slug ?? generateProjectSlug(resolved);
|
|
4783
|
+
try {
|
|
4784
|
+
const switchMode = modeId === "default" ? void 0 : await modeStore.getMode(modeId);
|
|
4785
|
+
const switchBuilder = new DefaultSystemPromptBuilder2({
|
|
4786
|
+
memoryStore,
|
|
4787
|
+
skillLoader,
|
|
4788
|
+
modeStore,
|
|
4789
|
+
modeId,
|
|
4790
|
+
modePrompt: switchMode?.prompt ?? "",
|
|
4791
|
+
modelCapabilities
|
|
4792
|
+
});
|
|
4793
|
+
context.systemPrompt = await switchBuilder.build({
|
|
4794
|
+
cwd: workingDir,
|
|
4795
|
+
projectRoot,
|
|
4796
|
+
tools: toolRegistry.list(),
|
|
4797
|
+
provider: config.provider,
|
|
4798
|
+
model: config.model
|
|
4799
|
+
});
|
|
4800
|
+
} catch {
|
|
4801
|
+
}
|
|
4802
|
+
const newSessionsDir = path9.join(
|
|
4803
|
+
path9.dirname(globalConfigPath),
|
|
4640
4804
|
"projects",
|
|
4641
|
-
|
|
4805
|
+
switchSlug,
|
|
4642
4806
|
"sessions"
|
|
4643
4807
|
);
|
|
4644
|
-
await
|
|
4808
|
+
await fs7.mkdir(newSessionsDir, { recursive: true });
|
|
4645
4809
|
const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
|
|
4646
4810
|
const oldSessionId = session.id;
|
|
4647
4811
|
try {
|
|
@@ -4667,12 +4831,25 @@ async function startWebUI(opts = {}) {
|
|
|
4667
4831
|
context.fileMtimes.clear();
|
|
4668
4832
|
tokenCounter.reset();
|
|
4669
4833
|
sessionStartedAt = Date.now();
|
|
4834
|
+
try {
|
|
4835
|
+
const registry = getSessionRegistry(wpaths.globalRoot);
|
|
4836
|
+
await registry.register({
|
|
4837
|
+
sessionId: session.id,
|
|
4838
|
+
projectSlug: switchSlug,
|
|
4839
|
+
projectRoot,
|
|
4840
|
+
projectName: path9.basename(projectRoot),
|
|
4841
|
+
workingDir,
|
|
4842
|
+
pid: process.pid,
|
|
4843
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4844
|
+
});
|
|
4845
|
+
} catch {
|
|
4846
|
+
}
|
|
4670
4847
|
send(ws, {
|
|
4671
4848
|
type: "projects.selected",
|
|
4672
4849
|
payload: {
|
|
4673
4850
|
root: resolved,
|
|
4674
|
-
name: selName ||
|
|
4675
|
-
message: `Switched to ${selName ||
|
|
4851
|
+
name: selName || path9.basename(resolved),
|
|
4852
|
+
message: `Switched to ${selName || path9.basename(resolved)}`
|
|
4676
4853
|
}
|
|
4677
4854
|
});
|
|
4678
4855
|
broadcast(clients, {
|
|
@@ -4695,7 +4872,7 @@ async function startWebUI(opts = {}) {
|
|
|
4695
4872
|
type: "projects.selected",
|
|
4696
4873
|
payload: {
|
|
4697
4874
|
root: selRoot,
|
|
4698
|
-
name: selName ||
|
|
4875
|
+
name: selName || path9.basename(selRoot),
|
|
4699
4876
|
message: errMessage(err)
|
|
4700
4877
|
}
|
|
4701
4878
|
});
|
|
@@ -4706,14 +4883,14 @@ async function startWebUI(opts = {}) {
|
|
|
4706
4883
|
case "working_dir.set": {
|
|
4707
4884
|
const { path: newPath } = msg.payload;
|
|
4708
4885
|
try {
|
|
4709
|
-
const resolved =
|
|
4710
|
-
if (!resolved.startsWith(projectRoot +
|
|
4886
|
+
const resolved = path9.resolve(projectRoot, newPath);
|
|
4887
|
+
if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== projectRoot) {
|
|
4711
4888
|
sendResult(ws, false, `Path must stay inside the project root: ${projectRoot}`);
|
|
4712
4889
|
break;
|
|
4713
4890
|
}
|
|
4714
4891
|
try {
|
|
4715
|
-
await
|
|
4716
|
-
const stat2 = await
|
|
4892
|
+
await fs7.access(resolved);
|
|
4893
|
+
const stat2 = await fs7.stat(resolved);
|
|
4717
4894
|
if (!stat2.isDirectory()) throw new Error("Not a directory");
|
|
4718
4895
|
} catch {
|
|
4719
4896
|
sendResult(ws, false, `Directory not found or not accessible: ${resolved}`);
|
|
@@ -4733,58 +4910,30 @@ async function startWebUI(opts = {}) {
|
|
|
4733
4910
|
}
|
|
4734
4911
|
// ── Shell open — spawn terminal or file manager at a path ─────────
|
|
4735
4912
|
case "shell.open": {
|
|
4736
|
-
const
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
const platform = process.platform;
|
|
4742
|
-
let cmd;
|
|
4743
|
-
if (target === "file-manager") {
|
|
4744
|
-
if (platform === "win32") {
|
|
4745
|
-
cmd = `explorer "${resolved}"`;
|
|
4746
|
-
} else if (platform === "darwin") {
|
|
4747
|
-
cmd = `open "${resolved}"`;
|
|
4748
|
-
} else {
|
|
4749
|
-
cmd = `xdg-open "${resolved}"`;
|
|
4750
|
-
}
|
|
4751
|
-
} else {
|
|
4752
|
-
if (platform === "win32") {
|
|
4753
|
-
cmd = `start cmd /k cd /d "${resolved}"`;
|
|
4754
|
-
} else if (platform === "darwin") {
|
|
4755
|
-
cmd = `open -a Terminal "${resolved}"`;
|
|
4756
|
-
} else {
|
|
4757
|
-
cmd = `x-terminal-emulator --working-directory="${resolved}" 2>/dev/null || gnome-terminal --working-directory="${resolved}" 2>/dev/null || xterm -e "cd '${resolved}' && $SHELL"`;
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
exec(cmd, { timeout: 5e3 }, (err) => {
|
|
4761
|
-
if (err) {
|
|
4762
|
-
logger.warn(`shell.open failed: ${err.message}`);
|
|
4763
|
-
}
|
|
4764
|
-
});
|
|
4765
|
-
sendResult(ws, true, `Opened ${target} at ${resolved}`);
|
|
4766
|
-
} catch (err) {
|
|
4767
|
-
sendResult(ws, false, errMessage(err));
|
|
4768
|
-
}
|
|
4913
|
+
const result = await handleShellOpen(
|
|
4914
|
+
msg.payload,
|
|
4915
|
+
logger
|
|
4916
|
+
);
|
|
4917
|
+
sendResult(ws, result.success, result.message);
|
|
4769
4918
|
break;
|
|
4770
4919
|
}
|
|
4771
4920
|
// ── Mailbox operations — project-level inter-agent messaging ────
|
|
4772
4921
|
case "mailbox.messages":
|
|
4773
4922
|
return handleMailboxMessages(
|
|
4774
4923
|
ws,
|
|
4775
|
-
{ projectRoot, globalRoot:
|
|
4924
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
4776
4925
|
msg.payload
|
|
4777
4926
|
);
|
|
4778
4927
|
case "mailbox.agents":
|
|
4779
4928
|
return handleMailboxAgents(
|
|
4780
4929
|
ws,
|
|
4781
|
-
{ projectRoot, globalRoot:
|
|
4930
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
4782
4931
|
msg.payload
|
|
4783
4932
|
);
|
|
4784
4933
|
case "mailbox.clear":
|
|
4785
4934
|
return handleMailboxClear(
|
|
4786
4935
|
ws,
|
|
4787
|
-
{ projectRoot, globalRoot:
|
|
4936
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) }
|
|
4788
4937
|
);
|
|
4789
4938
|
// ── Brain — status, autonomy ceiling, direct decision support ───
|
|
4790
4939
|
case "brain.status":
|
|
@@ -4852,10 +5001,12 @@ async function startWebUI(opts = {}) {
|
|
|
4852
5001
|
});
|
|
4853
5002
|
const httpServer = createHttpServer({
|
|
4854
5003
|
host: wsHost,
|
|
4855
|
-
distDir:
|
|
4856
|
-
wsPort
|
|
5004
|
+
distDir: path9.resolve(import.meta.dirname, "../../dist"),
|
|
5005
|
+
wsPort,
|
|
5006
|
+
globalRoot: wpaths.globalRoot,
|
|
5007
|
+
apiToken: wsToken
|
|
4857
5008
|
});
|
|
4858
|
-
const registryBaseDir =
|
|
5009
|
+
const registryBaseDir = path9.dirname(globalConfigPath);
|
|
4859
5010
|
httpServer.listen(httpPort, wsHost, () => {
|
|
4860
5011
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
4861
5012
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -4867,7 +5018,7 @@ async function startWebUI(opts = {}) {
|
|
|
4867
5018
|
wsPort,
|
|
4868
5019
|
host: wsHost,
|
|
4869
5020
|
projectRoot,
|
|
4870
|
-
projectName:
|
|
5021
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
4871
5022
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4872
5023
|
url: `http://${wsHost}:${httpPort}`
|
|
4873
5024
|
},
|
|
@@ -4894,6 +5045,10 @@ async function startWebUI(opts = {}) {
|
|
|
4894
5045
|
// reality. Crash exits are healed by the next register()/list() prune pass.
|
|
4895
5046
|
onShutdown: () => {
|
|
4896
5047
|
brainMonitor.stop();
|
|
5048
|
+
if (eternalSubscription) {
|
|
5049
|
+
eternalSubscription.dispose();
|
|
5050
|
+
eternalSubscription = null;
|
|
5051
|
+
}
|
|
4897
5052
|
return unregisterInstance(process.pid, registryBaseDir);
|
|
4898
5053
|
}
|
|
4899
5054
|
});
|