@wrongstack/webui 0.66.13 → 0.68.0
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-BeXRAkSS.js +94 -0
- package/dist/assets/index-C_0-qbQ-.css +1 -0
- package/dist/assets/{vendor-wUxgMlp-.js → vendor-CzdG0ns2.js} +88 -88
- package/dist/index.css +0 -14
- package/dist/index.css.map +1 -1
- package/dist/index.html +3 -3
- package/dist/index.js +2502 -2908
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +192 -278
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +200 -286
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/assets/index-CnURnGh-.css +0 -1
- package/dist/assets/index-aAReViZF.js +0 -94
package/dist/server/index.js
CHANGED
|
@@ -1624,6 +1624,68 @@ function computeUsageCost(usage, rates) {
|
|
|
1624
1624
|
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
|
+
// src/server/provider-config-io.ts
|
|
1628
|
+
import * as fs3 from "fs/promises";
|
|
1629
|
+
import * as path3 from "path";
|
|
1630
|
+
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
1631
|
+
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
1632
|
+
import { DefaultSecretVault } from "@wrongstack/core";
|
|
1633
|
+
async function loadSavedProviders(configPath, vault) {
|
|
1634
|
+
let raw;
|
|
1635
|
+
try {
|
|
1636
|
+
raw = await fs3.readFile(configPath, "utf8");
|
|
1637
|
+
} catch {
|
|
1638
|
+
return {};
|
|
1639
|
+
}
|
|
1640
|
+
let parsed = {};
|
|
1641
|
+
try {
|
|
1642
|
+
parsed = JSON.parse(raw);
|
|
1643
|
+
} catch {
|
|
1644
|
+
return {};
|
|
1645
|
+
}
|
|
1646
|
+
if (!parsed.providers) return {};
|
|
1647
|
+
return decryptConfigSecrets(parsed.providers, vault);
|
|
1648
|
+
}
|
|
1649
|
+
async function saveProviders(configPath, vault, providers) {
|
|
1650
|
+
let raw;
|
|
1651
|
+
let fileExists = true;
|
|
1652
|
+
try {
|
|
1653
|
+
raw = await fs3.readFile(configPath, "utf8");
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
if (err.code !== "ENOENT") {
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
`Refusing to mutate ${configPath}: ${err.message}`,
|
|
1658
|
+
{ cause: err }
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
fileExists = false;
|
|
1662
|
+
raw = "{}";
|
|
1663
|
+
}
|
|
1664
|
+
let parsed;
|
|
1665
|
+
try {
|
|
1666
|
+
parsed = JSON.parse(raw);
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
if (fileExists) {
|
|
1669
|
+
throw new Error(
|
|
1670
|
+
`Refusing to overwrite corrupt config at ${configPath} (${err.message}). Fix or move the file aside before retrying.`,
|
|
1671
|
+
{ cause: err }
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
parsed = {};
|
|
1675
|
+
}
|
|
1676
|
+
parsed.providers = providers;
|
|
1677
|
+
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1678
|
+
await atomicWrite2(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1679
|
+
}
|
|
1680
|
+
function createProviderConfigIO(configPath) {
|
|
1681
|
+
const keyFile = path3.join(path3.dirname(configPath), ".key");
|
|
1682
|
+
const vault = new DefaultSecretVault({ keyFile });
|
|
1683
|
+
return {
|
|
1684
|
+
load: () => loadSavedProviders(configPath, vault),
|
|
1685
|
+
save: (providers) => saveProviders(configPath, vault, providers)
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1627
1689
|
// src/server/provider-keys.ts
|
|
1628
1690
|
function normalizeKeys(cfg) {
|
|
1629
1691
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
@@ -1719,68 +1781,6 @@ function removeProvider(providers, providerId) {
|
|
|
1719
1781
|
return { ok: true, message: `Provider "${providerId}" removed` };
|
|
1720
1782
|
}
|
|
1721
1783
|
|
|
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
1784
|
// src/server/ws-utils.ts
|
|
1785
1785
|
import { randomBytes } from "crypto";
|
|
1786
1786
|
import { WebSocket } from "ws";
|
|
@@ -1810,6 +1810,130 @@ function generateAuthToken() {
|
|
|
1810
1810
|
return randomBytes(16).toString("hex");
|
|
1811
1811
|
}
|
|
1812
1812
|
|
|
1813
|
+
// src/server/provider-handlers.ts
|
|
1814
|
+
function createProviderHandlers(deps) {
|
|
1815
|
+
const { globalConfigPath, vault } = deps;
|
|
1816
|
+
let configWriteLock = deps.getConfigWriteLock();
|
|
1817
|
+
async function loadConfigProviders() {
|
|
1818
|
+
return loadSavedProviders(globalConfigPath, vault);
|
|
1819
|
+
}
|
|
1820
|
+
async function saveConfigProviders(providers) {
|
|
1821
|
+
const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers));
|
|
1822
|
+
configWriteLock = next;
|
|
1823
|
+
deps.setConfigWriteLock(next);
|
|
1824
|
+
await next;
|
|
1825
|
+
}
|
|
1826
|
+
async function handleKeyUpsert(ws, providerId, label, apiKey) {
|
|
1827
|
+
try {
|
|
1828
|
+
const providers = await loadConfigProviders();
|
|
1829
|
+
const result = upsertKey(providers, providerId, label, apiKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
1830
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
1831
|
+
sendResult(ws, result.ok, result.message);
|
|
1832
|
+
} catch (err) {
|
|
1833
|
+
sendResult(ws, false, errMessage(err));
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
async function handleKeyDelete(ws, providerId, label) {
|
|
1837
|
+
try {
|
|
1838
|
+
const providers = await loadConfigProviders();
|
|
1839
|
+
const result = deleteKey(providers, providerId, label);
|
|
1840
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
1841
|
+
sendResult(ws, result.ok, result.message);
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
sendResult(ws, false, errMessage(err));
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
async function handleKeySetActive(ws, providerId, label) {
|
|
1847
|
+
try {
|
|
1848
|
+
const providers = await loadConfigProviders();
|
|
1849
|
+
const result = setActiveKey(providers, providerId, label);
|
|
1850
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
1851
|
+
sendResult(ws, result.ok, result.message);
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
sendResult(ws, false, errMessage(err));
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
async function handleProviderAdd(ws, payload) {
|
|
1857
|
+
try {
|
|
1858
|
+
const providers = await loadConfigProviders();
|
|
1859
|
+
const result = addProvider(providers, payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
1860
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
1861
|
+
sendResult(ws, result.ok, result.message);
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
sendResult(ws, false, errMessage(err));
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function handleProviderRemove(ws, providerId) {
|
|
1867
|
+
try {
|
|
1868
|
+
const providers = await loadConfigProviders();
|
|
1869
|
+
const result = removeProvider(providers, providerId);
|
|
1870
|
+
if (result.ok) await saveConfigProviders(providers);
|
|
1871
|
+
sendResult(ws, result.ok, result.message);
|
|
1872
|
+
} catch (err) {
|
|
1873
|
+
sendResult(ws, false, errMessage(err));
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
return { handleKeyUpsert, handleKeyDelete, handleKeySetActive, handleProviderAdd, handleProviderRemove, loadConfigProviders };
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/server/setup-events.ts
|
|
1880
|
+
function setupEvents(deps) {
|
|
1881
|
+
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms } = deps;
|
|
1882
|
+
events.on("iteration.started", (e) => {
|
|
1883
|
+
broadcast2(clients, {
|
|
1884
|
+
type: "iteration.started",
|
|
1885
|
+
payload: { index: e.index, maxIterations: config.tools?.maxIterations ?? 100 }
|
|
1886
|
+
});
|
|
1887
|
+
});
|
|
1888
|
+
events.on("provider.text_delta", (e) => {
|
|
1889
|
+
broadcast2(clients, { type: "provider.text_delta", payload: { text: e.text, messageId: "current" } });
|
|
1890
|
+
});
|
|
1891
|
+
events.on("provider.thinking_delta", (e) => {
|
|
1892
|
+
broadcast2(clients, { type: "provider.thinking_delta", payload: { text: e.text } });
|
|
1893
|
+
});
|
|
1894
|
+
events.on("tool.started", (e) => {
|
|
1895
|
+
broadcast2(clients, {
|
|
1896
|
+
type: "tool.started",
|
|
1897
|
+
payload: { id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` }
|
|
1898
|
+
});
|
|
1899
|
+
});
|
|
1900
|
+
events.on("tool.progress", (e) => {
|
|
1901
|
+
broadcast2(clients, {
|
|
1902
|
+
type: "tool.progress",
|
|
1903
|
+
payload: { id: e.id, name: e.name, eventType: e.event.type, text: e.event.text }
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1906
|
+
events.on("tool.executed", (e) => {
|
|
1907
|
+
broadcast2(clients, {
|
|
1908
|
+
type: "tool.executed",
|
|
1909
|
+
payload: { id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output }
|
|
1910
|
+
});
|
|
1911
|
+
broadcast2(clients, { type: "todos.updated", payload: { todos: [...context.todos] } });
|
|
1912
|
+
});
|
|
1913
|
+
events.on("provider.response", (e) => {
|
|
1914
|
+
broadcast2(clients, { type: "provider.response", payload: { usage: e.usage, stopReason: e.stopReason, messageId: "current" } });
|
|
1915
|
+
});
|
|
1916
|
+
events.on("context.repaired", (e) => {
|
|
1917
|
+
broadcast2(clients, { type: "context.repaired", payload: { removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages } });
|
|
1918
|
+
});
|
|
1919
|
+
events.on("tool.confirm_needed", (e) => {
|
|
1920
|
+
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
1921
|
+
pendingConfirms.set(id, e.resolve);
|
|
1922
|
+
broadcast2(clients, { type: "tool.confirm_needed", payload: { id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern } });
|
|
1923
|
+
});
|
|
1924
|
+
events.on("error", (e) => {
|
|
1925
|
+
broadcast2(clients, { type: "error", payload: { phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) } });
|
|
1926
|
+
});
|
|
1927
|
+
const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: { kind, ...payload } });
|
|
1928
|
+
events.on("subagent.spawned", (e) => forwardSubagent("spawned", { subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
|
|
1929
|
+
events.on("subagent.task_started", (e) => forwardSubagent("task_started", { subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
|
|
1930
|
+
events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
|
|
1931
|
+
events.on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool }));
|
|
1932
|
+
events.on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { subagentId: e.subagentId, totalExtensions: e.totalExtensions }));
|
|
1933
|
+
events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
|
|
1934
|
+
events.on("subagent.task_completed", (e) => forwardSubagent("task_completed", { subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1813
1937
|
// src/server/token-estimator.ts
|
|
1814
1938
|
function estimateTokens(s) {
|
|
1815
1939
|
return Math.ceil(s.length / 4);
|
|
@@ -2190,165 +2314,6 @@ async function startWebUI(opts = {}) {
|
|
|
2190
2314
|
`[WebUI] WebSocket server running on ws://${wsHost}:${wsPort}` + (wssSecondary ? ` (and ws://[::1]:${wsPort})` : "")
|
|
2191
2315
|
);
|
|
2192
2316
|
const pendingConfirms = /* @__PURE__ */ new Map();
|
|
2193
|
-
function setupEvents() {
|
|
2194
|
-
events.on("iteration.started", (e) => {
|
|
2195
|
-
broadcast(clients, {
|
|
2196
|
-
type: "iteration.started",
|
|
2197
|
-
payload: { index: e.index, maxIterations: config.tools?.maxIterations ?? 100 }
|
|
2198
|
-
});
|
|
2199
|
-
});
|
|
2200
|
-
events.on("provider.text_delta", (e) => {
|
|
2201
|
-
broadcast(clients, { type: "provider.text_delta", payload: { text: e.text, messageId: "current" } });
|
|
2202
|
-
});
|
|
2203
|
-
events.on("provider.thinking_delta", (e) => {
|
|
2204
|
-
broadcast(clients, { type: "provider.thinking_delta", payload: { text: e.text } });
|
|
2205
|
-
});
|
|
2206
|
-
events.on("tool.started", (e) => {
|
|
2207
|
-
broadcast(clients, {
|
|
2208
|
-
type: "tool.started",
|
|
2209
|
-
payload: { id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` }
|
|
2210
|
-
});
|
|
2211
|
-
});
|
|
2212
|
-
events.on("tool.progress", (e) => {
|
|
2213
|
-
broadcast(clients, {
|
|
2214
|
-
type: "tool.progress",
|
|
2215
|
-
payload: {
|
|
2216
|
-
id: e.id,
|
|
2217
|
-
name: e.name,
|
|
2218
|
-
eventType: e.event.type,
|
|
2219
|
-
text: e.event.text
|
|
2220
|
-
}
|
|
2221
|
-
});
|
|
2222
|
-
});
|
|
2223
|
-
events.on("tool.executed", (e) => {
|
|
2224
|
-
broadcast(clients, {
|
|
2225
|
-
type: "tool.executed",
|
|
2226
|
-
payload: {
|
|
2227
|
-
// Forward the tool_use id so frontend can correlate with the
|
|
2228
|
-
// matching tool.started bubble — without this, parallel tool calls
|
|
2229
|
-
// all stay stuck on "Running…" because the frontend can't tell
|
|
2230
|
-
// which bubble this result belongs to.
|
|
2231
|
-
id: e.id,
|
|
2232
|
-
name: e.name,
|
|
2233
|
-
durationMs: e.durationMs,
|
|
2234
|
-
ok: e.ok,
|
|
2235
|
-
input: e.input,
|
|
2236
|
-
output: e.output
|
|
2237
|
-
}
|
|
2238
|
-
});
|
|
2239
|
-
broadcast(clients, {
|
|
2240
|
-
type: "todos.updated",
|
|
2241
|
-
payload: { todos: [...context.todos] }
|
|
2242
|
-
});
|
|
2243
|
-
});
|
|
2244
|
-
events.on("provider.response", (e) => {
|
|
2245
|
-
broadcast(clients, {
|
|
2246
|
-
type: "provider.response",
|
|
2247
|
-
payload: {
|
|
2248
|
-
usage: e.usage,
|
|
2249
|
-
stopReason: e.stopReason,
|
|
2250
|
-
messageId: "current"
|
|
2251
|
-
}
|
|
2252
|
-
});
|
|
2253
|
-
});
|
|
2254
|
-
events.on("context.repaired", (e) => {
|
|
2255
|
-
broadcast(clients, {
|
|
2256
|
-
type: "context.repaired",
|
|
2257
|
-
payload: {
|
|
2258
|
-
removedToolUses: e.removedToolUses,
|
|
2259
|
-
removedToolResults: e.removedToolResults,
|
|
2260
|
-
removedMessages: e.removedMessages
|
|
2261
|
-
}
|
|
2262
|
-
});
|
|
2263
|
-
});
|
|
2264
|
-
events.on("tool.confirm_needed", (e) => {
|
|
2265
|
-
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
2266
|
-
pendingConfirms.set(id, e.resolve);
|
|
2267
|
-
broadcast(clients, {
|
|
2268
|
-
type: "tool.confirm_needed",
|
|
2269
|
-
payload: {
|
|
2270
|
-
id,
|
|
2271
|
-
toolName: e.tool?.name ?? "unknown",
|
|
2272
|
-
input: e.input,
|
|
2273
|
-
suggestedPattern: e.suggestedPattern
|
|
2274
|
-
}
|
|
2275
|
-
});
|
|
2276
|
-
});
|
|
2277
|
-
events.on("error", (e) => {
|
|
2278
|
-
broadcast(clients, {
|
|
2279
|
-
type: "error",
|
|
2280
|
-
payload: {
|
|
2281
|
-
phase: e.phase,
|
|
2282
|
-
message: e.err instanceof Error ? e.err.message : String(e.err)
|
|
2283
|
-
}
|
|
2284
|
-
});
|
|
2285
|
-
});
|
|
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
|
-
);
|
|
2351
|
-
}
|
|
2352
2317
|
const handleConnection = (ws) => {
|
|
2353
2318
|
const client = { ws, sessionId: session.id, connectedAt: Date.now() };
|
|
2354
2319
|
clients.set(ws, client);
|
|
@@ -2406,7 +2371,7 @@ async function startWebUI(opts = {}) {
|
|
|
2406
2371
|
if (eventsArmed) return;
|
|
2407
2372
|
eventsArmed = true;
|
|
2408
2373
|
console.log(`[WebUI] Backend ready (${label})`);
|
|
2409
|
-
setupEvents();
|
|
2374
|
+
setupEvents({ events, broadcast, clients, config, context, pendingConfirms });
|
|
2410
2375
|
};
|
|
2411
2376
|
wssPrimary.on("listening", () => armOnce(`${wsHost}:${wsPort}`));
|
|
2412
2377
|
wssPrimary.on("connection", handleConnection);
|
|
@@ -2643,7 +2608,7 @@ async function startWebUI(opts = {}) {
|
|
|
2643
2608
|
break;
|
|
2644
2609
|
}
|
|
2645
2610
|
case "providers.saved": {
|
|
2646
|
-
const saved = await loadConfigProviders();
|
|
2611
|
+
const saved = await providerHandlers.loadConfigProviders();
|
|
2647
2612
|
send(ws, {
|
|
2648
2613
|
type: "providers.saved",
|
|
2649
2614
|
payload: {
|
|
@@ -2732,27 +2697,27 @@ async function startWebUI(opts = {}) {
|
|
|
2732
2697
|
case "key.add":
|
|
2733
2698
|
case "key.update": {
|
|
2734
2699
|
const { providerId, label, apiKey } = msg.payload;
|
|
2735
|
-
await handleKeyUpsert(ws, providerId, label, apiKey);
|
|
2700
|
+
await providerHandlers.handleKeyUpsert(ws, providerId, label, apiKey);
|
|
2736
2701
|
break;
|
|
2737
2702
|
}
|
|
2738
2703
|
case "key.delete": {
|
|
2739
2704
|
const { providerId, label } = msg.payload;
|
|
2740
|
-
await handleKeyDelete(ws, providerId, label);
|
|
2705
|
+
await providerHandlers.handleKeyDelete(ws, providerId, label);
|
|
2741
2706
|
break;
|
|
2742
2707
|
}
|
|
2743
2708
|
case "key.set_active": {
|
|
2744
2709
|
const { providerId, label } = msg.payload;
|
|
2745
|
-
await handleKeySetActive(ws, providerId, label);
|
|
2710
|
+
await providerHandlers.handleKeySetActive(ws, providerId, label);
|
|
2746
2711
|
break;
|
|
2747
2712
|
}
|
|
2748
2713
|
case "provider.add": {
|
|
2749
2714
|
const p = msg.payload;
|
|
2750
|
-
await handleProviderAdd(ws, p);
|
|
2715
|
+
await providerHandlers.handleProviderAdd(ws, p);
|
|
2751
2716
|
break;
|
|
2752
2717
|
}
|
|
2753
2718
|
case "provider.remove": {
|
|
2754
2719
|
const { providerId } = msg.payload;
|
|
2755
|
-
await handleProviderRemove(ws, providerId);
|
|
2720
|
+
await providerHandlers.handleProviderRemove(ws, providerId);
|
|
2756
2721
|
break;
|
|
2757
2722
|
}
|
|
2758
2723
|
case "sessions.list": {
|
|
@@ -3137,65 +3102,14 @@ async function startWebUI(opts = {}) {
|
|
|
3137
3102
|
}
|
|
3138
3103
|
}
|
|
3139
3104
|
}
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
);
|
|
3147
|
-
await configWriteLock;
|
|
3148
|
-
}
|
|
3149
|
-
async function handleKeyUpsert(ws, providerId, label, apiKey) {
|
|
3150
|
-
try {
|
|
3151
|
-
const providers = await loadConfigProviders();
|
|
3152
|
-
const result = upsertKey(providers, providerId, label, apiKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
3153
|
-
if (result.ok) await saveConfigProviders(providers);
|
|
3154
|
-
sendResult(ws, result.ok, result.message);
|
|
3155
|
-
} catch (err) {
|
|
3156
|
-
sendResult(ws, false, errMessage(err));
|
|
3105
|
+
const providerHandlers = createProviderHandlers({
|
|
3106
|
+
globalConfigPath,
|
|
3107
|
+
vault,
|
|
3108
|
+
getConfigWriteLock: () => configWriteLock,
|
|
3109
|
+
setConfigWriteLock: (p) => {
|
|
3110
|
+
configWriteLock = p;
|
|
3157
3111
|
}
|
|
3158
|
-
}
|
|
3159
|
-
async function handleKeyDelete(ws, providerId, label) {
|
|
3160
|
-
try {
|
|
3161
|
-
const providers = await loadConfigProviders();
|
|
3162
|
-
const result = deleteKey(providers, providerId, label);
|
|
3163
|
-
if (result.ok) await saveConfigProviders(providers);
|
|
3164
|
-
sendResult(ws, result.ok, result.message);
|
|
3165
|
-
} catch (err) {
|
|
3166
|
-
sendResult(ws, false, errMessage(err));
|
|
3167
|
-
}
|
|
3168
|
-
}
|
|
3169
|
-
async function handleKeySetActive(ws, providerId, label) {
|
|
3170
|
-
try {
|
|
3171
|
-
const providers = await loadConfigProviders();
|
|
3172
|
-
const result = setActiveKey(providers, providerId, label);
|
|
3173
|
-
if (result.ok) await saveConfigProviders(providers);
|
|
3174
|
-
sendResult(ws, result.ok, result.message);
|
|
3175
|
-
} catch (err) {
|
|
3176
|
-
sendResult(ws, false, errMessage(err));
|
|
3177
|
-
}
|
|
3178
|
-
}
|
|
3179
|
-
async function handleProviderAdd(ws, payload) {
|
|
3180
|
-
try {
|
|
3181
|
-
const providers = await loadConfigProviders();
|
|
3182
|
-
const result = addProvider(providers, payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
3183
|
-
if (result.ok) await saveConfigProviders(providers);
|
|
3184
|
-
sendResult(ws, result.ok, result.message);
|
|
3185
|
-
} catch (err) {
|
|
3186
|
-
sendResult(ws, false, errMessage(err));
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
async function handleProviderRemove(ws, providerId) {
|
|
3190
|
-
try {
|
|
3191
|
-
const providers = await loadConfigProviders();
|
|
3192
|
-
const result = removeProvider(providers, providerId);
|
|
3193
|
-
if (result.ok) await saveConfigProviders(providers);
|
|
3194
|
-
sendResult(ws, result.ok, result.message);
|
|
3195
|
-
} catch (err) {
|
|
3196
|
-
sendResult(ws, false, errMessage(err));
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3112
|
+
});
|
|
3199
3113
|
const httpServer = createHttpServer({
|
|
3200
3114
|
host: wsHost,
|
|
3201
3115
|
distDir: path4.resolve(import.meta.dirname, "../../dist"),
|