@wrongstack/webui 0.260.0 → 0.264.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-BBPaC1tO.js +170 -0
- package/dist/assets/index-DJmqJ5Wo.css +2 -0
- package/dist/assets/{vendor-BRkhRU94.js → vendor-pWpGJmMc.js} +182 -182
- package/dist/index.html +3 -3
- package/dist/index.js +887 -390
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +376 -115
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +377 -116
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +1208 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +5 -5
- package/dist/assets/index-6rPVh7TJ.css +0 -2
- package/dist/assets/index-CfIQObXO.js +0 -165
package/dist/server/index.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/server/index.ts
|
|
9
2
|
import { expectDefined as expectDefined2, GlobalMailbox as GlobalMailbox2, projectSlug, getSessionRegistry, AgentStatusTracker } from "@wrongstack/core";
|
|
10
3
|
import { makeMailboxTool, makeMailSendTool, makeMailInboxTool, mailboxSessionTag } from "@wrongstack/core";
|
|
4
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
11
5
|
import {
|
|
12
6
|
BrainMonitor,
|
|
13
7
|
DefaultBrainArbiter,
|
|
@@ -16,7 +10,7 @@ import {
|
|
|
16
10
|
createTieredBrainArbiter
|
|
17
11
|
} from "@wrongstack/core";
|
|
18
12
|
import * as fs7 from "fs/promises";
|
|
19
|
-
import * as
|
|
13
|
+
import * as path8 from "path";
|
|
20
14
|
|
|
21
15
|
// src/server/http-server.ts
|
|
22
16
|
import * as fs from "fs/promises";
|
|
@@ -24,7 +18,7 @@ import * as http from "http";
|
|
|
24
18
|
import * as path from "path";
|
|
25
19
|
|
|
26
20
|
// src/server/ws-auth.ts
|
|
27
|
-
import { Buffer
|
|
21
|
+
import { Buffer } from "buffer";
|
|
28
22
|
import { timingSafeEqual } from "crypto";
|
|
29
23
|
function isLoopbackHostname(hostname) {
|
|
30
24
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
@@ -43,8 +37,8 @@ function isLoopbackBind(wsHost) {
|
|
|
43
37
|
}
|
|
44
38
|
function tokenMatches(provided, expected) {
|
|
45
39
|
if (!provided) return false;
|
|
46
|
-
const a =
|
|
47
|
-
const b =
|
|
40
|
+
const a = Buffer.from(provided);
|
|
41
|
+
const b = Buffer.from(expected);
|
|
48
42
|
if (a.length !== b.length) return false;
|
|
49
43
|
return timingSafeEqual(a, b);
|
|
50
44
|
}
|
|
@@ -710,6 +704,7 @@ function patchConfig(config, updates) {
|
|
|
710
704
|
|
|
711
705
|
// src/server/autophase-ws-handler.ts
|
|
712
706
|
import { spawnSync } from "child_process";
|
|
707
|
+
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
713
708
|
import {
|
|
714
709
|
AutoPhasePlanner,
|
|
715
710
|
PhaseGraphBuilder,
|
|
@@ -879,7 +874,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
879
874
|
);
|
|
880
875
|
this.broadcastState();
|
|
881
876
|
}).catch((err) => {
|
|
882
|
-
this.logger.error(`[AutoPhase] Aborted: ${
|
|
877
|
+
this.logger.error(`[AutoPhase] Aborted: ${toErrorMessage(err)}`);
|
|
883
878
|
this.stopBroadcast();
|
|
884
879
|
this.broadcast({ type: "autophase.failed", payload: { title, error: String(err) } });
|
|
885
880
|
});
|
|
@@ -912,7 +907,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
912
907
|
}
|
|
913
908
|
this.logger.info(`[AutoPhase] Planner produced no phases; using defaults for: ${goal}`);
|
|
914
909
|
} catch (err) {
|
|
915
|
-
this.logger.error(`[AutoPhase] Planning failed, using defaults: ${
|
|
910
|
+
this.logger.error(`[AutoPhase] Planning failed, using defaults: ${toErrorMessage(err)}`);
|
|
916
911
|
}
|
|
917
912
|
return this.defaultPhases();
|
|
918
913
|
}
|
|
@@ -1039,6 +1034,7 @@ Type: ${task.type}`;
|
|
|
1039
1034
|
|
|
1040
1035
|
// src/server/collaboration-ws-handler.ts
|
|
1041
1036
|
import { randomUUID } from "crypto";
|
|
1037
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
1042
1038
|
var REPLAY_LIMIT = 50;
|
|
1043
1039
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
1044
1040
|
var CollaborationWebSocketHandler = class {
|
|
@@ -1166,7 +1162,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1166
1162
|
if (this.reader) {
|
|
1167
1163
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
1168
1164
|
this.logger.debug?.(
|
|
1169
|
-
`collab: replay failed for ${sessionId}: ${
|
|
1165
|
+
`collab: replay failed for ${sessionId}: ${toErrorMessage2(err)}`
|
|
1170
1166
|
);
|
|
1171
1167
|
});
|
|
1172
1168
|
}
|
|
@@ -1276,7 +1272,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1276
1272
|
this.send(
|
|
1277
1273
|
ws,
|
|
1278
1274
|
this.errorMessage(
|
|
1279
|
-
`annotation rejected: ${
|
|
1275
|
+
`annotation rejected: ${toErrorMessage2(err)}`
|
|
1280
1276
|
)
|
|
1281
1277
|
);
|
|
1282
1278
|
}
|
|
@@ -1343,7 +1339,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1343
1339
|
this.send(
|
|
1344
1340
|
ws,
|
|
1345
1341
|
this.errorMessage(
|
|
1346
|
-
`resolve failed: ${
|
|
1342
|
+
`resolve failed: ${toErrorMessage2(err)}`
|
|
1347
1343
|
)
|
|
1348
1344
|
);
|
|
1349
1345
|
}
|
|
@@ -1391,7 +1387,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1391
1387
|
if (p.ws.readyState === 1) p.ws.send(data);
|
|
1392
1388
|
} catch (err) {
|
|
1393
1389
|
this.logger.debug?.(
|
|
1394
|
-
`collab broadcast failed: ${
|
|
1390
|
+
`collab broadcast failed: ${toErrorMessage2(err)}`
|
|
1395
1391
|
);
|
|
1396
1392
|
}
|
|
1397
1393
|
}
|
|
@@ -1418,7 +1414,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1418
1414
|
}
|
|
1419
1415
|
} catch (err) {
|
|
1420
1416
|
this.logger.debug?.(
|
|
1421
|
-
`collab: session reader rejected ${sessionId}: ${
|
|
1417
|
+
`collab: session reader rejected ${sessionId}: ${toErrorMessage2(err)}`
|
|
1422
1418
|
);
|
|
1423
1419
|
return;
|
|
1424
1420
|
}
|
|
@@ -1499,7 +1495,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1499
1495
|
if (p.ws.readyState === 1) p.ws.send(data);
|
|
1500
1496
|
} catch (err) {
|
|
1501
1497
|
this.logger.debug?.(
|
|
1502
|
-
`collab broadcast failed: ${
|
|
1498
|
+
`collab broadcast failed: ${toErrorMessage2(err)}`
|
|
1503
1499
|
);
|
|
1504
1500
|
}
|
|
1505
1501
|
}
|
|
@@ -1696,6 +1692,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1696
1692
|
};
|
|
1697
1693
|
|
|
1698
1694
|
// src/server/worktree-ws-handler.ts
|
|
1695
|
+
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
1699
1696
|
var MAX_ACTIVITY = 6;
|
|
1700
1697
|
var WorktreeWebSocketHandler = class {
|
|
1701
1698
|
constructor(events, logger) {
|
|
@@ -1821,7 +1818,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
1821
1818
|
try {
|
|
1822
1819
|
if (ws.readyState === 1) ws.send(data);
|
|
1823
1820
|
} catch (err) {
|
|
1824
|
-
this.logger.debug?.(`worktree broadcast failed: ${
|
|
1821
|
+
this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage3(err)}`);
|
|
1825
1822
|
}
|
|
1826
1823
|
}
|
|
1827
1824
|
}
|
|
@@ -1834,22 +1831,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
1834
1831
|
};
|
|
1835
1832
|
|
|
1836
1833
|
// src/server/mailbox-handlers.ts
|
|
1837
|
-
import
|
|
1838
|
-
import { GlobalMailbox } from "@wrongstack/core";
|
|
1839
|
-
function resolveProjectDir(projectRoot, globalRoot) {
|
|
1840
|
-
const { createHash } = __require("crypto");
|
|
1841
|
-
const hash = createHash("sha256").update(path3.resolve(projectRoot)).digest("hex").slice(0, 6);
|
|
1842
|
-
const slug = path3.basename(projectRoot).toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40) || "project";
|
|
1843
|
-
return path3.join(globalRoot, "projects", `${slug}-${hash}`);
|
|
1844
|
-
}
|
|
1834
|
+
import { GlobalMailbox, resolveProjectDir } from "@wrongstack/core";
|
|
1845
1835
|
async function handleMailboxMessages(ws, deps, payload) {
|
|
1846
1836
|
try {
|
|
1847
1837
|
const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
|
|
1848
1838
|
const mb = new GlobalMailbox(dir);
|
|
1849
1839
|
const messages = await mb.query({
|
|
1850
1840
|
limit: payload?.limit ?? 30,
|
|
1851
|
-
|
|
1852
|
-
unreadBy: payload?.unreadOnly ? payload.agentId : void 0
|
|
1841
|
+
incompleteOnly: payload?.incompleteOnly ?? false
|
|
1853
1842
|
});
|
|
1854
1843
|
send(ws, {
|
|
1855
1844
|
type: "mailbox.messages",
|
|
@@ -1866,10 +1855,12 @@ async function handleMailboxMessages(ws, deps, payload) {
|
|
|
1866
1855
|
readByCount: Object.keys(m.readBy).length,
|
|
1867
1856
|
completed: m.completed,
|
|
1868
1857
|
completedBy: m.completedBy,
|
|
1858
|
+
completedAt: m.completedAt,
|
|
1869
1859
|
outcome: m.outcome,
|
|
1870
1860
|
timestamp: m.timestamp,
|
|
1871
1861
|
replyTo: m.replyTo,
|
|
1872
|
-
senderSessionId: m.senderSessionId
|
|
1862
|
+
senderSessionId: m.senderSessionId,
|
|
1863
|
+
taskContext: m.taskContext
|
|
1873
1864
|
}))
|
|
1874
1865
|
}
|
|
1875
1866
|
});
|
|
@@ -1916,6 +1907,16 @@ async function handleMailboxClear(ws, deps) {
|
|
|
1916
1907
|
send(ws, { type: "mailbox.cleared", payload: { error: errMessage(err) } });
|
|
1917
1908
|
}
|
|
1918
1909
|
}
|
|
1910
|
+
async function handleMailboxPurge(ws, deps, opts) {
|
|
1911
|
+
try {
|
|
1912
|
+
const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
|
|
1913
|
+
const mb = new GlobalMailbox(dir);
|
|
1914
|
+
const result = await mb.purgeStale(opts);
|
|
1915
|
+
send(ws, { type: "mailbox.purged", payload: result });
|
|
1916
|
+
} catch (err) {
|
|
1917
|
+
send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1919
1920
|
|
|
1920
1921
|
// src/server/lifecycle.ts
|
|
1921
1922
|
function createShutdown(res) {
|
|
@@ -1955,14 +1956,14 @@ function registerShutdownHandlers(res) {
|
|
|
1955
1956
|
|
|
1956
1957
|
// src/server/instance-registry.ts
|
|
1957
1958
|
import * as os from "os";
|
|
1958
|
-
import * as
|
|
1959
|
+
import * as path3 from "path";
|
|
1959
1960
|
import * as fs3 from "fs/promises";
|
|
1960
1961
|
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
1961
1962
|
function defaultBaseDir() {
|
|
1962
|
-
return
|
|
1963
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
1963
1964
|
}
|
|
1964
1965
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
1965
|
-
return
|
|
1966
|
+
return path3.join(baseDir, "webui-instances.json");
|
|
1966
1967
|
}
|
|
1967
1968
|
function isPidAlive(pid) {
|
|
1968
1969
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2034,16 +2035,16 @@ function formatInstances(instances) {
|
|
|
2034
2035
|
// src/server/port-utils.ts
|
|
2035
2036
|
import * as net from "net";
|
|
2036
2037
|
function isPortFree(host, port) {
|
|
2037
|
-
return new Promise((
|
|
2038
|
+
return new Promise((resolve5) => {
|
|
2038
2039
|
const srv = net.createServer();
|
|
2039
|
-
srv.once("error", () =>
|
|
2040
|
+
srv.once("error", () => resolve5(false));
|
|
2040
2041
|
srv.once("listening", () => {
|
|
2041
|
-
srv.close(() =>
|
|
2042
|
+
srv.close(() => resolve5(true));
|
|
2042
2043
|
});
|
|
2043
2044
|
try {
|
|
2044
2045
|
srv.listen(port, host);
|
|
2045
2046
|
} catch {
|
|
2046
|
-
|
|
2047
|
+
resolve5(false);
|
|
2047
2048
|
}
|
|
2048
2049
|
});
|
|
2049
2050
|
}
|
|
@@ -2118,9 +2119,13 @@ function computeUsageCost(usage, rates) {
|
|
|
2118
2119
|
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
2119
2120
|
}
|
|
2120
2121
|
|
|
2122
|
+
// src/server/provider-handlers.ts
|
|
2123
|
+
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core";
|
|
2124
|
+
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
2125
|
+
|
|
2121
2126
|
// src/server/provider-config-io.ts
|
|
2122
2127
|
import * as fs4 from "fs/promises";
|
|
2123
|
-
import * as
|
|
2128
|
+
import * as path4 from "path";
|
|
2124
2129
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
2125
2130
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
2126
2131
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
@@ -2172,7 +2177,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
2172
2177
|
await atomicWrite3(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2173
2178
|
}
|
|
2174
2179
|
function createProviderConfigIO(configPath) {
|
|
2175
|
-
const keyFile =
|
|
2180
|
+
const keyFile = path4.join(path4.dirname(configPath), ".key");
|
|
2176
2181
|
const vault = new DefaultSecretVault({ keyFile });
|
|
2177
2182
|
return {
|
|
2178
2183
|
load: () => loadSavedProviders(configPath, vault),
|
|
@@ -2180,6 +2185,9 @@ function createProviderConfigIO(configPath) {
|
|
|
2180
2185
|
};
|
|
2181
2186
|
}
|
|
2182
2187
|
|
|
2188
|
+
// src/server/provider-handlers.ts
|
|
2189
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
2190
|
+
|
|
2183
2191
|
// src/server/provider-keys.ts
|
|
2184
2192
|
import { expectDefined } from "@wrongstack/core";
|
|
2185
2193
|
function normalizeKeys(cfg) {
|
|
@@ -2277,6 +2285,28 @@ function removeProvider(providers, providerId) {
|
|
|
2277
2285
|
}
|
|
2278
2286
|
|
|
2279
2287
|
// src/server/provider-handlers.ts
|
|
2288
|
+
function projectSavedProviders(providers) {
|
|
2289
|
+
return Object.entries(providers).map(([id, cfg]) => {
|
|
2290
|
+
const keys = normalizeKeys(cfg);
|
|
2291
|
+
const models = cfg.models;
|
|
2292
|
+
const view = {
|
|
2293
|
+
id,
|
|
2294
|
+
family: cfg.family ?? id,
|
|
2295
|
+
baseUrl: cfg.baseUrl,
|
|
2296
|
+
models,
|
|
2297
|
+
apiKeys: keys.map((k) => ({
|
|
2298
|
+
label: k.label,
|
|
2299
|
+
maskedKey: maskedKey(k.apiKey),
|
|
2300
|
+
isActive: k.label === cfg.activeKey,
|
|
2301
|
+
createdAt: k.createdAt
|
|
2302
|
+
}))
|
|
2303
|
+
};
|
|
2304
|
+
const picked = models && models.length > 0 ? models[0] : void 0;
|
|
2305
|
+
if (picked !== void 0) view.pickedModelId = picked;
|
|
2306
|
+
return view;
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
var probeScrubber = new DefaultSecretScrubber2();
|
|
2280
2310
|
function createProviderHandlers(deps) {
|
|
2281
2311
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps;
|
|
2282
2312
|
let configWriteLock = deps.getConfigWriteLock();
|
|
@@ -2285,7 +2315,7 @@ function createProviderHandlers(deps) {
|
|
|
2285
2315
|
}
|
|
2286
2316
|
async function saveConfigProviders(providers) {
|
|
2287
2317
|
const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers)).catch((err) => {
|
|
2288
|
-
const msg =
|
|
2318
|
+
const msg = toErrorMessage4(err);
|
|
2289
2319
|
console.error(JSON.stringify({
|
|
2290
2320
|
level: "error",
|
|
2291
2321
|
event: "webui.provider_save_failed",
|
|
@@ -2335,25 +2365,7 @@ function createProviderHandlers(deps) {
|
|
|
2335
2365
|
sendResult(ws, result.ok, result.message);
|
|
2336
2366
|
if (result.ok) {
|
|
2337
2367
|
console.log(`[WebUI] Provider "${payload.id}" added via provider.add`);
|
|
2338
|
-
|
|
2339
|
-
type: "providers.saved",
|
|
2340
|
-
payload: {
|
|
2341
|
-
providers: Object.entries(providers).map(([id, cfg]) => {
|
|
2342
|
-
const keys = normalizeKeys(cfg);
|
|
2343
|
-
return {
|
|
2344
|
-
id,
|
|
2345
|
-
family: cfg.family ?? id,
|
|
2346
|
-
baseUrl: cfg.baseUrl,
|
|
2347
|
-
apiKeys: keys.map((k) => ({
|
|
2348
|
-
label: k.label,
|
|
2349
|
-
maskedKey: maskedKey(k.apiKey),
|
|
2350
|
-
isActive: k.label === cfg.activeKey,
|
|
2351
|
-
createdAt: k.createdAt
|
|
2352
|
-
}))
|
|
2353
|
-
};
|
|
2354
|
-
})
|
|
2355
|
-
}
|
|
2356
|
-
});
|
|
2368
|
+
broadcastSaved(providers);
|
|
2357
2369
|
}
|
|
2358
2370
|
} catch (err) {
|
|
2359
2371
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2369,11 +2381,106 @@ function createProviderHandlers(deps) {
|
|
|
2369
2381
|
sendResult(ws, false, errMessage(err));
|
|
2370
2382
|
}
|
|
2371
2383
|
}
|
|
2372
|
-
|
|
2384
|
+
function broadcastSaved(providers) {
|
|
2385
|
+
broadcast2(clients, {
|
|
2386
|
+
type: "providers.saved",
|
|
2387
|
+
payload: { providers: projectSavedProviders(providers) }
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
async function handleProviderClearModels(ws, providerId) {
|
|
2391
|
+
try {
|
|
2392
|
+
const providers = await loadConfigProviders();
|
|
2393
|
+
const cfg = providers[providerId];
|
|
2394
|
+
if (!cfg) {
|
|
2395
|
+
sendResult(ws, false, `Unknown provider "${providerId}"`);
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
delete cfg.models;
|
|
2399
|
+
await saveConfigProviders(providers);
|
|
2400
|
+
sendResult(ws, true, `Cleared model allowlist for ${providerId}`);
|
|
2401
|
+
broadcastSaved(providers);
|
|
2402
|
+
} catch (err) {
|
|
2403
|
+
sendResult(ws, false, errMessage(err));
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
async function handleProviderUndoClear(ws, providerId, previousModels) {
|
|
2407
|
+
try {
|
|
2408
|
+
const providers = await loadConfigProviders();
|
|
2409
|
+
const cfg = providers[providerId];
|
|
2410
|
+
if (!cfg) {
|
|
2411
|
+
sendResult(ws, false, `Unknown provider "${providerId}"`);
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
cfg.models = [...previousModels];
|
|
2415
|
+
await saveConfigProviders(providers);
|
|
2416
|
+
sendResult(ws, true, `Restored ${previousModels.length} model(s) for ${providerId}`);
|
|
2417
|
+
broadcastSaved(providers);
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
sendResult(ws, false, errMessage(err));
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
async function handleProviderUpdate(ws, payload) {
|
|
2423
|
+
try {
|
|
2424
|
+
const providers = await loadConfigProviders();
|
|
2425
|
+
const cfg = providers[payload.id];
|
|
2426
|
+
if (!cfg) {
|
|
2427
|
+
sendResult(ws, false, `Unknown provider "${payload.id}"`);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
if (payload.family !== void 0) cfg.family = payload.family;
|
|
2431
|
+
if (payload.baseUrl !== void 0) cfg.baseUrl = payload.baseUrl;
|
|
2432
|
+
if (payload.envVars !== void 0) cfg.envVars = payload.envVars;
|
|
2433
|
+
if (payload.models !== void 0) cfg.models = payload.models;
|
|
2434
|
+
await saveConfigProviders(providers);
|
|
2435
|
+
sendResult(ws, true, `Updated ${payload.id}`);
|
|
2436
|
+
broadcastSaved(providers);
|
|
2437
|
+
} catch (err) {
|
|
2438
|
+
sendResult(ws, false, errMessage(err));
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
async function handleProviderProbe(ws, providerId, timeoutMs) {
|
|
2442
|
+
const reply = (payload) => send(ws, { type: "provider.probe", payload: { providerId, ...payload } });
|
|
2443
|
+
try {
|
|
2444
|
+
const providers = await loadConfigProviders();
|
|
2445
|
+
const cfg = providers[providerId];
|
|
2446
|
+
if (!cfg) {
|
|
2447
|
+
reply({ ok: false, status: "no_provider" });
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
if (!cfg.baseUrl) {
|
|
2451
|
+
reply({ ok: false, status: "no_base_url" });
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
const keys = normalizeKeys(cfg);
|
|
2455
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
2456
|
+
const result = await probeLocalLlm({
|
|
2457
|
+
baseUrl: cfg.baseUrl,
|
|
2458
|
+
apiKey: active?.apiKey,
|
|
2459
|
+
noAuth: false,
|
|
2460
|
+
scrubber: probeScrubber,
|
|
2461
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
2462
|
+
});
|
|
2463
|
+
reply(result);
|
|
2464
|
+
} catch (err) {
|
|
2465
|
+
reply({ ok: false, status: "unreachable", detail: errMessage(err) });
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return {
|
|
2469
|
+
handleKeyUpsert,
|
|
2470
|
+
handleKeyDelete,
|
|
2471
|
+
handleKeySetActive,
|
|
2472
|
+
handleProviderAdd,
|
|
2473
|
+
handleProviderRemove,
|
|
2474
|
+
handleProviderClearModels,
|
|
2475
|
+
handleProviderUndoClear,
|
|
2476
|
+
handleProviderUpdate,
|
|
2477
|
+
handleProviderProbe,
|
|
2478
|
+
loadConfigProviders
|
|
2479
|
+
};
|
|
2373
2480
|
}
|
|
2374
2481
|
|
|
2375
2482
|
// src/server/setup-events.ts
|
|
2376
|
-
import * as
|
|
2483
|
+
import * as path5 from "path";
|
|
2377
2484
|
function setupEvents(deps) {
|
|
2378
2485
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge } = deps;
|
|
2379
2486
|
events.on("iteration.started", (e) => {
|
|
@@ -2572,7 +2679,7 @@ function setupEvents(deps) {
|
|
|
2572
2679
|
events.onPattern("brain.*", (eventName, payload) => {
|
|
2573
2680
|
broadcast2(clients, { type: "brain.event", payload: { event: eventName, ...payload } });
|
|
2574
2681
|
});
|
|
2575
|
-
const globalRoot = globalConfigPath ?
|
|
2682
|
+
const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
|
|
2576
2683
|
if (globalRoot) {
|
|
2577
2684
|
const statusInterval = setInterval(async () => {
|
|
2578
2685
|
try {
|
|
@@ -2611,10 +2718,10 @@ function setupEvents(deps) {
|
|
|
2611
2718
|
// src/server/custom-context-modes.ts
|
|
2612
2719
|
import { listContextWindowModes, atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
2613
2720
|
import * as fs5 from "fs/promises";
|
|
2614
|
-
import * as
|
|
2721
|
+
import * as path6 from "path";
|
|
2615
2722
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
2616
2723
|
function storePath(wrongstackDir) {
|
|
2617
|
-
return
|
|
2724
|
+
return path6.join(wrongstackDir, STORE_FILENAME);
|
|
2618
2725
|
}
|
|
2619
2726
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
2620
2727
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -2803,12 +2910,12 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
2803
2910
|
|
|
2804
2911
|
// src/server/shell-open.ts
|
|
2805
2912
|
import * as fs6 from "fs/promises";
|
|
2806
|
-
import * as
|
|
2913
|
+
import * as path7 from "path";
|
|
2807
2914
|
import { spawn as spawn2 } from "child_process";
|
|
2808
2915
|
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
2809
2916
|
async function handleShellOpen(req, logger) {
|
|
2810
2917
|
try {
|
|
2811
|
-
const resolved =
|
|
2918
|
+
const resolved = path7.resolve(req.path);
|
|
2812
2919
|
await fs6.access(resolved);
|
|
2813
2920
|
if (METACHAR_REGEX.test(resolved)) {
|
|
2814
2921
|
return { success: false, message: "Path contains unsupported characters." };
|
|
@@ -2920,7 +3027,7 @@ async function startWebUI(opts = {}) {
|
|
|
2920
3027
|
console.warn(JSON.stringify({
|
|
2921
3028
|
level: "warn",
|
|
2922
3029
|
event: "webui.provider_registry_load_failed",
|
|
2923
|
-
message:
|
|
3030
|
+
message: toErrorMessage5(err),
|
|
2924
3031
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2925
3032
|
}));
|
|
2926
3033
|
}
|
|
@@ -2969,7 +3076,7 @@ async function startWebUI(opts = {}) {
|
|
|
2969
3076
|
sessionId: session.id,
|
|
2970
3077
|
projectSlug: wpaths.projectSlug,
|
|
2971
3078
|
projectRoot,
|
|
2972
|
-
projectName:
|
|
3079
|
+
projectName: path8.basename(projectRoot),
|
|
2973
3080
|
workingDir,
|
|
2974
3081
|
pid: process.pid,
|
|
2975
3082
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3057,7 +3164,7 @@ async function startWebUI(opts = {}) {
|
|
|
3057
3164
|
console.error(JSON.stringify({
|
|
3058
3165
|
level: "error",
|
|
3059
3166
|
event: "webui.provider_create_failed",
|
|
3060
|
-
message:
|
|
3167
|
+
message: toErrorMessage5(err),
|
|
3061
3168
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3062
3169
|
}));
|
|
3063
3170
|
throw err;
|
|
@@ -3079,7 +3186,7 @@ async function startWebUI(opts = {}) {
|
|
|
3079
3186
|
console.error(JSON.stringify({
|
|
3080
3187
|
level: "error",
|
|
3081
3188
|
event: "webui.provider_stub_create_failed",
|
|
3082
|
-
message:
|
|
3189
|
+
message: toErrorMessage5(err),
|
|
3083
3190
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3084
3191
|
}));
|
|
3085
3192
|
throw err;
|
|
@@ -3128,6 +3235,12 @@ async function startWebUI(opts = {}) {
|
|
|
3128
3235
|
context.meta["logLevel"] = config.log?.level ?? "info";
|
|
3129
3236
|
context.meta["auditLevel"] = config.session?.auditLevel ?? "standard";
|
|
3130
3237
|
context.meta["maxIterations"] = config.tools?.maxIterations ?? 500;
|
|
3238
|
+
const tgExt = config.extensions?.["telegram"];
|
|
3239
|
+
context.meta["tgConfigured"] = typeof tgExt?.["botToken"] === "string" && tgExt["botToken"].length > 0;
|
|
3240
|
+
context.meta["tgSessionEnd"] = tgExt?.["notifyOnSessionEnd"] === true;
|
|
3241
|
+
context.meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
3242
|
+
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
3243
|
+
context.meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
3131
3244
|
}
|
|
3132
3245
|
const PREF_KEYS = [
|
|
3133
3246
|
"autonomy",
|
|
@@ -3151,7 +3264,11 @@ async function startWebUI(opts = {}) {
|
|
|
3151
3264
|
"contextAutoCompact",
|
|
3152
3265
|
"contextStrategy",
|
|
3153
3266
|
"logLevel",
|
|
3154
|
-
"auditLevel"
|
|
3267
|
+
"auditLevel",
|
|
3268
|
+
"tgConfigured",
|
|
3269
|
+
"tgSessionEnd",
|
|
3270
|
+
"tgDelegate",
|
|
3271
|
+
"tgLongToolMs"
|
|
3155
3272
|
];
|
|
3156
3273
|
const prefSnapshot = () => {
|
|
3157
3274
|
const snapshot = {};
|
|
@@ -3236,6 +3353,22 @@ async function startWebUI(opts = {}) {
|
|
|
3236
3353
|
toolsCfg.maxIterations = payload["maxIterations"];
|
|
3237
3354
|
decrypted.tools = toolsCfg;
|
|
3238
3355
|
}
|
|
3356
|
+
const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
|
|
3357
|
+
if (tgTouched) {
|
|
3358
|
+
const ext = decrypted.extensions ?? {};
|
|
3359
|
+
const tg = ext["telegram"] ?? {};
|
|
3360
|
+
if (typeof payload["tgSessionEnd"] === "boolean") {
|
|
3361
|
+
tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
|
|
3362
|
+
}
|
|
3363
|
+
if (typeof payload["tgDelegate"] === "boolean") {
|
|
3364
|
+
tg["notifyOnDelegate"] = payload["tgDelegate"];
|
|
3365
|
+
}
|
|
3366
|
+
if (typeof payload["tgLongToolMs"] === "number") {
|
|
3367
|
+
tg["longToolThresholdMs"] = payload["tgLongToolMs"];
|
|
3368
|
+
}
|
|
3369
|
+
ext["telegram"] = tg;
|
|
3370
|
+
decrypted.extensions = ext;
|
|
3371
|
+
}
|
|
3239
3372
|
const encrypted = encryptConfigSecrets2(decrypted, vault);
|
|
3240
3373
|
await atomicWrite5(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3241
3374
|
};
|
|
@@ -3457,7 +3590,7 @@ async function startWebUI(opts = {}) {
|
|
|
3457
3590
|
inputCost,
|
|
3458
3591
|
outputCost,
|
|
3459
3592
|
cacheReadCost,
|
|
3460
|
-
projectName:
|
|
3593
|
+
projectName: path8.basename(projectRoot) || projectRoot,
|
|
3461
3594
|
projectRoot,
|
|
3462
3595
|
cwd: workingDir,
|
|
3463
3596
|
mode: modeId,
|
|
@@ -3538,7 +3671,7 @@ async function startWebUI(opts = {}) {
|
|
|
3538
3671
|
console.warn(JSON.stringify({
|
|
3539
3672
|
level: "warn",
|
|
3540
3673
|
event: "webui.session_start_payload_failed",
|
|
3541
|
-
message:
|
|
3674
|
+
message: toErrorMessage5(err),
|
|
3542
3675
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3543
3676
|
}));
|
|
3544
3677
|
});
|
|
@@ -3575,7 +3708,7 @@ async function startWebUI(opts = {}) {
|
|
|
3575
3708
|
console.error(JSON.stringify({
|
|
3576
3709
|
level: "error",
|
|
3577
3710
|
event: "webui.ws_message_parse_failed",
|
|
3578
|
-
message:
|
|
3711
|
+
message: toErrorMessage5(err),
|
|
3579
3712
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3580
3713
|
}));
|
|
3581
3714
|
}
|
|
@@ -3584,8 +3717,8 @@ async function startWebUI(opts = {}) {
|
|
|
3584
3717
|
clients.delete(ws);
|
|
3585
3718
|
rateLimits.delete(String(ws));
|
|
3586
3719
|
if (pendingConfirms.size > 0) {
|
|
3587
|
-
for (const [id,
|
|
3588
|
-
|
|
3720
|
+
for (const [id, resolve5] of pendingConfirms) {
|
|
3721
|
+
resolve5("no");
|
|
3589
3722
|
pendingConfirms.delete(id);
|
|
3590
3723
|
}
|
|
3591
3724
|
}
|
|
@@ -3621,7 +3754,7 @@ async function startWebUI(opts = {}) {
|
|
|
3621
3754
|
level: "error",
|
|
3622
3755
|
event: "webui.ws_server_error",
|
|
3623
3756
|
host: wsHost,
|
|
3624
|
-
message:
|
|
3757
|
+
message: toErrorMessage5(err),
|
|
3625
3758
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3626
3759
|
}));
|
|
3627
3760
|
});
|
|
@@ -3649,29 +3782,29 @@ async function startWebUI(opts = {}) {
|
|
|
3649
3782
|
});
|
|
3650
3783
|
}
|
|
3651
3784
|
async function touchProjectEntry(root, workDir) {
|
|
3652
|
-
const resolved =
|
|
3785
|
+
const resolved = path8.resolve(root);
|
|
3653
3786
|
const manifest = await loadManifest(globalConfigPath);
|
|
3654
3787
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3655
|
-
const existing = manifest.projects.find((p) =>
|
|
3788
|
+
const existing = manifest.projects.find((p) => path8.resolve(p.root) === resolved);
|
|
3656
3789
|
if (existing) {
|
|
3657
3790
|
existing.lastSeen = now;
|
|
3658
|
-
if (workDir) existing.lastWorkingDir =
|
|
3791
|
+
if (workDir) existing.lastWorkingDir = path8.resolve(workDir);
|
|
3659
3792
|
} else {
|
|
3660
3793
|
manifest.projects.push({
|
|
3661
|
-
name:
|
|
3794
|
+
name: path8.basename(resolved),
|
|
3662
3795
|
root: resolved,
|
|
3663
3796
|
slug: generateProjectSlug(resolved),
|
|
3664
3797
|
createdAt: now,
|
|
3665
3798
|
lastSeen: now,
|
|
3666
|
-
lastWorkingDir: workDir ?
|
|
3799
|
+
lastWorkingDir: workDir ? path8.resolve(workDir) : void 0
|
|
3667
3800
|
});
|
|
3668
3801
|
}
|
|
3669
3802
|
await saveManifest(manifest, globalConfigPath);
|
|
3670
3803
|
await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
|
|
3671
3804
|
}
|
|
3672
3805
|
function projectsJsonPath(globalConfigPath2) {
|
|
3673
|
-
const base =
|
|
3674
|
-
return
|
|
3806
|
+
const base = path8.dirname(globalConfigPath2);
|
|
3807
|
+
return path8.join(base, "projects.json");
|
|
3675
3808
|
}
|
|
3676
3809
|
async function loadManifest(globalConfigPath2) {
|
|
3677
3810
|
try {
|
|
@@ -3684,15 +3817,15 @@ async function startWebUI(opts = {}) {
|
|
|
3684
3817
|
}
|
|
3685
3818
|
async function saveManifest(manifest, globalConfigPath2) {
|
|
3686
3819
|
const file = projectsJsonPath(globalConfigPath2);
|
|
3687
|
-
await fs7.mkdir(
|
|
3820
|
+
await fs7.mkdir(path8.dirname(file), { recursive: true });
|
|
3688
3821
|
await fs7.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
3689
3822
|
}
|
|
3690
3823
|
function generateProjectSlug(rootPath) {
|
|
3691
3824
|
return projectSlug(rootPath);
|
|
3692
3825
|
}
|
|
3693
3826
|
async function ensureProjectDataDir(slug, globalConfigPath2) {
|
|
3694
|
-
const base =
|
|
3695
|
-
const dir =
|
|
3827
|
+
const base = path8.dirname(globalConfigPath2);
|
|
3828
|
+
const dir = path8.join(base, "projects", slug);
|
|
3696
3829
|
await fs7.mkdir(dir, { recursive: true });
|
|
3697
3830
|
return dir;
|
|
3698
3831
|
}
|
|
@@ -3704,7 +3837,9 @@ async function startWebUI(opts = {}) {
|
|
|
3704
3837
|
case "collab.join":
|
|
3705
3838
|
case "collab.leave":
|
|
3706
3839
|
case "collab.annotate":
|
|
3707
|
-
case "collab.resolve":
|
|
3840
|
+
case "collab.resolve":
|
|
3841
|
+
case "collab.request_pause":
|
|
3842
|
+
case "collab.resume": {
|
|
3708
3843
|
collabHandler.handleMessage(ws, msg);
|
|
3709
3844
|
return;
|
|
3710
3845
|
}
|
|
@@ -3755,10 +3890,10 @@ async function startWebUI(opts = {}) {
|
|
|
3755
3890
|
}
|
|
3756
3891
|
case "tool.confirm_result": {
|
|
3757
3892
|
const { id, decision } = msg.payload;
|
|
3758
|
-
const
|
|
3759
|
-
if (
|
|
3893
|
+
const resolve5 = pendingConfirms.get(id);
|
|
3894
|
+
if (resolve5) {
|
|
3760
3895
|
pendingConfirms.delete(id);
|
|
3761
|
-
|
|
3896
|
+
resolve5(decision);
|
|
3762
3897
|
}
|
|
3763
3898
|
break;
|
|
3764
3899
|
}
|
|
@@ -4043,7 +4178,7 @@ async function startWebUI(opts = {}) {
|
|
|
4043
4178
|
console.warn(JSON.stringify({
|
|
4044
4179
|
level: "warn",
|
|
4045
4180
|
event: "webui.config_save_failed",
|
|
4046
|
-
message:
|
|
4181
|
+
message: toErrorMessage5(err),
|
|
4047
4182
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4048
4183
|
}));
|
|
4049
4184
|
}
|
|
@@ -4141,6 +4276,26 @@ async function startWebUI(opts = {}) {
|
|
|
4141
4276
|
await providerHandlers.handleProviderRemove(ws, providerId);
|
|
4142
4277
|
break;
|
|
4143
4278
|
}
|
|
4279
|
+
case "provider.clear_models": {
|
|
4280
|
+
const { providerId } = msg.payload;
|
|
4281
|
+
await providerHandlers.handleProviderClearModels(ws, providerId);
|
|
4282
|
+
break;
|
|
4283
|
+
}
|
|
4284
|
+
case "provider.undo_clear": {
|
|
4285
|
+
const { providerId, previousModels } = msg.payload;
|
|
4286
|
+
await providerHandlers.handleProviderUndoClear(ws, providerId, previousModels);
|
|
4287
|
+
break;
|
|
4288
|
+
}
|
|
4289
|
+
case "provider.update": {
|
|
4290
|
+
const p = msg.payload;
|
|
4291
|
+
await providerHandlers.handleProviderUpdate(ws, p);
|
|
4292
|
+
break;
|
|
4293
|
+
}
|
|
4294
|
+
case "provider.probe": {
|
|
4295
|
+
const { providerId, timeoutMs } = msg.payload;
|
|
4296
|
+
await providerHandlers.handleProviderProbe(ws, providerId, timeoutMs);
|
|
4297
|
+
break;
|
|
4298
|
+
}
|
|
4144
4299
|
case "sessions.list": {
|
|
4145
4300
|
const limit = msg.payload?.limit ?? 50;
|
|
4146
4301
|
try {
|
|
@@ -4429,6 +4584,75 @@ async function startWebUI(opts = {}) {
|
|
|
4429
4584
|
}
|
|
4430
4585
|
break;
|
|
4431
4586
|
}
|
|
4587
|
+
case "todo.update": {
|
|
4588
|
+
const payload = msg.payload;
|
|
4589
|
+
const idx = context.todos.findIndex((t) => t.id === payload.id);
|
|
4590
|
+
if (idx === -1) {
|
|
4591
|
+
sendResult(ws, false, "Todo not found");
|
|
4592
|
+
break;
|
|
4593
|
+
}
|
|
4594
|
+
const next = [...context.todos];
|
|
4595
|
+
const existing = expectDefined2(next[idx]);
|
|
4596
|
+
next[idx] = {
|
|
4597
|
+
...existing,
|
|
4598
|
+
status: payload.status ?? existing.status,
|
|
4599
|
+
activeForm: payload.activeForm !== void 0 ? payload.activeForm : existing.activeForm
|
|
4600
|
+
};
|
|
4601
|
+
context.state.replaceTodos(next);
|
|
4602
|
+
sendResult(ws, true, `Todo "${existing.content}" updated`);
|
|
4603
|
+
broadcast(clients, { type: "todos.updated", payload: { todos: next } });
|
|
4604
|
+
break;
|
|
4605
|
+
}
|
|
4606
|
+
case "task.update": {
|
|
4607
|
+
const payload = msg.payload;
|
|
4608
|
+
const taskPath = context.meta["task.path"];
|
|
4609
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
4610
|
+
sendResult(ws, false, "Task storage not configured.");
|
|
4611
|
+
break;
|
|
4612
|
+
}
|
|
4613
|
+
try {
|
|
4614
|
+
const { mutateTasks } = await import("@wrongstack/core");
|
|
4615
|
+
const file = await mutateTasks(taskPath, session.id, async (f) => {
|
|
4616
|
+
const task = f.tasks.find((t) => t.id === payload.id);
|
|
4617
|
+
if (!task) return f;
|
|
4618
|
+
task.status = payload.status;
|
|
4619
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4620
|
+
return f;
|
|
4621
|
+
});
|
|
4622
|
+
sendResult(ws, true, `Task status updated to "${payload.status}".`);
|
|
4623
|
+
broadcast(clients, { type: "tasks.updated", payload: { tasks: file.tasks } });
|
|
4624
|
+
} catch (err) {
|
|
4625
|
+
sendResult(ws, false, errMessage(err));
|
|
4626
|
+
}
|
|
4627
|
+
break;
|
|
4628
|
+
}
|
|
4629
|
+
case "plan.item.update": {
|
|
4630
|
+
const payload = msg.payload;
|
|
4631
|
+
const planPath = context.meta["plan.path"];
|
|
4632
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
4633
|
+
sendResult(ws, false, "Plan storage is not configured for this session.");
|
|
4634
|
+
break;
|
|
4635
|
+
}
|
|
4636
|
+
try {
|
|
4637
|
+
const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
4638
|
+
let changed = false;
|
|
4639
|
+
const plan = await mutatePlan(planPath, session.id, async (p) => {
|
|
4640
|
+
const before = p.updatedAt;
|
|
4641
|
+
const updated = setPlanItemStatus(p, payload.target, payload.status);
|
|
4642
|
+
changed = updated.updatedAt !== before;
|
|
4643
|
+
return updated;
|
|
4644
|
+
});
|
|
4645
|
+
if (!changed) {
|
|
4646
|
+
sendResult(ws, false, `No plan item matched "${payload.target}".`);
|
|
4647
|
+
break;
|
|
4648
|
+
}
|
|
4649
|
+
sendResult(ws, true, `Plan item status updated to "${payload.status}".`);
|
|
4650
|
+
broadcast(clients, { type: "plan.updated", payload: { plan } });
|
|
4651
|
+
} catch (err) {
|
|
4652
|
+
sendResult(ws, false, errMessage(err));
|
|
4653
|
+
}
|
|
4654
|
+
break;
|
|
4655
|
+
}
|
|
4432
4656
|
// ── File operations — delegated to shared handlers (file-handlers.ts) ──
|
|
4433
4657
|
// These handlers are also used by the CLI's webui-server.ts. When
|
|
4434
4658
|
// adding or modifying file-operation WebSocket messages, update
|
|
@@ -4576,9 +4800,40 @@ async function startWebUI(opts = {}) {
|
|
|
4576
4800
|
}
|
|
4577
4801
|
break;
|
|
4578
4802
|
}
|
|
4803
|
+
case "git.info": {
|
|
4804
|
+
const cwd = projectRoot;
|
|
4805
|
+
const execFile = (cmd, args) => new Promise((resolve5) => {
|
|
4806
|
+
import("child_process").then(({ execFile: ef }) => {
|
|
4807
|
+
ef(cmd, args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
4808
|
+
resolve5(err ? "" : stdout.trim());
|
|
4809
|
+
});
|
|
4810
|
+
});
|
|
4811
|
+
});
|
|
4812
|
+
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
4813
|
+
execFile("git", ["branch", "--show-current"]),
|
|
4814
|
+
execFile("git", ["diff", "--stat"]),
|
|
4815
|
+
execFile("git", ["status", "--porcelain"]),
|
|
4816
|
+
execFile("git", ["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
|
|
4817
|
+
]);
|
|
4818
|
+
const branch = branchRaw || "(detached)";
|
|
4819
|
+
const diffMatch = /\+\s*(\d+)\s*deletion/i.exec(diffRaw);
|
|
4820
|
+
const addMatch = /(\d+)\s*insertion/i.exec(diffRaw) ?? /(\d+)\s*addition/i.exec(diffRaw);
|
|
4821
|
+
const delMatch = /\+\s*(\d+)\s*deletion/i.exec(diffRaw);
|
|
4822
|
+
const added = addMatch ? Number(addMatch[1]) : 0;
|
|
4823
|
+
const deleted = delMatch ? Number(delMatch[1]) : 0;
|
|
4824
|
+
const untracked = statusRaw.split("\n").filter((l) => l.startsWith("??")).length;
|
|
4825
|
+
const [aheadRaw, behindRaw] = (upstreamRaw || "0 0").split(" ");
|
|
4826
|
+
const ahead = Number(aheadRaw) || 0;
|
|
4827
|
+
const behind = Number(behindRaw) || 0;
|
|
4828
|
+
send(ws, {
|
|
4829
|
+
type: "git.info",
|
|
4830
|
+
payload: { branch, added, deleted, untracked, ahead, behind }
|
|
4831
|
+
});
|
|
4832
|
+
break;
|
|
4833
|
+
}
|
|
4579
4834
|
case "goal.get": {
|
|
4580
4835
|
try {
|
|
4581
|
-
const goalPath =
|
|
4836
|
+
const goalPath = path8.join(projectRoot, ".wrongstack", "goal.json");
|
|
4582
4837
|
const raw = await fs7.readFile(goalPath, "utf8");
|
|
4583
4838
|
const goal = JSON.parse(raw);
|
|
4584
4839
|
broadcast(clients, { type: "goal.updated", payload: goal });
|
|
@@ -4639,7 +4894,7 @@ async function startWebUI(opts = {}) {
|
|
|
4639
4894
|
try {
|
|
4640
4895
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4641
4896
|
const rewinder = new DefaultSessionRewinder(
|
|
4642
|
-
|
|
4897
|
+
path8.join(projectRoot, ".wrongstack", "sessions"),
|
|
4643
4898
|
projectRoot
|
|
4644
4899
|
);
|
|
4645
4900
|
const checkpoints = await rewinder.listCheckpoints(session.id);
|
|
@@ -4660,7 +4915,7 @@ async function startWebUI(opts = {}) {
|
|
|
4660
4915
|
try {
|
|
4661
4916
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4662
4917
|
const rewinder = new DefaultSessionRewinder(
|
|
4663
|
-
|
|
4918
|
+
path8.join(projectRoot, ".wrongstack", "sessions"),
|
|
4664
4919
|
projectRoot
|
|
4665
4920
|
);
|
|
4666
4921
|
await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
|
|
@@ -4694,7 +4949,7 @@ async function startWebUI(opts = {}) {
|
|
|
4694
4949
|
case "projects.add": {
|
|
4695
4950
|
const { root: addRoot, name: displayName } = msg.payload;
|
|
4696
4951
|
try {
|
|
4697
|
-
const resolved =
|
|
4952
|
+
const resolved = path8.resolve(addRoot);
|
|
4698
4953
|
await fs7.access(resolved);
|
|
4699
4954
|
const stat2 = await fs7.stat(resolved);
|
|
4700
4955
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
@@ -4712,7 +4967,7 @@ async function startWebUI(opts = {}) {
|
|
|
4712
4967
|
});
|
|
4713
4968
|
break;
|
|
4714
4969
|
}
|
|
4715
|
-
const name = displayName?.trim() ||
|
|
4970
|
+
const name = displayName?.trim() || path8.basename(resolved);
|
|
4716
4971
|
const slug = generateProjectSlug(resolved);
|
|
4717
4972
|
await ensureProjectDataDir(slug, globalConfigPath);
|
|
4718
4973
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4731,7 +4986,7 @@ async function startWebUI(opts = {}) {
|
|
|
4731
4986
|
send(ws, {
|
|
4732
4987
|
type: "projects.added",
|
|
4733
4988
|
payload: {
|
|
4734
|
-
name:
|
|
4989
|
+
name: path8.basename(addRoot),
|
|
4735
4990
|
root: addRoot,
|
|
4736
4991
|
slug: "",
|
|
4737
4992
|
message: errMessage(err)
|
|
@@ -4743,7 +4998,7 @@ async function startWebUI(opts = {}) {
|
|
|
4743
4998
|
case "projects.select": {
|
|
4744
4999
|
const { root: selRoot, name: selName } = msg.payload;
|
|
4745
5000
|
try {
|
|
4746
|
-
const resolved =
|
|
5001
|
+
const resolved = path8.resolve(selRoot);
|
|
4747
5002
|
try {
|
|
4748
5003
|
await fs7.access(resolved);
|
|
4749
5004
|
const stat2 = await fs7.stat(resolved);
|
|
@@ -4753,7 +5008,7 @@ async function startWebUI(opts = {}) {
|
|
|
4753
5008
|
type: "projects.selected",
|
|
4754
5009
|
payload: {
|
|
4755
5010
|
root: selRoot,
|
|
4756
|
-
name: selName ||
|
|
5011
|
+
name: selName || path8.basename(selRoot),
|
|
4757
5012
|
message: `Cannot switch: ${errMessage(err)}`
|
|
4758
5013
|
}
|
|
4759
5014
|
});
|
|
@@ -4765,7 +5020,7 @@ async function startWebUI(opts = {}) {
|
|
|
4765
5020
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
4766
5021
|
entry.lastWorkingDir = resolved;
|
|
4767
5022
|
} else {
|
|
4768
|
-
const name = selName?.trim() ||
|
|
5023
|
+
const name = selName?.trim() || path8.basename(resolved);
|
|
4769
5024
|
const slug = generateProjectSlug(resolved);
|
|
4770
5025
|
manifest.projects.push({
|
|
4771
5026
|
name,
|
|
@@ -4806,8 +5061,8 @@ async function startWebUI(opts = {}) {
|
|
|
4806
5061
|
});
|
|
4807
5062
|
} catch {
|
|
4808
5063
|
}
|
|
4809
|
-
const newSessionsDir =
|
|
4810
|
-
|
|
5064
|
+
const newSessionsDir = path8.join(
|
|
5065
|
+
path8.dirname(globalConfigPath),
|
|
4811
5066
|
"projects",
|
|
4812
5067
|
switchSlug,
|
|
4813
5068
|
"sessions"
|
|
@@ -4844,7 +5099,7 @@ async function startWebUI(opts = {}) {
|
|
|
4844
5099
|
sessionId: session.id,
|
|
4845
5100
|
projectSlug: switchSlug,
|
|
4846
5101
|
projectRoot,
|
|
4847
|
-
projectName:
|
|
5102
|
+
projectName: path8.basename(projectRoot),
|
|
4848
5103
|
workingDir,
|
|
4849
5104
|
pid: process.pid,
|
|
4850
5105
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4855,8 +5110,8 @@ async function startWebUI(opts = {}) {
|
|
|
4855
5110
|
type: "projects.selected",
|
|
4856
5111
|
payload: {
|
|
4857
5112
|
root: resolved,
|
|
4858
|
-
name: selName ||
|
|
4859
|
-
message: `Switched to ${selName ||
|
|
5113
|
+
name: selName || path8.basename(resolved),
|
|
5114
|
+
message: `Switched to ${selName || path8.basename(resolved)}`
|
|
4860
5115
|
}
|
|
4861
5116
|
});
|
|
4862
5117
|
broadcast(clients, {
|
|
@@ -4879,7 +5134,7 @@ async function startWebUI(opts = {}) {
|
|
|
4879
5134
|
type: "projects.selected",
|
|
4880
5135
|
payload: {
|
|
4881
5136
|
root: selRoot,
|
|
4882
|
-
name: selName ||
|
|
5137
|
+
name: selName || path8.basename(selRoot),
|
|
4883
5138
|
message: errMessage(err)
|
|
4884
5139
|
}
|
|
4885
5140
|
});
|
|
@@ -4890,8 +5145,8 @@ async function startWebUI(opts = {}) {
|
|
|
4890
5145
|
case "working_dir.set": {
|
|
4891
5146
|
const { path: newPath } = msg.payload;
|
|
4892
5147
|
try {
|
|
4893
|
-
const resolved =
|
|
4894
|
-
if (!resolved.startsWith(projectRoot +
|
|
5148
|
+
const resolved = path8.resolve(projectRoot, newPath);
|
|
5149
|
+
if (!resolved.startsWith(projectRoot + path8.sep) && resolved !== projectRoot) {
|
|
4895
5150
|
sendResult(ws, false, `Path must stay inside the project root: ${projectRoot}`);
|
|
4896
5151
|
break;
|
|
4897
5152
|
}
|
|
@@ -4928,19 +5183,25 @@ async function startWebUI(opts = {}) {
|
|
|
4928
5183
|
case "mailbox.messages":
|
|
4929
5184
|
return handleMailboxMessages(
|
|
4930
5185
|
ws,
|
|
4931
|
-
{ projectRoot, globalRoot:
|
|
5186
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
4932
5187
|
msg.payload
|
|
4933
5188
|
);
|
|
4934
5189
|
case "mailbox.agents":
|
|
4935
5190
|
return handleMailboxAgents(
|
|
4936
5191
|
ws,
|
|
4937
|
-
{ projectRoot, globalRoot:
|
|
5192
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
4938
5193
|
msg.payload
|
|
4939
5194
|
);
|
|
4940
5195
|
case "mailbox.clear":
|
|
4941
5196
|
return handleMailboxClear(
|
|
4942
5197
|
ws,
|
|
4943
|
-
{ projectRoot, globalRoot:
|
|
5198
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) }
|
|
5199
|
+
);
|
|
5200
|
+
case "mailbox.purge":
|
|
5201
|
+
return handleMailboxPurge(
|
|
5202
|
+
ws,
|
|
5203
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
5204
|
+
msg.payload
|
|
4944
5205
|
);
|
|
4945
5206
|
// ── Brain — status, autonomy ceiling, direct decision support ───
|
|
4946
5207
|
case "brain.status":
|
|
@@ -5008,12 +5269,12 @@ async function startWebUI(opts = {}) {
|
|
|
5008
5269
|
});
|
|
5009
5270
|
const httpServer = createHttpServer({
|
|
5010
5271
|
host: wsHost,
|
|
5011
|
-
distDir:
|
|
5272
|
+
distDir: path8.resolve(import.meta.dirname, "../../dist"),
|
|
5012
5273
|
wsPort,
|
|
5013
5274
|
globalRoot: wpaths.globalRoot,
|
|
5014
5275
|
apiToken: wsToken
|
|
5015
5276
|
});
|
|
5016
|
-
const registryBaseDir =
|
|
5277
|
+
const registryBaseDir = path8.dirname(globalConfigPath);
|
|
5017
5278
|
httpServer.listen(httpPort, wsHost, () => {
|
|
5018
5279
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
5019
5280
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -5025,7 +5286,7 @@ async function startWebUI(opts = {}) {
|
|
|
5025
5286
|
wsPort,
|
|
5026
5287
|
host: wsHost,
|
|
5027
5288
|
projectRoot,
|
|
5028
|
-
projectName:
|
|
5289
|
+
projectName: path8.basename(projectRoot) || projectRoot,
|
|
5029
5290
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5030
5291
|
url: `http://${wsHost}:${httpPort}`
|
|
5031
5292
|
},
|