@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/index.js
CHANGED
|
@@ -15,13 +15,99 @@ import {
|
|
|
15
15
|
createAutonomyBrain,
|
|
16
16
|
createTieredBrainArbiter
|
|
17
17
|
} from "@wrongstack/core";
|
|
18
|
-
import * as
|
|
19
|
-
import * as
|
|
18
|
+
import * as fs7 from "fs/promises";
|
|
19
|
+
import * as path9 from "path";
|
|
20
20
|
|
|
21
21
|
// src/server/http-server.ts
|
|
22
22
|
import * as fs from "fs/promises";
|
|
23
23
|
import * as http from "http";
|
|
24
24
|
import * as path from "path";
|
|
25
|
+
|
|
26
|
+
// src/server/ws-auth.ts
|
|
27
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
28
|
+
import { timingSafeEqual } from "crypto";
|
|
29
|
+
function isLoopbackHostname(hostname) {
|
|
30
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
31
|
+
}
|
|
32
|
+
function isTrustedLoopbackOrigin(origin) {
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(origin);
|
|
35
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
36
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isLoopbackBind(wsHost) {
|
|
42
|
+
return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
|
|
43
|
+
}
|
|
44
|
+
function tokenMatches(provided, expected) {
|
|
45
|
+
if (!provided) return false;
|
|
46
|
+
const a = Buffer2.from(provided);
|
|
47
|
+
const b = Buffer2.from(expected);
|
|
48
|
+
if (a.length !== b.length) return false;
|
|
49
|
+
return timingSafeEqual(a, b);
|
|
50
|
+
}
|
|
51
|
+
function extractToken(url) {
|
|
52
|
+
const match = url.match(/[?&]token=([^&]+)/);
|
|
53
|
+
return match ? match[1] : void 0;
|
|
54
|
+
}
|
|
55
|
+
function extractTokenFromCookie(cookieHeader) {
|
|
56
|
+
if (!cookieHeader) return void 0;
|
|
57
|
+
const raw = Array.isArray(cookieHeader) ? cookieHeader.join("; ") : cookieHeader;
|
|
58
|
+
for (const part of raw.split(";")) {
|
|
59
|
+
const eq = part.indexOf("=");
|
|
60
|
+
if (eq < 0) continue;
|
|
61
|
+
const name = part.slice(0, eq).trim();
|
|
62
|
+
if (name === "ws_token") {
|
|
63
|
+
try {
|
|
64
|
+
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
65
|
+
} catch {
|
|
66
|
+
return part.slice(eq + 1).trim();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
function hostHeaderOk(input) {
|
|
73
|
+
if (!isLoopbackBind(input.wsHost)) return true;
|
|
74
|
+
const hostHeader = (input.hostHeader ?? "").trim();
|
|
75
|
+
if (!hostHeader) return false;
|
|
76
|
+
let hostname;
|
|
77
|
+
try {
|
|
78
|
+
hostname = new URL(`http://${hostHeader}`).hostname;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return isLoopbackHostname(hostname);
|
|
83
|
+
}
|
|
84
|
+
function verifyClient(input) {
|
|
85
|
+
const { origin, url, hostHeader, remoteAddress, cookieHeader, wsHost, expectedToken } = input;
|
|
86
|
+
const urlToken = extractToken(url ?? "");
|
|
87
|
+
const cookieToken = extractTokenFromCookie(cookieHeader);
|
|
88
|
+
const tokenOk = tokenMatches(urlToken, expectedToken) || tokenMatches(cookieToken, expectedToken);
|
|
89
|
+
if (!hostHeaderOk({ hostHeader, wsHost })) return false;
|
|
90
|
+
if (!origin) {
|
|
91
|
+
const remoteIp = remoteAddress ?? "";
|
|
92
|
+
const isRemoteLoopback = remoteIp === "127.0.0.1" || remoteIp === "::1";
|
|
93
|
+
if (!isRemoteLoopback && wsHost === "0.0.0.0") return false;
|
|
94
|
+
return tokenOk || isLoopbackBind(wsHost);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const { hostname } = new URL(origin);
|
|
98
|
+
if (isLoopbackHostname(hostname)) {
|
|
99
|
+
if (wsHost === "0.0.0.0" && !isTrustedLoopbackOrigin(origin)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return tokenOk;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/server/http-server.ts
|
|
25
111
|
var MIME_TYPES = {
|
|
26
112
|
".html": "text/html",
|
|
27
113
|
".js": "application/javascript",
|
|
@@ -53,15 +139,47 @@ function createHttpServer(opts) {
|
|
|
53
139
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
54
140
|
const distDir = path.resolve(opts.distDir);
|
|
55
141
|
const wsPort = opts.wsPort;
|
|
142
|
+
const requireApiToken = !isLoopbackBind(opts.host) && Boolean(opts.apiToken);
|
|
56
143
|
return http.createServer(async (req, res) => {
|
|
57
144
|
try {
|
|
58
145
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
146
|
+
if (url.pathname === "/ws-auth" && req.method === "GET" && (opts.enableWsCookie ?? true)) {
|
|
147
|
+
const provided = url.searchParams.get("token") ?? req.headers["x-ws-token"];
|
|
148
|
+
if (!provided || !opts.apiToken || !tokenMatches(provided, opts.apiToken)) {
|
|
149
|
+
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
150
|
+
res.end("Unauthorized");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
res.writeHead(200, {
|
|
154
|
+
"Content-Type": "text/plain",
|
|
155
|
+
"Set-Cookie": `ws_token=${encodeURIComponent(opts.apiToken)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600`,
|
|
156
|
+
// Belt-and-braces: tell any caches the cookie response itself
|
|
157
|
+
// is sensitive.
|
|
158
|
+
"Cache-Control": "no-store"
|
|
159
|
+
});
|
|
160
|
+
res.end("ok");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
59
163
|
if (url.pathname === "/api/sessions" && req.method === "GET") {
|
|
164
|
+
const headerToken = req.headers["x-ws-token"];
|
|
165
|
+
const provided = Array.isArray(headerToken) ? headerToken[0] : headerToken;
|
|
166
|
+
if (requireApiToken && !tokenMatches(provided, opts.apiToken ?? "")) {
|
|
167
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
168
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
60
171
|
await handleApiSessions(res, opts.globalRoot);
|
|
61
172
|
return;
|
|
62
173
|
}
|
|
63
174
|
const agentsMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/agents$/);
|
|
64
175
|
if (agentsMatch && req.method === "GET") {
|
|
176
|
+
const headerToken = req.headers["x-ws-token"];
|
|
177
|
+
const provided = Array.isArray(headerToken) ? headerToken[0] : headerToken;
|
|
178
|
+
if (requireApiToken && !tokenMatches(provided, opts.apiToken ?? "")) {
|
|
179
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
180
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
65
183
|
await handleApiSessionAgents(res, opts.globalRoot, agentsMatch[1]);
|
|
66
184
|
return;
|
|
67
185
|
}
|
|
@@ -1799,71 +1917,6 @@ async function handleMailboxClear(ws, deps) {
|
|
|
1799
1917
|
}
|
|
1800
1918
|
}
|
|
1801
1919
|
|
|
1802
|
-
// src/server/ws-auth.ts
|
|
1803
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
1804
|
-
import { timingSafeEqual } from "crypto";
|
|
1805
|
-
function isLoopbackHostname(hostname) {
|
|
1806
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1807
|
-
}
|
|
1808
|
-
function isTrustedLoopbackOrigin(origin) {
|
|
1809
|
-
try {
|
|
1810
|
-
const url = new URL(origin);
|
|
1811
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
1812
|
-
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
1813
|
-
} catch {
|
|
1814
|
-
return false;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
function isLoopbackBind(wsHost) {
|
|
1818
|
-
return wsHost === "127.0.0.1" || wsHost === "::1" || wsHost === "localhost";
|
|
1819
|
-
}
|
|
1820
|
-
function tokenMatches(provided, expected) {
|
|
1821
|
-
if (!provided) return false;
|
|
1822
|
-
const a = Buffer2.from(provided);
|
|
1823
|
-
const b = Buffer2.from(expected);
|
|
1824
|
-
if (a.length !== b.length) return false;
|
|
1825
|
-
return timingSafeEqual(a, b);
|
|
1826
|
-
}
|
|
1827
|
-
function extractToken(url) {
|
|
1828
|
-
const match = url.match(/[?&]token=([^&]+)/);
|
|
1829
|
-
return match ? match[1] : void 0;
|
|
1830
|
-
}
|
|
1831
|
-
function hostHeaderOk(input) {
|
|
1832
|
-
if (!isLoopbackBind(input.wsHost)) return true;
|
|
1833
|
-
const hostHeader = (input.hostHeader ?? "").trim();
|
|
1834
|
-
if (!hostHeader) return false;
|
|
1835
|
-
let hostname;
|
|
1836
|
-
try {
|
|
1837
|
-
hostname = new URL(`http://${hostHeader}`).hostname;
|
|
1838
|
-
} catch {
|
|
1839
|
-
return false;
|
|
1840
|
-
}
|
|
1841
|
-
return isLoopbackHostname(hostname);
|
|
1842
|
-
}
|
|
1843
|
-
function verifyClient(input) {
|
|
1844
|
-
const { origin, url, hostHeader, remoteAddress, wsHost, expectedToken } = input;
|
|
1845
|
-
const tokenOk = tokenMatches(extractToken(url ?? ""), expectedToken);
|
|
1846
|
-
if (!hostHeaderOk({ hostHeader, wsHost })) return false;
|
|
1847
|
-
if (!origin) {
|
|
1848
|
-
const remoteIp = remoteAddress ?? "";
|
|
1849
|
-
const isRemoteLoopback = remoteIp === "127.0.0.1" || remoteIp === "::1";
|
|
1850
|
-
if (!isRemoteLoopback && wsHost === "0.0.0.0") return false;
|
|
1851
|
-
return tokenOk || isLoopbackBind(wsHost);
|
|
1852
|
-
}
|
|
1853
|
-
try {
|
|
1854
|
-
const { hostname } = new URL(origin);
|
|
1855
|
-
if (isLoopbackHostname(hostname)) {
|
|
1856
|
-
if (wsHost === "0.0.0.0" && !isTrustedLoopbackOrigin(origin)) {
|
|
1857
|
-
return false;
|
|
1858
|
-
}
|
|
1859
|
-
return true;
|
|
1860
|
-
}
|
|
1861
|
-
return tokenOk;
|
|
1862
|
-
} catch {
|
|
1863
|
-
return false;
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
1920
|
// src/server/lifecycle.ts
|
|
1868
1921
|
function createShutdown(res) {
|
|
1869
1922
|
const log = res.log ?? ((m) => console.log(m));
|
|
@@ -1981,16 +2034,16 @@ function formatInstances(instances) {
|
|
|
1981
2034
|
// src/server/port-utils.ts
|
|
1982
2035
|
import * as net from "net";
|
|
1983
2036
|
function isPortFree(host, port) {
|
|
1984
|
-
return new Promise((
|
|
2037
|
+
return new Promise((resolve6) => {
|
|
1985
2038
|
const srv = net.createServer();
|
|
1986
|
-
srv.once("error", () =>
|
|
2039
|
+
srv.once("error", () => resolve6(false));
|
|
1987
2040
|
srv.once("listening", () => {
|
|
1988
|
-
srv.close(() =>
|
|
2041
|
+
srv.close(() => resolve6(true));
|
|
1989
2042
|
});
|
|
1990
2043
|
try {
|
|
1991
2044
|
srv.listen(port, host);
|
|
1992
2045
|
} catch {
|
|
1993
|
-
|
|
2046
|
+
resolve6(false);
|
|
1994
2047
|
}
|
|
1995
2048
|
});
|
|
1996
2049
|
}
|
|
@@ -2729,6 +2782,79 @@ function estimateContextBreakdown(input) {
|
|
|
2729
2782
|
};
|
|
2730
2783
|
}
|
|
2731
2784
|
|
|
2785
|
+
// src/server/eternal-iteration-broadcast.ts
|
|
2786
|
+
function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
2787
|
+
let disposed = false;
|
|
2788
|
+
const dispose = subscribe((entry) => {
|
|
2789
|
+
if (disposed) return;
|
|
2790
|
+
broadcast2(clientsRef(), {
|
|
2791
|
+
type: "eternal.iteration",
|
|
2792
|
+
payload: { entry }
|
|
2793
|
+
});
|
|
2794
|
+
});
|
|
2795
|
+
return {
|
|
2796
|
+
dispose() {
|
|
2797
|
+
if (disposed) return;
|
|
2798
|
+
disposed = true;
|
|
2799
|
+
dispose();
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// src/server/shell-open.ts
|
|
2805
|
+
import * as fs6 from "fs/promises";
|
|
2806
|
+
import * as path8 from "path";
|
|
2807
|
+
import { spawn as spawn2 } from "child_process";
|
|
2808
|
+
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
2809
|
+
async function handleShellOpen(req, logger) {
|
|
2810
|
+
try {
|
|
2811
|
+
const resolved = path8.resolve(req.path);
|
|
2812
|
+
await fs6.access(resolved);
|
|
2813
|
+
if (METACHAR_REGEX.test(resolved)) {
|
|
2814
|
+
return { success: false, message: "Path contains unsupported characters." };
|
|
2815
|
+
}
|
|
2816
|
+
const platform = process.platform;
|
|
2817
|
+
const launch = (cmd, args, onError) => {
|
|
2818
|
+
const child = spawn2(cmd, args, {
|
|
2819
|
+
detached: true,
|
|
2820
|
+
stdio: "ignore",
|
|
2821
|
+
windowsHide: true
|
|
2822
|
+
});
|
|
2823
|
+
child.on("error", (err) => {
|
|
2824
|
+
logger.warn(`shell.open spawn failed: ${err.message}`);
|
|
2825
|
+
onError?.();
|
|
2826
|
+
});
|
|
2827
|
+
child.unref();
|
|
2828
|
+
};
|
|
2829
|
+
if (req.target === "file-manager") {
|
|
2830
|
+
if (platform === "win32") launch("explorer", [resolved]);
|
|
2831
|
+
else if (platform === "darwin") launch("open", [resolved]);
|
|
2832
|
+
else launch("xdg-open", [resolved]);
|
|
2833
|
+
} else if (req.target === "terminal") {
|
|
2834
|
+
if (platform === "win32") {
|
|
2835
|
+
launch("cmd", ["/c", "start", "cmd", "/k", "cd", "/d", resolved]);
|
|
2836
|
+
} else if (platform === "darwin") {
|
|
2837
|
+
launch("open", ["-a", "Terminal", resolved]);
|
|
2838
|
+
} else {
|
|
2839
|
+
launch(
|
|
2840
|
+
"x-terminal-emulator",
|
|
2841
|
+
[`--working-directory=${resolved}`],
|
|
2842
|
+
() => launch(
|
|
2843
|
+
"gnome-terminal",
|
|
2844
|
+
[`--working-directory=${resolved}`],
|
|
2845
|
+
() => launch("xterm", ["-e", `cd '${resolved}' && ${process.env["SHELL"] ?? "sh"}`])
|
|
2846
|
+
)
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
} else {
|
|
2850
|
+
return { success: false, message: `Unknown shell.open target: ${String(req.target)}` };
|
|
2851
|
+
}
|
|
2852
|
+
return { success: true, message: `Opened ${req.target} at ${resolved}` };
|
|
2853
|
+
} catch (err) {
|
|
2854
|
+
return { success: false, message: err instanceof Error ? err.message : String(err) };
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2732
2858
|
// src/server/index.ts
|
|
2733
2859
|
async function startWebUI(opts = {}) {
|
|
2734
2860
|
const requestedWsPort = opts.wsPort ?? 3457;
|
|
@@ -2763,7 +2889,8 @@ async function startWebUI(opts = {}) {
|
|
|
2763
2889
|
}
|
|
2764
2890
|
console.log("[WebUI] Starting backend services...");
|
|
2765
2891
|
const boot = await bootConfig();
|
|
2766
|
-
const { config: baseConfig,
|
|
2892
|
+
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
2893
|
+
const vault = opts.services?.vault ?? boot.vault;
|
|
2767
2894
|
let config = baseConfig;
|
|
2768
2895
|
let projectRoot = boot.projectRoot;
|
|
2769
2896
|
let workingDir = projectRoot;
|
|
@@ -2775,12 +2902,12 @@ async function startWebUI(opts = {}) {
|
|
|
2775
2902
|
console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
|
|
2776
2903
|
}
|
|
2777
2904
|
const needsProvider = !config.provider || !config.model;
|
|
2778
|
-
const modelsRegistry = new DefaultModelsRegistry({
|
|
2905
|
+
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
2779
2906
|
cacheFile: wpaths.modelsCache,
|
|
2780
2907
|
ttlSeconds: 24 * 3600
|
|
2781
2908
|
});
|
|
2782
2909
|
const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
|
|
2783
|
-
const configStore = container.resolve(TOKENS2.ConfigStore);
|
|
2910
|
+
const configStore = opts.services?.configStore ?? container.resolve(TOKENS2.ConfigStore);
|
|
2784
2911
|
const providerRegistry = new ProviderRegistry();
|
|
2785
2912
|
try {
|
|
2786
2913
|
const factories = await buildProviderFactoriesFromRegistry({
|
|
@@ -2797,8 +2924,11 @@ async function startWebUI(opts = {}) {
|
|
|
2797
2924
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2798
2925
|
}));
|
|
2799
2926
|
}
|
|
2800
|
-
const toolRegistry =
|
|
2801
|
-
|
|
2927
|
+
const toolRegistry = opts.services?.toolRegistry ?? (() => {
|
|
2928
|
+
const r = new ToolRegistry();
|
|
2929
|
+
r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
2930
|
+
return r;
|
|
2931
|
+
})();
|
|
2802
2932
|
const memoryStore = new DefaultMemoryStore2({ paths: wpaths });
|
|
2803
2933
|
if (config.features.memory) {
|
|
2804
2934
|
toolRegistry.register(rememberTool(memoryStore));
|
|
@@ -2806,16 +2936,18 @@ async function startWebUI(opts = {}) {
|
|
|
2806
2936
|
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
2807
2937
|
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
2808
2938
|
}
|
|
2809
|
-
const events = new EventBus();
|
|
2939
|
+
const events = opts.services?.events ?? new EventBus();
|
|
2810
2940
|
events.setLogger(logger);
|
|
2811
2941
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
2812
2942
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
2813
2943
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
2814
2944
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
2815
|
-
let sessionStore = new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2945
|
+
let sessionStore = opts.services?.session ?? new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
2946
|
+
if (!opts.services?.session) {
|
|
2947
|
+
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
2948
|
+
if (count > 0) logger.info(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
|
|
2949
|
+
}).catch(() => void 0);
|
|
2950
|
+
}
|
|
2819
2951
|
const sessionReader = new DefaultSessionReader({ store: sessionStore });
|
|
2820
2952
|
const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions });
|
|
2821
2953
|
let session = await sessionStore.create({
|
|
@@ -2837,7 +2969,7 @@ async function startWebUI(opts = {}) {
|
|
|
2837
2969
|
sessionId: session.id,
|
|
2838
2970
|
projectSlug: wpaths.projectSlug,
|
|
2839
2971
|
projectRoot,
|
|
2840
|
-
projectName:
|
|
2972
|
+
projectName: path9.basename(projectRoot),
|
|
2841
2973
|
workingDir,
|
|
2842
2974
|
pid: process.pid,
|
|
2843
2975
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3032,7 +3164,7 @@ async function startWebUI(opts = {}) {
|
|
|
3032
3164
|
const write = async () => {
|
|
3033
3165
|
let raw;
|
|
3034
3166
|
try {
|
|
3035
|
-
raw = await
|
|
3167
|
+
raw = await fs7.readFile(globalConfigPath, "utf8");
|
|
3036
3168
|
} catch {
|
|
3037
3169
|
raw = "{}";
|
|
3038
3170
|
}
|
|
@@ -3325,12 +3457,11 @@ async function startWebUI(opts = {}) {
|
|
|
3325
3457
|
inputCost,
|
|
3326
3458
|
outputCost,
|
|
3327
3459
|
cacheReadCost,
|
|
3328
|
-
projectName:
|
|
3460
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
3329
3461
|
projectRoot,
|
|
3330
3462
|
cwd: workingDir,
|
|
3331
3463
|
mode: modeId,
|
|
3332
|
-
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID)
|
|
3333
|
-
wsToken
|
|
3464
|
+
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID)
|
|
3334
3465
|
};
|
|
3335
3466
|
}
|
|
3336
3467
|
const wsToken = generateAuthToken();
|
|
@@ -3340,6 +3471,11 @@ async function startWebUI(opts = {}) {
|
|
|
3340
3471
|
url: info.req.url ?? "",
|
|
3341
3472
|
hostHeader: info.req.headers.host,
|
|
3342
3473
|
remoteAddress: info.req.socket.remoteAddress,
|
|
3474
|
+
// C-2 fix: accept the token via the HttpOnly cookie set by
|
|
3475
|
+
// `/ws-auth` (preferred) OR the URL query param (non-browser
|
|
3476
|
+
// fallback). The cookie path closes the C-598 query-string
|
|
3477
|
+
// exposure class.
|
|
3478
|
+
cookieHeader: info.req.headers.cookie,
|
|
3343
3479
|
wsHost,
|
|
3344
3480
|
expectedToken: wsToken
|
|
3345
3481
|
});
|
|
@@ -3364,6 +3500,14 @@ async function startWebUI(opts = {}) {
|
|
|
3364
3500
|
payload: { cwd: newDir, projectRoot }
|
|
3365
3501
|
});
|
|
3366
3502
|
});
|
|
3503
|
+
let eternalSubscription = null;
|
|
3504
|
+
if (opts.subscribeEternalIteration) {
|
|
3505
|
+
eternalSubscription = createEternalSubscription(
|
|
3506
|
+
opts.subscribeEternalIteration,
|
|
3507
|
+
broadcast,
|
|
3508
|
+
() => clients
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3367
3511
|
const RATE_LIMIT_MESSAGES = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
|
|
3368
3512
|
const RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3369
3513
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
@@ -3440,8 +3584,8 @@ async function startWebUI(opts = {}) {
|
|
|
3440
3584
|
clients.delete(ws);
|
|
3441
3585
|
rateLimits.delete(String(ws));
|
|
3442
3586
|
if (pendingConfirms.size > 0) {
|
|
3443
|
-
for (const [id,
|
|
3444
|
-
|
|
3587
|
+
for (const [id, resolve6] of pendingConfirms) {
|
|
3588
|
+
resolve6("no");
|
|
3445
3589
|
pendingConfirms.delete(id);
|
|
3446
3590
|
}
|
|
3447
3591
|
}
|
|
@@ -3505,33 +3649,33 @@ async function startWebUI(opts = {}) {
|
|
|
3505
3649
|
});
|
|
3506
3650
|
}
|
|
3507
3651
|
async function touchProjectEntry(root, workDir) {
|
|
3508
|
-
const resolved =
|
|
3652
|
+
const resolved = path9.resolve(root);
|
|
3509
3653
|
const manifest = await loadManifest(globalConfigPath);
|
|
3510
3654
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3511
|
-
const existing = manifest.projects.find((p) =>
|
|
3655
|
+
const existing = manifest.projects.find((p) => path9.resolve(p.root) === resolved);
|
|
3512
3656
|
if (existing) {
|
|
3513
3657
|
existing.lastSeen = now;
|
|
3514
|
-
if (workDir) existing.lastWorkingDir =
|
|
3658
|
+
if (workDir) existing.lastWorkingDir = path9.resolve(workDir);
|
|
3515
3659
|
} else {
|
|
3516
3660
|
manifest.projects.push({
|
|
3517
|
-
name:
|
|
3661
|
+
name: path9.basename(resolved),
|
|
3518
3662
|
root: resolved,
|
|
3519
3663
|
slug: generateProjectSlug(resolved),
|
|
3520
3664
|
createdAt: now,
|
|
3521
3665
|
lastSeen: now,
|
|
3522
|
-
lastWorkingDir: workDir ?
|
|
3666
|
+
lastWorkingDir: workDir ? path9.resolve(workDir) : void 0
|
|
3523
3667
|
});
|
|
3524
3668
|
}
|
|
3525
3669
|
await saveManifest(manifest, globalConfigPath);
|
|
3526
3670
|
await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
|
|
3527
3671
|
}
|
|
3528
3672
|
function projectsJsonPath(globalConfigPath2) {
|
|
3529
|
-
const base =
|
|
3530
|
-
return
|
|
3673
|
+
const base = path9.dirname(globalConfigPath2);
|
|
3674
|
+
return path9.join(base, "projects.json");
|
|
3531
3675
|
}
|
|
3532
3676
|
async function loadManifest(globalConfigPath2) {
|
|
3533
3677
|
try {
|
|
3534
|
-
const raw = await
|
|
3678
|
+
const raw = await fs7.readFile(projectsJsonPath(globalConfigPath2), "utf8");
|
|
3535
3679
|
const parsed = JSON.parse(raw);
|
|
3536
3680
|
return { projects: parsed.projects ?? [] };
|
|
3537
3681
|
} catch {
|
|
@@ -3540,16 +3684,16 @@ async function startWebUI(opts = {}) {
|
|
|
3540
3684
|
}
|
|
3541
3685
|
async function saveManifest(manifest, globalConfigPath2) {
|
|
3542
3686
|
const file = projectsJsonPath(globalConfigPath2);
|
|
3543
|
-
await
|
|
3544
|
-
await
|
|
3687
|
+
await fs7.mkdir(path9.dirname(file), { recursive: true });
|
|
3688
|
+
await fs7.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
3545
3689
|
}
|
|
3546
3690
|
function generateProjectSlug(rootPath) {
|
|
3547
3691
|
return projectSlug(rootPath);
|
|
3548
3692
|
}
|
|
3549
3693
|
async function ensureProjectDataDir(slug, globalConfigPath2) {
|
|
3550
|
-
const base =
|
|
3551
|
-
const dir =
|
|
3552
|
-
await
|
|
3694
|
+
const base = path9.dirname(globalConfigPath2);
|
|
3695
|
+
const dir = path9.join(base, "projects", slug);
|
|
3696
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
3553
3697
|
return dir;
|
|
3554
3698
|
}
|
|
3555
3699
|
async function handleMessage(ws, _client, msg) {
|
|
@@ -3611,10 +3755,10 @@ async function startWebUI(opts = {}) {
|
|
|
3611
3755
|
}
|
|
3612
3756
|
case "tool.confirm_result": {
|
|
3613
3757
|
const { id, decision } = msg.payload;
|
|
3614
|
-
const
|
|
3615
|
-
if (
|
|
3758
|
+
const resolve6 = pendingConfirms.get(id);
|
|
3759
|
+
if (resolve6) {
|
|
3616
3760
|
pendingConfirms.delete(id);
|
|
3617
|
-
|
|
3761
|
+
resolve6(decision);
|
|
3618
3762
|
}
|
|
3619
3763
|
break;
|
|
3620
3764
|
}
|
|
@@ -3888,7 +4032,7 @@ async function startWebUI(opts = {}) {
|
|
|
3888
4032
|
updateAutoCompactionMaxContext?.(newProv);
|
|
3889
4033
|
try {
|
|
3890
4034
|
configWriteLock = configWriteLock.then(async () => {
|
|
3891
|
-
const raw = await
|
|
4035
|
+
const raw = await fs7.readFile(globalConfigPath, "utf8");
|
|
3892
4036
|
const parsed = JSON.parse(raw);
|
|
3893
4037
|
parsed.provider = newProvider;
|
|
3894
4038
|
parsed.model = newModel;
|
|
@@ -4434,8 +4578,8 @@ async function startWebUI(opts = {}) {
|
|
|
4434
4578
|
}
|
|
4435
4579
|
case "goal.get": {
|
|
4436
4580
|
try {
|
|
4437
|
-
const goalPath =
|
|
4438
|
-
const raw = await
|
|
4581
|
+
const goalPath = path9.join(projectRoot, ".wrongstack", "goal.json");
|
|
4582
|
+
const raw = await fs7.readFile(goalPath, "utf8");
|
|
4439
4583
|
const goal = JSON.parse(raw);
|
|
4440
4584
|
broadcast(clients, { type: "goal.updated", payload: goal });
|
|
4441
4585
|
} catch {
|
|
@@ -4495,7 +4639,7 @@ async function startWebUI(opts = {}) {
|
|
|
4495
4639
|
try {
|
|
4496
4640
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4497
4641
|
const rewinder = new DefaultSessionRewinder(
|
|
4498
|
-
|
|
4642
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
4499
4643
|
projectRoot
|
|
4500
4644
|
);
|
|
4501
4645
|
const checkpoints = await rewinder.listCheckpoints(session.id);
|
|
@@ -4516,7 +4660,7 @@ async function startWebUI(opts = {}) {
|
|
|
4516
4660
|
try {
|
|
4517
4661
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4518
4662
|
const rewinder = new DefaultSessionRewinder(
|
|
4519
|
-
|
|
4663
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
4520
4664
|
projectRoot
|
|
4521
4665
|
);
|
|
4522
4666
|
await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
|
|
@@ -4550,9 +4694,9 @@ async function startWebUI(opts = {}) {
|
|
|
4550
4694
|
case "projects.add": {
|
|
4551
4695
|
const { root: addRoot, name: displayName } = msg.payload;
|
|
4552
4696
|
try {
|
|
4553
|
-
const resolved =
|
|
4554
|
-
await
|
|
4555
|
-
const stat2 = await
|
|
4697
|
+
const resolved = path9.resolve(addRoot);
|
|
4698
|
+
await fs7.access(resolved);
|
|
4699
|
+
const stat2 = await fs7.stat(resolved);
|
|
4556
4700
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
4557
4701
|
const manifest = await loadManifest(globalConfigPath);
|
|
4558
4702
|
const existing = manifest.projects.find((p) => p.root === resolved);
|
|
@@ -4568,7 +4712,7 @@ async function startWebUI(opts = {}) {
|
|
|
4568
4712
|
});
|
|
4569
4713
|
break;
|
|
4570
4714
|
}
|
|
4571
|
-
const name = displayName?.trim() ||
|
|
4715
|
+
const name = displayName?.trim() || path9.basename(resolved);
|
|
4572
4716
|
const slug = generateProjectSlug(resolved);
|
|
4573
4717
|
await ensureProjectDataDir(slug, globalConfigPath);
|
|
4574
4718
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4587,7 +4731,7 @@ async function startWebUI(opts = {}) {
|
|
|
4587
4731
|
send(ws, {
|
|
4588
4732
|
type: "projects.added",
|
|
4589
4733
|
payload: {
|
|
4590
|
-
name:
|
|
4734
|
+
name: path9.basename(addRoot),
|
|
4591
4735
|
root: addRoot,
|
|
4592
4736
|
slug: "",
|
|
4593
4737
|
message: errMessage(err)
|
|
@@ -4599,17 +4743,17 @@ async function startWebUI(opts = {}) {
|
|
|
4599
4743
|
case "projects.select": {
|
|
4600
4744
|
const { root: selRoot, name: selName } = msg.payload;
|
|
4601
4745
|
try {
|
|
4602
|
-
const resolved =
|
|
4746
|
+
const resolved = path9.resolve(selRoot);
|
|
4603
4747
|
try {
|
|
4604
|
-
await
|
|
4605
|
-
const stat2 = await
|
|
4748
|
+
await fs7.access(resolved);
|
|
4749
|
+
const stat2 = await fs7.stat(resolved);
|
|
4606
4750
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
4607
4751
|
} catch (err) {
|
|
4608
4752
|
send(ws, {
|
|
4609
4753
|
type: "projects.selected",
|
|
4610
4754
|
payload: {
|
|
4611
4755
|
root: selRoot,
|
|
4612
|
-
name: selName ||
|
|
4756
|
+
name: selName || path9.basename(selRoot),
|
|
4613
4757
|
message: `Cannot switch: ${errMessage(err)}`
|
|
4614
4758
|
}
|
|
4615
4759
|
});
|
|
@@ -4621,7 +4765,7 @@ async function startWebUI(opts = {}) {
|
|
|
4621
4765
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
4622
4766
|
entry.lastWorkingDir = resolved;
|
|
4623
4767
|
} else {
|
|
4624
|
-
const name = selName?.trim() ||
|
|
4768
|
+
const name = selName?.trim() || path9.basename(resolved);
|
|
4625
4769
|
const slug = generateProjectSlug(resolved);
|
|
4626
4770
|
manifest.projects.push({
|
|
4627
4771
|
name,
|
|
@@ -4642,13 +4786,33 @@ async function startWebUI(opts = {}) {
|
|
|
4642
4786
|
workingDir = resolved;
|
|
4643
4787
|
context.cwd = workingDir;
|
|
4644
4788
|
context.projectRoot = projectRoot;
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4789
|
+
const switchSlug = entry?.slug ?? generateProjectSlug(resolved);
|
|
4790
|
+
try {
|
|
4791
|
+
const switchMode = modeId === "default" ? void 0 : await modeStore.getMode(modeId);
|
|
4792
|
+
const switchBuilder = new DefaultSystemPromptBuilder2({
|
|
4793
|
+
memoryStore,
|
|
4794
|
+
skillLoader,
|
|
4795
|
+
modeStore,
|
|
4796
|
+
modeId,
|
|
4797
|
+
modePrompt: switchMode?.prompt ?? "",
|
|
4798
|
+
modelCapabilities
|
|
4799
|
+
});
|
|
4800
|
+
context.systemPrompt = await switchBuilder.build({
|
|
4801
|
+
cwd: workingDir,
|
|
4802
|
+
projectRoot,
|
|
4803
|
+
tools: toolRegistry.list(),
|
|
4804
|
+
provider: config.provider,
|
|
4805
|
+
model: config.model
|
|
4806
|
+
});
|
|
4807
|
+
} catch {
|
|
4808
|
+
}
|
|
4809
|
+
const newSessionsDir = path9.join(
|
|
4810
|
+
path9.dirname(globalConfigPath),
|
|
4647
4811
|
"projects",
|
|
4648
|
-
|
|
4812
|
+
switchSlug,
|
|
4649
4813
|
"sessions"
|
|
4650
4814
|
);
|
|
4651
|
-
await
|
|
4815
|
+
await fs7.mkdir(newSessionsDir, { recursive: true });
|
|
4652
4816
|
const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
|
|
4653
4817
|
const oldSessionId = session.id;
|
|
4654
4818
|
try {
|
|
@@ -4674,12 +4838,25 @@ async function startWebUI(opts = {}) {
|
|
|
4674
4838
|
context.fileMtimes.clear();
|
|
4675
4839
|
tokenCounter.reset();
|
|
4676
4840
|
sessionStartedAt = Date.now();
|
|
4841
|
+
try {
|
|
4842
|
+
const registry = getSessionRegistry(wpaths.globalRoot);
|
|
4843
|
+
await registry.register({
|
|
4844
|
+
sessionId: session.id,
|
|
4845
|
+
projectSlug: switchSlug,
|
|
4846
|
+
projectRoot,
|
|
4847
|
+
projectName: path9.basename(projectRoot),
|
|
4848
|
+
workingDir,
|
|
4849
|
+
pid: process.pid,
|
|
4850
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4851
|
+
});
|
|
4852
|
+
} catch {
|
|
4853
|
+
}
|
|
4677
4854
|
send(ws, {
|
|
4678
4855
|
type: "projects.selected",
|
|
4679
4856
|
payload: {
|
|
4680
4857
|
root: resolved,
|
|
4681
|
-
name: selName ||
|
|
4682
|
-
message: `Switched to ${selName ||
|
|
4858
|
+
name: selName || path9.basename(resolved),
|
|
4859
|
+
message: `Switched to ${selName || path9.basename(resolved)}`
|
|
4683
4860
|
}
|
|
4684
4861
|
});
|
|
4685
4862
|
broadcast(clients, {
|
|
@@ -4702,7 +4879,7 @@ async function startWebUI(opts = {}) {
|
|
|
4702
4879
|
type: "projects.selected",
|
|
4703
4880
|
payload: {
|
|
4704
4881
|
root: selRoot,
|
|
4705
|
-
name: selName ||
|
|
4882
|
+
name: selName || path9.basename(selRoot),
|
|
4706
4883
|
message: errMessage(err)
|
|
4707
4884
|
}
|
|
4708
4885
|
});
|
|
@@ -4713,14 +4890,14 @@ async function startWebUI(opts = {}) {
|
|
|
4713
4890
|
case "working_dir.set": {
|
|
4714
4891
|
const { path: newPath } = msg.payload;
|
|
4715
4892
|
try {
|
|
4716
|
-
const resolved =
|
|
4717
|
-
if (!resolved.startsWith(projectRoot +
|
|
4893
|
+
const resolved = path9.resolve(projectRoot, newPath);
|
|
4894
|
+
if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== projectRoot) {
|
|
4718
4895
|
sendResult(ws, false, `Path must stay inside the project root: ${projectRoot}`);
|
|
4719
4896
|
break;
|
|
4720
4897
|
}
|
|
4721
4898
|
try {
|
|
4722
|
-
await
|
|
4723
|
-
const stat2 = await
|
|
4899
|
+
await fs7.access(resolved);
|
|
4900
|
+
const stat2 = await fs7.stat(resolved);
|
|
4724
4901
|
if (!stat2.isDirectory()) throw new Error("Not a directory");
|
|
4725
4902
|
} catch {
|
|
4726
4903
|
sendResult(ws, false, `Directory not found or not accessible: ${resolved}`);
|
|
@@ -4740,58 +4917,30 @@ async function startWebUI(opts = {}) {
|
|
|
4740
4917
|
}
|
|
4741
4918
|
// ── Shell open — spawn terminal or file manager at a path ─────────
|
|
4742
4919
|
case "shell.open": {
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
const platform = process.platform;
|
|
4749
|
-
let cmd;
|
|
4750
|
-
if (target === "file-manager") {
|
|
4751
|
-
if (platform === "win32") {
|
|
4752
|
-
cmd = `explorer "${resolved}"`;
|
|
4753
|
-
} else if (platform === "darwin") {
|
|
4754
|
-
cmd = `open "${resolved}"`;
|
|
4755
|
-
} else {
|
|
4756
|
-
cmd = `xdg-open "${resolved}"`;
|
|
4757
|
-
}
|
|
4758
|
-
} else {
|
|
4759
|
-
if (platform === "win32") {
|
|
4760
|
-
cmd = `start cmd /k cd /d "${resolved}"`;
|
|
4761
|
-
} else if (platform === "darwin") {
|
|
4762
|
-
cmd = `open -a Terminal "${resolved}"`;
|
|
4763
|
-
} else {
|
|
4764
|
-
cmd = `x-terminal-emulator --working-directory="${resolved}" 2>/dev/null || gnome-terminal --working-directory="${resolved}" 2>/dev/null || xterm -e "cd '${resolved}' && $SHELL"`;
|
|
4765
|
-
}
|
|
4766
|
-
}
|
|
4767
|
-
exec(cmd, { timeout: 5e3 }, (err) => {
|
|
4768
|
-
if (err) {
|
|
4769
|
-
logger.warn(`shell.open failed: ${err.message}`);
|
|
4770
|
-
}
|
|
4771
|
-
});
|
|
4772
|
-
sendResult(ws, true, `Opened ${target} at ${resolved}`);
|
|
4773
|
-
} catch (err) {
|
|
4774
|
-
sendResult(ws, false, errMessage(err));
|
|
4775
|
-
}
|
|
4920
|
+
const result = await handleShellOpen(
|
|
4921
|
+
msg.payload,
|
|
4922
|
+
logger
|
|
4923
|
+
);
|
|
4924
|
+
sendResult(ws, result.success, result.message);
|
|
4776
4925
|
break;
|
|
4777
4926
|
}
|
|
4778
4927
|
// ── Mailbox operations — project-level inter-agent messaging ────
|
|
4779
4928
|
case "mailbox.messages":
|
|
4780
4929
|
return handleMailboxMessages(
|
|
4781
4930
|
ws,
|
|
4782
|
-
{ projectRoot, globalRoot:
|
|
4931
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
4783
4932
|
msg.payload
|
|
4784
4933
|
);
|
|
4785
4934
|
case "mailbox.agents":
|
|
4786
4935
|
return handleMailboxAgents(
|
|
4787
4936
|
ws,
|
|
4788
|
-
{ projectRoot, globalRoot:
|
|
4937
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
4789
4938
|
msg.payload
|
|
4790
4939
|
);
|
|
4791
4940
|
case "mailbox.clear":
|
|
4792
4941
|
return handleMailboxClear(
|
|
4793
4942
|
ws,
|
|
4794
|
-
{ projectRoot, globalRoot:
|
|
4943
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) }
|
|
4795
4944
|
);
|
|
4796
4945
|
// ── Brain — status, autonomy ceiling, direct decision support ───
|
|
4797
4946
|
case "brain.status":
|
|
@@ -4859,10 +5008,12 @@ async function startWebUI(opts = {}) {
|
|
|
4859
5008
|
});
|
|
4860
5009
|
const httpServer = createHttpServer({
|
|
4861
5010
|
host: wsHost,
|
|
4862
|
-
distDir:
|
|
4863
|
-
wsPort
|
|
5011
|
+
distDir: path9.resolve(import.meta.dirname, "../../dist"),
|
|
5012
|
+
wsPort,
|
|
5013
|
+
globalRoot: wpaths.globalRoot,
|
|
5014
|
+
apiToken: wsToken
|
|
4864
5015
|
});
|
|
4865
|
-
const registryBaseDir =
|
|
5016
|
+
const registryBaseDir = path9.dirname(globalConfigPath);
|
|
4866
5017
|
httpServer.listen(httpPort, wsHost, () => {
|
|
4867
5018
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
4868
5019
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -4874,7 +5025,7 @@ async function startWebUI(opts = {}) {
|
|
|
4874
5025
|
wsPort,
|
|
4875
5026
|
host: wsHost,
|
|
4876
5027
|
projectRoot,
|
|
4877
|
-
projectName:
|
|
5028
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
4878
5029
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4879
5030
|
url: `http://${wsHost}:${httpPort}`
|
|
4880
5031
|
},
|
|
@@ -4901,6 +5052,10 @@ async function startWebUI(opts = {}) {
|
|
|
4901
5052
|
// reality. Crash exits are healed by the next register()/list() prune pass.
|
|
4902
5053
|
onShutdown: () => {
|
|
4903
5054
|
brainMonitor.stop();
|
|
5055
|
+
if (eternalSubscription) {
|
|
5056
|
+
eternalSubscription.dispose();
|
|
5057
|
+
eternalSubscription = null;
|
|
5058
|
+
}
|
|
4904
5059
|
return unregisterInstance(process.pid, registryBaseDir);
|
|
4905
5060
|
}
|
|
4906
5061
|
});
|
|
@@ -4912,11 +5067,13 @@ export {
|
|
|
4912
5067
|
browserOpenCommand,
|
|
4913
5068
|
buildCspHeader,
|
|
4914
5069
|
createCustomModeStore,
|
|
5070
|
+
createEternalSubscription,
|
|
4915
5071
|
createHttpServer,
|
|
4916
5072
|
createProviderConfigIO,
|
|
4917
5073
|
defaultBaseDir,
|
|
4918
5074
|
deleteKey,
|
|
4919
5075
|
errMessage,
|
|
5076
|
+
estimateTokens,
|
|
4920
5077
|
extractToken,
|
|
4921
5078
|
findFreePort,
|
|
4922
5079
|
formatInstances,
|
|
@@ -4928,6 +5085,7 @@ export {
|
|
|
4928
5085
|
handleMemoryForget,
|
|
4929
5086
|
handleMemoryList,
|
|
4930
5087
|
handleMemoryRemember,
|
|
5088
|
+
handleShellOpen,
|
|
4931
5089
|
hostHeaderOk,
|
|
4932
5090
|
injectWsPort,
|
|
4933
5091
|
isLoopbackBind,
|
|
@@ -4936,6 +5094,8 @@ export {
|
|
|
4936
5094
|
listInstances,
|
|
4937
5095
|
loadSavedProviders,
|
|
4938
5096
|
maskedKey,
|
|
5097
|
+
messagePreview,
|
|
5098
|
+
messageTokens,
|
|
4939
5099
|
normalizeKeys,
|
|
4940
5100
|
openBrowser,
|
|
4941
5101
|
registerInstance,
|
|
@@ -4946,6 +5106,7 @@ export {
|
|
|
4946
5106
|
sendResult,
|
|
4947
5107
|
setActiveKey,
|
|
4948
5108
|
startWebUI,
|
|
5109
|
+
stringifyContent,
|
|
4949
5110
|
tokenMatches,
|
|
4950
5111
|
unregisterInstance,
|
|
4951
5112
|
upsertKey,
|