@wrongstack/webui 0.257.2 → 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-BXyxPv8C.js → vendor-pWpGJmMc.js} +227 -227
- package/dist/index.html +3 -3
- package/dist/index.js +4514 -4579
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +377 -116
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +378 -117
- 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-C19_lY6u.css +0 -2
- package/dist/assets/index-DiFYgNpK.js +0 -163
package/dist/server/entry.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
2
|
// src/server/index.ts
|
|
10
3
|
import { expectDefined as expectDefined2, GlobalMailbox as GlobalMailbox2, projectSlug, getSessionRegistry, AgentStatusTracker } from "@wrongstack/core";
|
|
11
4
|
import { makeMailboxTool, makeMailSendTool, makeMailInboxTool, mailboxSessionTag } from "@wrongstack/core";
|
|
5
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
12
6
|
import {
|
|
13
7
|
BrainMonitor,
|
|
14
8
|
DefaultBrainArbiter,
|
|
@@ -17,7 +11,7 @@ import {
|
|
|
17
11
|
createTieredBrainArbiter
|
|
18
12
|
} from "@wrongstack/core";
|
|
19
13
|
import * as fs7 from "fs/promises";
|
|
20
|
-
import * as
|
|
14
|
+
import * as path8 from "path";
|
|
21
15
|
|
|
22
16
|
// src/server/http-server.ts
|
|
23
17
|
import * as fs from "fs/promises";
|
|
@@ -25,7 +19,7 @@ import * as http from "http";
|
|
|
25
19
|
import * as path from "path";
|
|
26
20
|
|
|
27
21
|
// src/server/ws-auth.ts
|
|
28
|
-
import { Buffer
|
|
22
|
+
import { Buffer } from "buffer";
|
|
29
23
|
import { timingSafeEqual } from "crypto";
|
|
30
24
|
function isLoopbackHostname(hostname) {
|
|
31
25
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
@@ -44,8 +38,8 @@ function isLoopbackBind(wsHost) {
|
|
|
44
38
|
}
|
|
45
39
|
function tokenMatches(provided, expected) {
|
|
46
40
|
if (!provided) return false;
|
|
47
|
-
const a =
|
|
48
|
-
const b =
|
|
41
|
+
const a = Buffer.from(provided);
|
|
42
|
+
const b = Buffer.from(expected);
|
|
49
43
|
if (a.length !== b.length) return false;
|
|
50
44
|
return timingSafeEqual(a, b);
|
|
51
45
|
}
|
|
@@ -711,6 +705,7 @@ function patchConfig(config, updates) {
|
|
|
711
705
|
|
|
712
706
|
// src/server/autophase-ws-handler.ts
|
|
713
707
|
import { spawnSync } from "child_process";
|
|
708
|
+
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
714
709
|
import {
|
|
715
710
|
AutoPhasePlanner,
|
|
716
711
|
PhaseGraphBuilder,
|
|
@@ -880,7 +875,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
880
875
|
);
|
|
881
876
|
this.broadcastState();
|
|
882
877
|
}).catch((err) => {
|
|
883
|
-
this.logger.error(`[AutoPhase] Aborted: ${
|
|
878
|
+
this.logger.error(`[AutoPhase] Aborted: ${toErrorMessage(err)}`);
|
|
884
879
|
this.stopBroadcast();
|
|
885
880
|
this.broadcast({ type: "autophase.failed", payload: { title, error: String(err) } });
|
|
886
881
|
});
|
|
@@ -913,7 +908,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
913
908
|
}
|
|
914
909
|
this.logger.info(`[AutoPhase] Planner produced no phases; using defaults for: ${goal}`);
|
|
915
910
|
} catch (err) {
|
|
916
|
-
this.logger.error(`[AutoPhase] Planning failed, using defaults: ${
|
|
911
|
+
this.logger.error(`[AutoPhase] Planning failed, using defaults: ${toErrorMessage(err)}`);
|
|
917
912
|
}
|
|
918
913
|
return this.defaultPhases();
|
|
919
914
|
}
|
|
@@ -1040,6 +1035,7 @@ Type: ${task.type}`;
|
|
|
1040
1035
|
|
|
1041
1036
|
// src/server/collaboration-ws-handler.ts
|
|
1042
1037
|
import { randomUUID } from "crypto";
|
|
1038
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
1043
1039
|
var REPLAY_LIMIT = 50;
|
|
1044
1040
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
1045
1041
|
var CollaborationWebSocketHandler = class {
|
|
@@ -1167,7 +1163,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1167
1163
|
if (this.reader) {
|
|
1168
1164
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
1169
1165
|
this.logger.debug?.(
|
|
1170
|
-
`collab: replay failed for ${sessionId}: ${
|
|
1166
|
+
`collab: replay failed for ${sessionId}: ${toErrorMessage2(err)}`
|
|
1171
1167
|
);
|
|
1172
1168
|
});
|
|
1173
1169
|
}
|
|
@@ -1277,7 +1273,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1277
1273
|
this.send(
|
|
1278
1274
|
ws,
|
|
1279
1275
|
this.errorMessage(
|
|
1280
|
-
`annotation rejected: ${
|
|
1276
|
+
`annotation rejected: ${toErrorMessage2(err)}`
|
|
1281
1277
|
)
|
|
1282
1278
|
);
|
|
1283
1279
|
}
|
|
@@ -1344,7 +1340,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1344
1340
|
this.send(
|
|
1345
1341
|
ws,
|
|
1346
1342
|
this.errorMessage(
|
|
1347
|
-
`resolve failed: ${
|
|
1343
|
+
`resolve failed: ${toErrorMessage2(err)}`
|
|
1348
1344
|
)
|
|
1349
1345
|
);
|
|
1350
1346
|
}
|
|
@@ -1392,7 +1388,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1392
1388
|
if (p.ws.readyState === 1) p.ws.send(data);
|
|
1393
1389
|
} catch (err) {
|
|
1394
1390
|
this.logger.debug?.(
|
|
1395
|
-
`collab broadcast failed: ${
|
|
1391
|
+
`collab broadcast failed: ${toErrorMessage2(err)}`
|
|
1396
1392
|
);
|
|
1397
1393
|
}
|
|
1398
1394
|
}
|
|
@@ -1419,7 +1415,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1419
1415
|
}
|
|
1420
1416
|
} catch (err) {
|
|
1421
1417
|
this.logger.debug?.(
|
|
1422
|
-
`collab: session reader rejected ${sessionId}: ${
|
|
1418
|
+
`collab: session reader rejected ${sessionId}: ${toErrorMessage2(err)}`
|
|
1423
1419
|
);
|
|
1424
1420
|
return;
|
|
1425
1421
|
}
|
|
@@ -1500,7 +1496,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1500
1496
|
if (p.ws.readyState === 1) p.ws.send(data);
|
|
1501
1497
|
} catch (err) {
|
|
1502
1498
|
this.logger.debug?.(
|
|
1503
|
-
`collab broadcast failed: ${
|
|
1499
|
+
`collab broadcast failed: ${toErrorMessage2(err)}`
|
|
1504
1500
|
);
|
|
1505
1501
|
}
|
|
1506
1502
|
}
|
|
@@ -1697,6 +1693,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1697
1693
|
};
|
|
1698
1694
|
|
|
1699
1695
|
// src/server/worktree-ws-handler.ts
|
|
1696
|
+
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
1700
1697
|
var MAX_ACTIVITY = 6;
|
|
1701
1698
|
var WorktreeWebSocketHandler = class {
|
|
1702
1699
|
constructor(events, logger) {
|
|
@@ -1822,7 +1819,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
1822
1819
|
try {
|
|
1823
1820
|
if (ws.readyState === 1) ws.send(data);
|
|
1824
1821
|
} catch (err) {
|
|
1825
|
-
this.logger.debug?.(`worktree broadcast failed: ${
|
|
1822
|
+
this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage3(err)}`);
|
|
1826
1823
|
}
|
|
1827
1824
|
}
|
|
1828
1825
|
}
|
|
@@ -1835,22 +1832,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
1835
1832
|
};
|
|
1836
1833
|
|
|
1837
1834
|
// src/server/mailbox-handlers.ts
|
|
1838
|
-
import
|
|
1839
|
-
import { GlobalMailbox } from "@wrongstack/core";
|
|
1840
|
-
function resolveProjectDir(projectRoot, globalRoot) {
|
|
1841
|
-
const { createHash } = __require("crypto");
|
|
1842
|
-
const hash = createHash("sha256").update(path3.resolve(projectRoot)).digest("hex").slice(0, 6);
|
|
1843
|
-
const slug = path3.basename(projectRoot).toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40) || "project";
|
|
1844
|
-
return path3.join(globalRoot, "projects", `${slug}-${hash}`);
|
|
1845
|
-
}
|
|
1835
|
+
import { GlobalMailbox, resolveProjectDir } from "@wrongstack/core";
|
|
1846
1836
|
async function handleMailboxMessages(ws, deps, payload) {
|
|
1847
1837
|
try {
|
|
1848
1838
|
const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
|
|
1849
1839
|
const mb = new GlobalMailbox(dir);
|
|
1850
1840
|
const messages = await mb.query({
|
|
1851
1841
|
limit: payload?.limit ?? 30,
|
|
1852
|
-
|
|
1853
|
-
unreadBy: payload?.unreadOnly ? payload.agentId : void 0
|
|
1842
|
+
incompleteOnly: payload?.incompleteOnly ?? false
|
|
1854
1843
|
});
|
|
1855
1844
|
send(ws, {
|
|
1856
1845
|
type: "mailbox.messages",
|
|
@@ -1867,10 +1856,12 @@ async function handleMailboxMessages(ws, deps, payload) {
|
|
|
1867
1856
|
readByCount: Object.keys(m.readBy).length,
|
|
1868
1857
|
completed: m.completed,
|
|
1869
1858
|
completedBy: m.completedBy,
|
|
1859
|
+
completedAt: m.completedAt,
|
|
1870
1860
|
outcome: m.outcome,
|
|
1871
1861
|
timestamp: m.timestamp,
|
|
1872
1862
|
replyTo: m.replyTo,
|
|
1873
|
-
senderSessionId: m.senderSessionId
|
|
1863
|
+
senderSessionId: m.senderSessionId,
|
|
1864
|
+
taskContext: m.taskContext
|
|
1874
1865
|
}))
|
|
1875
1866
|
}
|
|
1876
1867
|
});
|
|
@@ -1917,6 +1908,16 @@ async function handleMailboxClear(ws, deps) {
|
|
|
1917
1908
|
send(ws, { type: "mailbox.cleared", payload: { error: errMessage(err) } });
|
|
1918
1909
|
}
|
|
1919
1910
|
}
|
|
1911
|
+
async function handleMailboxPurge(ws, deps, opts) {
|
|
1912
|
+
try {
|
|
1913
|
+
const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
|
|
1914
|
+
const mb = new GlobalMailbox(dir);
|
|
1915
|
+
const result = await mb.purgeStale(opts);
|
|
1916
|
+
send(ws, { type: "mailbox.purged", payload: result });
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1920
1921
|
|
|
1921
1922
|
// src/server/lifecycle.ts
|
|
1922
1923
|
function createShutdown(res) {
|
|
@@ -1956,14 +1957,14 @@ function registerShutdownHandlers(res) {
|
|
|
1956
1957
|
|
|
1957
1958
|
// src/server/instance-registry.ts
|
|
1958
1959
|
import * as os from "os";
|
|
1959
|
-
import * as
|
|
1960
|
+
import * as path3 from "path";
|
|
1960
1961
|
import * as fs3 from "fs/promises";
|
|
1961
1962
|
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
1962
1963
|
function defaultBaseDir() {
|
|
1963
|
-
return
|
|
1964
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
1964
1965
|
}
|
|
1965
1966
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
1966
|
-
return
|
|
1967
|
+
return path3.join(baseDir, "webui-instances.json");
|
|
1967
1968
|
}
|
|
1968
1969
|
function isPidAlive(pid) {
|
|
1969
1970
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2035,16 +2036,16 @@ function formatInstances(instances) {
|
|
|
2035
2036
|
// src/server/port-utils.ts
|
|
2036
2037
|
import * as net from "net";
|
|
2037
2038
|
function isPortFree(host, port) {
|
|
2038
|
-
return new Promise((
|
|
2039
|
+
return new Promise((resolve5) => {
|
|
2039
2040
|
const srv = net.createServer();
|
|
2040
|
-
srv.once("error", () =>
|
|
2041
|
+
srv.once("error", () => resolve5(false));
|
|
2041
2042
|
srv.once("listening", () => {
|
|
2042
|
-
srv.close(() =>
|
|
2043
|
+
srv.close(() => resolve5(true));
|
|
2043
2044
|
});
|
|
2044
2045
|
try {
|
|
2045
2046
|
srv.listen(port, host);
|
|
2046
2047
|
} catch {
|
|
2047
|
-
|
|
2048
|
+
resolve5(false);
|
|
2048
2049
|
}
|
|
2049
2050
|
});
|
|
2050
2051
|
}
|
|
@@ -2119,9 +2120,13 @@ function computeUsageCost(usage, rates) {
|
|
|
2119
2120
|
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
2120
2121
|
}
|
|
2121
2122
|
|
|
2123
|
+
// src/server/provider-handlers.ts
|
|
2124
|
+
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core";
|
|
2125
|
+
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
2126
|
+
|
|
2122
2127
|
// src/server/provider-config-io.ts
|
|
2123
2128
|
import * as fs4 from "fs/promises";
|
|
2124
|
-
import * as
|
|
2129
|
+
import * as path4 from "path";
|
|
2125
2130
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
2126
2131
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
2127
2132
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
@@ -2173,6 +2178,9 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
2173
2178
|
await atomicWrite3(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2174
2179
|
}
|
|
2175
2180
|
|
|
2181
|
+
// src/server/provider-handlers.ts
|
|
2182
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
2183
|
+
|
|
2176
2184
|
// src/server/provider-keys.ts
|
|
2177
2185
|
import { expectDefined } from "@wrongstack/core";
|
|
2178
2186
|
function normalizeKeys(cfg) {
|
|
@@ -2270,6 +2278,28 @@ function removeProvider(providers, providerId) {
|
|
|
2270
2278
|
}
|
|
2271
2279
|
|
|
2272
2280
|
// src/server/provider-handlers.ts
|
|
2281
|
+
function projectSavedProviders(providers) {
|
|
2282
|
+
return Object.entries(providers).map(([id, cfg]) => {
|
|
2283
|
+
const keys = normalizeKeys(cfg);
|
|
2284
|
+
const models = cfg.models;
|
|
2285
|
+
const view = {
|
|
2286
|
+
id,
|
|
2287
|
+
family: cfg.family ?? id,
|
|
2288
|
+
baseUrl: cfg.baseUrl,
|
|
2289
|
+
models,
|
|
2290
|
+
apiKeys: keys.map((k) => ({
|
|
2291
|
+
label: k.label,
|
|
2292
|
+
maskedKey: maskedKey(k.apiKey),
|
|
2293
|
+
isActive: k.label === cfg.activeKey,
|
|
2294
|
+
createdAt: k.createdAt
|
|
2295
|
+
}))
|
|
2296
|
+
};
|
|
2297
|
+
const picked = models && models.length > 0 ? models[0] : void 0;
|
|
2298
|
+
if (picked !== void 0) view.pickedModelId = picked;
|
|
2299
|
+
return view;
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
var probeScrubber = new DefaultSecretScrubber2();
|
|
2273
2303
|
function createProviderHandlers(deps) {
|
|
2274
2304
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps;
|
|
2275
2305
|
let configWriteLock = deps.getConfigWriteLock();
|
|
@@ -2278,7 +2308,7 @@ function createProviderHandlers(deps) {
|
|
|
2278
2308
|
}
|
|
2279
2309
|
async function saveConfigProviders(providers) {
|
|
2280
2310
|
const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers)).catch((err) => {
|
|
2281
|
-
const msg =
|
|
2311
|
+
const msg = toErrorMessage4(err);
|
|
2282
2312
|
console.error(JSON.stringify({
|
|
2283
2313
|
level: "error",
|
|
2284
2314
|
event: "webui.provider_save_failed",
|
|
@@ -2328,25 +2358,7 @@ function createProviderHandlers(deps) {
|
|
|
2328
2358
|
sendResult(ws, result.ok, result.message);
|
|
2329
2359
|
if (result.ok) {
|
|
2330
2360
|
console.log(`[WebUI] Provider "${payload.id}" added via provider.add`);
|
|
2331
|
-
|
|
2332
|
-
type: "providers.saved",
|
|
2333
|
-
payload: {
|
|
2334
|
-
providers: Object.entries(providers).map(([id, cfg]) => {
|
|
2335
|
-
const keys = normalizeKeys(cfg);
|
|
2336
|
-
return {
|
|
2337
|
-
id,
|
|
2338
|
-
family: cfg.family ?? id,
|
|
2339
|
-
baseUrl: cfg.baseUrl,
|
|
2340
|
-
apiKeys: keys.map((k) => ({
|
|
2341
|
-
label: k.label,
|
|
2342
|
-
maskedKey: maskedKey(k.apiKey),
|
|
2343
|
-
isActive: k.label === cfg.activeKey,
|
|
2344
|
-
createdAt: k.createdAt
|
|
2345
|
-
}))
|
|
2346
|
-
};
|
|
2347
|
-
})
|
|
2348
|
-
}
|
|
2349
|
-
});
|
|
2361
|
+
broadcastSaved(providers);
|
|
2350
2362
|
}
|
|
2351
2363
|
} catch (err) {
|
|
2352
2364
|
sendResult(ws, false, errMessage(err));
|
|
@@ -2362,11 +2374,106 @@ function createProviderHandlers(deps) {
|
|
|
2362
2374
|
sendResult(ws, false, errMessage(err));
|
|
2363
2375
|
}
|
|
2364
2376
|
}
|
|
2365
|
-
|
|
2377
|
+
function broadcastSaved(providers) {
|
|
2378
|
+
broadcast2(clients, {
|
|
2379
|
+
type: "providers.saved",
|
|
2380
|
+
payload: { providers: projectSavedProviders(providers) }
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
async function handleProviderClearModels(ws, providerId) {
|
|
2384
|
+
try {
|
|
2385
|
+
const providers = await loadConfigProviders();
|
|
2386
|
+
const cfg = providers[providerId];
|
|
2387
|
+
if (!cfg) {
|
|
2388
|
+
sendResult(ws, false, `Unknown provider "${providerId}"`);
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
delete cfg.models;
|
|
2392
|
+
await saveConfigProviders(providers);
|
|
2393
|
+
sendResult(ws, true, `Cleared model allowlist for ${providerId}`);
|
|
2394
|
+
broadcastSaved(providers);
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
sendResult(ws, false, errMessage(err));
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
async function handleProviderUndoClear(ws, providerId, previousModels) {
|
|
2400
|
+
try {
|
|
2401
|
+
const providers = await loadConfigProviders();
|
|
2402
|
+
const cfg = providers[providerId];
|
|
2403
|
+
if (!cfg) {
|
|
2404
|
+
sendResult(ws, false, `Unknown provider "${providerId}"`);
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
cfg.models = [...previousModels];
|
|
2408
|
+
await saveConfigProviders(providers);
|
|
2409
|
+
sendResult(ws, true, `Restored ${previousModels.length} model(s) for ${providerId}`);
|
|
2410
|
+
broadcastSaved(providers);
|
|
2411
|
+
} catch (err) {
|
|
2412
|
+
sendResult(ws, false, errMessage(err));
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
async function handleProviderUpdate(ws, payload) {
|
|
2416
|
+
try {
|
|
2417
|
+
const providers = await loadConfigProviders();
|
|
2418
|
+
const cfg = providers[payload.id];
|
|
2419
|
+
if (!cfg) {
|
|
2420
|
+
sendResult(ws, false, `Unknown provider "${payload.id}"`);
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
if (payload.family !== void 0) cfg.family = payload.family;
|
|
2424
|
+
if (payload.baseUrl !== void 0) cfg.baseUrl = payload.baseUrl;
|
|
2425
|
+
if (payload.envVars !== void 0) cfg.envVars = payload.envVars;
|
|
2426
|
+
if (payload.models !== void 0) cfg.models = payload.models;
|
|
2427
|
+
await saveConfigProviders(providers);
|
|
2428
|
+
sendResult(ws, true, `Updated ${payload.id}`);
|
|
2429
|
+
broadcastSaved(providers);
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
sendResult(ws, false, errMessage(err));
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
async function handleProviderProbe(ws, providerId, timeoutMs) {
|
|
2435
|
+
const reply = (payload) => send(ws, { type: "provider.probe", payload: { providerId, ...payload } });
|
|
2436
|
+
try {
|
|
2437
|
+
const providers = await loadConfigProviders();
|
|
2438
|
+
const cfg = providers[providerId];
|
|
2439
|
+
if (!cfg) {
|
|
2440
|
+
reply({ ok: false, status: "no_provider" });
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (!cfg.baseUrl) {
|
|
2444
|
+
reply({ ok: false, status: "no_base_url" });
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
const keys = normalizeKeys(cfg);
|
|
2448
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
2449
|
+
const result = await probeLocalLlm({
|
|
2450
|
+
baseUrl: cfg.baseUrl,
|
|
2451
|
+
apiKey: active?.apiKey,
|
|
2452
|
+
noAuth: false,
|
|
2453
|
+
scrubber: probeScrubber,
|
|
2454
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
2455
|
+
});
|
|
2456
|
+
reply(result);
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
reply({ ok: false, status: "unreachable", detail: errMessage(err) });
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return {
|
|
2462
|
+
handleKeyUpsert,
|
|
2463
|
+
handleKeyDelete,
|
|
2464
|
+
handleKeySetActive,
|
|
2465
|
+
handleProviderAdd,
|
|
2466
|
+
handleProviderRemove,
|
|
2467
|
+
handleProviderClearModels,
|
|
2468
|
+
handleProviderUndoClear,
|
|
2469
|
+
handleProviderUpdate,
|
|
2470
|
+
handleProviderProbe,
|
|
2471
|
+
loadConfigProviders
|
|
2472
|
+
};
|
|
2366
2473
|
}
|
|
2367
2474
|
|
|
2368
2475
|
// src/server/setup-events.ts
|
|
2369
|
-
import * as
|
|
2476
|
+
import * as path5 from "path";
|
|
2370
2477
|
function setupEvents(deps) {
|
|
2371
2478
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge } = deps;
|
|
2372
2479
|
events.on("iteration.started", (e) => {
|
|
@@ -2565,7 +2672,7 @@ function setupEvents(deps) {
|
|
|
2565
2672
|
events.onPattern("brain.*", (eventName, payload) => {
|
|
2566
2673
|
broadcast2(clients, { type: "brain.event", payload: { event: eventName, ...payload } });
|
|
2567
2674
|
});
|
|
2568
|
-
const globalRoot = globalConfigPath ?
|
|
2675
|
+
const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
|
|
2569
2676
|
if (globalRoot) {
|
|
2570
2677
|
const statusInterval = setInterval(async () => {
|
|
2571
2678
|
try {
|
|
@@ -2604,10 +2711,10 @@ function setupEvents(deps) {
|
|
|
2604
2711
|
// src/server/custom-context-modes.ts
|
|
2605
2712
|
import { listContextWindowModes, atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
2606
2713
|
import * as fs5 from "fs/promises";
|
|
2607
|
-
import * as
|
|
2714
|
+
import * as path6 from "path";
|
|
2608
2715
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
2609
2716
|
function storePath(wrongstackDir) {
|
|
2610
|
-
return
|
|
2717
|
+
return path6.join(wrongstackDir, STORE_FILENAME);
|
|
2611
2718
|
}
|
|
2612
2719
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
2613
2720
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -2796,12 +2903,12 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
2796
2903
|
|
|
2797
2904
|
// src/server/shell-open.ts
|
|
2798
2905
|
import * as fs6 from "fs/promises";
|
|
2799
|
-
import * as
|
|
2906
|
+
import * as path7 from "path";
|
|
2800
2907
|
import { spawn as spawn2 } from "child_process";
|
|
2801
2908
|
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
2802
2909
|
async function handleShellOpen(req, logger) {
|
|
2803
2910
|
try {
|
|
2804
|
-
const resolved =
|
|
2911
|
+
const resolved = path7.resolve(req.path);
|
|
2805
2912
|
await fs6.access(resolved);
|
|
2806
2913
|
if (METACHAR_REGEX.test(resolved)) {
|
|
2807
2914
|
return { success: false, message: "Path contains unsupported characters." };
|
|
@@ -2913,7 +3020,7 @@ async function startWebUI(opts = {}) {
|
|
|
2913
3020
|
console.warn(JSON.stringify({
|
|
2914
3021
|
level: "warn",
|
|
2915
3022
|
event: "webui.provider_registry_load_failed",
|
|
2916
|
-
message:
|
|
3023
|
+
message: toErrorMessage5(err),
|
|
2917
3024
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2918
3025
|
}));
|
|
2919
3026
|
}
|
|
@@ -2942,7 +3049,7 @@ async function startWebUI(opts = {}) {
|
|
|
2942
3049
|
}).catch(() => void 0);
|
|
2943
3050
|
}
|
|
2944
3051
|
const sessionReader = new DefaultSessionReader({ store: sessionStore });
|
|
2945
|
-
const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions });
|
|
3052
|
+
const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions, events });
|
|
2946
3053
|
let session = await sessionStore.create({
|
|
2947
3054
|
id: "",
|
|
2948
3055
|
title: "",
|
|
@@ -2962,7 +3069,7 @@ async function startWebUI(opts = {}) {
|
|
|
2962
3069
|
sessionId: session.id,
|
|
2963
3070
|
projectSlug: wpaths.projectSlug,
|
|
2964
3071
|
projectRoot,
|
|
2965
|
-
projectName:
|
|
3072
|
+
projectName: path8.basename(projectRoot),
|
|
2966
3073
|
workingDir,
|
|
2967
3074
|
pid: process.pid,
|
|
2968
3075
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3050,7 +3157,7 @@ async function startWebUI(opts = {}) {
|
|
|
3050
3157
|
console.error(JSON.stringify({
|
|
3051
3158
|
level: "error",
|
|
3052
3159
|
event: "webui.provider_create_failed",
|
|
3053
|
-
message:
|
|
3160
|
+
message: toErrorMessage5(err),
|
|
3054
3161
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3055
3162
|
}));
|
|
3056
3163
|
throw err;
|
|
@@ -3072,7 +3179,7 @@ async function startWebUI(opts = {}) {
|
|
|
3072
3179
|
console.error(JSON.stringify({
|
|
3073
3180
|
level: "error",
|
|
3074
3181
|
event: "webui.provider_stub_create_failed",
|
|
3075
|
-
message:
|
|
3182
|
+
message: toErrorMessage5(err),
|
|
3076
3183
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3077
3184
|
}));
|
|
3078
3185
|
throw err;
|
|
@@ -3121,6 +3228,12 @@ async function startWebUI(opts = {}) {
|
|
|
3121
3228
|
context.meta["logLevel"] = config.log?.level ?? "info";
|
|
3122
3229
|
context.meta["auditLevel"] = config.session?.auditLevel ?? "standard";
|
|
3123
3230
|
context.meta["maxIterations"] = config.tools?.maxIterations ?? 500;
|
|
3231
|
+
const tgExt = config.extensions?.["telegram"];
|
|
3232
|
+
context.meta["tgConfigured"] = typeof tgExt?.["botToken"] === "string" && tgExt["botToken"].length > 0;
|
|
3233
|
+
context.meta["tgSessionEnd"] = tgExt?.["notifyOnSessionEnd"] === true;
|
|
3234
|
+
context.meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
3235
|
+
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
3236
|
+
context.meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
3124
3237
|
}
|
|
3125
3238
|
const PREF_KEYS = [
|
|
3126
3239
|
"autonomy",
|
|
@@ -3144,7 +3257,11 @@ async function startWebUI(opts = {}) {
|
|
|
3144
3257
|
"contextAutoCompact",
|
|
3145
3258
|
"contextStrategy",
|
|
3146
3259
|
"logLevel",
|
|
3147
|
-
"auditLevel"
|
|
3260
|
+
"auditLevel",
|
|
3261
|
+
"tgConfigured",
|
|
3262
|
+
"tgSessionEnd",
|
|
3263
|
+
"tgDelegate",
|
|
3264
|
+
"tgLongToolMs"
|
|
3148
3265
|
];
|
|
3149
3266
|
const prefSnapshot = () => {
|
|
3150
3267
|
const snapshot = {};
|
|
@@ -3229,6 +3346,22 @@ async function startWebUI(opts = {}) {
|
|
|
3229
3346
|
toolsCfg.maxIterations = payload["maxIterations"];
|
|
3230
3347
|
decrypted.tools = toolsCfg;
|
|
3231
3348
|
}
|
|
3349
|
+
const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
|
|
3350
|
+
if (tgTouched) {
|
|
3351
|
+
const ext = decrypted.extensions ?? {};
|
|
3352
|
+
const tg = ext["telegram"] ?? {};
|
|
3353
|
+
if (typeof payload["tgSessionEnd"] === "boolean") {
|
|
3354
|
+
tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
|
|
3355
|
+
}
|
|
3356
|
+
if (typeof payload["tgDelegate"] === "boolean") {
|
|
3357
|
+
tg["notifyOnDelegate"] = payload["tgDelegate"];
|
|
3358
|
+
}
|
|
3359
|
+
if (typeof payload["tgLongToolMs"] === "number") {
|
|
3360
|
+
tg["longToolThresholdMs"] = payload["tgLongToolMs"];
|
|
3361
|
+
}
|
|
3362
|
+
ext["telegram"] = tg;
|
|
3363
|
+
decrypted.extensions = ext;
|
|
3364
|
+
}
|
|
3232
3365
|
const encrypted = encryptConfigSecrets2(decrypted, vault);
|
|
3233
3366
|
await atomicWrite5(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3234
3367
|
};
|
|
@@ -3450,7 +3583,7 @@ async function startWebUI(opts = {}) {
|
|
|
3450
3583
|
inputCost,
|
|
3451
3584
|
outputCost,
|
|
3452
3585
|
cacheReadCost,
|
|
3453
|
-
projectName:
|
|
3586
|
+
projectName: path8.basename(projectRoot) || projectRoot,
|
|
3454
3587
|
projectRoot,
|
|
3455
3588
|
cwd: workingDir,
|
|
3456
3589
|
mode: modeId,
|
|
@@ -3531,7 +3664,7 @@ async function startWebUI(opts = {}) {
|
|
|
3531
3664
|
console.warn(JSON.stringify({
|
|
3532
3665
|
level: "warn",
|
|
3533
3666
|
event: "webui.session_start_payload_failed",
|
|
3534
|
-
message:
|
|
3667
|
+
message: toErrorMessage5(err),
|
|
3535
3668
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3536
3669
|
}));
|
|
3537
3670
|
});
|
|
@@ -3568,7 +3701,7 @@ async function startWebUI(opts = {}) {
|
|
|
3568
3701
|
console.error(JSON.stringify({
|
|
3569
3702
|
level: "error",
|
|
3570
3703
|
event: "webui.ws_message_parse_failed",
|
|
3571
|
-
message:
|
|
3704
|
+
message: toErrorMessage5(err),
|
|
3572
3705
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3573
3706
|
}));
|
|
3574
3707
|
}
|
|
@@ -3577,8 +3710,8 @@ async function startWebUI(opts = {}) {
|
|
|
3577
3710
|
clients.delete(ws);
|
|
3578
3711
|
rateLimits.delete(String(ws));
|
|
3579
3712
|
if (pendingConfirms.size > 0) {
|
|
3580
|
-
for (const [id,
|
|
3581
|
-
|
|
3713
|
+
for (const [id, resolve5] of pendingConfirms) {
|
|
3714
|
+
resolve5("no");
|
|
3582
3715
|
pendingConfirms.delete(id);
|
|
3583
3716
|
}
|
|
3584
3717
|
}
|
|
@@ -3614,7 +3747,7 @@ async function startWebUI(opts = {}) {
|
|
|
3614
3747
|
level: "error",
|
|
3615
3748
|
event: "webui.ws_server_error",
|
|
3616
3749
|
host: wsHost,
|
|
3617
|
-
message:
|
|
3750
|
+
message: toErrorMessage5(err),
|
|
3618
3751
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3619
3752
|
}));
|
|
3620
3753
|
});
|
|
@@ -3642,29 +3775,29 @@ async function startWebUI(opts = {}) {
|
|
|
3642
3775
|
});
|
|
3643
3776
|
}
|
|
3644
3777
|
async function touchProjectEntry(root, workDir) {
|
|
3645
|
-
const resolved =
|
|
3778
|
+
const resolved = path8.resolve(root);
|
|
3646
3779
|
const manifest = await loadManifest(globalConfigPath);
|
|
3647
3780
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3648
|
-
const existing = manifest.projects.find((p) =>
|
|
3781
|
+
const existing = manifest.projects.find((p) => path8.resolve(p.root) === resolved);
|
|
3649
3782
|
if (existing) {
|
|
3650
3783
|
existing.lastSeen = now;
|
|
3651
|
-
if (workDir) existing.lastWorkingDir =
|
|
3784
|
+
if (workDir) existing.lastWorkingDir = path8.resolve(workDir);
|
|
3652
3785
|
} else {
|
|
3653
3786
|
manifest.projects.push({
|
|
3654
|
-
name:
|
|
3787
|
+
name: path8.basename(resolved),
|
|
3655
3788
|
root: resolved,
|
|
3656
3789
|
slug: generateProjectSlug(resolved),
|
|
3657
3790
|
createdAt: now,
|
|
3658
3791
|
lastSeen: now,
|
|
3659
|
-
lastWorkingDir: workDir ?
|
|
3792
|
+
lastWorkingDir: workDir ? path8.resolve(workDir) : void 0
|
|
3660
3793
|
});
|
|
3661
3794
|
}
|
|
3662
3795
|
await saveManifest(manifest, globalConfigPath);
|
|
3663
3796
|
await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
|
|
3664
3797
|
}
|
|
3665
3798
|
function projectsJsonPath(globalConfigPath2) {
|
|
3666
|
-
const base =
|
|
3667
|
-
return
|
|
3799
|
+
const base = path8.dirname(globalConfigPath2);
|
|
3800
|
+
return path8.join(base, "projects.json");
|
|
3668
3801
|
}
|
|
3669
3802
|
async function loadManifest(globalConfigPath2) {
|
|
3670
3803
|
try {
|
|
@@ -3677,15 +3810,15 @@ async function startWebUI(opts = {}) {
|
|
|
3677
3810
|
}
|
|
3678
3811
|
async function saveManifest(manifest, globalConfigPath2) {
|
|
3679
3812
|
const file = projectsJsonPath(globalConfigPath2);
|
|
3680
|
-
await fs7.mkdir(
|
|
3813
|
+
await fs7.mkdir(path8.dirname(file), { recursive: true });
|
|
3681
3814
|
await fs7.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
3682
3815
|
}
|
|
3683
3816
|
function generateProjectSlug(rootPath) {
|
|
3684
3817
|
return projectSlug(rootPath);
|
|
3685
3818
|
}
|
|
3686
3819
|
async function ensureProjectDataDir(slug, globalConfigPath2) {
|
|
3687
|
-
const base =
|
|
3688
|
-
const dir =
|
|
3820
|
+
const base = path8.dirname(globalConfigPath2);
|
|
3821
|
+
const dir = path8.join(base, "projects", slug);
|
|
3689
3822
|
await fs7.mkdir(dir, { recursive: true });
|
|
3690
3823
|
return dir;
|
|
3691
3824
|
}
|
|
@@ -3697,7 +3830,9 @@ async function startWebUI(opts = {}) {
|
|
|
3697
3830
|
case "collab.join":
|
|
3698
3831
|
case "collab.leave":
|
|
3699
3832
|
case "collab.annotate":
|
|
3700
|
-
case "collab.resolve":
|
|
3833
|
+
case "collab.resolve":
|
|
3834
|
+
case "collab.request_pause":
|
|
3835
|
+
case "collab.resume": {
|
|
3701
3836
|
collabHandler.handleMessage(ws, msg);
|
|
3702
3837
|
return;
|
|
3703
3838
|
}
|
|
@@ -3748,10 +3883,10 @@ async function startWebUI(opts = {}) {
|
|
|
3748
3883
|
}
|
|
3749
3884
|
case "tool.confirm_result": {
|
|
3750
3885
|
const { id, decision } = msg.payload;
|
|
3751
|
-
const
|
|
3752
|
-
if (
|
|
3886
|
+
const resolve5 = pendingConfirms.get(id);
|
|
3887
|
+
if (resolve5) {
|
|
3753
3888
|
pendingConfirms.delete(id);
|
|
3754
|
-
|
|
3889
|
+
resolve5(decision);
|
|
3755
3890
|
}
|
|
3756
3891
|
break;
|
|
3757
3892
|
}
|
|
@@ -4036,7 +4171,7 @@ async function startWebUI(opts = {}) {
|
|
|
4036
4171
|
console.warn(JSON.stringify({
|
|
4037
4172
|
level: "warn",
|
|
4038
4173
|
event: "webui.config_save_failed",
|
|
4039
|
-
message:
|
|
4174
|
+
message: toErrorMessage5(err),
|
|
4040
4175
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4041
4176
|
}));
|
|
4042
4177
|
}
|
|
@@ -4134,6 +4269,26 @@ async function startWebUI(opts = {}) {
|
|
|
4134
4269
|
await providerHandlers.handleProviderRemove(ws, providerId);
|
|
4135
4270
|
break;
|
|
4136
4271
|
}
|
|
4272
|
+
case "provider.clear_models": {
|
|
4273
|
+
const { providerId } = msg.payload;
|
|
4274
|
+
await providerHandlers.handleProviderClearModels(ws, providerId);
|
|
4275
|
+
break;
|
|
4276
|
+
}
|
|
4277
|
+
case "provider.undo_clear": {
|
|
4278
|
+
const { providerId, previousModels } = msg.payload;
|
|
4279
|
+
await providerHandlers.handleProviderUndoClear(ws, providerId, previousModels);
|
|
4280
|
+
break;
|
|
4281
|
+
}
|
|
4282
|
+
case "provider.update": {
|
|
4283
|
+
const p = msg.payload;
|
|
4284
|
+
await providerHandlers.handleProviderUpdate(ws, p);
|
|
4285
|
+
break;
|
|
4286
|
+
}
|
|
4287
|
+
case "provider.probe": {
|
|
4288
|
+
const { providerId, timeoutMs } = msg.payload;
|
|
4289
|
+
await providerHandlers.handleProviderProbe(ws, providerId, timeoutMs);
|
|
4290
|
+
break;
|
|
4291
|
+
}
|
|
4137
4292
|
case "sessions.list": {
|
|
4138
4293
|
const limit = msg.payload?.limit ?? 50;
|
|
4139
4294
|
try {
|
|
@@ -4422,6 +4577,75 @@ async function startWebUI(opts = {}) {
|
|
|
4422
4577
|
}
|
|
4423
4578
|
break;
|
|
4424
4579
|
}
|
|
4580
|
+
case "todo.update": {
|
|
4581
|
+
const payload = msg.payload;
|
|
4582
|
+
const idx = context.todos.findIndex((t) => t.id === payload.id);
|
|
4583
|
+
if (idx === -1) {
|
|
4584
|
+
sendResult(ws, false, "Todo not found");
|
|
4585
|
+
break;
|
|
4586
|
+
}
|
|
4587
|
+
const next = [...context.todos];
|
|
4588
|
+
const existing = expectDefined2(next[idx]);
|
|
4589
|
+
next[idx] = {
|
|
4590
|
+
...existing,
|
|
4591
|
+
status: payload.status ?? existing.status,
|
|
4592
|
+
activeForm: payload.activeForm !== void 0 ? payload.activeForm : existing.activeForm
|
|
4593
|
+
};
|
|
4594
|
+
context.state.replaceTodos(next);
|
|
4595
|
+
sendResult(ws, true, `Todo "${existing.content}" updated`);
|
|
4596
|
+
broadcast(clients, { type: "todos.updated", payload: { todos: next } });
|
|
4597
|
+
break;
|
|
4598
|
+
}
|
|
4599
|
+
case "task.update": {
|
|
4600
|
+
const payload = msg.payload;
|
|
4601
|
+
const taskPath = context.meta["task.path"];
|
|
4602
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
4603
|
+
sendResult(ws, false, "Task storage not configured.");
|
|
4604
|
+
break;
|
|
4605
|
+
}
|
|
4606
|
+
try {
|
|
4607
|
+
const { mutateTasks } = await import("@wrongstack/core");
|
|
4608
|
+
const file = await mutateTasks(taskPath, session.id, async (f) => {
|
|
4609
|
+
const task = f.tasks.find((t) => t.id === payload.id);
|
|
4610
|
+
if (!task) return f;
|
|
4611
|
+
task.status = payload.status;
|
|
4612
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4613
|
+
return f;
|
|
4614
|
+
});
|
|
4615
|
+
sendResult(ws, true, `Task status updated to "${payload.status}".`);
|
|
4616
|
+
broadcast(clients, { type: "tasks.updated", payload: { tasks: file.tasks } });
|
|
4617
|
+
} catch (err) {
|
|
4618
|
+
sendResult(ws, false, errMessage(err));
|
|
4619
|
+
}
|
|
4620
|
+
break;
|
|
4621
|
+
}
|
|
4622
|
+
case "plan.item.update": {
|
|
4623
|
+
const payload = msg.payload;
|
|
4624
|
+
const planPath = context.meta["plan.path"];
|
|
4625
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
4626
|
+
sendResult(ws, false, "Plan storage is not configured for this session.");
|
|
4627
|
+
break;
|
|
4628
|
+
}
|
|
4629
|
+
try {
|
|
4630
|
+
const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
4631
|
+
let changed = false;
|
|
4632
|
+
const plan = await mutatePlan(planPath, session.id, async (p) => {
|
|
4633
|
+
const before = p.updatedAt;
|
|
4634
|
+
const updated = setPlanItemStatus(p, payload.target, payload.status);
|
|
4635
|
+
changed = updated.updatedAt !== before;
|
|
4636
|
+
return updated;
|
|
4637
|
+
});
|
|
4638
|
+
if (!changed) {
|
|
4639
|
+
sendResult(ws, false, `No plan item matched "${payload.target}".`);
|
|
4640
|
+
break;
|
|
4641
|
+
}
|
|
4642
|
+
sendResult(ws, true, `Plan item status updated to "${payload.status}".`);
|
|
4643
|
+
broadcast(clients, { type: "plan.updated", payload: { plan } });
|
|
4644
|
+
} catch (err) {
|
|
4645
|
+
sendResult(ws, false, errMessage(err));
|
|
4646
|
+
}
|
|
4647
|
+
break;
|
|
4648
|
+
}
|
|
4425
4649
|
// ── File operations — delegated to shared handlers (file-handlers.ts) ──
|
|
4426
4650
|
// These handlers are also used by the CLI's webui-server.ts. When
|
|
4427
4651
|
// adding or modifying file-operation WebSocket messages, update
|
|
@@ -4569,9 +4793,40 @@ async function startWebUI(opts = {}) {
|
|
|
4569
4793
|
}
|
|
4570
4794
|
break;
|
|
4571
4795
|
}
|
|
4796
|
+
case "git.info": {
|
|
4797
|
+
const cwd = projectRoot;
|
|
4798
|
+
const execFile = (cmd, args) => new Promise((resolve5) => {
|
|
4799
|
+
import("child_process").then(({ execFile: ef }) => {
|
|
4800
|
+
ef(cmd, args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
4801
|
+
resolve5(err ? "" : stdout.trim());
|
|
4802
|
+
});
|
|
4803
|
+
});
|
|
4804
|
+
});
|
|
4805
|
+
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
4806
|
+
execFile("git", ["branch", "--show-current"]),
|
|
4807
|
+
execFile("git", ["diff", "--stat"]),
|
|
4808
|
+
execFile("git", ["status", "--porcelain"]),
|
|
4809
|
+
execFile("git", ["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
|
|
4810
|
+
]);
|
|
4811
|
+
const branch = branchRaw || "(detached)";
|
|
4812
|
+
const diffMatch = /\+\s*(\d+)\s*deletion/i.exec(diffRaw);
|
|
4813
|
+
const addMatch = /(\d+)\s*insertion/i.exec(diffRaw) ?? /(\d+)\s*addition/i.exec(diffRaw);
|
|
4814
|
+
const delMatch = /\+\s*(\d+)\s*deletion/i.exec(diffRaw);
|
|
4815
|
+
const added = addMatch ? Number(addMatch[1]) : 0;
|
|
4816
|
+
const deleted = delMatch ? Number(delMatch[1]) : 0;
|
|
4817
|
+
const untracked = statusRaw.split("\n").filter((l) => l.startsWith("??")).length;
|
|
4818
|
+
const [aheadRaw, behindRaw] = (upstreamRaw || "0 0").split(" ");
|
|
4819
|
+
const ahead = Number(aheadRaw) || 0;
|
|
4820
|
+
const behind = Number(behindRaw) || 0;
|
|
4821
|
+
send(ws, {
|
|
4822
|
+
type: "git.info",
|
|
4823
|
+
payload: { branch, added, deleted, untracked, ahead, behind }
|
|
4824
|
+
});
|
|
4825
|
+
break;
|
|
4826
|
+
}
|
|
4572
4827
|
case "goal.get": {
|
|
4573
4828
|
try {
|
|
4574
|
-
const goalPath =
|
|
4829
|
+
const goalPath = path8.join(projectRoot, ".wrongstack", "goal.json");
|
|
4575
4830
|
const raw = await fs7.readFile(goalPath, "utf8");
|
|
4576
4831
|
const goal = JSON.parse(raw);
|
|
4577
4832
|
broadcast(clients, { type: "goal.updated", payload: goal });
|
|
@@ -4632,7 +4887,7 @@ async function startWebUI(opts = {}) {
|
|
|
4632
4887
|
try {
|
|
4633
4888
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4634
4889
|
const rewinder = new DefaultSessionRewinder(
|
|
4635
|
-
|
|
4890
|
+
path8.join(projectRoot, ".wrongstack", "sessions"),
|
|
4636
4891
|
projectRoot
|
|
4637
4892
|
);
|
|
4638
4893
|
const checkpoints = await rewinder.listCheckpoints(session.id);
|
|
@@ -4653,7 +4908,7 @@ async function startWebUI(opts = {}) {
|
|
|
4653
4908
|
try {
|
|
4654
4909
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
4655
4910
|
const rewinder = new DefaultSessionRewinder(
|
|
4656
|
-
|
|
4911
|
+
path8.join(projectRoot, ".wrongstack", "sessions"),
|
|
4657
4912
|
projectRoot
|
|
4658
4913
|
);
|
|
4659
4914
|
await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
|
|
@@ -4687,7 +4942,7 @@ async function startWebUI(opts = {}) {
|
|
|
4687
4942
|
case "projects.add": {
|
|
4688
4943
|
const { root: addRoot, name: displayName } = msg.payload;
|
|
4689
4944
|
try {
|
|
4690
|
-
const resolved =
|
|
4945
|
+
const resolved = path8.resolve(addRoot);
|
|
4691
4946
|
await fs7.access(resolved);
|
|
4692
4947
|
const stat2 = await fs7.stat(resolved);
|
|
4693
4948
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
@@ -4705,7 +4960,7 @@ async function startWebUI(opts = {}) {
|
|
|
4705
4960
|
});
|
|
4706
4961
|
break;
|
|
4707
4962
|
}
|
|
4708
|
-
const name = displayName?.trim() ||
|
|
4963
|
+
const name = displayName?.trim() || path8.basename(resolved);
|
|
4709
4964
|
const slug = generateProjectSlug(resolved);
|
|
4710
4965
|
await ensureProjectDataDir(slug, globalConfigPath);
|
|
4711
4966
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4724,7 +4979,7 @@ async function startWebUI(opts = {}) {
|
|
|
4724
4979
|
send(ws, {
|
|
4725
4980
|
type: "projects.added",
|
|
4726
4981
|
payload: {
|
|
4727
|
-
name:
|
|
4982
|
+
name: path8.basename(addRoot),
|
|
4728
4983
|
root: addRoot,
|
|
4729
4984
|
slug: "",
|
|
4730
4985
|
message: errMessage(err)
|
|
@@ -4736,7 +4991,7 @@ async function startWebUI(opts = {}) {
|
|
|
4736
4991
|
case "projects.select": {
|
|
4737
4992
|
const { root: selRoot, name: selName } = msg.payload;
|
|
4738
4993
|
try {
|
|
4739
|
-
const resolved =
|
|
4994
|
+
const resolved = path8.resolve(selRoot);
|
|
4740
4995
|
try {
|
|
4741
4996
|
await fs7.access(resolved);
|
|
4742
4997
|
const stat2 = await fs7.stat(resolved);
|
|
@@ -4746,7 +5001,7 @@ async function startWebUI(opts = {}) {
|
|
|
4746
5001
|
type: "projects.selected",
|
|
4747
5002
|
payload: {
|
|
4748
5003
|
root: selRoot,
|
|
4749
|
-
name: selName ||
|
|
5004
|
+
name: selName || path8.basename(selRoot),
|
|
4750
5005
|
message: `Cannot switch: ${errMessage(err)}`
|
|
4751
5006
|
}
|
|
4752
5007
|
});
|
|
@@ -4758,7 +5013,7 @@ async function startWebUI(opts = {}) {
|
|
|
4758
5013
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
4759
5014
|
entry.lastWorkingDir = resolved;
|
|
4760
5015
|
} else {
|
|
4761
|
-
const name = selName?.trim() ||
|
|
5016
|
+
const name = selName?.trim() || path8.basename(resolved);
|
|
4762
5017
|
const slug = generateProjectSlug(resolved);
|
|
4763
5018
|
manifest.projects.push({
|
|
4764
5019
|
name,
|
|
@@ -4799,8 +5054,8 @@ async function startWebUI(opts = {}) {
|
|
|
4799
5054
|
});
|
|
4800
5055
|
} catch {
|
|
4801
5056
|
}
|
|
4802
|
-
const newSessionsDir =
|
|
4803
|
-
|
|
5057
|
+
const newSessionsDir = path8.join(
|
|
5058
|
+
path8.dirname(globalConfigPath),
|
|
4804
5059
|
"projects",
|
|
4805
5060
|
switchSlug,
|
|
4806
5061
|
"sessions"
|
|
@@ -4837,7 +5092,7 @@ async function startWebUI(opts = {}) {
|
|
|
4837
5092
|
sessionId: session.id,
|
|
4838
5093
|
projectSlug: switchSlug,
|
|
4839
5094
|
projectRoot,
|
|
4840
|
-
projectName:
|
|
5095
|
+
projectName: path8.basename(projectRoot),
|
|
4841
5096
|
workingDir,
|
|
4842
5097
|
pid: process.pid,
|
|
4843
5098
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4848,8 +5103,8 @@ async function startWebUI(opts = {}) {
|
|
|
4848
5103
|
type: "projects.selected",
|
|
4849
5104
|
payload: {
|
|
4850
5105
|
root: resolved,
|
|
4851
|
-
name: selName ||
|
|
4852
|
-
message: `Switched to ${selName ||
|
|
5106
|
+
name: selName || path8.basename(resolved),
|
|
5107
|
+
message: `Switched to ${selName || path8.basename(resolved)}`
|
|
4853
5108
|
}
|
|
4854
5109
|
});
|
|
4855
5110
|
broadcast(clients, {
|
|
@@ -4872,7 +5127,7 @@ async function startWebUI(opts = {}) {
|
|
|
4872
5127
|
type: "projects.selected",
|
|
4873
5128
|
payload: {
|
|
4874
5129
|
root: selRoot,
|
|
4875
|
-
name: selName ||
|
|
5130
|
+
name: selName || path8.basename(selRoot),
|
|
4876
5131
|
message: errMessage(err)
|
|
4877
5132
|
}
|
|
4878
5133
|
});
|
|
@@ -4883,8 +5138,8 @@ async function startWebUI(opts = {}) {
|
|
|
4883
5138
|
case "working_dir.set": {
|
|
4884
5139
|
const { path: newPath } = msg.payload;
|
|
4885
5140
|
try {
|
|
4886
|
-
const resolved =
|
|
4887
|
-
if (!resolved.startsWith(projectRoot +
|
|
5141
|
+
const resolved = path8.resolve(projectRoot, newPath);
|
|
5142
|
+
if (!resolved.startsWith(projectRoot + path8.sep) && resolved !== projectRoot) {
|
|
4888
5143
|
sendResult(ws, false, `Path must stay inside the project root: ${projectRoot}`);
|
|
4889
5144
|
break;
|
|
4890
5145
|
}
|
|
@@ -4921,19 +5176,25 @@ async function startWebUI(opts = {}) {
|
|
|
4921
5176
|
case "mailbox.messages":
|
|
4922
5177
|
return handleMailboxMessages(
|
|
4923
5178
|
ws,
|
|
4924
|
-
{ projectRoot, globalRoot:
|
|
5179
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
4925
5180
|
msg.payload
|
|
4926
5181
|
);
|
|
4927
5182
|
case "mailbox.agents":
|
|
4928
5183
|
return handleMailboxAgents(
|
|
4929
5184
|
ws,
|
|
4930
|
-
{ projectRoot, globalRoot:
|
|
5185
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
4931
5186
|
msg.payload
|
|
4932
5187
|
);
|
|
4933
5188
|
case "mailbox.clear":
|
|
4934
5189
|
return handleMailboxClear(
|
|
4935
5190
|
ws,
|
|
4936
|
-
{ projectRoot, globalRoot:
|
|
5191
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) }
|
|
5192
|
+
);
|
|
5193
|
+
case "mailbox.purge":
|
|
5194
|
+
return handleMailboxPurge(
|
|
5195
|
+
ws,
|
|
5196
|
+
{ projectRoot, globalRoot: path8.dirname(globalConfigPath) },
|
|
5197
|
+
msg.payload
|
|
4937
5198
|
);
|
|
4938
5199
|
// ── Brain — status, autonomy ceiling, direct decision support ───
|
|
4939
5200
|
case "brain.status":
|
|
@@ -5001,12 +5262,12 @@ async function startWebUI(opts = {}) {
|
|
|
5001
5262
|
});
|
|
5002
5263
|
const httpServer = createHttpServer({
|
|
5003
5264
|
host: wsHost,
|
|
5004
|
-
distDir:
|
|
5265
|
+
distDir: path8.resolve(import.meta.dirname, "../../dist"),
|
|
5005
5266
|
wsPort,
|
|
5006
5267
|
globalRoot: wpaths.globalRoot,
|
|
5007
5268
|
apiToken: wsToken
|
|
5008
5269
|
});
|
|
5009
|
-
const registryBaseDir =
|
|
5270
|
+
const registryBaseDir = path8.dirname(globalConfigPath);
|
|
5010
5271
|
httpServer.listen(httpPort, wsHost, () => {
|
|
5011
5272
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
5012
5273
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -5018,7 +5279,7 @@ async function startWebUI(opts = {}) {
|
|
|
5018
5279
|
wsPort,
|
|
5019
5280
|
host: wsHost,
|
|
5020
5281
|
projectRoot,
|
|
5021
|
-
projectName:
|
|
5282
|
+
projectName: path8.basename(projectRoot) || projectRoot,
|
|
5022
5283
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5023
5284
|
url: `http://${wsHost}:${httpPort}`
|
|
5024
5285
|
},
|