@wrongstack/webui 0.54.1 → 0.66.13
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/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/dist/assets/index-CnURnGh-.css +1 -0
- package/dist/assets/index-aAReViZF.js +94 -0
- package/dist/assets/vendor-DW1jimNH.css +1 -0
- package/dist/index.css +333 -200
- package/dist/index.css.map +1 -1
- package/dist/index.html +4 -3
- package/dist/index.js +984 -641
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +444 -126
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.d.ts +298 -2
- package/dist/server/index.js +445 -96
- package/dist/server/index.js.map +1 -1
- package/package.json +9 -6
- package/dist/assets/index-5ECutVTP.css +0 -1
- package/dist/assets/index-BRHGqfHg.js +0 -94
- /package/dist/assets/{vendor-oYD55Pw4.js → vendor-wUxgMlp-.js} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/server/index.ts
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs4 from "fs/promises";
|
|
3
|
+
import * as path4 from "path";
|
|
4
4
|
|
|
5
5
|
// src/server/http-server.ts
|
|
6
6
|
import * as fs from "fs/promises";
|
|
@@ -15,6 +15,16 @@ var MIME_TYPES = {
|
|
|
15
15
|
".png": "image/png",
|
|
16
16
|
".ico": "image/x-icon"
|
|
17
17
|
};
|
|
18
|
+
function injectWsPort(html, wsPort) {
|
|
19
|
+
const tag = `<meta name="wrongstack-ws-port" content="${wsPort}" />`;
|
|
20
|
+
if (html.includes('name="wrongstack-ws-port"')) return html;
|
|
21
|
+
if (html.includes("</head>")) {
|
|
22
|
+
return html.replace("</head>", ` ${tag}
|
|
23
|
+
</head>`);
|
|
24
|
+
}
|
|
25
|
+
return `${tag}
|
|
26
|
+
${html}`;
|
|
27
|
+
}
|
|
18
28
|
function buildCspHeader(wsPort) {
|
|
19
29
|
return `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws://127.0.0.1:${wsPort} wss://127.0.0.1:${wsPort} ws://[::1]:${wsPort} wss://[::1]:${wsPort}; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
20
30
|
}
|
|
@@ -55,6 +65,10 @@ function createHttpServer(opts) {
|
|
|
55
65
|
if (ext === ".html") {
|
|
56
66
|
res.setHeader("Cache-Control", "no-cache");
|
|
57
67
|
res.setHeader("Content-Security-Policy", buildCspHeader(wsPort));
|
|
68
|
+
const html = await fs.readFile(resolvedPath, "utf8");
|
|
69
|
+
res.writeHead(200);
|
|
70
|
+
res.end(injectWsPort(html, wsPort));
|
|
71
|
+
return;
|
|
58
72
|
}
|
|
59
73
|
const fileContent = await fs.readFile(resolvedPath);
|
|
60
74
|
res.writeHead(200);
|
|
@@ -62,7 +76,7 @@ function createHttpServer(opts) {
|
|
|
62
76
|
} catch (err) {
|
|
63
77
|
if (err.code === "ENOENT") {
|
|
64
78
|
try {
|
|
65
|
-
const
|
|
79
|
+
const html = await fs.readFile(path.join(distDir, "index.html"), "utf8");
|
|
66
80
|
res.writeHead(200, {
|
|
67
81
|
"Content-Type": "text/html",
|
|
68
82
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -70,7 +84,7 @@ function createHttpServer(opts) {
|
|
|
70
84
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
71
85
|
"Content-Security-Policy": buildCspHeader(wsPort)
|
|
72
86
|
});
|
|
73
|
-
res.end(
|
|
87
|
+
res.end(injectWsPort(html, wsPort));
|
|
74
88
|
} catch {
|
|
75
89
|
res.writeHead(404);
|
|
76
90
|
res.end("Not found");
|
|
@@ -154,7 +168,7 @@ import {
|
|
|
154
168
|
ProviderRegistry,
|
|
155
169
|
TOKENS as TOKENS2,
|
|
156
170
|
ToolRegistry,
|
|
157
|
-
atomicWrite,
|
|
171
|
+
atomicWrite as atomicWrite3,
|
|
158
172
|
createDefaultPipelines,
|
|
159
173
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
160
174
|
DEFAULT_TOOLS_CONFIG,
|
|
@@ -163,11 +177,9 @@ import {
|
|
|
163
177
|
resolveContextWindowPolicy
|
|
164
178
|
} from "@wrongstack/core";
|
|
165
179
|
import { ToolExecutor } from "@wrongstack/core/execution";
|
|
166
|
-
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
167
180
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
|
|
168
181
|
import { builtinToolsPack, forgetTool, rememberTool } from "@wrongstack/tools";
|
|
169
|
-
import {
|
|
170
|
-
import { randomBytes } from "crypto";
|
|
182
|
+
import { WebSocketServer } from "ws";
|
|
171
183
|
|
|
172
184
|
// ../runtime/src/container.ts
|
|
173
185
|
import {
|
|
@@ -226,7 +238,8 @@ function createDefaultContainer(opts) {
|
|
|
226
238
|
() => new DefaultPermissionPolicy({
|
|
227
239
|
trustFile: wpaths.projectTrust,
|
|
228
240
|
yolo: opts.permission?.yolo ?? false,
|
|
229
|
-
|
|
241
|
+
yoloDestructive: opts.permission?.yoloDestructive ?? opts.permission?.forceAllYolo ?? false,
|
|
242
|
+
confirmDestructive: opts.permission?.confirmDestructive ?? false,
|
|
230
243
|
promptDelegate: opts.permission?.promptDelegate
|
|
231
244
|
})
|
|
232
245
|
);
|
|
@@ -1446,6 +1459,13 @@ function createShutdown(res) {
|
|
|
1446
1459
|
}
|
|
1447
1460
|
for (const ws of res.clients()) ws.close();
|
|
1448
1461
|
for (const server of res.servers) server?.close();
|
|
1462
|
+
if (res.onShutdown) {
|
|
1463
|
+
try {
|
|
1464
|
+
await res.onShutdown();
|
|
1465
|
+
} catch (e) {
|
|
1466
|
+
log(`[WebUI] Error during shutdown cleanup: ${e instanceof Error ? e.message : String(e)}`);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1449
1469
|
exit(0);
|
|
1450
1470
|
};
|
|
1451
1471
|
}
|
|
@@ -1459,6 +1479,138 @@ function registerShutdownHandlers(res) {
|
|
|
1459
1479
|
};
|
|
1460
1480
|
}
|
|
1461
1481
|
|
|
1482
|
+
// src/server/instance-registry.ts
|
|
1483
|
+
import * as os from "os";
|
|
1484
|
+
import * as path2 from "path";
|
|
1485
|
+
import * as fs2 from "fs/promises";
|
|
1486
|
+
import { atomicWrite } from "@wrongstack/core";
|
|
1487
|
+
function defaultBaseDir() {
|
|
1488
|
+
return path2.join(os.homedir(), ".wrongstack");
|
|
1489
|
+
}
|
|
1490
|
+
function registryPath(baseDir = defaultBaseDir()) {
|
|
1491
|
+
return path2.join(baseDir, "webui-instances.json");
|
|
1492
|
+
}
|
|
1493
|
+
function isPidAlive(pid) {
|
|
1494
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
1495
|
+
try {
|
|
1496
|
+
process.kill(pid, 0);
|
|
1497
|
+
return true;
|
|
1498
|
+
} catch (err) {
|
|
1499
|
+
return err.code !== "ESRCH";
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
async function load(file) {
|
|
1503
|
+
try {
|
|
1504
|
+
const raw = await fs2.readFile(file, "utf8");
|
|
1505
|
+
const parsed = JSON.parse(raw);
|
|
1506
|
+
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
1507
|
+
return parsed;
|
|
1508
|
+
}
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
return { version: 1, instances: [] };
|
|
1512
|
+
}
|
|
1513
|
+
async function save(file, instances) {
|
|
1514
|
+
await atomicWrite(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
|
|
1515
|
+
`, {
|
|
1516
|
+
mode: 384
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
function prune(instances, excludePid) {
|
|
1520
|
+
return instances.filter((i) => i.pid !== excludePid && isPidAlive(i.pid));
|
|
1521
|
+
}
|
|
1522
|
+
async function registerInstance(record, baseDir = defaultBaseDir()) {
|
|
1523
|
+
const file = registryPath(baseDir);
|
|
1524
|
+
const data = await load(file);
|
|
1525
|
+
const instances = prune(data.instances, record.pid);
|
|
1526
|
+
instances.push(record);
|
|
1527
|
+
await save(file, instances);
|
|
1528
|
+
}
|
|
1529
|
+
async function unregisterInstance(pid, baseDir = defaultBaseDir()) {
|
|
1530
|
+
const file = registryPath(baseDir);
|
|
1531
|
+
const data = await load(file);
|
|
1532
|
+
const instances = prune(data.instances, pid);
|
|
1533
|
+
await save(file, instances);
|
|
1534
|
+
}
|
|
1535
|
+
async function listInstances(baseDir = defaultBaseDir()) {
|
|
1536
|
+
const file = registryPath(baseDir);
|
|
1537
|
+
const data = await load(file);
|
|
1538
|
+
const live = prune(data.instances);
|
|
1539
|
+
if (live.length !== data.instances.length) {
|
|
1540
|
+
await save(file, live).catch(() => {
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return live;
|
|
1544
|
+
}
|
|
1545
|
+
function formatInstances(instances) {
|
|
1546
|
+
if (instances.length === 0) {
|
|
1547
|
+
return "No WebUI instances are currently running.";
|
|
1548
|
+
}
|
|
1549
|
+
const lines = [`Running WebUI instances (${instances.length}):`, ""];
|
|
1550
|
+
for (const i of instances) {
|
|
1551
|
+
lines.push(
|
|
1552
|
+
` \u2022 ${i.url} \xB7 ws:${i.wsPort} \xB7 pid ${i.pid}`,
|
|
1553
|
+
` project: ${i.projectName} (${i.projectRoot})`,
|
|
1554
|
+
` since: ${i.startedAt}`
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
return lines.join("\n");
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/server/port-utils.ts
|
|
1561
|
+
import * as net from "net";
|
|
1562
|
+
function isPortFree(host, port) {
|
|
1563
|
+
return new Promise((resolve3) => {
|
|
1564
|
+
const srv = net.createServer();
|
|
1565
|
+
srv.once("error", () => resolve3(false));
|
|
1566
|
+
srv.once("listening", () => {
|
|
1567
|
+
srv.close(() => resolve3(true));
|
|
1568
|
+
});
|
|
1569
|
+
try {
|
|
1570
|
+
srv.listen(port, host);
|
|
1571
|
+
} catch {
|
|
1572
|
+
resolve3(false);
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
async function findFreePort(host, startPort, opts = {}) {
|
|
1577
|
+
const exclude = opts.exclude ?? /* @__PURE__ */ new Set();
|
|
1578
|
+
const maxTries = opts.maxTries ?? 200;
|
|
1579
|
+
let port = startPort;
|
|
1580
|
+
for (let i = 0; i < maxTries; i++) {
|
|
1581
|
+
if (port > 65535) port = 1024 + port % 5e4;
|
|
1582
|
+
if (!exclude.has(port) && await isPortFree(host, port)) {
|
|
1583
|
+
return port;
|
|
1584
|
+
}
|
|
1585
|
+
port++;
|
|
1586
|
+
}
|
|
1587
|
+
throw new Error(
|
|
1588
|
+
`No free port found near ${startPort} on ${host} after ${maxTries} attempts.`
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/server/open-browser.ts
|
|
1593
|
+
import { spawn } from "child_process";
|
|
1594
|
+
function browserOpenCommand(url, platform = process.platform) {
|
|
1595
|
+
if (platform === "win32") {
|
|
1596
|
+
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
1597
|
+
}
|
|
1598
|
+
if (platform === "darwin") {
|
|
1599
|
+
return { command: "open", args: [url] };
|
|
1600
|
+
}
|
|
1601
|
+
return { command: "xdg-open", args: [url] };
|
|
1602
|
+
}
|
|
1603
|
+
function openBrowser(url, platform = process.platform) {
|
|
1604
|
+
try {
|
|
1605
|
+
const { command, args } = browserOpenCommand(url, platform);
|
|
1606
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
1607
|
+
child.on("error", () => {
|
|
1608
|
+
});
|
|
1609
|
+
child.unref();
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1462
1614
|
// src/server/usage-cost.ts
|
|
1463
1615
|
function getCostRates(model) {
|
|
1464
1616
|
const cost = model?.cost;
|
|
@@ -1567,6 +1719,97 @@ function removeProvider(providers, providerId) {
|
|
|
1567
1719
|
return { ok: true, message: `Provider "${providerId}" removed` };
|
|
1568
1720
|
}
|
|
1569
1721
|
|
|
1722
|
+
// src/server/provider-config-io.ts
|
|
1723
|
+
import * as fs3 from "fs/promises";
|
|
1724
|
+
import * as path3 from "path";
|
|
1725
|
+
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
1726
|
+
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
1727
|
+
import { DefaultSecretVault } from "@wrongstack/core";
|
|
1728
|
+
async function loadSavedProviders(configPath, vault) {
|
|
1729
|
+
let raw;
|
|
1730
|
+
try {
|
|
1731
|
+
raw = await fs3.readFile(configPath, "utf8");
|
|
1732
|
+
} catch {
|
|
1733
|
+
return {};
|
|
1734
|
+
}
|
|
1735
|
+
let parsed = {};
|
|
1736
|
+
try {
|
|
1737
|
+
parsed = JSON.parse(raw);
|
|
1738
|
+
} catch {
|
|
1739
|
+
return {};
|
|
1740
|
+
}
|
|
1741
|
+
if (!parsed.providers) return {};
|
|
1742
|
+
return decryptConfigSecrets(parsed.providers, vault);
|
|
1743
|
+
}
|
|
1744
|
+
async function saveProviders(configPath, vault, providers) {
|
|
1745
|
+
let raw;
|
|
1746
|
+
let fileExists = true;
|
|
1747
|
+
try {
|
|
1748
|
+
raw = await fs3.readFile(configPath, "utf8");
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
if (err.code !== "ENOENT") {
|
|
1751
|
+
throw new Error(
|
|
1752
|
+
`Refusing to mutate ${configPath}: ${err.message}`,
|
|
1753
|
+
{ cause: err }
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
fileExists = false;
|
|
1757
|
+
raw = "{}";
|
|
1758
|
+
}
|
|
1759
|
+
let parsed;
|
|
1760
|
+
try {
|
|
1761
|
+
parsed = JSON.parse(raw);
|
|
1762
|
+
} catch (err) {
|
|
1763
|
+
if (fileExists) {
|
|
1764
|
+
throw new Error(
|
|
1765
|
+
`Refusing to overwrite corrupt config at ${configPath} (${err.message}). Fix or move the file aside before retrying.`,
|
|
1766
|
+
{ cause: err }
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
parsed = {};
|
|
1770
|
+
}
|
|
1771
|
+
parsed.providers = providers;
|
|
1772
|
+
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1773
|
+
await atomicWrite2(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1774
|
+
}
|
|
1775
|
+
function createProviderConfigIO(configPath) {
|
|
1776
|
+
const keyFile = path3.join(path3.dirname(configPath), ".key");
|
|
1777
|
+
const vault = new DefaultSecretVault({ keyFile });
|
|
1778
|
+
return {
|
|
1779
|
+
load: () => loadSavedProviders(configPath, vault),
|
|
1780
|
+
save: (providers) => saveProviders(configPath, vault, providers)
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/server/ws-utils.ts
|
|
1785
|
+
import { randomBytes } from "crypto";
|
|
1786
|
+
import { WebSocket } from "ws";
|
|
1787
|
+
function send(ws, msg) {
|
|
1788
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1789
|
+
ws.send(JSON.stringify(msg));
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function broadcast(clients, msg) {
|
|
1793
|
+
const data = JSON.stringify(msg);
|
|
1794
|
+
for (const [ws] of clients) {
|
|
1795
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1796
|
+
try {
|
|
1797
|
+
ws.send(data);
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function sendResult(ws, success, message) {
|
|
1804
|
+
send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
1805
|
+
}
|
|
1806
|
+
function errMessage(err) {
|
|
1807
|
+
return err instanceof Error ? err.message : String(err);
|
|
1808
|
+
}
|
|
1809
|
+
function generateAuthToken() {
|
|
1810
|
+
return randomBytes(16).toString("hex");
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1570
1813
|
// src/server/token-estimator.ts
|
|
1571
1814
|
function estimateTokens(s) {
|
|
1572
1815
|
return Math.ceil(s.length / 4);
|
|
@@ -1625,12 +1868,23 @@ function estimateContextBreakdown(input) {
|
|
|
1625
1868
|
}
|
|
1626
1869
|
|
|
1627
1870
|
// src/server/index.ts
|
|
1628
|
-
function errMessage(err) {
|
|
1629
|
-
return err instanceof Error ? err.message : String(err);
|
|
1630
|
-
}
|
|
1631
1871
|
async function startWebUI(opts = {}) {
|
|
1632
|
-
const
|
|
1872
|
+
const requestedWsPort = opts.wsPort ?? 3457;
|
|
1633
1873
|
const wsHost = opts.wsHost ?? "127.0.0.1";
|
|
1874
|
+
const requestedHttpPort = Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
1875
|
+
const strictPort = process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true";
|
|
1876
|
+
let wsPort = requestedWsPort;
|
|
1877
|
+
let httpPort = requestedHttpPort;
|
|
1878
|
+
if (!strictPort) {
|
|
1879
|
+
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
1880
|
+
wsPort = await findFreePort(wsHost, requestedWsPort, { exclude: /* @__PURE__ */ new Set([httpPort]) });
|
|
1881
|
+
if (httpPort !== requestedHttpPort) {
|
|
1882
|
+
console.warn(`[WebUI] HTTP port ${requestedHttpPort} in use \u2192 using ${httpPort}`);
|
|
1883
|
+
}
|
|
1884
|
+
if (wsPort !== requestedWsPort) {
|
|
1885
|
+
console.warn(`[WebUI] WS port ${requestedWsPort} in use \u2192 using ${wsPort}`);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1634
1888
|
console.log("[WebUI] Starting backend services...");
|
|
1635
1889
|
const boot = await bootConfig();
|
|
1636
1890
|
const { config: baseConfig, vault, globalConfigPath, projectRoot, wpaths, logger } = boot;
|
|
@@ -1780,7 +2034,15 @@ async function startWebUI(opts = {}) {
|
|
|
1780
2034
|
});
|
|
1781
2035
|
let autoCompactor;
|
|
1782
2036
|
if (config.context?.autoCompact !== false) {
|
|
1783
|
-
|
|
2037
|
+
let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
2038
|
+
if (!effectiveMaxContext) {
|
|
2039
|
+
try {
|
|
2040
|
+
const m = await modelsRegistry.getModel(provider.id, context.model);
|
|
2041
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
2042
|
+
} catch {
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
1784
2046
|
autoCompactor = new AutoCompactionMiddleware(
|
|
1785
2047
|
compactor,
|
|
1786
2048
|
effectiveMaxContext,
|
|
@@ -1876,14 +2138,14 @@ async function startWebUI(opts = {}) {
|
|
|
1876
2138
|
inputCost,
|
|
1877
2139
|
outputCost,
|
|
1878
2140
|
cacheReadCost,
|
|
1879
|
-
projectName:
|
|
2141
|
+
projectName: path4.basename(projectRoot) || projectRoot,
|
|
1880
2142
|
cwd: projectRoot,
|
|
1881
2143
|
mode: modeId,
|
|
1882
2144
|
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID),
|
|
1883
2145
|
wsToken
|
|
1884
2146
|
};
|
|
1885
2147
|
}
|
|
1886
|
-
const wsToken =
|
|
2148
|
+
const wsToken = generateAuthToken();
|
|
1887
2149
|
console.log(`[WebUI] WS auth token: ${wsToken.slice(0, 4)}\u2026${wsToken.slice(-4)} (masked)`);
|
|
1888
2150
|
const verifyClient2 = (info) => verifyClient({
|
|
1889
2151
|
origin: info.origin,
|
|
@@ -1907,10 +2169,11 @@ async function startWebUI(opts = {}) {
|
|
|
1907
2169
|
maxPayload: WS_MAX_PAYLOAD
|
|
1908
2170
|
}) : null;
|
|
1909
2171
|
const clients = /* @__PURE__ */ new Map();
|
|
1910
|
-
const RATE_LIMIT_MESSAGES =
|
|
2172
|
+
const RATE_LIMIT_MESSAGES = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
|
|
1911
2173
|
const RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1912
2174
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
1913
2175
|
function checkRateLimit(ws, client) {
|
|
2176
|
+
if (RATE_LIMIT_MESSAGES <= 0) return true;
|
|
1914
2177
|
const now = Date.now();
|
|
1915
2178
|
const key = client.sessionId ?? String(ws);
|
|
1916
2179
|
const limit = rateLimits.get(key);
|
|
@@ -1929,25 +2192,25 @@ async function startWebUI(opts = {}) {
|
|
|
1929
2192
|
const pendingConfirms = /* @__PURE__ */ new Map();
|
|
1930
2193
|
function setupEvents() {
|
|
1931
2194
|
events.on("iteration.started", (e) => {
|
|
1932
|
-
broadcast({
|
|
2195
|
+
broadcast(clients, {
|
|
1933
2196
|
type: "iteration.started",
|
|
1934
2197
|
payload: { index: e.index, maxIterations: config.tools?.maxIterations ?? 100 }
|
|
1935
2198
|
});
|
|
1936
2199
|
});
|
|
1937
2200
|
events.on("provider.text_delta", (e) => {
|
|
1938
|
-
broadcast({ type: "provider.text_delta", payload: { text: e.text, messageId: "current" } });
|
|
2201
|
+
broadcast(clients, { type: "provider.text_delta", payload: { text: e.text, messageId: "current" } });
|
|
1939
2202
|
});
|
|
1940
2203
|
events.on("provider.thinking_delta", (e) => {
|
|
1941
|
-
broadcast({ type: "provider.thinking_delta", payload: { text: e.text } });
|
|
2204
|
+
broadcast(clients, { type: "provider.thinking_delta", payload: { text: e.text } });
|
|
1942
2205
|
});
|
|
1943
2206
|
events.on("tool.started", (e) => {
|
|
1944
|
-
broadcast({
|
|
2207
|
+
broadcast(clients, {
|
|
1945
2208
|
type: "tool.started",
|
|
1946
2209
|
payload: { id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` }
|
|
1947
2210
|
});
|
|
1948
2211
|
});
|
|
1949
2212
|
events.on("tool.progress", (e) => {
|
|
1950
|
-
broadcast({
|
|
2213
|
+
broadcast(clients, {
|
|
1951
2214
|
type: "tool.progress",
|
|
1952
2215
|
payload: {
|
|
1953
2216
|
id: e.id,
|
|
@@ -1958,7 +2221,7 @@ async function startWebUI(opts = {}) {
|
|
|
1958
2221
|
});
|
|
1959
2222
|
});
|
|
1960
2223
|
events.on("tool.executed", (e) => {
|
|
1961
|
-
broadcast({
|
|
2224
|
+
broadcast(clients, {
|
|
1962
2225
|
type: "tool.executed",
|
|
1963
2226
|
payload: {
|
|
1964
2227
|
// Forward the tool_use id so frontend can correlate with the
|
|
@@ -1973,13 +2236,13 @@ async function startWebUI(opts = {}) {
|
|
|
1973
2236
|
output: e.output
|
|
1974
2237
|
}
|
|
1975
2238
|
});
|
|
1976
|
-
broadcast({
|
|
2239
|
+
broadcast(clients, {
|
|
1977
2240
|
type: "todos.updated",
|
|
1978
2241
|
payload: { todos: [...context.todos] }
|
|
1979
2242
|
});
|
|
1980
2243
|
});
|
|
1981
2244
|
events.on("provider.response", (e) => {
|
|
1982
|
-
broadcast({
|
|
2245
|
+
broadcast(clients, {
|
|
1983
2246
|
type: "provider.response",
|
|
1984
2247
|
payload: {
|
|
1985
2248
|
usage: e.usage,
|
|
@@ -1989,7 +2252,7 @@ async function startWebUI(opts = {}) {
|
|
|
1989
2252
|
});
|
|
1990
2253
|
});
|
|
1991
2254
|
events.on("context.repaired", (e) => {
|
|
1992
|
-
broadcast({
|
|
2255
|
+
broadcast(clients, {
|
|
1993
2256
|
type: "context.repaired",
|
|
1994
2257
|
payload: {
|
|
1995
2258
|
removedToolUses: e.removedToolUses,
|
|
@@ -2001,7 +2264,7 @@ async function startWebUI(opts = {}) {
|
|
|
2001
2264
|
events.on("tool.confirm_needed", (e) => {
|
|
2002
2265
|
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
2003
2266
|
pendingConfirms.set(id, e.resolve);
|
|
2004
|
-
broadcast({
|
|
2267
|
+
broadcast(clients, {
|
|
2005
2268
|
type: "tool.confirm_needed",
|
|
2006
2269
|
payload: {
|
|
2007
2270
|
id,
|
|
@@ -2012,7 +2275,7 @@ async function startWebUI(opts = {}) {
|
|
|
2012
2275
|
});
|
|
2013
2276
|
});
|
|
2014
2277
|
events.on("error", (e) => {
|
|
2015
|
-
broadcast({
|
|
2278
|
+
broadcast(clients, {
|
|
2016
2279
|
type: "error",
|
|
2017
2280
|
payload: {
|
|
2018
2281
|
phase: e.phase,
|
|
@@ -2020,19 +2283,71 @@ async function startWebUI(opts = {}) {
|
|
|
2020
2283
|
}
|
|
2021
2284
|
});
|
|
2022
2285
|
});
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2286
|
+
const forwardSubagent = (kind, payload) => broadcast(clients, { type: "subagent.event", payload: { kind, ...payload } });
|
|
2287
|
+
events.on(
|
|
2288
|
+
"subagent.spawned",
|
|
2289
|
+
(e) => forwardSubagent("spawned", {
|
|
2290
|
+
subagentId: e.subagentId,
|
|
2291
|
+
taskId: e.taskId,
|
|
2292
|
+
name: e.name,
|
|
2293
|
+
provider: e.provider,
|
|
2294
|
+
model: e.model,
|
|
2295
|
+
description: e.description
|
|
2296
|
+
})
|
|
2297
|
+
);
|
|
2298
|
+
events.on(
|
|
2299
|
+
"subagent.task_started",
|
|
2300
|
+
(e) => forwardSubagent("task_started", {
|
|
2301
|
+
subagentId: e.subagentId,
|
|
2302
|
+
taskId: e.taskId,
|
|
2303
|
+
description: e.description
|
|
2304
|
+
})
|
|
2305
|
+
);
|
|
2306
|
+
events.on(
|
|
2307
|
+
"subagent.tool_executed",
|
|
2308
|
+
(e) => forwardSubagent("tool_executed", {
|
|
2309
|
+
subagentId: e.subagentId,
|
|
2310
|
+
toolName: e.name,
|
|
2311
|
+
durationMs: e.durationMs,
|
|
2312
|
+
ok: e.ok
|
|
2313
|
+
})
|
|
2314
|
+
);
|
|
2315
|
+
events.on(
|
|
2316
|
+
"subagent.iteration_summary",
|
|
2317
|
+
(e) => forwardSubagent("iteration_summary", {
|
|
2318
|
+
subagentId: e.subagentId,
|
|
2319
|
+
iteration: e.iteration,
|
|
2320
|
+
toolCalls: e.toolCalls,
|
|
2321
|
+
costUsd: e.costUsd,
|
|
2322
|
+
currentTool: e.currentTool
|
|
2323
|
+
})
|
|
2324
|
+
);
|
|
2325
|
+
events.on(
|
|
2326
|
+
"subagent.budget_extended",
|
|
2327
|
+
(e) => forwardSubagent("budget_extended", {
|
|
2328
|
+
subagentId: e.subagentId,
|
|
2329
|
+
totalExtensions: e.totalExtensions
|
|
2330
|
+
})
|
|
2331
|
+
);
|
|
2332
|
+
events.on(
|
|
2333
|
+
"subagent.ctx_pct",
|
|
2334
|
+
(e) => forwardSubagent("ctx_pct", {
|
|
2335
|
+
subagentId: e.subagentId,
|
|
2336
|
+
load: e.load,
|
|
2337
|
+
tokens: e.tokens,
|
|
2338
|
+
maxContext: e.maxContext
|
|
2339
|
+
})
|
|
2340
|
+
);
|
|
2341
|
+
events.on(
|
|
2342
|
+
"subagent.task_completed",
|
|
2343
|
+
(e) => forwardSubagent("task_completed", {
|
|
2344
|
+
subagentId: e.subagentId,
|
|
2345
|
+
status: e.status,
|
|
2346
|
+
iterations: e.iterations,
|
|
2347
|
+
toolCalls: e.toolCalls,
|
|
2348
|
+
error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
|
|
2349
|
+
})
|
|
2350
|
+
);
|
|
2036
2351
|
}
|
|
2037
2352
|
const handleConnection = (ws) => {
|
|
2038
2353
|
const client = { ws, sessionId: session.id, connectedAt: Date.now() };
|
|
@@ -2176,7 +2491,7 @@ async function startWebUI(opts = {}) {
|
|
|
2176
2491
|
}
|
|
2177
2492
|
case "abort":
|
|
2178
2493
|
runLock?.abort();
|
|
2179
|
-
broadcast({ type: "error", payload: { phase: "abort", message: "User aborted" } });
|
|
2494
|
+
broadcast(clients, { type: "error", payload: { phase: "abort", message: "User aborted" } });
|
|
2180
2495
|
break;
|
|
2181
2496
|
case "ping":
|
|
2182
2497
|
send(ws, { type: "pong", payload: {} });
|
|
@@ -2195,7 +2510,7 @@ async function startWebUI(opts = {}) {
|
|
|
2195
2510
|
context.fileMtimes.clear();
|
|
2196
2511
|
tokenCounter.reset();
|
|
2197
2512
|
sessionStartedAt = Date.now();
|
|
2198
|
-
broadcast({ type: "session.start", payload: await sessionStartPayload() });
|
|
2513
|
+
broadcast(clients, { type: "session.start", payload: await sessionStartPayload() });
|
|
2199
2514
|
break;
|
|
2200
2515
|
}
|
|
2201
2516
|
case "context.clear": {
|
|
@@ -2205,7 +2520,7 @@ async function startWebUI(opts = {}) {
|
|
|
2205
2520
|
context.fileMtimes.clear();
|
|
2206
2521
|
tokenCounter.reset();
|
|
2207
2522
|
sendResult(ws, true, "Context cleared");
|
|
2208
|
-
broadcast({
|
|
2523
|
+
broadcast(clients, {
|
|
2209
2524
|
type: "session.start",
|
|
2210
2525
|
payload: { ...await sessionStartPayload(), reset: true }
|
|
2211
2526
|
});
|
|
@@ -2264,7 +2579,7 @@ async function startWebUI(opts = {}) {
|
|
|
2264
2579
|
beforeMessages,
|
|
2265
2580
|
afterMessages: context.messages.length
|
|
2266
2581
|
};
|
|
2267
|
-
broadcast({ type: "context.repaired", payload });
|
|
2582
|
+
broadcast(clients, { type: "context.repaired", payload });
|
|
2268
2583
|
const removed = payload.removedToolUses.length + payload.removedToolResults.length + payload.removedMessages;
|
|
2269
2584
|
sendResult(
|
|
2270
2585
|
ws,
|
|
@@ -2302,7 +2617,7 @@ async function startWebUI(opts = {}) {
|
|
|
2302
2617
|
context.meta["contextWindowMode"] = policy.id;
|
|
2303
2618
|
context.meta["contextWindowPolicy"] = policy;
|
|
2304
2619
|
sendResult(ws, true, `Context mode switched to ${policy.id}`);
|
|
2305
|
-
broadcast({
|
|
2620
|
+
broadcast(clients, {
|
|
2306
2621
|
type: "context.mode.changed",
|
|
2307
2622
|
payload: { id: policy.id, name: policy.name, policy }
|
|
2308
2623
|
});
|
|
@@ -2328,7 +2643,7 @@ async function startWebUI(opts = {}) {
|
|
|
2328
2643
|
break;
|
|
2329
2644
|
}
|
|
2330
2645
|
case "providers.saved": {
|
|
2331
|
-
const saved = await
|
|
2646
|
+
const saved = await loadConfigProviders();
|
|
2332
2647
|
send(ws, {
|
|
2333
2648
|
type: "providers.saved",
|
|
2334
2649
|
payload: {
|
|
@@ -2387,11 +2702,11 @@ async function startWebUI(opts = {}) {
|
|
|
2387
2702
|
updateAutoCompactionMaxContext?.(newProv);
|
|
2388
2703
|
try {
|
|
2389
2704
|
configWriteLock = configWriteLock.then(async () => {
|
|
2390
|
-
const raw = await
|
|
2705
|
+
const raw = await fs4.readFile(globalConfigPath, "utf8");
|
|
2391
2706
|
const parsed = JSON.parse(raw);
|
|
2392
2707
|
parsed.provider = newProvider;
|
|
2393
2708
|
parsed.model = newModel;
|
|
2394
|
-
await
|
|
2709
|
+
await atomicWrite3(globalConfigPath, JSON.stringify(parsed, null, 2));
|
|
2395
2710
|
});
|
|
2396
2711
|
await configWriteLock;
|
|
2397
2712
|
} catch (err) {
|
|
@@ -2411,7 +2726,7 @@ async function startWebUI(opts = {}) {
|
|
|
2411
2726
|
});
|
|
2412
2727
|
break;
|
|
2413
2728
|
}
|
|
2414
|
-
broadcast({ type: "session.start", payload: await sessionStartPayload() });
|
|
2729
|
+
broadcast(clients, { type: "session.start", payload: await sessionStartPayload() });
|
|
2415
2730
|
break;
|
|
2416
2731
|
}
|
|
2417
2732
|
case "key.add":
|
|
@@ -2500,7 +2815,7 @@ async function startWebUI(opts = {}) {
|
|
|
2500
2815
|
tokenCounter.reset();
|
|
2501
2816
|
tokenCounter.account(resumed.data.usage, config.model);
|
|
2502
2817
|
sessionStartedAt = Date.now();
|
|
2503
|
-
broadcast({
|
|
2818
|
+
broadcast(clients, {
|
|
2504
2819
|
type: "session.start",
|
|
2505
2820
|
payload: {
|
|
2506
2821
|
...await sessionStartPayload(),
|
|
@@ -2640,7 +2955,7 @@ async function startWebUI(opts = {}) {
|
|
|
2640
2955
|
case "todos.clear": {
|
|
2641
2956
|
context.state.replaceTodos([]);
|
|
2642
2957
|
sendResult(ws, true, "Todos cleared");
|
|
2643
|
-
broadcast({ type: "todos.updated", payload: { todos: [] } });
|
|
2958
|
+
broadcast(clients, { type: "todos.updated", payload: { todos: [] } });
|
|
2644
2959
|
break;
|
|
2645
2960
|
}
|
|
2646
2961
|
case "plan.get": {
|
|
@@ -2687,7 +3002,7 @@ async function startWebUI(opts = {}) {
|
|
|
2687
3002
|
}
|
|
2688
3003
|
await savePlan(planPath, plan);
|
|
2689
3004
|
sendResult(ws, true, `Applied template "${tpl.name}" \u2014 ${tpl.items.length} items added.`);
|
|
2690
|
-
broadcast({
|
|
3005
|
+
broadcast(clients, {
|
|
2691
3006
|
type: "plan.updated",
|
|
2692
3007
|
payload: { plan }
|
|
2693
3008
|
});
|
|
@@ -2704,7 +3019,7 @@ async function startWebUI(opts = {}) {
|
|
|
2704
3019
|
if (depth > 8 || results.length >= 600) return;
|
|
2705
3020
|
let entries = [];
|
|
2706
3021
|
try {
|
|
2707
|
-
entries = await
|
|
3022
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2708
3023
|
} catch {
|
|
2709
3024
|
return;
|
|
2710
3025
|
}
|
|
@@ -2714,7 +3029,7 @@ async function startWebUI(opts = {}) {
|
|
|
2714
3029
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
2715
3030
|
if (e.isDirectory()) {
|
|
2716
3031
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
2717
|
-
await walk(
|
|
3032
|
+
await walk(path4.join(dir, e.name), childRel, depth + 1);
|
|
2718
3033
|
} else if (e.isFile()) {
|
|
2719
3034
|
results.push(childRel);
|
|
2720
3035
|
}
|
|
@@ -2783,7 +3098,7 @@ async function startWebUI(opts = {}) {
|
|
|
2783
3098
|
model: config.model
|
|
2784
3099
|
});
|
|
2785
3100
|
sendResult(ws, true, `Switched to mode "${id}"`);
|
|
2786
|
-
broadcast({
|
|
3101
|
+
broadcast(clients, {
|
|
2787
3102
|
type: "session.start",
|
|
2788
3103
|
payload: { ...await sessionStartPayload() }
|
|
2789
3104
|
});
|
|
@@ -2822,39 +3137,20 @@ async function startWebUI(opts = {}) {
|
|
|
2822
3137
|
}
|
|
2823
3138
|
}
|
|
2824
3139
|
}
|
|
2825
|
-
async function
|
|
2826
|
-
|
|
2827
|
-
const raw = await fs2.readFile(globalConfigPath, "utf8");
|
|
2828
|
-
const parsed = JSON.parse(raw);
|
|
2829
|
-
if (!parsed.providers) return {};
|
|
2830
|
-
return decryptConfigSecrets(parsed.providers, vault);
|
|
2831
|
-
} catch {
|
|
2832
|
-
return {};
|
|
2833
|
-
}
|
|
3140
|
+
async function loadConfigProviders() {
|
|
3141
|
+
return loadSavedProviders(globalConfigPath, vault);
|
|
2834
3142
|
}
|
|
2835
|
-
async function
|
|
2836
|
-
configWriteLock = configWriteLock.then(
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
const raw = await fs2.readFile(globalConfigPath, "utf8");
|
|
2840
|
-
parsed = JSON.parse(raw);
|
|
2841
|
-
} catch {
|
|
2842
|
-
parsed = {};
|
|
2843
|
-
}
|
|
2844
|
-
parsed["providers"] = providers;
|
|
2845
|
-
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
2846
|
-
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2847
|
-
});
|
|
3143
|
+
async function saveConfigProviders(providers) {
|
|
3144
|
+
configWriteLock = configWriteLock.then(
|
|
3145
|
+
() => saveProviders(globalConfigPath, vault, providers)
|
|
3146
|
+
);
|
|
2848
3147
|
await configWriteLock;
|
|
2849
3148
|
}
|
|
2850
|
-
function sendResult(ws, success, message) {
|
|
2851
|
-
send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
2852
|
-
}
|
|
2853
3149
|
async function handleKeyUpsert(ws, providerId, label, apiKey) {
|
|
2854
3150
|
try {
|
|
2855
|
-
const providers = await
|
|
3151
|
+
const providers = await loadConfigProviders();
|
|
2856
3152
|
const result = upsertKey(providers, providerId, label, apiKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
2857
|
-
if (result.ok) await
|
|
3153
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
2858
3154
|
sendResult(ws, result.ok, result.message);
|
|
2859
3155
|
} catch (err) {
|
|
2860
3156
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2862,9 +3158,9 @@ async function startWebUI(opts = {}) {
|
|
|
2862
3158
|
}
|
|
2863
3159
|
async function handleKeyDelete(ws, providerId, label) {
|
|
2864
3160
|
try {
|
|
2865
|
-
const providers = await
|
|
3161
|
+
const providers = await loadConfigProviders();
|
|
2866
3162
|
const result = deleteKey(providers, providerId, label);
|
|
2867
|
-
if (result.ok) await
|
|
3163
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
2868
3164
|
sendResult(ws, result.ok, result.message);
|
|
2869
3165
|
} catch (err) {
|
|
2870
3166
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2872,9 +3168,9 @@ async function startWebUI(opts = {}) {
|
|
|
2872
3168
|
}
|
|
2873
3169
|
async function handleKeySetActive(ws, providerId, label) {
|
|
2874
3170
|
try {
|
|
2875
|
-
const providers = await
|
|
3171
|
+
const providers = await loadConfigProviders();
|
|
2876
3172
|
const result = setActiveKey(providers, providerId, label);
|
|
2877
|
-
if (result.ok) await
|
|
3173
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
2878
3174
|
sendResult(ws, result.ok, result.message);
|
|
2879
3175
|
} catch (err) {
|
|
2880
3176
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2882,9 +3178,9 @@ async function startWebUI(opts = {}) {
|
|
|
2882
3178
|
}
|
|
2883
3179
|
async function handleProviderAdd(ws, payload) {
|
|
2884
3180
|
try {
|
|
2885
|
-
const providers = await
|
|
3181
|
+
const providers = await loadConfigProviders();
|
|
2886
3182
|
const result = addProvider(providers, payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
2887
|
-
if (result.ok) await
|
|
3183
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
2888
3184
|
sendResult(ws, result.ok, result.message);
|
|
2889
3185
|
} catch (err) {
|
|
2890
3186
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2892,9 +3188,9 @@ async function startWebUI(opts = {}) {
|
|
|
2892
3188
|
}
|
|
2893
3189
|
async function handleProviderRemove(ws, providerId) {
|
|
2894
3190
|
try {
|
|
2895
|
-
const providers = await
|
|
3191
|
+
const providers = await loadConfigProviders();
|
|
2896
3192
|
const result = removeProvider(providers, providerId);
|
|
2897
|
-
if (result.ok) await
|
|
3193
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
2898
3194
|
sendResult(ws, result.ok, result.message);
|
|
2899
3195
|
} catch (err) {
|
|
2900
3196
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2902,12 +3198,27 @@ async function startWebUI(opts = {}) {
|
|
|
2902
3198
|
}
|
|
2903
3199
|
const httpServer = createHttpServer({
|
|
2904
3200
|
host: wsHost,
|
|
2905
|
-
distDir:
|
|
3201
|
+
distDir: path4.resolve(import.meta.dirname, "../../dist"),
|
|
2906
3202
|
wsPort
|
|
2907
3203
|
});
|
|
2908
|
-
const
|
|
3204
|
+
const registryBaseDir = path4.dirname(globalConfigPath);
|
|
2909
3205
|
httpServer.listen(httpPort, wsHost, () => {
|
|
2910
|
-
|
|
3206
|
+
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
3207
|
+
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
3208
|
+
if (opts.open) openBrowser(openUrl);
|
|
3209
|
+
void registerInstance(
|
|
3210
|
+
{
|
|
3211
|
+
pid: process.pid,
|
|
3212
|
+
httpPort,
|
|
3213
|
+
wsPort,
|
|
3214
|
+
host: wsHost,
|
|
3215
|
+
projectRoot,
|
|
3216
|
+
projectName: path4.basename(projectRoot) || projectRoot,
|
|
3217
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3218
|
+
url: `http://${wsHost}:${httpPort}`
|
|
3219
|
+
},
|
|
3220
|
+
registryBaseDir
|
|
3221
|
+
).catch((err) => console.warn("[WebUI] Could not record instance:", errMessage(err)));
|
|
2911
3222
|
});
|
|
2912
3223
|
registerShutdownHandlers({
|
|
2913
3224
|
flushSession: async () => {
|
|
@@ -2919,10 +3230,48 @@ async function startWebUI(opts = {}) {
|
|
|
2919
3230
|
await session.close();
|
|
2920
3231
|
},
|
|
2921
3232
|
clients: () => clients.keys(),
|
|
2922
|
-
servers: [httpServer, wssPrimary, wssSecondary]
|
|
3233
|
+
servers: [httpServer, wssPrimary, wssSecondary],
|
|
3234
|
+
// Drop this instance from the registry on a clean exit so the file reflects
|
|
3235
|
+
// reality. Crash exits are healed by the next register()/list() prune pass.
|
|
3236
|
+
onShutdown: () => unregisterInstance(process.pid, registryBaseDir)
|
|
2923
3237
|
});
|
|
2924
3238
|
}
|
|
2925
3239
|
export {
|
|
2926
|
-
|
|
3240
|
+
addProvider,
|
|
3241
|
+
broadcast,
|
|
3242
|
+
browserOpenCommand,
|
|
3243
|
+
buildCspHeader,
|
|
3244
|
+
createHttpServer,
|
|
3245
|
+
createProviderConfigIO,
|
|
3246
|
+
defaultBaseDir,
|
|
3247
|
+
deleteKey,
|
|
3248
|
+
errMessage,
|
|
3249
|
+
extractToken,
|
|
3250
|
+
findFreePort,
|
|
3251
|
+
formatInstances,
|
|
3252
|
+
generateAuthToken,
|
|
3253
|
+
hostHeaderOk,
|
|
3254
|
+
injectWsPort,
|
|
3255
|
+
isLoopbackBind,
|
|
3256
|
+
isLoopbackHostname,
|
|
3257
|
+
isPortFree,
|
|
3258
|
+
listInstances,
|
|
3259
|
+
loadSavedProviders,
|
|
3260
|
+
maskedKey,
|
|
3261
|
+
normalizeKeys,
|
|
3262
|
+
openBrowser,
|
|
3263
|
+
registerInstance,
|
|
3264
|
+
registryPath,
|
|
3265
|
+
removeProvider,
|
|
3266
|
+
saveProviders,
|
|
3267
|
+
send,
|
|
3268
|
+
sendResult,
|
|
3269
|
+
setActiveKey,
|
|
3270
|
+
startWebUI,
|
|
3271
|
+
tokenMatches,
|
|
3272
|
+
unregisterInstance,
|
|
3273
|
+
upsertKey,
|
|
3274
|
+
verifyClient,
|
|
3275
|
+
writeKeysBack
|
|
2927
3276
|
};
|
|
2928
3277
|
//# sourceMappingURL=index.js.map
|