@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.
@@ -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 path9 from "path";
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 as Buffer2 } from "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 = Buffer2.from(provided);
48
- const b = Buffer2.from(expected);
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: ${err instanceof Error ? err.message : String(err)}`);
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: ${err instanceof Error ? err.message : String(err)}`);
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}: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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}: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`);
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 * as path3 from "path";
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
- to: payload?.agentId,
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 path4 from "path";
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 path4.join(os.homedir(), ".wrongstack");
1964
+ return path3.join(os.homedir(), ".wrongstack");
1964
1965
  }
1965
1966
  function registryPath(baseDir = defaultBaseDir()) {
1966
- return path4.join(baseDir, "webui-instances.json");
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((resolve6) => {
2039
+ return new Promise((resolve5) => {
2039
2040
  const srv = net.createServer();
2040
- srv.once("error", () => resolve6(false));
2041
+ srv.once("error", () => resolve5(false));
2041
2042
  srv.once("listening", () => {
2042
- srv.close(() => resolve6(true));
2043
+ srv.close(() => resolve5(true));
2043
2044
  });
2044
2045
  try {
2045
2046
  srv.listen(port, host);
2046
2047
  } catch {
2047
- resolve6(false);
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 path5 from "path";
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 = err instanceof Error ? err.message : String(err);
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
- broadcast2(clients, {
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
- return { handleKeyUpsert, handleKeyDelete, handleKeySetActive, handleProviderAdd, handleProviderRemove, loadConfigProviders };
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 path6 from "path";
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 ? path6.dirname(globalConfigPath) : void 0;
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 path7 from "path";
2714
+ import * as path6 from "path";
2608
2715
  var STORE_FILENAME = "custom-context-modes.json";
2609
2716
  function storePath(wrongstackDir) {
2610
- return path7.join(wrongstackDir, STORE_FILENAME);
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 path8 from "path";
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 = path8.resolve(req.path);
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: err instanceof Error ? err.message : String(err),
3023
+ message: toErrorMessage5(err),
2917
3024
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2918
3025
  }));
2919
3026
  }
@@ -2962,7 +3069,7 @@ async function startWebUI(opts = {}) {
2962
3069
  sessionId: session.id,
2963
3070
  projectSlug: wpaths.projectSlug,
2964
3071
  projectRoot,
2965
- projectName: path9.basename(projectRoot),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: path9.basename(projectRoot) || projectRoot,
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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, resolve6] of pendingConfirms) {
3581
- resolve6("no");
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: err instanceof Error ? err.message : String(err),
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 = path9.resolve(root);
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) => path9.resolve(p.root) === resolved);
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 = path9.resolve(workDir);
3784
+ if (workDir) existing.lastWorkingDir = path8.resolve(workDir);
3652
3785
  } else {
3653
3786
  manifest.projects.push({
3654
- name: path9.basename(resolved),
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 ? path9.resolve(workDir) : void 0
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 = path9.dirname(globalConfigPath2);
3667
- return path9.join(base, "projects.json");
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(path9.dirname(file), { recursive: true });
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 = path9.dirname(globalConfigPath2);
3688
- const dir = path9.join(base, "projects", slug);
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 resolve6 = pendingConfirms.get(id);
3752
- if (resolve6) {
3886
+ const resolve5 = pendingConfirms.get(id);
3887
+ if (resolve5) {
3753
3888
  pendingConfirms.delete(id);
3754
- resolve6(decision);
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: err instanceof Error ? err.message : String(err),
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 = path9.join(projectRoot, ".wrongstack", "goal.json");
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
- path9.join(projectRoot, ".wrongstack", "sessions"),
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
- path9.join(projectRoot, ".wrongstack", "sessions"),
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 = path9.resolve(addRoot);
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() || path9.basename(resolved);
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: path9.basename(addRoot),
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 = path9.resolve(selRoot);
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 || path9.basename(selRoot),
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() || path9.basename(resolved);
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 = path9.join(
4803
- path9.dirname(globalConfigPath),
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: path9.basename(projectRoot),
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 || path9.basename(resolved),
4852
- message: `Switched to ${selName || path9.basename(resolved)}`
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 || path9.basename(selRoot),
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 = path9.resolve(projectRoot, newPath);
4887
- if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== 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: path9.dirname(globalConfigPath) },
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: path9.dirname(globalConfigPath) },
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: path9.dirname(globalConfigPath) }
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: path9.resolve(import.meta.dirname, "../../dist"),
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 = path9.dirname(globalConfigPath);
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: path9.basename(projectRoot) || projectRoot,
5282
+ projectName: path8.basename(projectRoot) || projectRoot,
5022
5283
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5023
5284
  url: `http://${wsHost}:${httpPort}`
5024
5285
  },