@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.
@@ -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 path9 from "path";
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 as Buffer2 } from "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 = Buffer2.from(provided);
47
- const b = Buffer2.from(expected);
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: ${err instanceof Error ? err.message : String(err)}`);
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: ${err instanceof Error ? err.message : String(err)}`);
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}: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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}: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`);
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 * as path3 from "path";
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
- to: payload?.agentId,
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 path4 from "path";
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 path4.join(os.homedir(), ".wrongstack");
1963
+ return path3.join(os.homedir(), ".wrongstack");
1963
1964
  }
1964
1965
  function registryPath(baseDir = defaultBaseDir()) {
1965
- return path4.join(baseDir, "webui-instances.json");
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((resolve6) => {
2038
+ return new Promise((resolve5) => {
2038
2039
  const srv = net.createServer();
2039
- srv.once("error", () => resolve6(false));
2040
+ srv.once("error", () => resolve5(false));
2040
2041
  srv.once("listening", () => {
2041
- srv.close(() => resolve6(true));
2042
+ srv.close(() => resolve5(true));
2042
2043
  });
2043
2044
  try {
2044
2045
  srv.listen(port, host);
2045
2046
  } catch {
2046
- resolve6(false);
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 path5 from "path";
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 = path5.join(path5.dirname(configPath), ".key");
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 = err instanceof Error ? err.message : String(err);
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
- broadcast2(clients, {
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
- return { handleKeyUpsert, handleKeyDelete, handleKeySetActive, handleProviderAdd, handleProviderRemove, loadConfigProviders };
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 path6 from "path";
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 ? path6.dirname(globalConfigPath) : void 0;
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 path7 from "path";
2721
+ import * as path6 from "path";
2615
2722
  var STORE_FILENAME = "custom-context-modes.json";
2616
2723
  function storePath(wrongstackDir) {
2617
- return path7.join(wrongstackDir, STORE_FILENAME);
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 path8 from "path";
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 = path8.resolve(req.path);
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: err instanceof Error ? err.message : String(err),
3030
+ message: toErrorMessage5(err),
2924
3031
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2925
3032
  }));
2926
3033
  }
@@ -2949,7 +3056,7 @@ async function startWebUI(opts = {}) {
2949
3056
  }).catch(() => void 0);
2950
3057
  }
2951
3058
  const sessionReader = new DefaultSessionReader({ store: sessionStore });
2952
- const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions });
3059
+ const annotationsStore = new AnnotationsStore({ dir: wpaths.projectSessions, events });
2953
3060
  let session = await sessionStore.create({
2954
3061
  id: "",
2955
3062
  title: "",
@@ -2969,7 +3076,7 @@ async function startWebUI(opts = {}) {
2969
3076
  sessionId: session.id,
2970
3077
  projectSlug: wpaths.projectSlug,
2971
3078
  projectRoot,
2972
- projectName: path9.basename(projectRoot),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: path9.basename(projectRoot) || projectRoot,
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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, resolve6] of pendingConfirms) {
3588
- resolve6("no");
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: err instanceof Error ? err.message : String(err),
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 = path9.resolve(root);
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) => path9.resolve(p.root) === resolved);
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 = path9.resolve(workDir);
3791
+ if (workDir) existing.lastWorkingDir = path8.resolve(workDir);
3659
3792
  } else {
3660
3793
  manifest.projects.push({
3661
- name: path9.basename(resolved),
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 ? path9.resolve(workDir) : void 0
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 = path9.dirname(globalConfigPath2);
3674
- return path9.join(base, "projects.json");
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(path9.dirname(file), { recursive: true });
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 = path9.dirname(globalConfigPath2);
3695
- const dir = path9.join(base, "projects", slug);
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 resolve6 = pendingConfirms.get(id);
3759
- if (resolve6) {
3893
+ const resolve5 = pendingConfirms.get(id);
3894
+ if (resolve5) {
3760
3895
  pendingConfirms.delete(id);
3761
- resolve6(decision);
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: err instanceof Error ? err.message : String(err),
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 = path9.join(projectRoot, ".wrongstack", "goal.json");
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
- path9.join(projectRoot, ".wrongstack", "sessions"),
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
- path9.join(projectRoot, ".wrongstack", "sessions"),
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 = path9.resolve(addRoot);
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() || path9.basename(resolved);
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: path9.basename(addRoot),
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 = path9.resolve(selRoot);
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 || path9.basename(selRoot),
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() || path9.basename(resolved);
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 = path9.join(
4810
- path9.dirname(globalConfigPath),
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: path9.basename(projectRoot),
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 || path9.basename(resolved),
4859
- message: `Switched to ${selName || path9.basename(resolved)}`
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 || path9.basename(selRoot),
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 = path9.resolve(projectRoot, newPath);
4894
- if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== 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: path9.dirname(globalConfigPath) },
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: path9.dirname(globalConfigPath) },
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: path9.dirname(globalConfigPath) }
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: path9.resolve(import.meta.dirname, "../../dist"),
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 = path9.dirname(globalConfigPath);
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: path9.basename(projectRoot) || projectRoot,
5289
+ projectName: path8.basename(projectRoot) || projectRoot,
5029
5290
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5030
5291
  url: `http://${wsHost}:${httpPort}`
5031
5292
  },