@wrongstack/webui 0.277.2 → 0.280.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.
Files changed (58) hide show
  1. package/dist/assets/activity-BEjzb11U.js +5 -0
  2. package/dist/assets/activity-ByEwzh5Q.js +5 -0
  3. package/dist/assets/activity-CVWYwjgf.js +5 -0
  4. package/dist/assets/activity-Cjm0UoMi.js +5 -0
  5. package/dist/assets/activity-CqiCPjKb.js +5 -0
  6. package/dist/assets/activity-DN6Bmnaw.js +5 -0
  7. package/dist/assets/chat-CdLXf1yN.js +1 -0
  8. package/dist/assets/chat-Cfq81NCh.js +1 -0
  9. package/dist/assets/chat-Cvy-F2d6.js +1 -0
  10. package/dist/assets/chat-DNIePdaQ.js +1 -0
  11. package/dist/assets/chat-DhofDrgA.js +1 -0
  12. package/dist/assets/chat-cxuX4gFa.js +1 -0
  13. package/dist/assets/commandPalette-2M1hYRuH.js +1 -0
  14. package/dist/assets/commandPalette-BE_ljQwl.js +1 -0
  15. package/dist/assets/commandPalette-BYf22WAi.js +1 -0
  16. package/dist/assets/commandPalette-BZhacjvy.js +1 -0
  17. package/dist/assets/commandPalette-CTE9ITWg.js +1 -0
  18. package/dist/assets/commandPalette-RxWSjQIg.js +1 -0
  19. package/dist/assets/common-BAg8_MKg.js +1 -0
  20. package/dist/assets/common-BH1mb9Im.js +1 -0
  21. package/dist/assets/common-C29pCVfm.js +1 -0
  22. package/dist/assets/common-CrGjglH4.js +1 -0
  23. package/dist/assets/common-KzRPhobM.js +1 -0
  24. package/dist/assets/common-tmj0IcQS.js +1 -0
  25. package/dist/assets/index-BMDGZBnY.css +2 -0
  26. package/dist/assets/index-Ci_SxQGe.js +157 -0
  27. package/dist/assets/settings-BVvx94WK.js +1 -0
  28. package/dist/assets/settings-CQ4x-iFD.js +1 -0
  29. package/dist/assets/settings-CmHCQRVI.js +1 -0
  30. package/dist/assets/settings-DgGu17XI.js +1 -0
  31. package/dist/assets/settings-OOQ-99Tn.js +1 -0
  32. package/dist/assets/settings-TqprB9wO.js +1 -0
  33. package/dist/assets/setup-B5TfeR6q.js +1 -0
  34. package/dist/assets/setup-BBwNpiE3.js +1 -0
  35. package/dist/assets/setup-BT2iTMm4.js +1 -0
  36. package/dist/assets/setup-CVMjqrZh.js +1 -0
  37. package/dist/assets/setup-DOXm3hFc.js +1 -0
  38. package/dist/assets/setup-Dhb_xOpk.js +1 -0
  39. package/dist/assets/toasts-BCDGDcyq.js +1 -0
  40. package/dist/assets/toasts-BwQJwYLe.js +1 -0
  41. package/dist/assets/toasts-CFzweRH5.js +1 -0
  42. package/dist/assets/toasts-DDYQsJV8.js +1 -0
  43. package/dist/assets/toasts-Dm12Nq8t.js +1 -0
  44. package/dist/assets/toasts-frBt6fvE.js +1 -0
  45. package/dist/assets/{vendor-C604QguY.js → vendor-B_dTeUDT.js} +252 -252
  46. package/dist/index.html +3 -3
  47. package/dist/index.js +26536 -8227
  48. package/dist/index.js.map +1 -1
  49. package/dist/server/entry.js +875 -582
  50. package/dist/server/entry.js.map +1 -1
  51. package/dist/server/handlers.js.map +1 -1
  52. package/dist/server/index.d.ts +5 -0
  53. package/dist/server/index.js +870 -577
  54. package/dist/server/index.js.map +1 -1
  55. package/dist/types.d.ts +5 -2
  56. package/package.json +9 -6
  57. package/dist/assets/index-Bmo3qaVg.js +0 -141
  58. package/dist/assets/index-D8YeqUJA.css +0 -2
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // src/server/entry.ts
3
- import { ToolValidationError as ToolValidationError7 } from "@wrongstack/core";
3
+ import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core";
4
4
 
5
5
  // src/server/autophase-ws-handler.ts
6
6
  import { spawnSync } from "child_process";
@@ -1401,11 +1401,11 @@ async function handleDesignVerify(ws, ctx) {
1401
1401
  }
1402
1402
 
1403
1403
  // src/server/eternal-iteration-broadcast.ts
1404
- function createEternalSubscription(subscribe, broadcast3, clientsRef) {
1404
+ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
1405
1405
  let disposed = false;
1406
1406
  const dispose = subscribe((entry) => {
1407
1407
  if (disposed) return;
1408
- broadcast3(clientsRef(), {
1408
+ broadcast2(clientsRef(), {
1409
1409
  type: "eternal.iteration",
1410
1410
  payload: { entry }
1411
1411
  });
@@ -1477,21 +1477,21 @@ import * as fs3 from "fs/promises";
1477
1477
  import * as path4 from "path";
1478
1478
  import { ToolValidationError } from "@wrongstack/core";
1479
1479
  function isPathInside(root, target) {
1480
- const relative3 = path4.relative(root, target);
1481
- return relative3 === "" || !relative3.startsWith("..") && !path4.isAbsolute(relative3);
1480
+ const relative4 = path4.relative(root, target);
1481
+ return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
1482
1482
  }
1483
1483
  async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1484
1484
  const resolved = path4.resolve(projectRoot, inputPath);
1485
- let stat3;
1485
+ let stat2;
1486
1486
  try {
1487
- stat3 = await fs3.stat(resolved);
1487
+ stat2 = await fs3.stat(resolved);
1488
1488
  } catch {
1489
1489
  throw new ToolValidationError({
1490
1490
  message: `Directory not found or not accessible: ${resolved}`,
1491
1491
  field: "path"
1492
1492
  });
1493
1493
  }
1494
- if (!stat3.isDirectory()) {
1494
+ if (!stat2.isDirectory()) {
1495
1495
  throw new ToolValidationError({
1496
1496
  message: `Directory not found or not accessible: ${resolved}`,
1497
1497
  field: "path"
@@ -1745,9 +1745,9 @@ async function handleGitInfo(ws, projectRoot) {
1745
1745
  const cwd = projectRoot || void 0;
1746
1746
  try {
1747
1747
  const { execFile: ef } = await import("child_process");
1748
- const git = (args) => new Promise((resolve11) => {
1748
+ const git = (args) => new Promise((resolve10) => {
1749
1749
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1750
- resolve11(err ? "" : stdout.trim());
1750
+ resolve10(err ? "" : stdout.trim());
1751
1751
  });
1752
1752
  });
1753
1753
  const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
@@ -1773,12 +1773,12 @@ async function handleGitInfo(ws, projectRoot) {
1773
1773
  function makeGit(cwd) {
1774
1774
  return async (args) => {
1775
1775
  const { execFile: ef } = await import("child_process");
1776
- return new Promise((resolve11) => {
1776
+ return new Promise((resolve10) => {
1777
1777
  ef(
1778
1778
  "git",
1779
1779
  args,
1780
1780
  { cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
1781
- (err, stdout) => resolve11(err ? "" : stdout)
1781
+ (err, stdout) => resolve10(err ? "" : stdout)
1782
1782
  );
1783
1783
  });
1784
1784
  };
@@ -1802,15 +1802,15 @@ async function handleGitChanges(ws, projectRoot) {
1802
1802
  if (!m) continue;
1803
1803
  const added = m[1] === "-" ? 0 : Number(m[1]);
1804
1804
  const deleted = m[2] === "-" ? 0 : Number(m[2]);
1805
- let path21 = m[3] ?? "";
1806
- if (path21 === "") {
1805
+ let path23 = m[3] ?? "";
1806
+ if (path23 === "") {
1807
1807
  i += 1;
1808
- path21 = parts[i + 1] ?? parts[i] ?? "";
1808
+ path23 = parts[i + 1] ?? parts[i] ?? "";
1809
1809
  i += 1;
1810
1810
  }
1811
- if (!path21) continue;
1812
- const prev = counts.get(path21) ?? { added: 0, deleted: 0 };
1813
- counts.set(path21, { added: prev.added + added, deleted: prev.deleted + deleted });
1811
+ if (!path23) continue;
1812
+ const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
1813
+ counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
1814
1814
  }
1815
1815
  };
1816
1816
  parseNumstat(unstagedNumstat);
@@ -1822,7 +1822,7 @@ async function handleGitChanges(ws, projectRoot) {
1822
1822
  if (!rec || rec.length < 3) continue;
1823
1823
  const x = rec[0] ?? " ";
1824
1824
  const y = rec[1] ?? " ";
1825
- const path21 = rec.slice(3);
1825
+ const path23 = rec.slice(3);
1826
1826
  const isRename = x === "R" || x === "C" || y === "R" || y === "C";
1827
1827
  if (isRename) i += 1;
1828
1828
  let status;
@@ -1834,13 +1834,13 @@ async function handleGitChanges(ws, projectRoot) {
1834
1834
  else if (x === "D" || y === "D") status = "D";
1835
1835
  else status = "M";
1836
1836
  const staged = x !== " " && x !== "?";
1837
- let added = counts.get(path21)?.added ?? 0;
1838
- let deleted = counts.get(path21)?.deleted ?? 0;
1837
+ let added = counts.get(path23)?.added ?? 0;
1838
+ let deleted = counts.get(path23)?.deleted ?? 0;
1839
1839
  if (status === "?") {
1840
1840
  added = 0;
1841
1841
  deleted = 0;
1842
1842
  }
1843
- files.push({ path: path21, status, added, deleted, staged });
1843
+ files.push({ path: path23, status, added, deleted, staged });
1844
1844
  }
1845
1845
  send(ws, { type: "git.changes", payload: { files } });
1846
1846
  } catch (err) {
@@ -1851,22 +1851,22 @@ async function handleGitChanges(ws, projectRoot) {
1851
1851
  }
1852
1852
  }
1853
1853
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1854
- async function handleGitDiff(ws, projectRoot, path21) {
1854
+ async function handleGitDiff(ws, projectRoot, path23) {
1855
1855
  const cwd = projectRoot || void 0;
1856
- const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path21, ...extra } });
1857
- if (!path21 || path21.includes("\0") || path21.includes("..") || nodePath.isAbsolute(path21)) {
1856
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1857
+ if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
1858
1858
  reply({ oldText: "", newText: "", error: "invalid path" });
1859
1859
  return;
1860
1860
  }
1861
1861
  try {
1862
1862
  const git = makeGit(cwd);
1863
- const { readFile: readFile9 } = await import("fs/promises");
1863
+ const { readFile: readFile10 } = await import("fs/promises");
1864
1864
  const { join: join14 } = await import("path");
1865
- const oldText = await git(["show", `HEAD:${path21}`]);
1865
+ const oldText = await git(["show", `HEAD:${path23}`]);
1866
1866
  let newText = "";
1867
1867
  try {
1868
- const abs = cwd ? join14(cwd, path21) : path21;
1869
- const buf = await readFile9(abs);
1868
+ const abs = cwd ? join14(cwd, path23) : path23;
1869
+ const buf = await readFile10(abs);
1870
1870
  if (buf.includes(0)) {
1871
1871
  reply({ oldText: "", newText: "", binary: true });
1872
1872
  return;
@@ -2068,7 +2068,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2068
2068
  return;
2069
2069
  }
2070
2070
  try {
2071
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore4, DefaultSessionReader: DefaultSessionReader3 } = await import("@wrongstack/core");
2071
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2072
2072
  const registry = new SessionRegistry(globalRoot);
2073
2073
  const entry = await registry.get(sessionId);
2074
2074
  if (!entry) {
@@ -2076,9 +2076,9 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2076
2076
  res.end(JSON.stringify({ error: "Session not found" }));
2077
2077
  return;
2078
2078
  }
2079
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2080
- const store = new DefaultSessionStore4({ dir: paths.projectSessions });
2081
- const reader = new DefaultSessionReader3({ store });
2079
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2080
+ const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2081
+ const reader = new DefaultSessionReader2({ store });
2082
2082
  const rawEntries = [];
2083
2083
  for await (const ev of reader.replay(sessionId)) {
2084
2084
  const mapped = mapWatchEntry(ev);
@@ -2103,7 +2103,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2103
2103
  }
2104
2104
  }
2105
2105
  function readJsonBody(req) {
2106
- return new Promise((resolve11, reject) => {
2106
+ return new Promise((resolve10, reject) => {
2107
2107
  let data = "";
2108
2108
  req.on("data", (chunk) => {
2109
2109
  data += chunk;
@@ -2114,7 +2114,7 @@ function readJsonBody(req) {
2114
2114
  });
2115
2115
  req.on("end", () => {
2116
2116
  try {
2117
- resolve11(data ? JSON.parse(data) : {});
2117
+ resolve10(data ? JSON.parse(data) : {});
2118
2118
  } catch (err) {
2119
2119
  reject(err instanceof Error ? err : new Error(String(err)));
2120
2120
  }
@@ -2150,7 +2150,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2150
2150
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2151
2151
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2152
2152
  try {
2153
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2153
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2154
2154
  const registry = new SessionRegistry(globalRoot);
2155
2155
  const entry = await registry.get(sessionId);
2156
2156
  if (!entry) {
@@ -2158,9 +2158,9 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2158
2158
  res.end(JSON.stringify({ error: "Session not found" }));
2159
2159
  return;
2160
2160
  }
2161
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2162
- const mailbox = new GlobalMailbox5(paths.projectDir);
2163
- const to = `leader@${mailboxSessionTag3(sessionId)}`;
2161
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2162
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2163
+ const to = `leader@${mailboxSessionTag2(sessionId)}`;
2164
2164
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
2165
2165
  res.writeHead(200, { "Content-Type": "application/json" });
2166
2166
  res.end(JSON.stringify({ ok: true, id: sent.id, to, type, delivered: entry.status }));
@@ -2176,7 +2176,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2176
2176
  return;
2177
2177
  }
2178
2178
  try {
2179
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2179
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2180
2180
  const registry = new SessionRegistry(globalRoot);
2181
2181
  const entry = await registry.get(sessionId);
2182
2182
  if (!entry) {
@@ -2184,9 +2184,9 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2184
2184
  res.end(JSON.stringify({ error: "Session not found" }));
2185
2185
  return;
2186
2186
  }
2187
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2188
- const mailbox = new GlobalMailbox5(paths.projectDir);
2189
- const leaderAddr = `leader@${mailboxSessionTag3(sessionId)}`;
2187
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2188
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2189
+ const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2190
2190
  const [inbound, outbound] = await Promise.all([
2191
2191
  mailbox.query({ to: leaderAddr, limit: 50 }),
2192
2192
  mailbox.query({ from: leaderAddr, limit: 50 })
@@ -2234,7 +2234,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2234
2234
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2235
2235
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2236
2236
  try {
2237
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2237
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2238
2238
  const registry = new SessionRegistry(globalRoot);
2239
2239
  const entry = await registry.get(sessionId);
2240
2240
  if (!entry) {
@@ -2242,9 +2242,9 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2242
2242
  res.end(JSON.stringify({ error: "Session not found" }));
2243
2243
  return;
2244
2244
  }
2245
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2246
- const mailbox = new GlobalMailbox5(paths.projectDir);
2247
- const to = `leader@${mailboxSessionTag3(sessionId)}`;
2245
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2246
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2247
+ const to = `leader@${mailboxSessionTag2(sessionId)}`;
2248
2248
  const sent = await mailbox.send({
2249
2249
  from,
2250
2250
  to,
@@ -2282,7 +2282,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2282
2282
  }
2283
2283
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2284
2284
  try {
2285
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2285
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2286
2286
  const registry = new SessionRegistry(globalRoot);
2287
2287
  const all = await registry.list();
2288
2288
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2294,10 +2294,10 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2294
2294
  }
2295
2295
  const mbByDir = /* @__PURE__ */ new Map();
2296
2296
  const mailboxFor = (projectRoot) => {
2297
- const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2297
+ const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2298
2298
  let mb = mbByDir.get(dir);
2299
2299
  if (!mb) {
2300
- mb = new GlobalMailbox5(dir);
2300
+ mb = new GlobalMailbox4(dir);
2301
2301
  mbByDir.set(dir, mb);
2302
2302
  }
2303
2303
  return mb;
@@ -2309,7 +2309,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2309
2309
  const mb = mailboxFor(s.projectRoot);
2310
2310
  await mb.send({
2311
2311
  from,
2312
- to: `leader@${mailboxSessionTag3(s.sessionId)}`,
2312
+ to: `leader@${mailboxSessionTag2(s.sessionId)}`,
2313
2313
  type: "steer",
2314
2314
  subject: "Broadcast from Fleet HQ",
2315
2315
  body: text,
@@ -2357,14 +2357,14 @@ function pushEvent(event) {
2357
2357
  }
2358
2358
  }
2359
2359
  function parseBody(req) {
2360
- return new Promise((resolve11, reject) => {
2360
+ return new Promise((resolve10, reject) => {
2361
2361
  let body = "";
2362
2362
  req.on("data", (chunk) => {
2363
2363
  body += chunk.toString("utf-8");
2364
2364
  });
2365
2365
  req.on("end", () => {
2366
2366
  try {
2367
- resolve11(JSON.parse(body));
2367
+ resolve10(JSON.parse(body));
2368
2368
  } catch {
2369
2369
  reject(new Error("Invalid JSON"));
2370
2370
  }
@@ -2558,7 +2558,7 @@ function escapeHtmlAttr(value) {
2558
2558
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2559
2559
  }
2560
2560
  function injectWsConfig(html, opts) {
2561
- let out = injectWsPort(html, opts.wsPort);
2561
+ const out = injectWsPort(html, opts.wsPort);
2562
2562
  if (!opts.publicWsUrl || out.includes('name="wrongstack-ws-url"')) return out;
2563
2563
  const tag = `<meta name="wrongstack-ws-url" content="${escapeHtmlAttr(opts.publicWsUrl)}" />`;
2564
2564
  if (out.includes("</head>")) {
@@ -3241,16 +3241,16 @@ function openBrowser(url, platform = process.platform) {
3241
3241
  import * as net from "net";
3242
3242
  import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3243
3243
  function isPortFree(host, port) {
3244
- return new Promise((resolve11) => {
3244
+ return new Promise((resolve10) => {
3245
3245
  const srv = net.createServer();
3246
- srv.once("error", () => resolve11(false));
3246
+ srv.once("error", () => resolve10(false));
3247
3247
  srv.once("listening", () => {
3248
- srv.close(() => resolve11(true));
3248
+ srv.close(() => resolve10(true));
3249
3249
  });
3250
3250
  try {
3251
3251
  srv.listen(port, host);
3252
3252
  } catch {
3253
- resolve11(false);
3253
+ resolve10(false);
3254
3254
  }
3255
3255
  });
3256
3256
  }
@@ -4128,6 +4128,7 @@ function validateMailboxMessagesPayload(payload) {
4128
4128
  const limit = payload["limit"];
4129
4129
  const agentId = payload["agentId"];
4130
4130
  const unreadOnly = payload["unreadOnly"];
4131
+ const incompleteOnly = payload["incompleteOnly"];
4131
4132
  if (limit !== void 0 && (typeof limit !== "number" || !Number.isFinite(limit) || limit < 1)) {
4132
4133
  return {
4133
4134
  ok: false,
@@ -4146,7 +4147,13 @@ function validateMailboxMessagesPayload(payload) {
4146
4147
  message: "mailbox.messages payload.unreadOnly must be a boolean when provided"
4147
4148
  };
4148
4149
  }
4149
- return { ok: true, value: { limit, agentId, unreadOnly } };
4150
+ if (incompleteOnly !== void 0 && typeof incompleteOnly !== "boolean") {
4151
+ return {
4152
+ ok: false,
4153
+ message: "mailbox.messages payload.incompleteOnly must be a boolean when provided"
4154
+ };
4155
+ }
4156
+ return { ok: true, value: { limit, agentId, unreadOnly, incompleteOnly } };
4150
4157
  }
4151
4158
  function validateMailboxAgentsPayload(payload) {
4152
4159
  if (payload === void 0) return { ok: true, value: void 0 };
@@ -4281,7 +4288,7 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4281
4288
  "enhanceDelayMs",
4282
4289
  "tgLongToolMs"
4283
4290
  ]);
4284
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken"]);
4291
+ var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4285
4292
  var ENUM_PREF_KEYS = {
4286
4293
  autonomy: AUTONOMY_VALUES,
4287
4294
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4294,6 +4301,37 @@ var ENUM_PREF_KEYS = {
4294
4301
  reasoningEffort: REASONING_EFFORT_VALUES,
4295
4302
  cacheTtl: CACHE_TTL_VALUES
4296
4303
  };
4304
+ function validateModelRuntimeValue(modelRuntime, path23) {
4305
+ const reasoning = modelRuntime["reasoning"];
4306
+ if (reasoning !== void 0) {
4307
+ if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
4308
+ const mode = reasoning["mode"];
4309
+ const effort = reasoning["effort"];
4310
+ const preserve = reasoning["preserve"];
4311
+ if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
4312
+ return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4313
+ }
4314
+ if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
4315
+ return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4316
+ }
4317
+ if (preserve !== void 0 && typeof preserve !== "boolean") {
4318
+ return `${path23}.reasoning.preserve must be a boolean when provided`;
4319
+ }
4320
+ }
4321
+ const cache = modelRuntime["cache"];
4322
+ if (cache !== void 0) {
4323
+ if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
4324
+ const ttl = cache["ttl"];
4325
+ if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
4326
+ return `${path23}.cache.ttl must be one of: 5m, 1h`;
4327
+ }
4328
+ }
4329
+ const parameters = modelRuntime["parameters"];
4330
+ if (parameters !== void 0 && !isRecord(parameters)) {
4331
+ return `${path23}.parameters must be an object when provided`;
4332
+ }
4333
+ return null;
4334
+ }
4297
4335
  function validatePreferenceValue(key, value) {
4298
4336
  if (BOOLEAN_PREF_KEYS.has(key)) {
4299
4337
  return typeof value === "boolean" ? null : `prefs.update payload.${key} must be a boolean`;
@@ -4319,6 +4357,7 @@ function validatePreferenceValue(key, value) {
4319
4357
  const provider = entry["provider"];
4320
4358
  const model = entry["model"];
4321
4359
  const fallbackProfile = entry["fallbackProfile"];
4360
+ const modelRuntime = entry["modelRuntime"];
4322
4361
  if (provider !== void 0 && typeof provider !== "string") {
4323
4362
  return `prefs.update payload.${key}.provider must be a string when provided`;
4324
4363
  }
@@ -4328,8 +4367,15 @@ function validatePreferenceValue(key, value) {
4328
4367
  if (fallbackProfile !== void 0 && typeof fallbackProfile !== "string") {
4329
4368
  return `prefs.update payload.${key}.fallbackProfile must be a string when provided`;
4330
4369
  }
4331
- if (model === void 0 && fallbackProfile === void 0) {
4332
- return `prefs.update payload.${key} entries require model or fallbackProfile`;
4370
+ if (modelRuntime !== void 0 && !isRecord(modelRuntime)) {
4371
+ return `prefs.update payload.${key}.modelRuntime must be an object when provided`;
4372
+ }
4373
+ if (isRecord(modelRuntime)) {
4374
+ const runtimeError = validateModelRuntimeValue(modelRuntime, `prefs.update payload.${key}.modelRuntime`);
4375
+ if (runtimeError) return runtimeError;
4376
+ }
4377
+ if (model === void 0 && fallbackProfile === void 0 && modelRuntime === void 0) {
4378
+ return `prefs.update payload.${key} entries require model, fallbackProfile, or modelRuntime`;
4333
4379
  }
4334
4380
  }
4335
4381
  return null;
@@ -4562,8 +4608,8 @@ function validateShellOpenPayload(payload) {
4562
4608
  if (!isRecord(payload)) {
4563
4609
  return { ok: false, message: "shell.open payload must be an object with string path" };
4564
4610
  }
4565
- const path21 = payload["path"];
4566
- if (typeof path21 !== "string" || path21.trim().length === 0) {
4611
+ const path23 = payload["path"];
4612
+ if (typeof path23 !== "string" || path23.trim().length === 0) {
4567
4613
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4568
4614
  }
4569
4615
  const target = payload["target"];
@@ -4573,48 +4619,20 @@ function validateShellOpenPayload(payload) {
4573
4619
  message: 'shell.open payload.target must be "file" or "terminal" when provided'
4574
4620
  };
4575
4621
  }
4576
- return { ok: true, value: { path: path21, target } };
4622
+ return { ok: true, value: { path: path23, target } };
4577
4623
  }
4578
4624
  function validateGitDiffPayload(payload) {
4579
4625
  if (!isRecord(payload)) {
4580
4626
  return { ok: false, message: "git.diff payload must be an object" };
4581
4627
  }
4582
- const path21 = payload["path"];
4583
- if (path21 === void 0 || path21 === null) {
4628
+ const path23 = payload["path"];
4629
+ if (path23 === void 0 || path23 === null) {
4584
4630
  return { ok: true, value: { path: "" } };
4585
4631
  }
4586
- if (typeof path21 !== "string") {
4632
+ if (typeof path23 !== "string") {
4587
4633
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
4588
4634
  }
4589
- return { ok: true, value: { path: path21 } };
4590
- }
4591
- function validateProjectsAddPayload(payload) {
4592
- if (!isRecord(payload)) {
4593
- return { ok: false, message: "projects.add payload must be an object with string root" };
4594
- }
4595
- const root = payload["root"];
4596
- if (typeof root !== "string" || root.trim().length === 0) {
4597
- return { ok: false, message: "projects.add payload.root must be a non-empty string" };
4598
- }
4599
- const name2 = payload["name"];
4600
- if (name2 !== void 0 && typeof name2 !== "string") {
4601
- return { ok: false, message: "projects.add payload.name must be a string when provided" };
4602
- }
4603
- return { ok: true, value: { root, name: typeof name2 === "string" ? name2 : void 0 } };
4604
- }
4605
- function validateProjectsSelectPayload(payload) {
4606
- if (!isRecord(payload)) {
4607
- return { ok: false, message: "projects.select payload must be an object with string root" };
4608
- }
4609
- const root = payload["root"];
4610
- if (typeof root !== "string" || root.trim().length === 0) {
4611
- return { ok: false, message: "projects.select payload.root must be a non-empty string" };
4612
- }
4613
- const name2 = payload["name"];
4614
- if (name2 !== void 0 && typeof name2 !== "string") {
4615
- return { ok: false, message: "projects.select payload.name must be a string when provided" };
4616
- }
4617
- return { ok: true, value: { root, name: typeof name2 === "string" ? name2 : void 0 } };
4635
+ return { ok: true, value: { path: path23 } };
4618
4636
  }
4619
4637
 
4620
4638
  // src/server/skills-handlers.ts
@@ -4787,7 +4805,13 @@ async function handleSkillsCreate(ws, ctx, msg) {
4787
4805
  }
4788
4806
  const createPayload = parsed.value;
4789
4807
  try {
4790
- const targetDir = createPayload.scope === "global" ? path11.join(wstackGlobalRoot(), "skills", createPayload.name.trim()) : path11.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
4808
+ const targetDir = createPayload.scope === "global" ? path11.join(
4809
+ ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
4810
+ createPayload.name.trim()
4811
+ ) : path11.join(
4812
+ ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
4813
+ createPayload.name.trim()
4814
+ );
4791
4815
  try {
4792
4816
  await fs9.access(targetDir);
4793
4817
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
@@ -4873,8 +4897,9 @@ async function handleSkillsEdit(ws, ctx, msg) {
4873
4897
  send(ws, { type: "skills.edited", payload: { success: false, error: `Skill "${editPayload.name}" not found` } });
4874
4898
  return;
4875
4899
  }
4876
- if (entry.scope.includes("bundled")) {
4877
- send(ws, { type: "skills.edited", payload: { success: false, error: "Bundled skills cannot be edited" } });
4900
+ if (entry.source !== "project" && entry.source !== "user") {
4901
+ const label = entry.source === "bundled" ? "Bundled" : "Foreign (read-only)";
4902
+ send(ws, { type: "skills.edited", payload: { success: false, error: `${label} skills cannot be edited` } });
4878
4903
  return;
4879
4904
  }
4880
4905
  await atomicWrite5(entry.path, editPayload.body);
@@ -5499,8 +5524,20 @@ function registerShutdownHandlers(res) {
5499
5524
  import * as fs10 from "fs/promises";
5500
5525
  import { watch as fsWatch } from "fs";
5501
5526
  import * as path12 from "path";
5527
+ function statusProjectHashFromWatchFilename(projectsDir, filename) {
5528
+ const raw = String(filename);
5529
+ const relative4 = path12.isAbsolute(raw) ? path12.relative(projectsDir, raw) : raw;
5530
+ const parts = relative4.split(/[\\/]+/).filter(Boolean);
5531
+ if (parts.length < 2) return null;
5532
+ if (parts[parts.length - 1] !== "status.json") return null;
5533
+ return parts[parts.length - 2] ?? null;
5534
+ }
5535
+ function shouldLogWatcherStats() {
5536
+ const value = process.env["WRONGSTACK_WEBUI_WATCHER_STATS"]?.trim().toLowerCase();
5537
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5538
+ }
5502
5539
  function setupEvents(deps2) {
5503
- const { events, broadcast: broadcast3, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5540
+ const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5504
5541
  const disposers = [];
5505
5542
  const currentSessionId = () => context.session?.id ?? "";
5506
5543
  const sessionPayload2 = (payload) => {
@@ -5519,19 +5556,19 @@ function setupEvents(deps2) {
5519
5556
  };
5520
5557
  events.on("iteration.started", (e) => {
5521
5558
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5522
- broadcast3(clients, {
5559
+ broadcast2(clients, {
5523
5560
  type: "iteration.started",
5524
5561
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5525
5562
  });
5526
5563
  });
5527
5564
  events.on("iteration.completed", (e) => {
5528
- broadcast3(clients, {
5565
+ broadcast2(clients, {
5529
5566
  type: "iteration.completed",
5530
5567
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5531
5568
  });
5532
5569
  });
5533
5570
  events.on("iteration.limit_reached", (e) => {
5534
- broadcast3(clients, {
5571
+ broadcast2(clients, {
5535
5572
  type: "iteration.limit_reached",
5536
5573
  payload: sessionPayload2({
5537
5574
  sessionId: e.sessionId,
@@ -5541,19 +5578,19 @@ function setupEvents(deps2) {
5541
5578
  });
5542
5579
  });
5543
5580
  events.on("provider.text_delta", (e) => {
5544
- broadcast3(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5581
+ broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5545
5582
  });
5546
5583
  events.on("provider.thinking_delta", (e) => {
5547
- broadcast3(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5584
+ broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5548
5585
  });
5549
5586
  events.on("provider.stream_error", (e) => {
5550
- broadcast3(clients, {
5587
+ broadcast2(clients, {
5551
5588
  type: "provider.stream_error",
5552
5589
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5553
5590
  });
5554
5591
  });
5555
5592
  events.on("tool.started", (e) => {
5556
- broadcast3(clients, {
5593
+ broadcast2(clients, {
5557
5594
  type: "tool.started",
5558
5595
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
5559
5596
  });
@@ -5566,7 +5603,7 @@ function setupEvents(deps2) {
5566
5603
  });
5567
5604
  });
5568
5605
  events.on("tool.progress", (e) => {
5569
- broadcast3(clients, {
5606
+ broadcast2(clients, {
5570
5607
  type: "tool.progress",
5571
5608
  // Nested `event` shape — the client handler reads `payload.event?.text`
5572
5609
  // and early-returns on a falsy text, so a flat { eventType, text } payload
@@ -5583,7 +5620,7 @@ function setupEvents(deps2) {
5583
5620
  });
5584
5621
  });
5585
5622
  events.on("tool.executed", (e) => {
5586
- broadcast3(clients, {
5623
+ broadcast2(clients, {
5587
5624
  type: "tool.executed",
5588
5625
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
5589
5626
  });
@@ -5599,10 +5636,10 @@ function setupEvents(deps2) {
5599
5636
  outputTokens: e.outputTokens,
5600
5637
  outputLines: e.outputLines
5601
5638
  });
5602
- broadcast3(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
5639
+ broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
5603
5640
  const sideEffects = context.sideEffects ?? [];
5604
5641
  if (sideEffects.length > 0) {
5605
- broadcast3(clients, {
5642
+ broadcast2(clients, {
5606
5643
  type: "side_effects",
5607
5644
  payload: sessionPayload2({
5608
5645
  sessionId: e.sessionId,
@@ -5624,7 +5661,7 @@ function setupEvents(deps2) {
5624
5661
  if (typeof taskPath === "string" && taskPath) {
5625
5662
  const { loadTasks } = await import("@wrongstack/core");
5626
5663
  const file = await loadTasks(taskPath);
5627
- broadcast3(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5664
+ broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5628
5665
  }
5629
5666
  } catch {
5630
5667
  }
@@ -5633,7 +5670,7 @@ function setupEvents(deps2) {
5633
5670
  if (typeof planPath === "string" && planPath) {
5634
5671
  const { loadPlan } = await import("@wrongstack/core");
5635
5672
  const plan = await loadPlan(planPath);
5636
- broadcast3(clients, { type: "plan.updated", payload: sessionPayload2({ sessionId: e.sessionId, plan: plan ?? { version: 1, sessionId: e.sessionId ?? context.session?.id ?? "", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }) });
5673
+ broadcast2(clients, { type: "plan.updated", payload: sessionPayload2({ sessionId: e.sessionId, plan: plan ?? { version: 1, sessionId: e.sessionId ?? context.session?.id ?? "", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }) });
5637
5674
  }
5638
5675
  } catch {
5639
5676
  }
@@ -5641,7 +5678,7 @@ function setupEvents(deps2) {
5641
5678
  }
5642
5679
  });
5643
5680
  events.on("tool.loop_detected", (e) => {
5644
- broadcast3(clients, {
5681
+ broadcast2(clients, {
5645
5682
  type: "tool.loop_detected",
5646
5683
  payload: sessionPayload2({
5647
5684
  sessionId: e.sessionId,
@@ -5653,19 +5690,19 @@ function setupEvents(deps2) {
5653
5690
  });
5654
5691
  });
5655
5692
  events.on("trust.persisted", (e) => {
5656
- broadcast3(clients, {
5693
+ broadcast2(clients, {
5657
5694
  type: "trust.persisted",
5658
5695
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5659
5696
  });
5660
5697
  });
5661
5698
  events.on("delegate.started", (e) => {
5662
- broadcast3(clients, {
5699
+ broadcast2(clients, {
5663
5700
  type: "delegate.started",
5664
5701
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5665
5702
  });
5666
5703
  });
5667
5704
  events.on("delegate.completed", (e) => {
5668
- broadcast3(clients, {
5705
+ broadcast2(clients, {
5669
5706
  type: "delegate.completed",
5670
5707
  payload: sessionPayload2({
5671
5708
  sessionId: e.sessionId,
@@ -5683,14 +5720,14 @@ function setupEvents(deps2) {
5683
5720
  });
5684
5721
  });
5685
5722
  events.on("provider.response", (e) => {
5686
- broadcast3(clients, { type: "provider.response", payload: sessionPayload2({ sessionId: e.sessionId, usage: e.usage, stopReason: e.stopReason, messageId: "current" }) });
5723
+ broadcast2(clients, { type: "provider.response", payload: sessionPayload2({ sessionId: e.sessionId, usage: e.usage, stopReason: e.stopReason, messageId: "current" }) });
5687
5724
  });
5688
5725
  events.on("ctx.pct", (e) => {
5689
- broadcast3(clients, {
5726
+ broadcast2(clients, {
5690
5727
  type: "ctx.pct",
5691
5728
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
5692
5729
  });
5693
- broadcast3(clients, {
5730
+ broadcast2(clients, {
5694
5731
  type: "subagent.event",
5695
5732
  payload: sessionPayload2({
5696
5733
  sessionId: e.sessionId,
@@ -5703,33 +5740,37 @@ function setupEvents(deps2) {
5703
5740
  });
5704
5741
  });
5705
5742
  events.on("ctx.max_context", (e) => {
5706
- broadcast3(clients, {
5743
+ broadcast2(clients, {
5707
5744
  type: "ctx.max_context",
5708
5745
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5709
5746
  });
5710
5747
  });
5711
5748
  events.on("token.threshold", (e) => {
5712
- broadcast3(clients, {
5749
+ broadcast2(clients, {
5713
5750
  type: "token.threshold",
5714
5751
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5715
5752
  });
5716
5753
  });
5717
5754
  events.on("token.cost_estimate_unavailable", (e) => {
5718
- broadcast3(clients, {
5755
+ broadcast2(clients, {
5719
5756
  type: "token.cost_estimate_unavailable",
5720
5757
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5721
5758
  });
5722
5759
  });
5723
5760
  events.on("context.repaired", (e) => {
5724
- broadcast3(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5761
+ broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5725
5762
  });
5726
5763
  events.on("tool.confirm_needed", (e) => {
5727
5764
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5728
- pendingConfirms.set(id, e.resolve);
5729
- broadcast3(clients, { type: "tool.confirm_needed", payload: sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern }) });
5765
+ pendingConfirms.set(id, {
5766
+ resolve: e.resolve,
5767
+ decisionSource: e.decisionSource,
5768
+ riskTier: e.riskTier
5769
+ });
5770
+ broadcast2(clients, { type: "tool.confirm_needed", payload: sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier }) });
5730
5771
  });
5731
5772
  events.on("error", (e) => {
5732
- broadcast3(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5773
+ broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5733
5774
  appendForCurrentSession(e.sessionId, {
5734
5775
  type: "error",
5735
5776
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5738,13 +5779,13 @@ function setupEvents(deps2) {
5738
5779
  });
5739
5780
  });
5740
5781
  events.on("session.damaged", (e) => {
5741
- broadcast3(clients, {
5782
+ broadcast2(clients, {
5742
5783
  type: "session.damaged",
5743
5784
  payload: { sessionId: e.sessionId, detail: e.detail }
5744
5785
  });
5745
5786
  });
5746
5787
  events.on("session.rewound", (e) => {
5747
- broadcast3(clients, {
5788
+ broadcast2(clients, {
5748
5789
  type: "session.rewound",
5749
5790
  payload: sessionPayload2({
5750
5791
  sessionId: e.sessionId,
@@ -5755,7 +5796,7 @@ function setupEvents(deps2) {
5755
5796
  });
5756
5797
  });
5757
5798
  events.on("checkpoint.written", (e) => {
5758
- broadcast3(clients, {
5799
+ broadcast2(clients, {
5759
5800
  type: "checkpoint.written",
5760
5801
  payload: sessionPayload2({
5761
5802
  sessionId: e.sessionId,
@@ -5767,19 +5808,19 @@ function setupEvents(deps2) {
5767
5808
  });
5768
5809
  });
5769
5810
  events.on("in_flight.started", (e) => {
5770
- broadcast3(clients, {
5811
+ broadcast2(clients, {
5771
5812
  type: "in_flight.started",
5772
5813
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
5773
5814
  });
5774
5815
  });
5775
5816
  events.on("in_flight.ended", (e) => {
5776
- broadcast3(clients, {
5817
+ broadcast2(clients, {
5777
5818
  type: "in_flight.ended",
5778
5819
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
5779
5820
  });
5780
5821
  });
5781
5822
  events.on("provider.retry", (e) => {
5782
- broadcast3(clients, {
5823
+ broadcast2(clients, {
5783
5824
  type: "provider.retry",
5784
5825
  payload: sessionPayload2({
5785
5826
  sessionId: e.sessionId,
@@ -5801,7 +5842,7 @@ function setupEvents(deps2) {
5801
5842
  });
5802
5843
  });
5803
5844
  events.on("provider.error", (e) => {
5804
- broadcast3(clients, {
5845
+ broadcast2(clients, {
5805
5846
  type: "provider.error",
5806
5847
  payload: sessionPayload2({
5807
5848
  sessionId: e.sessionId,
@@ -5821,7 +5862,7 @@ function setupEvents(deps2) {
5821
5862
  });
5822
5863
  });
5823
5864
  events.on("provider.fallback", (e) => {
5824
- broadcast3(clients, {
5865
+ broadcast2(clients, {
5825
5866
  type: "provider.fallback",
5826
5867
  payload: sessionPayload2({
5827
5868
  sessionId: e.sessionId,
@@ -5833,7 +5874,7 @@ function setupEvents(deps2) {
5833
5874
  });
5834
5875
  });
5835
5876
  events.on("compaction.fired", (e) => {
5836
- broadcast3(clients, {
5877
+ broadcast2(clients, {
5837
5878
  type: "context.compacted",
5838
5879
  payload: sessionPayload2({
5839
5880
  sessionId: e.sessionId,
@@ -5845,7 +5886,7 @@ function setupEvents(deps2) {
5845
5886
  });
5846
5887
  });
5847
5888
  events.on("compaction.failed", (e) => {
5848
- broadcast3(clients, {
5889
+ broadcast2(clients, {
5849
5890
  type: "compaction.failed",
5850
5891
  payload: sessionPayload2({
5851
5892
  sessionId: e.sessionId,
@@ -5860,25 +5901,25 @@ function setupEvents(deps2) {
5860
5901
  });
5861
5902
  });
5862
5903
  events.on("mcp.server.connected", (e) => {
5863
- broadcast3(clients, {
5904
+ broadcast2(clients, {
5864
5905
  type: "mcp.server.connected",
5865
5906
  payload: { name: e.name, toolCount: e.toolCount }
5866
5907
  });
5867
5908
  });
5868
5909
  events.on("mcp.server.reconnected", (e) => {
5869
- broadcast3(clients, {
5910
+ broadcast2(clients, {
5870
5911
  type: "mcp.server.reconnected",
5871
5912
  payload: { name: e.name, toolCount: e.toolCount }
5872
5913
  });
5873
5914
  });
5874
5915
  events.on("mcp.server.disconnected", (e) => {
5875
- broadcast3(clients, {
5916
+ broadcast2(clients, {
5876
5917
  type: "mcp.server.disconnected",
5877
5918
  payload: { name: e.name, reason: e.reason }
5878
5919
  });
5879
5920
  });
5880
5921
  events.on("coordinator.stats", (e) => {
5881
- broadcast3(clients, {
5922
+ broadcast2(clients, {
5882
5923
  type: "coordinator.stats",
5883
5924
  payload: sessionPayload2({
5884
5925
  sessionId: e.sessionId,
@@ -5899,12 +5940,12 @@ function setupEvents(deps2) {
5899
5940
  });
5900
5941
  });
5901
5942
  events.onPattern("mailbox.received", (_e, payload) => {
5902
- broadcast3(clients, { type: "mailbox.received", payload });
5943
+ broadcast2(clients, { type: "mailbox.received", payload });
5903
5944
  });
5904
5945
  events.onPattern("mailbox.agent_registered", (_e, payload) => {
5905
- broadcast3(clients, { type: "mailbox.agent_registered", payload });
5946
+ broadcast2(clients, { type: "mailbox.agent_registered", payload });
5906
5947
  });
5907
- const forwardSubagent = (kind, payload) => broadcast3(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5948
+ const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5908
5949
  events.on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
5909
5950
  events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
5910
5951
  events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
@@ -5914,7 +5955,7 @@ function setupEvents(deps2) {
5914
5955
  events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
5915
5956
  events.on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
5916
5957
  events.on("agent.timeline.message", (e) => {
5917
- broadcast3(clients, {
5958
+ broadcast2(clients, {
5918
5959
  type: "agent.timeline.message",
5919
5960
  payload: sessionPayload2({
5920
5961
  sessionId: e.sessionId,
@@ -5930,7 +5971,7 @@ function setupEvents(deps2) {
5930
5971
  });
5931
5972
  });
5932
5973
  events.on("agent.status_changed", (e) => {
5933
- broadcast3(clients, {
5974
+ broadcast2(clients, {
5934
5975
  type: "agent.status_changed",
5935
5976
  payload: sessionPayload2({
5936
5977
  sessionId: e.sessionId,
@@ -5999,19 +6040,19 @@ function setupEvents(deps2) {
5999
6040
  }
6000
6041
  });
6001
6042
  events.onPattern("mailbox.*", (eventName, payload) => {
6002
- broadcast3(clients, {
6043
+ broadcast2(clients, {
6003
6044
  type: "mailbox.event",
6004
6045
  payload: sessionPayload2({ event: eventName, ...payload })
6005
6046
  });
6006
6047
  });
6007
6048
  events.onPattern("brain.*", (eventName, payload) => {
6008
- broadcast3(clients, {
6049
+ broadcast2(clients, {
6009
6050
  type: "brain.event",
6010
6051
  payload: sessionPayload2({ event: eventName, ...payload })
6011
6052
  });
6012
6053
  });
6013
6054
  events.on("client.status", async (e) => {
6014
- broadcast3(clients, { type: "client.status_update", payload: e });
6055
+ broadcast2(clients, { type: "client.status_update", payload: e });
6015
6056
  if (wpaths?.projectStatus) {
6016
6057
  try {
6017
6058
  const statusFile = wpaths.projectStatus(e.projectHash);
@@ -6043,79 +6084,71 @@ function setupEvents(deps2) {
6043
6084
  if (!watcherMetrics || watcherMetrics.broadcastsSent === 0) return 0;
6044
6085
  return watcherMetrics.totalDebounceDelayMs / watcherMetrics.broadcastsSent;
6045
6086
  };
6087
+ const logWatcherMetricsEnabled = shouldLogWatcherStats();
6046
6088
  const logWatcherMetrics = () => {
6047
- if (!watcherMetrics) return;
6089
+ if (!watcherMetrics || !logWatcherMetricsEnabled) return;
6048
6090
  watcherMetrics.averageDebounceDelayMs = getAverageDebounceDelay();
6049
6091
  console.log(
6050
6092
  `[setup-events] File watcher stats: ${watcherMetrics.broadcastsSent} broadcasts, ${watcherMetrics.fileChangesDetected} file changes, ${watcherMetrics.debounceResets} debounce resets, avg delay: ${watcherMetrics.averageDebounceDelayMs.toFixed(1)}ms, ${watcherMetrics.activeProjects} active projects`
6051
6093
  );
6052
6094
  };
6053
- const metricsInterval = setInterval(logWatcherMetrics, 6e4);
6095
+ const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
6054
6096
  const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
6055
- broadcast3(clients, { type: "client.status_update", payload: statusData });
6097
+ broadcast2(clients, { type: "client.status_update", payload: statusData });
6056
6098
  if (watcherMetrics) {
6057
6099
  watcherMetrics.broadcastsSent++;
6058
6100
  watcherMetrics.totalDebounceDelayMs += actualDelayMs;
6059
6101
  watcherMetrics.averageDebounceDelayMs = getAverageDebounceDelay();
6060
6102
  }
6061
6103
  };
6062
- const scheduleBroadcast = (projectHash3, statusData) => {
6104
+ const scheduleBroadcast = (projectHash, statusData) => {
6063
6105
  const now = Date.now();
6064
- const existing = pendingStatuses.get(projectHash3);
6106
+ const existing = pendingStatuses.get(projectHash);
6065
6107
  if (existing && watcherMetrics) {
6066
6108
  watcherMetrics.debounceResets++;
6067
6109
  }
6068
- pendingStatuses.set(projectHash3, {
6110
+ pendingStatuses.set(projectHash, {
6069
6111
  data: statusData,
6070
6112
  firstWriteAt: existing ? existing.firstWriteAt : now
6071
6113
  });
6072
- const existingTimer = debounceTimers.get(projectHash3);
6114
+ const existingTimer = debounceTimers.get(projectHash);
6073
6115
  if (existingTimer) {
6074
6116
  clearTimeout(existingTimer);
6075
6117
  }
6076
6118
  const timer = setTimeout(() => {
6077
- debounceTimers.delete(projectHash3);
6078
- const pending = pendingStatuses.get(projectHash3);
6119
+ debounceTimers.delete(projectHash);
6120
+ const pending = pendingStatuses.get(projectHash);
6079
6121
  if (pending) {
6080
6122
  const actualDelay = Date.now() - pending.firstWriteAt;
6081
- broadcastStatus(projectHash3, pending.data, actualDelay);
6082
- pendingStatuses.delete(projectHash3);
6123
+ broadcastStatus(projectHash, pending.data, actualDelay);
6124
+ pendingStatuses.delete(projectHash);
6083
6125
  }
6084
6126
  }, DEBOUNCE_MS);
6085
- debounceTimers.set(projectHash3, timer);
6127
+ debounceTimers.set(projectHash, timer);
6086
6128
  };
6087
6129
  let watcher;
6088
6130
  const startWatcher = async () => {
6089
6131
  try {
6090
6132
  await fs10.mkdir(projectsDir, { recursive: true });
6091
6133
  watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6092
- if (eventType === "change") {
6093
- if (filename == null) return;
6094
- if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6095
- const targetFile = path12.join(projectsDir, String(filename));
6096
- if (targetFile.endsWith("status.json")) {
6097
- const projectHash3 = path12.basename(path12.dirname(targetFile));
6098
- if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash3)) {
6099
- return;
6100
- }
6101
- if (watcherMetrics) watcherMetrics.filesProcessed++;
6102
- try {
6103
- const content = await fs10.readFile(targetFile, "utf-8");
6104
- const statusData = JSON.parse(content);
6105
- if (statusData.projectHash) {
6106
- const hash = String(statusData.projectHash);
6107
- if (!knownProjectHashes.has(hash)) {
6108
- knownProjectHashes.add(hash);
6109
- if (watcherMetrics) watcherMetrics.activeProjects = knownProjectHashes.size;
6110
- }
6111
- }
6112
- scheduleBroadcast(projectHash3, statusData);
6113
- } catch {
6114
- }
6115
- }
6134
+ if (eventType !== "change" && eventType !== "rename") return;
6135
+ if (filename == null) return;
6136
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
6137
+ if (!projectHash) return;
6138
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6139
+ if (!knownProjectHashes.has(projectHash)) return;
6140
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
6141
+ try {
6142
+ const targetFile = path12.join(projectsDir, projectHash, "status.json");
6143
+ const content = await fs10.readFile(targetFile, "utf-8");
6144
+ const statusData = JSON.parse(content);
6145
+ scheduleBroadcast(projectHash, statusData);
6146
+ } catch {
6116
6147
  }
6117
6148
  });
6118
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
6149
+ if (logWatcherMetricsEnabled) {
6150
+ console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
6151
+ }
6119
6152
  } catch (err) {
6120
6153
  console.error("[setup-events] Failed to start status file watcher:", err);
6121
6154
  }
@@ -6131,14 +6164,14 @@ function setupEvents(deps2) {
6131
6164
  });
6132
6165
  startWatcher();
6133
6166
  disposers.push(() => {
6134
- clearInterval(metricsInterval);
6167
+ if (metricsInterval) clearInterval(metricsInterval);
6135
6168
  logWatcherMetrics();
6136
6169
  if (watcherMetrics) watcherMetrics.watcherActive = false;
6137
- for (const [projectHash3, pending] of pendingStatuses) {
6138
- const timer = debounceTimers.get(projectHash3);
6170
+ for (const [projectHash, pending] of pendingStatuses) {
6171
+ const timer = debounceTimers.get(projectHash);
6139
6172
  if (timer) {
6140
6173
  clearTimeout(timer);
6141
- broadcastStatus(projectHash3, pending.data, 0);
6174
+ broadcastStatus(projectHash, pending.data, 0);
6142
6175
  }
6143
6176
  }
6144
6177
  for (const timer of debounceTimers.values()) {
@@ -6148,7 +6181,7 @@ function setupEvents(deps2) {
6148
6181
  pendingStatuses.clear();
6149
6182
  if (watcher) {
6150
6183
  watcher.close();
6151
- console.log("[setup-events] Closed status file watcher");
6184
+ if (logWatcherMetricsEnabled) console.log("[setup-events] Closed status file watcher");
6152
6185
  }
6153
6186
  });
6154
6187
  }
@@ -6189,7 +6222,7 @@ function setupEvents(deps2) {
6189
6222
  lastActivityAt: a.lastActivityAt
6190
6223
  }))
6191
6224
  }));
6192
- broadcast3(clients, { type: "sessions.status_update", payload: { sessions: live } });
6225
+ broadcast2(clients, { type: "sessions.status_update", payload: { sessions: live } });
6193
6226
  } catch {
6194
6227
  }
6195
6228
  };
@@ -6236,6 +6269,115 @@ function computeUsageCost(usage, rates) {
6236
6269
  return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
6237
6270
  }
6238
6271
 
6272
+ // src/server/model-catalog.ts
6273
+ import { capabilitiesFor } from "@wrongstack/providers";
6274
+ var SIBLING_CATALOG = {
6275
+ "anthropic-oauth": "anthropic",
6276
+ "openai-codex": "openai",
6277
+ "github-copilot": "openai"
6278
+ };
6279
+ async function resolveProviderCatalogForModels(modelsRegistry, providerId, cfg) {
6280
+ return await modelsRegistry.getProvider(providerId) ?? (cfg?.type && cfg.type !== providerId ? await modelsRegistry.getProvider(cfg.type) : void 0);
6281
+ }
6282
+ async function resolveProviderModelMetadata(modelsRegistry, providerId, modelId, cfg) {
6283
+ const direct = await modelsRegistry.getModel(providerId, modelId).catch(() => void 0);
6284
+ if (direct) return overlayConfiguredCapabilities(direct, cfg);
6285
+ if (cfg?.type && cfg.type !== providerId) {
6286
+ const typed = await modelsRegistry.getModel(cfg.type, modelId).catch(() => void 0);
6287
+ if (typed) return overlayConfiguredCapabilities(typed, cfg);
6288
+ }
6289
+ const sibling = cfg?.family ? SIBLING_CATALOG[cfg.family] : void 0;
6290
+ if (sibling) {
6291
+ const siblingModel = await modelsRegistry.getModel(sibling, modelId).catch(() => void 0);
6292
+ if (siblingModel) return overlayConfiguredCapabilities(siblingModel, cfg);
6293
+ const caps2 = await capabilitiesFor(
6294
+ modelsRegistry,
6295
+ sibling,
6296
+ modelId,
6297
+ cfg?.customModels
6298
+ ).catch(() => void 0);
6299
+ if (caps2 && positiveNumber(caps2.maxContext)) {
6300
+ return syntheticModel(providerId, modelId, cfg, caps2);
6301
+ }
6302
+ }
6303
+ const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
6304
+ const caps = await capabilitiesFor(
6305
+ modelsRegistry,
6306
+ catalogId,
6307
+ modelId,
6308
+ cfg?.customModels
6309
+ ).catch(() => void 0);
6310
+ if (caps && positiveNumber(caps.maxContext)) {
6311
+ return syntheticModel(providerId, modelId, cfg, caps);
6312
+ }
6313
+ const crossCatalog = await findModelAcrossCatalog(modelsRegistry, modelId);
6314
+ if (crossCatalog) return overlayConfiguredCapabilities(crossCatalog, cfg);
6315
+ return syntheticModel(providerId, modelId, cfg);
6316
+ }
6317
+ async function findModelAcrossCatalog(modelsRegistry, modelId) {
6318
+ if (typeof modelsRegistry.listProviders !== "function" || typeof modelsRegistry.getModel !== "function") {
6319
+ return void 0;
6320
+ }
6321
+ const providers = await modelsRegistry.listProviders().catch(() => []);
6322
+ for (const provider of providers) {
6323
+ if (!provider.models.some((model) => model.id === modelId)) continue;
6324
+ const resolved = await modelsRegistry.getModel(provider.id, modelId).catch(() => void 0);
6325
+ if (resolved?.capabilities?.maxContext) return resolved;
6326
+ }
6327
+ return void 0;
6328
+ }
6329
+ function syntheticModel(providerId, modelId, cfg, capabilities) {
6330
+ const custom = cfg?.customModels?.[modelId];
6331
+ const configuredMax = positiveNumber(readConfiguredMaxContext(cfg));
6332
+ const customCaps = custom?.capabilities;
6333
+ const maxContext = positiveNumber(customCaps?.maxContext) ?? positiveNumber(capabilities?.maxContext) ?? configuredMax ?? 0;
6334
+ const isKnown = Boolean(custom) || Boolean(cfg?.models?.includes(modelId)) || maxContext > 0 || Boolean(capabilities);
6335
+ if (!isKnown) return void 0;
6336
+ return {
6337
+ providerId,
6338
+ modelId,
6339
+ capabilities: {
6340
+ tools: readConfiguredBool(cfg, "tools") ?? capabilities?.tools ?? customCaps?.tools ?? false,
6341
+ vision: readConfiguredBool(cfg, "vision") ?? capabilities?.vision ?? customCaps?.vision ?? false,
6342
+ reasoning: readConfiguredBool(cfg, "reasoning") ?? capabilities?.reasoning ?? customCaps?.reasoning ?? false,
6343
+ maxContext,
6344
+ maxOutput: custom?.maxOutput ?? customCaps?.maxOutput ?? capabilities?.maxOutput
6345
+ },
6346
+ cost: void 0
6347
+ };
6348
+ }
6349
+ function overlayConfiguredCapabilities(model, cfg) {
6350
+ const custom = cfg?.customModels?.[model.modelId];
6351
+ const configuredMax = positiveNumber(readConfiguredMaxContext(cfg));
6352
+ const customCaps = custom?.capabilities;
6353
+ if (!configuredMax && !customCaps && custom?.maxOutput === void 0) return model;
6354
+ return {
6355
+ ...model,
6356
+ capabilities: {
6357
+ ...model.capabilities,
6358
+ ...customCaps,
6359
+ maxContext: customCaps?.maxContext ?? configuredMax ?? model.capabilities.maxContext,
6360
+ maxOutput: custom?.maxOutput ?? customCaps?.maxOutput ?? model.capabilities.maxOutput
6361
+ }
6362
+ };
6363
+ }
6364
+ function readConfiguredMaxContext(providerConfig) {
6365
+ if (!providerConfig || typeof providerConfig !== "object") return void 0;
6366
+ const capabilities = providerConfig.capabilities;
6367
+ if (!capabilities || typeof capabilities !== "object") return void 0;
6368
+ return capabilities.maxContext;
6369
+ }
6370
+ function readConfiguredBool(providerConfig, key) {
6371
+ if (!providerConfig || typeof providerConfig !== "object") return void 0;
6372
+ const capabilities = providerConfig.capabilities;
6373
+ if (!capabilities || typeof capabilities !== "object") return void 0;
6374
+ const value = capabilities[key];
6375
+ return typeof value === "boolean" ? value : void 0;
6376
+ }
6377
+ function positiveNumber(value) {
6378
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
6379
+ }
6380
+
6239
6381
  // src/server/server-runtime.ts
6240
6382
  import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6241
6383
  async function resolvePorts(opts) {
@@ -6268,7 +6410,12 @@ function createSessionStartPayload(g) {
6268
6410
  let outputCost = 0;
6269
6411
  let cacheReadCost = 0;
6270
6412
  try {
6271
- const m = await g.modelsRegistry.getModel(config.provider, config.model);
6413
+ const m = await resolveProviderModelMetadata(
6414
+ g.modelsRegistry,
6415
+ config.provider,
6416
+ config.model,
6417
+ config.providers?.[config.provider]
6418
+ );
6272
6419
  maxContext = m?.capabilities?.maxContext ?? 0;
6273
6420
  if (!maxContext) {
6274
6421
  try {
@@ -6411,7 +6558,7 @@ function registerShutdown(deps2) {
6411
6558
  }
6412
6559
 
6413
6560
  // src/server/pre-context-services.ts
6414
- import * as path14 from "path";
6561
+ import * as path15 from "path";
6415
6562
  import { createRequire } from "module";
6416
6563
  import { WebSocket as WebSocket2 } from "ws";
6417
6564
  import {
@@ -6449,13 +6596,14 @@ import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
6449
6596
  import { createDefaultContainer } from "@wrongstack/runtime";
6450
6597
  import {
6451
6598
  builtinToolsPack,
6599
+ configureDangerBypass,
6452
6600
  configureExecPolicy,
6453
6601
  forgetTool,
6454
6602
  relatedMemoryTool,
6455
6603
  rememberTool,
6456
6604
  searchMemoryTool
6457
6605
  } from "@wrongstack/tools";
6458
- import { projectHash, toErrorMessage as toErrorMessage5, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
6606
+ import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6459
6607
 
6460
6608
  // src/server/setup-screen.ts
6461
6609
  import { expectDefined as expectDefined2 } from "@wrongstack/core";
@@ -6537,6 +6685,7 @@ function seedContextMeta(config, context) {
6537
6685
  meta["favoriteModelsOnly"] = config.favoriteModelsOnly === true;
6538
6686
  meta["modelMatrix"] = config.modelMatrix ?? {};
6539
6687
  meta["fallbackAuto"] = config.fallbackAuto !== false;
6688
+ if (typeof config.uiLocale === "string" && config.uiLocale) meta["uiLocale"] = config.uiLocale;
6540
6689
  meta["featureMcp"] = config.features.mcp !== false;
6541
6690
  meta["featurePlugins"] = config.features.plugins !== false;
6542
6691
  meta["featureMemory"] = config.features.memory !== false;
@@ -6575,10 +6724,117 @@ function seedContextMeta(config, context) {
6575
6724
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
6576
6725
  }
6577
6726
 
6727
+ // src/server/model-auto-discovery.ts
6728
+ import * as fs11 from "fs/promises";
6729
+ import * as path14 from "path";
6730
+ import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
6731
+ function isOverlayRegistry(value) {
6732
+ return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
6733
+ }
6734
+ function resolveKey(cfg) {
6735
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
6736
+ const active = cfg.activeKey ? cfg.apiKeys.find((key) => key.label === cfg.activeKey) : void 0;
6737
+ return (active ?? cfg.apiKeys[0])?.apiKey;
6738
+ }
6739
+ return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
6740
+ }
6741
+ function eligibleProviders(config) {
6742
+ const out = [];
6743
+ for (const [id, cfg] of Object.entries(config.providers ?? {})) {
6744
+ const preset = COMPATIBLE_PRESETS[id];
6745
+ const enabled = cfg.autoDiscoverModels ?? preset?.autoDiscover ?? false;
6746
+ if (!enabled) continue;
6747
+ const baseUrl = cfg.baseUrl ?? preset?.defaultBaseUrl;
6748
+ if (!baseUrl) continue;
6749
+ out.push({ id, cfg, baseUrl, apiKey: resolveKey(cfg) });
6750
+ }
6751
+ return out;
6752
+ }
6753
+ async function readCache(file) {
6754
+ try {
6755
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6756
+ } catch {
6757
+ return {};
6758
+ }
6759
+ }
6760
+ async function discoverAndMergeWebuiProviders(opts) {
6761
+ const registry = opts.registry;
6762
+ if (!isOverlayRegistry(registry)) return;
6763
+ const targets = eligibleProviders(opts.config);
6764
+ if (targets.length === 0) return;
6765
+ const cacheFile = path14.join(opts.cacheDir, "discovered-models-cache.json");
6766
+ const cache = await readCache(cacheFile);
6767
+ let cacheDirty = false;
6768
+ await Promise.all(
6769
+ targets.map(async ({ id, cfg, baseUrl, apiKey }) => {
6770
+ const cacheKey = `${id}\0${baseUrl}`;
6771
+ const provider = await discoverOpenAICompatibleModels(id, {
6772
+ baseUrl,
6773
+ apiKey,
6774
+ headers: cfg.headers,
6775
+ providerName: id,
6776
+ fetchImpl: opts.fetchImpl
6777
+ });
6778
+ if (provider) {
6779
+ cache[cacheKey] = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), provider };
6780
+ cacheDirty = true;
6781
+ registry.mergeOverlay({ [id]: provider });
6782
+ opts.logger?.info?.(
6783
+ `auto-discovered ${Object.keys(provider.models).length} models for "${id}" from ${baseUrl}`
6784
+ );
6785
+ return;
6786
+ }
6787
+ const cached = cache[cacheKey];
6788
+ if (cached) {
6789
+ registry.mergeOverlay({ [id]: cached.provider });
6790
+ opts.logger?.warn?.(
6791
+ `auto-discovery for "${id}" failed; using ${Object.keys(cached.provider.models).length} cached models from ${cached.fetchedAt}`
6792
+ );
6793
+ } else {
6794
+ opts.logger?.warn?.(
6795
+ `auto-discovery for "${id}" failed and no cache available (server at ${baseUrl} unreachable?)`
6796
+ );
6797
+ }
6798
+ })
6799
+ );
6800
+ if (cacheDirty) {
6801
+ try {
6802
+ await fs11.mkdir(path14.dirname(cacheFile), { recursive: true });
6803
+ await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
6804
+ } catch {
6805
+ opts.logger?.debug?.("provider auto-discovery cache write failed");
6806
+ }
6807
+ }
6808
+ }
6809
+
6578
6810
  // src/server/pre-context-services.ts
6811
+ var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
6579
6812
  async function createPreContextServices(input) {
6580
- const { config, wpaths, logger, opts, globalConfigPath, projectRoot, workingDir, needsProvider } = input;
6581
- const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({ cacheFile: wpaths.modelsCache, ttlSeconds: 24 * 3600 });
6813
+ const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
6814
+ const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
6815
+ cacheFile: wpaths.modelsCache,
6816
+ ttlSeconds: 0,
6817
+ overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
6818
+ overlayCacheFile: wpaths.modelsOverlayCache
6819
+ });
6820
+ if (!opts.services?.modelsRegistry) {
6821
+ try {
6822
+ await modelsRegistry.refresh();
6823
+ logger.info("models.dev catalog refreshed");
6824
+ } catch (err) {
6825
+ logger.warn(`models.dev refresh failed (${toErrorMessage5(err)}); using cached catalog`);
6826
+ }
6827
+ }
6828
+ try {
6829
+ await discoverAndMergeWebuiProviders({
6830
+ config,
6831
+ registry: modelsRegistry,
6832
+ cacheDir: path15.dirname(wpaths.modelsCache),
6833
+ logger
6834
+ });
6835
+ } catch (err) {
6836
+ logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
6837
+ }
6582
6838
  const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
6583
6839
  const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
6584
6840
  const providerRegistry = new ProviderRegistry();
@@ -6609,6 +6865,7 @@ async function createPreContextServices(input) {
6609
6865
  applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
6610
6866
  applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
6611
6867
  configureExecPolicy(config.tools?.exec ?? {});
6868
+ configureDangerBypass(config.tools?.exec?.danger ?? {});
6612
6869
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
6613
6870
  const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
6614
6871
  if (config.features.mcp && config.mcpServers) {
@@ -6619,7 +6876,7 @@ async function createPreContextServices(input) {
6619
6876
  });
6620
6877
  }
6621
6878
  }
6622
- let sessionStore = opts.services?.session ?? new DefaultSessionStore({ dir: wpaths.projectSessions });
6879
+ const sessionStore = opts.services?.session ?? new DefaultSessionStore({ dir: wpaths.projectSessions });
6623
6880
  if (!opts.services?.session) {
6624
6881
  sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
6625
6882
  if (count > 0) logger.info(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
@@ -6641,7 +6898,7 @@ async function createPreContextServices(input) {
6641
6898
  sessionId: session.id,
6642
6899
  projectSlug: wpaths.projectSlug,
6643
6900
  projectRoot,
6644
- projectName: path14.basename(projectRoot),
6901
+ projectName: path15.basename(projectRoot),
6645
6902
  workingDir,
6646
6903
  clientType: "webui",
6647
6904
  pid: process.pid,
@@ -6657,12 +6914,21 @@ async function createPreContextServices(input) {
6657
6914
  statusTracker.start();
6658
6915
  let stopHqSessionBridge;
6659
6916
  let hqTelemetryPublisher;
6917
+ const stopHqAuxBridges = [];
6660
6918
  try {
6661
- const { createHqPublisherFromEnv, startSessionTelemetryBridge } = await import("@wrongstack/core");
6919
+ const {
6920
+ createHqPublisherFromEnv,
6921
+ startSessionTelemetryBridge,
6922
+ startFleetTelemetryBridge,
6923
+ startBrainTelemetryBridge,
6924
+ startWorktreeTelemetryBridge,
6925
+ startToolTelemetryBridge,
6926
+ startCostTelemetryBridge
6927
+ } = await import("@wrongstack/core");
6662
6928
  const hqTelemetry = createHqPublisherFromEnv({
6663
6929
  clientKind: "webui",
6664
6930
  projectRoot,
6665
- projectName: path14.basename(projectRoot),
6931
+ projectName: path15.basename(projectRoot),
6666
6932
  appConfig: config,
6667
6933
  socketFactory: (url) => new WebSocket2(url)
6668
6934
  });
@@ -6674,11 +6940,41 @@ async function createPreContextServices(input) {
6674
6940
  events,
6675
6941
  sessionId: session.id,
6676
6942
  projectRoot,
6677
- projectName: path14.basename(projectRoot),
6943
+ projectName: path15.basename(projectRoot),
6678
6944
  globalRoot: wpaths.globalRoot,
6679
6945
  initialAgents: statusTracker?.getAgents(),
6680
6946
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
6681
6947
  });
6948
+ try {
6949
+ stopHqAuxBridges.push(
6950
+ startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
6951
+ );
6952
+ } catch {
6953
+ }
6954
+ try {
6955
+ stopHqAuxBridges.push(
6956
+ startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6957
+ );
6958
+ } catch {
6959
+ }
6960
+ try {
6961
+ stopHqAuxBridges.push(
6962
+ startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6963
+ );
6964
+ } catch {
6965
+ }
6966
+ try {
6967
+ stopHqAuxBridges.push(
6968
+ startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
6969
+ );
6970
+ } catch {
6971
+ }
6972
+ try {
6973
+ stopHqAuxBridges.push(
6974
+ startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6975
+ );
6976
+ } catch {
6977
+ }
6682
6978
  }
6683
6979
  } catch {
6684
6980
  }
@@ -6688,6 +6984,12 @@ async function createPreContextServices(input) {
6688
6984
  await registry.markClosing();
6689
6985
  statusTracker?.stop();
6690
6986
  stopHqSessionBridge?.();
6987
+ for (const stop of stopHqAuxBridges) {
6988
+ try {
6989
+ stop();
6990
+ } catch {
6991
+ }
6992
+ }
6691
6993
  hqTelemetryPublisher?.close();
6692
6994
  } catch {
6693
6995
  }
@@ -6717,22 +7019,27 @@ async function createPreContextServices(input) {
6717
7019
  const customModeStore = createCustomModeStore(wpaths.configDir);
6718
7020
  await customModeStore.load();
6719
7021
  console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
6720
- const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
7022
+ const resolvedModel = await resolveProviderModelMetadata(
7023
+ modelsRegistry,
7024
+ config.provider,
7025
+ config.model,
7026
+ config.providers?.[config.provider]
7027
+ );
6721
7028
  const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
6722
7029
  const modelCapabilitiesRef = { current: modelCapabilities };
6723
7030
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
6724
7031
  const skillInstaller = config.features.skills ? new SkillInstaller({
6725
- manifestPath: path14.join(wstackGlobalRoot2(), "installed-skills.json"),
6726
- projectSkillsDir: path14.join(projectRoot, ".wrongstack", "skills"),
6727
- globalSkillsDir: path14.join(wstackGlobalRoot2(), "skills"),
6728
- projectHash: projectHash(projectRoot),
7032
+ manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
7033
+ projectSkillsDir: wpaths.inProjectSkills,
7034
+ globalSkillsDir: wpaths.globalSkills,
7035
+ projectHash: wpaths.projectHash,
6729
7036
  skillLoader
6730
7037
  }) : void 0;
6731
7038
  const promptsEnabled = config.features.prompts !== false;
6732
7039
  const bundledPromptsDir = promptsEnabled ? (() => {
6733
7040
  try {
6734
7041
  const req = createRequire(import.meta.url);
6735
- return path14.join(path14.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7042
+ return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
6736
7043
  } catch {
6737
7044
  return void 0;
6738
7045
  }
@@ -6749,6 +7056,11 @@ async function createPreContextServices(input) {
6749
7056
  modelCapabilities: () => modelCapabilitiesRef.current,
6750
7057
  instructionPaths: { globalDir: wpaths.globalInstructions, projectDir: wpaths.inProjectInstructions }
6751
7058
  });
7059
+ if (container.has(TOKENS.SystemPromptBuilder)) {
7060
+ container.override(TOKENS.SystemPromptBuilder, () => systemPromptBuilder, { owner: "webui" });
7061
+ } else {
7062
+ container.bind(TOKENS.SystemPromptBuilder, () => systemPromptBuilder, { owner: "webui" });
7063
+ }
6752
7064
  let onlineAgents = [];
6753
7065
  try {
6754
7066
  const systemMailbox = new GlobalMailbox(wpaths.projectDir);
@@ -6810,11 +7122,11 @@ async function createPreContextServices(input) {
6810
7122
  }
6811
7123
 
6812
7124
  // src/server/start-webui.ts
6813
- import * as path20 from "path";
7125
+ import * as path22 from "path";
6814
7126
  import {
6815
- createDefaultPipelines as createDefaultPipelines2,
7127
+ createDefaultPipelines,
6816
7128
  createSessionEventBridge,
6817
- DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID3,
7129
+ DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
6818
7130
  expectDefined as expectDefined3,
6819
7131
  resolveSessionLoggingConfig,
6820
7132
  watchProviderConfig
@@ -7588,8 +7900,8 @@ var CollaborationWebSocketHandler = class {
7588
7900
  };
7589
7901
 
7590
7902
  // src/server/codebase-indexing.ts
7591
- import * as fs11 from "fs";
7592
- import * as path15 from "path";
7903
+ import * as fs12 from "fs";
7904
+ import * as path16 from "path";
7593
7905
  import {
7594
7906
  cancelPendingReindexes,
7595
7907
  enqueueReindex,
@@ -7638,11 +7950,11 @@ function setupWebUICodebaseIndexing(deps2) {
7638
7950
  let watcher;
7639
7951
  if (idx.watchExternal) {
7640
7952
  try {
7641
- watcher = fs11.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
7953
+ watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
7642
7954
  if (!filename) return;
7643
7955
  const rel = filename.toString();
7644
7956
  if (isIgnored(rel)) return;
7645
- const abs = path15.resolve(deps2.projectRoot, rel);
7957
+ const abs = path16.resolve(deps2.projectRoot, rel);
7646
7958
  enqueueFile(abs);
7647
7959
  });
7648
7960
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -7655,7 +7967,7 @@ function setupWebUICodebaseIndexing(deps2) {
7655
7967
  }
7656
7968
  function enqueueFile(filePath) {
7657
7969
  if (!idx.onEdit && !idx.watchExternal) return;
7658
- const abs = path15.isAbsolute(filePath) ? path15.normalize(filePath) : path15.resolve(deps2.projectRoot, filePath);
7970
+ const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
7659
7971
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
7660
7972
  enqueueReindex({
7661
7973
  projectRoot: deps2.projectRoot,
@@ -7692,75 +8004,150 @@ function isIgnored(rel) {
7692
8004
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
7693
8005
  }
7694
8006
  function isInside2(root, target) {
7695
- const normalizedRoot = path15.resolve(root);
7696
- const normalizedTarget = path15.resolve(target);
7697
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path15.sep);
8007
+ const normalizedRoot = path16.resolve(root);
8008
+ const normalizedTarget = path16.resolve(target);
8009
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
7698
8010
  }
7699
8011
 
7700
8012
  // src/server/discover-mailbox-bridge.ts
7701
- import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core";
8013
+ import { spawn as spawn3 } from "child_process";
8014
+ import { createRequire as createRequire2 } from "module";
8015
+ import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
7702
8016
  import { readLiveLock } from "@wrongstack/core/coordination";
8017
+ var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
7703
8018
  async function discoverMailboxBridgeForWebui(params) {
7704
8019
  const mode = params.config?.features?.mailboxBridge ?? "auto";
7705
8020
  if (mode === "off") return;
7706
- const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot3());
7707
- const result = await readLiveLock(projectDir);
8021
+ const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot2());
8022
+ let result = await readLiveLock(projectDir);
8023
+ let spawnedPid = null;
8024
+ if (result.kind !== "live") {
8025
+ spawnedPid = spawnMailboxBridge(params.projectRoot, params.logger);
8026
+ if (spawnedPid !== null) {
8027
+ const live = await waitForLiveMailboxBridge(projectDir, MAILBOX_BRIDGE_BOOT_TIMEOUT_MS);
8028
+ if (live) {
8029
+ stashBridge(params, live, projectDir, "spawned", spawnedPid);
8030
+ params.logger.debug("webui spawned mailbox bridge", {
8031
+ url: live.url,
8032
+ lockPath: projectDir,
8033
+ childPid: spawnedPid
8034
+ });
8035
+ return;
8036
+ }
8037
+ result = await readLiveLock(projectDir);
8038
+ }
8039
+ }
7708
8040
  switch (result.kind) {
7709
8041
  case "live": {
7710
8042
  params.logger.debug("webui joined existing mailbox bridge", {
7711
8043
  url: result.lock.url,
7712
8044
  lockPath: projectDir
7713
8045
  });
7714
- params.ctx.meta["mailboxBridge"] = {
7715
- url: result.lock.url,
7716
- token: result.lock.token,
7717
- lockPath: projectDir,
7718
- childPid: null,
7719
- source: "joined"
7720
- };
8046
+ stashBridge(params, result.lock, projectDir, "joined", null);
7721
8047
  break;
7722
8048
  }
7723
8049
  case "probe-failed": {
7724
8050
  params.logger.warn(
7725
8051
  "mailbox bridge present but /healthz unreachable; webui will start without external-agent connectivity",
7726
- { url: result.lock.url, lockPath: projectDir }
8052
+ { url: result.lock.url, lockPath: projectDir, spawnedPid }
7727
8053
  );
7728
- params.ctx.meta["mailboxBridge"] = {
7729
- url: result.lock.url,
7730
- token: result.lock.token,
7731
- lockPath: projectDir,
7732
- childPid: null,
7733
- source: "unhealthy"
7734
- };
8054
+ stashBridge(params, result.lock, projectDir, "unhealthy", null);
7735
8055
  break;
7736
8056
  }
7737
8057
  case "absent": {
7738
- params.logger.info(
7739
- "no mailbox bridge running; webui will start without external-agent connectivity. Run `wstack mailbox serve` or a CLI surface to bring one up.",
7740
- { projectDir }
8058
+ params.logger.warn(
8059
+ "mailbox bridge unavailable; webui will start without external-agent connectivity. Run `wstack mailbox serve` or a CLI surface to bring one up.",
8060
+ { projectDir, spawnedPid }
7741
8061
  );
7742
8062
  break;
7743
8063
  }
7744
8064
  }
7745
8065
  }
8066
+ function stashBridge(params, lock, lockPath, source, childPid) {
8067
+ params.ctx.meta["mailboxBridge"] = {
8068
+ url: lock.url,
8069
+ token: lock.token,
8070
+ lockPath,
8071
+ childPid,
8072
+ source
8073
+ };
8074
+ }
8075
+ async function waitForLiveMailboxBridge(projectDir, timeoutMs) {
8076
+ const deadline = Date.now() + timeoutMs;
8077
+ while (Date.now() < deadline) {
8078
+ await sleep(120);
8079
+ const result = await readLiveLock(projectDir);
8080
+ if (result.kind === "live") return result.lock;
8081
+ }
8082
+ return null;
8083
+ }
8084
+ function spawnMailboxBridge(projectRoot, logger) {
8085
+ const invocation = mailboxServeInvocation();
8086
+ try {
8087
+ const child = spawn3(invocation.command, invocation.args, {
8088
+ cwd: projectRoot,
8089
+ detached: process.platform !== "win32",
8090
+ env: process.env,
8091
+ stdio: "ignore",
8092
+ windowsHide: true,
8093
+ ...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {}
8094
+ });
8095
+ child.once("error", (err) => {
8096
+ logger.warn("failed to spawn mailbox bridge for webui", {
8097
+ command: invocation.command,
8098
+ err: err instanceof Error ? err.message : String(err)
8099
+ });
8100
+ });
8101
+ child.unref();
8102
+ return child.pid ?? null;
8103
+ } catch (err) {
8104
+ logger.warn("failed to spawn mailbox bridge for webui", {
8105
+ command: invocation.command,
8106
+ err: err instanceof Error ? err.message : String(err)
8107
+ });
8108
+ return null;
8109
+ }
8110
+ }
8111
+ function mailboxServeInvocation() {
8112
+ const explicitCliEntry = process.env["WRONGSTACK_CLI_ENTRY"];
8113
+ if (explicitCliEntry) {
8114
+ return { command: process.execPath, args: [explicitCliEntry, "mailbox", "serve"] };
8115
+ }
8116
+ try {
8117
+ const require2 = createRequire2(import.meta.url);
8118
+ const cliEntry = require2.resolve("@wrongstack/cli");
8119
+ return { command: process.execPath, args: [cliEntry, "mailbox", "serve"] };
8120
+ } catch {
8121
+ return {
8122
+ command: process.platform === "win32" ? "wstack.cmd" : "wstack",
8123
+ args: ["mailbox", "serve"]
8124
+ };
8125
+ }
8126
+ }
8127
+ function sleep(ms) {
8128
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
8129
+ }
7746
8130
 
7747
8131
  // src/server/terminal-ws-handler.ts
7748
- import { createRequire as createRequire2 } from "module";
8132
+ import { spawn as spawnChild } from "child_process";
8133
+ import { createRequire as createRequire3 } from "module";
7749
8134
  import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
7750
8135
  var MAX_SESSIONS_PER_CLIENT = 8;
7751
8136
  var DEFAULT_COLS = 80;
7752
8137
  var DEFAULT_ROWS = 24;
7753
- var requireFromHere = createRequire2(import.meta.url);
8138
+ var requireFromHere = createRequire3(import.meta.url);
7754
8139
  var cachedNodePty;
7755
8140
  var TerminalWebSocketHandler = class {
7756
- constructor(getCwd, logger, loadNodePty = defaultLoadNodePty) {
8141
+ constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
7757
8142
  this.getCwd = getCwd;
7758
8143
  this.logger = logger;
7759
8144
  this.loadNodePty = loadNodePty;
8145
+ this.killProcessTree = killProcessTree;
7760
8146
  }
7761
8147
  getCwd;
7762
8148
  logger;
7763
8149
  loadNodePty;
8150
+ killProcessTree;
7764
8151
  /** ws → (terminalId → pty). */
7765
8152
  sessions = /* @__PURE__ */ new Map();
7766
8153
  addClient(ws) {
@@ -7822,10 +8209,14 @@ var TerminalWebSocketHandler = class {
7822
8209
  cols: clampDim(payload.cols, DEFAULT_COLS),
7823
8210
  rows: clampDim(payload.rows, DEFAULT_ROWS),
7824
8211
  cwd: this.getCwd(),
7825
- env: process.env
8212
+ env: process.env,
8213
+ ...windowsPtyOptions()
7826
8214
  });
7827
8215
  } catch (err) {
8216
+ const msg = `Integrated terminal failed to start: ${toErrorMessage7(err)}`;
7828
8217
  this.logger.warn?.(`terminal spawn failed: ${toErrorMessage7(err)}`);
8218
+ this.send(ws, { type: "terminal.output", payload: { id: payload.id, data: `${msg}\r
8219
+ ` } });
7829
8220
  this.send(ws, { type: "terminal.exit", payload: { id: payload.id, exitCode: -1 } });
7830
8221
  return;
7831
8222
  }
@@ -7858,21 +8249,38 @@ var TerminalWebSocketHandler = class {
7858
8249
  const pty = map?.get(id);
7859
8250
  if (!pty) return;
7860
8251
  map?.delete(id);
7861
- try {
7862
- pty.kill();
7863
- } catch {
7864
- }
8252
+ this.killPty(pty, "terminal close");
7865
8253
  }
7866
8254
  disposeClient(ws) {
7867
8255
  const map = this.sessions.get(ws);
7868
8256
  if (!map) return;
7869
8257
  for (const pty of map.values()) {
8258
+ this.killPty(pty, "terminal client dispose");
8259
+ }
8260
+ this.sessions.delete(ws);
8261
+ }
8262
+ killPty(pty, reason) {
8263
+ if (process.platform === "win32" && isPositivePid(pty.pid)) {
8264
+ this.killWindowsProcessTree(pty, reason);
8265
+ return;
8266
+ }
8267
+ try {
8268
+ pty.kill();
8269
+ } catch (err) {
8270
+ this.logger.warn?.(`${reason} failed: ${toErrorMessage7(err)}`);
8271
+ }
8272
+ }
8273
+ killWindowsProcessTree(pty, reason) {
8274
+ try {
8275
+ this.killProcessTree(pty.pid);
7870
8276
  try {
7871
- pty.kill();
8277
+ pty.write("");
8278
+ pty.write("exit\r");
7872
8279
  } catch {
7873
8280
  }
8281
+ } catch (err) {
8282
+ this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage7(err)}`);
7874
8283
  }
7875
- this.sessions.delete(ws);
7876
8284
  }
7877
8285
  send(ws, msg) {
7878
8286
  try {
@@ -7896,6 +8304,21 @@ function isStr(v) {
7896
8304
  function numOrUndef(v) {
7897
8305
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
7898
8306
  }
8307
+ function windowsPtyOptions() {
8308
+ if (process.platform !== "win32") return {};
8309
+ if (process.env.WRONGSTACK_TERMINAL_USE_CONPTY_DLL !== "1") return {};
8310
+ return { useConptyDll: true };
8311
+ }
8312
+ function defaultKillProcessTree(pid) {
8313
+ const child = spawnChild("taskkill", ["/PID", String(pid), "/T", "/F"], {
8314
+ stdio: "ignore",
8315
+ windowsHide: true
8316
+ });
8317
+ child.once("error", () => void 0);
8318
+ }
8319
+ function isPositivePid(value) {
8320
+ return Number.isInteger(value) && Number(value) > 0;
8321
+ }
7899
8322
  function clampDim(value, fallback) {
7900
8323
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
7901
8324
  return Math.max(1, Math.min(1e3, Math.floor(value)));
@@ -7918,20 +8341,14 @@ async function createAgentServices(input) {
7918
8341
  providerRegistry,
7919
8342
  modelsRegistry,
7920
8343
  events,
7921
- mcpRegistry,
7922
- skillLoader,
7923
- skillInstaller,
7924
- tokenCounter,
7925
8344
  modelCapabilitiesRef
7926
8345
  } = input;
7927
8346
  const collabBus = new CollaborationBusCtor();
7928
8347
  const pipelines = input.pipelines;
7929
- const collabPause = collabPauseMiddleware(collabBus, { logger });
7930
- Object.defineProperty(collabPause, "name", { value: "collab-pause" });
7931
- pipelines.toolCall.prepend(collabPause);
7932
8348
  const collabInject = collabInjectMiddleware(collabBus, { logger });
7933
- Object.defineProperty(collabInject, "name", { value: "collab-inject" });
7934
8349
  pipelines.toolCall.prepend(collabInject);
8350
+ const collabPause = collabPauseMiddleware(collabBus, { logger });
8351
+ pipelines.toolCall.prepend(collabPause);
7935
8352
  installDesignStudioMiddleware({ pipelines, ctx: context });
7936
8353
  const codebaseIndexing = setupWebUICodebaseIndexing({
7937
8354
  config,
@@ -7952,7 +8369,12 @@ async function createAgentServices(input) {
7952
8369
  let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
7953
8370
  if (!effectiveMaxContext) {
7954
8371
  try {
7955
- const m = await modelsRegistry.getModel(provider.id, context.model);
8372
+ const m = await resolveProviderModelMetadata(
8373
+ modelsRegistry,
8374
+ config.provider,
8375
+ context.model,
8376
+ config.providers?.[config.provider]
8377
+ );
7956
8378
  effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
7957
8379
  } catch {
7958
8380
  }
@@ -7983,15 +8405,21 @@ async function createAgentServices(input) {
7983
8405
  );
7984
8406
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
7985
8407
  }
7986
- const updateAutoCompactionMaxContext = async (newProvider) => {
8408
+ const updateAutoCompactionMaxContext = async (newProvider, providerId = newProvider.id, providerCfg) => {
7987
8409
  await modelsRegistry.refresh().catch((err) => {
7988
8410
  logger.warn(
7989
- `models.dev refresh failed for ${newProvider.id}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
8411
+ `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
7990
8412
  );
7991
8413
  });
7992
- let newMaxContext = config.context?.effectiveMaxContext ?? newProvider.capabilities.maxContext;
8414
+ const currentConfig = input.config;
8415
+ let newMaxContext = currentConfig.context?.effectiveMaxContext ?? newProvider.capabilities.maxContext;
7993
8416
  try {
7994
- const m = await modelsRegistry.getModel(newProvider.id, context.model);
8417
+ const m = await resolveProviderModelMetadata(
8418
+ modelsRegistry,
8419
+ providerId,
8420
+ context.model,
8421
+ providerCfg ?? currentConfig.providers?.[providerId]
8422
+ );
7995
8423
  newMaxContext = m?.capabilities?.maxContext ?? newMaxContext;
7996
8424
  } catch {
7997
8425
  }
@@ -8200,6 +8628,24 @@ async function createAgentServices(input) {
8200
8628
  };
8201
8629
  }
8202
8630
 
8631
+ // src/server/pending-confirms.ts
8632
+ function isDestructivePendingConfirm(confirm) {
8633
+ return confirm.riskTier === "destructive" || confirm.decisionSource === "yolo_destructive";
8634
+ }
8635
+ function resolveYoloEligiblePendingConfirms(pendingConfirms) {
8636
+ for (const [id, confirm] of pendingConfirms) {
8637
+ if (isDestructivePendingConfirm(confirm)) continue;
8638
+ pendingConfirms.delete(id);
8639
+ confirm.resolve("yes");
8640
+ }
8641
+ }
8642
+ function resolveAllPendingConfirms(pendingConfirms, decision) {
8643
+ for (const [id, confirm] of pendingConfirms) {
8644
+ pendingConfirms.delete(id);
8645
+ confirm.resolve(decision);
8646
+ }
8647
+ }
8648
+
8203
8649
  // src/server/connection-handler.ts
8204
8650
  var REPLAY_MESSAGE_CAP = 2e3;
8205
8651
  var RATE_LIMIT_MESSAGES = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
@@ -8311,12 +8757,7 @@ function createConnectionHandler(opts) {
8311
8757
  const closing = opts.clients.get(ws);
8312
8758
  opts.clients.delete(ws);
8313
8759
  if (closing) rateLimits.delete(closing.connId);
8314
- if (opts.pendingConfirms.size > 0) {
8315
- for (const [, resolve11] of opts.pendingConfirms) {
8316
- resolve11("no");
8317
- }
8318
- opts.pendingConfirms.clear();
8319
- }
8760
+ resolveAllPendingConfirms(opts.pendingConfirms, "no");
8320
8761
  });
8321
8762
  ws.on("error", (err) => {
8322
8763
  console.warn(
@@ -8331,6 +8772,9 @@ function createConnectionHandler(opts) {
8331
8772
  };
8332
8773
  }
8333
8774
 
8775
+ // src/server/message-dispatcher.ts
8776
+ import path17 from "path";
8777
+
8334
8778
  // src/server/autophase-routes.ts
8335
8779
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
8336
8780
  if (!msg.type.startsWith("autophase.")) return false;
@@ -8357,15 +8801,15 @@ async function handleBrainRoute(ws, msg, handlers) {
8357
8801
 
8358
8802
  // src/server/goal-handlers.ts
8359
8803
  import { resolveWstackPaths } from "@wrongstack/core/utils";
8360
- async function handleGoalGet(projectRoot, broadcast3) {
8804
+ async function handleGoalGet(projectRoot, broadcast2) {
8361
8805
  try {
8362
8806
  const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
8363
- const { readFile: readFile9 } = await import("fs/promises");
8364
- const raw = await readFile9(goalPath, "utf8");
8807
+ const { readFile: readFile10 } = await import("fs/promises");
8808
+ const raw = await readFile10(goalPath, "utf8");
8365
8809
  const goal = JSON.parse(raw);
8366
- broadcast3({ type: "goal.updated", payload: goal });
8810
+ broadcast2({ type: "goal.updated", payload: goal });
8367
8811
  } catch {
8368
- broadcast3({ type: "goal.updated", payload: null });
8812
+ broadcast2({ type: "goal.updated", payload: null });
8369
8813
  }
8370
8814
  }
8371
8815
 
@@ -9025,6 +9469,16 @@ function createMessageDispatcher(opts) {
9025
9469
  broadcast: (m) => broadcast(state.getClients(), m)
9026
9470
  };
9027
9471
  }
9472
+ function makeSkillsContext() {
9473
+ const projectRoot = state.getProjectRoot();
9474
+ return {
9475
+ skillLoader: deps2.skillLoader,
9476
+ skillInstaller: deps2.skillInstaller,
9477
+ projectRoot,
9478
+ projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
9479
+ globalSkillsDir: deps2.wpaths.globalSkills
9480
+ };
9481
+ }
9028
9482
  function messageSessionId(msg) {
9029
9483
  const payload = msg.payload;
9030
9484
  return payload && typeof payload === "object" && typeof payload.sessionId === "string" ? payload.sessionId : void 0;
@@ -9137,10 +9591,10 @@ function createMessageDispatcher(opts) {
9137
9591
  case "tool.confirm_result": {
9138
9592
  if (!ensureCurrentSession(ws, msg, "tool.confirm_result")) return;
9139
9593
  const { id, decision } = msg.payload;
9140
- const resolve11 = pendingConfirms.get(id);
9141
- if (resolve11) {
9594
+ const confirm = pendingConfirms.get(id);
9595
+ if (confirm) {
9142
9596
  pendingConfirms.delete(id);
9143
- resolve11(decision);
9597
+ confirm.resolve(decision);
9144
9598
  }
9145
9599
  break;
9146
9600
  }
@@ -9198,60 +9652,28 @@ function createMessageDispatcher(opts) {
9198
9652
  throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
9199
9653
  // Skills — full request→response cycle lives in skills-handlers.ts.
9200
9654
  case "skills.list":
9201
- await handleSkillsList(ws, {
9202
- skillLoader: deps2.skillLoader,
9203
- skillInstaller: deps2.skillInstaller,
9204
- projectRoot: state.getProjectRoot()
9205
- });
9655
+ await handleSkillsList(ws, makeSkillsContext());
9206
9656
  break;
9207
9657
  case "skills.content":
9208
- await handleSkillsContent(ws, {
9209
- skillLoader: deps2.skillLoader,
9210
- skillInstaller: deps2.skillInstaller,
9211
- projectRoot: state.getProjectRoot()
9212
- }, msg);
9658
+ await handleSkillsContent(ws, makeSkillsContext(), msg);
9213
9659
  break;
9214
9660
  case "skills.install":
9215
- await handleSkillsInstall(ws, {
9216
- skillLoader: deps2.skillLoader,
9217
- skillInstaller: deps2.skillInstaller,
9218
- projectRoot: state.getProjectRoot()
9219
- }, msg);
9661
+ await handleSkillsInstall(ws, makeSkillsContext(), msg);
9220
9662
  break;
9221
9663
  case "skills.uninstall":
9222
- await handleSkillsUninstall(ws, {
9223
- skillLoader: deps2.skillLoader,
9224
- skillInstaller: deps2.skillInstaller,
9225
- projectRoot: state.getProjectRoot()
9226
- }, msg);
9664
+ await handleSkillsUninstall(ws, makeSkillsContext(), msg);
9227
9665
  break;
9228
9666
  case "skills.update":
9229
- await handleSkillsUpdate(ws, {
9230
- skillLoader: deps2.skillLoader,
9231
- skillInstaller: deps2.skillInstaller,
9232
- projectRoot: state.getProjectRoot()
9233
- }, msg);
9667
+ await handleSkillsUpdate(ws, makeSkillsContext(), msg);
9234
9668
  break;
9235
9669
  case "skills.create":
9236
- await handleSkillsCreate(ws, {
9237
- skillLoader: deps2.skillLoader,
9238
- skillInstaller: deps2.skillInstaller,
9239
- projectRoot: state.getProjectRoot()
9240
- }, msg);
9670
+ await handleSkillsCreate(ws, makeSkillsContext(), msg);
9241
9671
  break;
9242
9672
  case "skills.edit":
9243
- await handleSkillsEdit(ws, {
9244
- skillLoader: deps2.skillLoader,
9245
- skillInstaller: deps2.skillInstaller,
9246
- projectRoot: state.getProjectRoot()
9247
- }, msg);
9673
+ await handleSkillsEdit(ws, makeSkillsContext(), msg);
9248
9674
  break;
9249
9675
  case "skills.export":
9250
- await handleSkillsExport(ws, {
9251
- skillLoader: deps2.skillLoader,
9252
- skillInstaller: deps2.skillInstaller,
9253
- projectRoot: state.getProjectRoot()
9254
- });
9676
+ await handleSkillsExport(ws, makeSkillsContext());
9255
9677
  break;
9256
9678
  // Prompt library — shared handlers (prompts-handlers.ts).
9257
9679
  case "prompts.list":
@@ -9365,7 +9787,12 @@ function createMessageDispatcher(opts) {
9365
9787
  const session = state.getSession();
9366
9788
  const usage = deps2.tokenCounter.total();
9367
9789
  const cacheStats = deps2.tokenCounter.cacheStats();
9368
- const m = await deps2.modelsRegistry.getModel(config.provider, config.model).catch(() => null);
9790
+ const m = await resolveProviderModelMetadata(
9791
+ deps2.modelsRegistry,
9792
+ config.provider,
9793
+ config.model,
9794
+ config.providers?.[config.provider]
9795
+ ).catch(() => null);
9369
9796
  const cost = computeUsageCost(usage, getCostRates(m));
9370
9797
  send(ws, {
9371
9798
  type: "stats.get",
@@ -9456,7 +9883,7 @@ function createMessageDispatcher(opts) {
9456
9883
  // src/server/pref-helpers.ts
9457
9884
  import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
9458
9885
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
9459
- import * as fs12 from "fs/promises";
9886
+ import * as fs13 from "fs/promises";
9460
9887
  var PREF_KEYS = [
9461
9888
  "autonomy",
9462
9889
  "autonomyDelayMs",
@@ -9482,6 +9909,7 @@ var PREF_KEYS = [
9482
9909
  "tokenSavingTier",
9483
9910
  "maxConcurrent",
9484
9911
  "titleAnimation",
9912
+ "uiLocale",
9485
9913
  "logLevel",
9486
9914
  "auditLevel",
9487
9915
  "hqEnabled",
@@ -9515,7 +9943,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
9515
9943
  const write = async () => {
9516
9944
  let raw;
9517
9945
  try {
9518
- raw = await fs12.readFile(globalConfigPath, "utf8");
9946
+ raw = await fs13.readFile(globalConfigPath, "utf8");
9519
9947
  } catch {
9520
9948
  raw = "{}";
9521
9949
  }
@@ -9557,7 +9985,10 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9557
9985
  setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
9558
9986
  if (typeof payload["autoProceedMaxIterations"] === "number")
9559
9987
  setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
9560
- if (typeof payload["yolo"] === "boolean") setAutonomy("yolo", payload["yolo"]);
9988
+ if (typeof payload["yolo"] === "boolean") {
9989
+ setAutonomy("yolo", payload["yolo"]);
9990
+ decrypted.yolo = payload["yolo"];
9991
+ }
9561
9992
  if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
9562
9993
  if (typeof payload["confirmExit"] === "boolean")
9563
9994
  setAutonomy("confirmExit", payload["confirmExit"]);
@@ -9572,6 +10003,7 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9572
10003
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
9573
10004
  if (typeof payload["nextPrediction"] === "boolean")
9574
10005
  decrypted.nextPrediction = payload["nextPrediction"];
10006
+ if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
9575
10007
  if (Array.isArray(payload["fallbackModels"]))
9576
10008
  decrypted.fallbackModels = payload["fallbackModels"];
9577
10009
  if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
@@ -9689,16 +10121,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9689
10121
  }
9690
10122
 
9691
10123
  // src/server/projects-manifest.ts
9692
- import * as fs13 from "fs/promises";
9693
- import * as path16 from "path";
10124
+ import * as fs14 from "fs/promises";
10125
+ import * as path18 from "path";
9694
10126
  import { projectSlug } from "@wrongstack/core";
9695
10127
  function projectsJsonPath(globalConfigPath) {
9696
- const base = path16.dirname(globalConfigPath);
9697
- return path16.join(base, "projects.json");
10128
+ const base = path18.dirname(globalConfigPath);
10129
+ return path18.join(base, "projects.json");
9698
10130
  }
9699
10131
  async function loadManifest(globalConfigPath) {
9700
10132
  try {
9701
- const raw = await fs13.readFile(projectsJsonPath(globalConfigPath), "utf8");
10133
+ const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
9702
10134
  const parsed = JSON.parse(raw);
9703
10135
  return { projects: parsed.projects ?? [] };
9704
10136
  } catch {
@@ -9707,16 +10139,16 @@ async function loadManifest(globalConfigPath) {
9707
10139
  }
9708
10140
  async function saveManifest(manifest, globalConfigPath) {
9709
10141
  const file = projectsJsonPath(globalConfigPath);
9710
- await fs13.mkdir(path16.dirname(file), { recursive: true });
9711
- await fs13.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
10142
+ await fs14.mkdir(path18.dirname(file), { recursive: true });
10143
+ await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
9712
10144
  }
9713
10145
  function generateProjectSlug(rootPath) {
9714
10146
  return projectSlug(rootPath);
9715
10147
  }
9716
10148
  async function ensureProjectDataDir(slug, globalConfigPath) {
9717
- const base = path16.dirname(globalConfigPath);
9718
- const dir = path16.join(base, "projects", slug);
9719
- await fs13.mkdir(dir, { recursive: true });
10149
+ const base = path18.dirname(globalConfigPath);
10150
+ const dir = path18.join(base, "projects", slug);
10151
+ await fs14.mkdir(dir, { recursive: true });
9720
10152
  return dir;
9721
10153
  }
9722
10154
 
@@ -9750,7 +10182,7 @@ function projectSavedProviders(providers) {
9750
10182
  }
9751
10183
  var probeScrubber = new DefaultSecretScrubber();
9752
10184
  function createProviderHandlers(deps2) {
9753
- const { globalConfigPath, vault, broadcast: broadcast3, clients } = deps2;
10185
+ const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
9754
10186
  let configWriteLock = deps2.getConfigWriteLock();
9755
10187
  async function loadConfigProviders() {
9756
10188
  return loadSavedProviders(globalConfigPath, vault);
@@ -9838,7 +10270,7 @@ function createProviderHandlers(deps2) {
9838
10270
  }
9839
10271
  }
9840
10272
  function broadcastSaved(providers) {
9841
- broadcast3(clients, {
10273
+ broadcast2(clients, {
9842
10274
  type: "providers.saved",
9843
10275
  payload: { providers: projectSavedProviders(providers) }
9844
10276
  });
@@ -10031,7 +10463,7 @@ function createProviderHandlers(deps2) {
10031
10463
  }
10032
10464
 
10033
10465
  // src/server/routes.ts
10034
- import path19 from "path";
10466
+ import path21 from "path";
10035
10467
  import {
10036
10468
  enhanceUserPrompt,
10037
10469
  gatedEnhancerReasoning,
@@ -10046,14 +10478,19 @@ async function handleMailboxMessages(ws, deps2, payload) {
10046
10478
  try {
10047
10479
  const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
10048
10480
  const mb = new GlobalMailbox3(dir);
10481
+ const limit = payload?.limit ?? 30;
10482
+ const unreadForAgent = payload?.unreadOnly === true && payload.agentId !== void 0;
10049
10483
  const messages = await mb.query({
10050
- limit: payload?.limit ?? 30,
10484
+ limit: payload?.unreadOnly === true && payload.agentId === void 0 ? Math.max(limit * 5, 100) : limit,
10485
+ to: payload?.agentId,
10486
+ unreadBy: unreadForAgent ? payload.agentId : void 0,
10051
10487
  incompleteOnly: payload?.incompleteOnly ?? false
10052
10488
  });
10489
+ const visibleMessages = payload?.unreadOnly === true && payload.agentId === void 0 ? messages.filter((m) => Object.keys(m.readBy).length === 0).slice(0, limit) : messages;
10053
10490
  send(ws, {
10054
10491
  type: "mailbox.messages",
10055
10492
  payload: {
10056
- messages: messages.map((m) => ({
10493
+ messages: visibleMessages.map((m) => ({
10057
10494
  id: m.id,
10058
10495
  from: m.from,
10059
10496
  to: m.to,
@@ -10215,15 +10652,7 @@ function createModeHandlers(ctx) {
10215
10652
  }
10216
10653
 
10217
10654
  // src/server/project-handlers.ts
10218
- import * as fs14 from "fs/promises";
10219
- import * as path17 from "path";
10220
- import {
10221
- DefaultSessionStore as DefaultSessionStore2,
10222
- DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
10223
- getSessionRegistry as getSessionRegistry2,
10224
- resolveWstackPaths as resolveWstackPaths3,
10225
- ToolValidationError as ToolValidationError6
10226
- } from "@wrongstack/core";
10655
+ import * as path19 from "path";
10227
10656
  function createProjectHandlers(ctx) {
10228
10657
  return {
10229
10658
  listProjects: async (ws) => {
@@ -10235,213 +10664,29 @@ function createProjectHandlers(ctx) {
10235
10664
  }
10236
10665
  },
10237
10666
  addProject: async (ws, msg) => {
10238
- const parsed = validateProjectsAddPayload(msg.payload);
10239
- if (!parsed.ok) {
10240
- send(ws, {
10241
- type: "projects.added",
10242
- payload: { name: "", root: "", slug: "", message: parsed.message }
10243
- });
10244
- return;
10245
- }
10246
- const { root: addRoot, name: displayName } = parsed.value;
10247
- try {
10248
- const resolved = path17.resolve(addRoot);
10249
- await fs14.access(resolved);
10250
- const stat3 = await fs14.stat(resolved);
10251
- if (!stat3.isDirectory()) {
10252
- throw new ToolValidationError6({ message: `Not a directory: ${resolved}`, field: "path" });
10253
- }
10254
- const manifest = await loadManifest(ctx.globalConfigPath);
10255
- const existing = manifest.projects.find((p) => p.root === resolved);
10256
- if (existing) {
10257
- send(ws, {
10258
- type: "projects.added",
10259
- payload: {
10260
- name: existing.name,
10261
- root: existing.root,
10262
- slug: existing.slug,
10263
- message: `Already registered as "${existing.name}"`
10264
- }
10265
- });
10266
- return;
10667
+ const payload = msg.payload;
10668
+ send(ws, {
10669
+ type: "projects.added",
10670
+ payload: {
10671
+ name: typeof payload?.name === "string" ? payload.name : "",
10672
+ root: typeof payload?.root === "string" ? payload.root : "",
10673
+ slug: "",
10674
+ message: "Project registration is disabled in WebUI. Open/register projects from the launcher or desktop shell."
10267
10675
  }
10268
- const name2 = displayName?.trim() || path17.basename(resolved);
10269
- const slug = generateProjectSlug(resolved);
10270
- await ensureProjectDataDir(slug, ctx.globalConfigPath);
10271
- const now = (/* @__PURE__ */ new Date()).toISOString();
10272
- manifest.projects.push({ name: name2, root: resolved, slug, lastSeen: now, createdAt: now });
10273
- await saveManifest(manifest, ctx.globalConfigPath);
10274
- send(ws, {
10275
- type: "projects.added",
10276
- payload: { name: name2, root: resolved, slug, message: `Registered project "${name2}"` }
10277
- });
10278
- } catch (err) {
10279
- send(ws, {
10280
- type: "projects.added",
10281
- payload: { name: path17.basename(addRoot), root: addRoot, slug: "", message: errMessage(err) }
10282
- });
10283
- }
10676
+ });
10284
10677
  },
10285
10678
  selectProject: async (ws, msg) => {
10286
- const parsed = validateProjectsSelectPayload(msg.payload);
10287
- if (!parsed.ok) {
10288
- send(ws, {
10289
- type: "projects.selected",
10290
- payload: { root: "", name: "", message: parsed.message }
10291
- });
10292
- return;
10293
- }
10294
- const { root: selRoot, name: selName } = parsed.value;
10295
- try {
10296
- const resolved = path17.resolve(selRoot);
10297
- try {
10298
- await fs14.access(resolved);
10299
- const stat3 = await fs14.stat(resolved);
10300
- if (!stat3.isDirectory()) {
10301
- throw new ToolValidationError6({ message: `Not a directory: ${resolved}`, field: "path" });
10302
- }
10303
- } catch (err) {
10304
- send(ws, {
10305
- type: "projects.selected",
10306
- payload: {
10307
- root: selRoot,
10308
- name: selName || path17.basename(selRoot),
10309
- message: `Cannot switch: ${errMessage(err)}`
10310
- }
10311
- });
10312
- return;
10313
- }
10314
- const manifest = await loadManifest(ctx.globalConfigPath);
10315
- const entry = manifest.projects.find((p) => p.root === resolved);
10316
- if (entry) {
10317
- entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
10318
- entry.lastWorkingDir = resolved;
10319
- } else {
10320
- const name2 = selName?.trim() || path17.basename(resolved);
10321
- const slug = generateProjectSlug(resolved);
10322
- manifest.projects.push({
10323
- name: name2,
10324
- root: resolved,
10325
- slug,
10326
- lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
10327
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
10328
- lastWorkingDir: resolved
10329
- });
10330
- await ensureProjectDataDir(slug, ctx.globalConfigPath);
10331
- }
10332
- await saveManifest(manifest, ctx.globalConfigPath);
10333
- ctx.abortRunLock();
10334
- ctx.setProjectRoot(resolved);
10335
- ctx.setWorkingDir(resolved);
10336
- ctx.context.cwd = resolved;
10337
- ctx.context.projectRoot = resolved;
10338
- const switchSlug = entry?.slug ?? generateProjectSlug(resolved);
10339
- try {
10340
- const modeId = ctx.getModeId();
10341
- const switchMode = modeId === "default" ? void 0 : await ctx.modeStore.getMode(modeId);
10342
- const switchPaths = resolveWstackPaths3({
10343
- projectRoot: resolved,
10344
- globalRoot: ctx.wpaths.globalRoot
10345
- });
10346
- const switchBuilder = new DefaultSystemPromptBuilder3({
10347
- memoryStore: ctx.memoryStore,
10348
- skillLoader: ctx.skillLoader,
10349
- modeStore: ctx.modeStore,
10350
- modeId,
10351
- modePrompt: switchMode?.prompt ?? "",
10352
- modelCapabilities: ctx.modelCapabilities,
10353
- instructionPaths: {
10354
- globalDir: switchPaths.globalInstructions,
10355
- projectDir: switchPaths.inProjectInstructions
10356
- }
10357
- });
10358
- ctx.context.systemPrompt = await switchBuilder.build({
10359
- cwd: resolved,
10360
- projectRoot: resolved,
10361
- tools: ctx.toolRegistry.list(),
10362
- provider: ctx.config.provider,
10363
- model: ctx.config.model
10364
- });
10365
- } catch {
10366
- }
10367
- const newSessionsDir = path17.join(
10368
- path17.dirname(ctx.globalConfigPath),
10369
- "projects",
10370
- switchSlug,
10371
- "sessions"
10372
- );
10373
- await fs14.mkdir(newSessionsDir, { recursive: true });
10374
- const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
10375
- const oldSession = ctx.getSession();
10376
- const oldSessionId = oldSession.id;
10377
- try {
10378
- await oldSession.append({
10379
- type: "session_end",
10380
- ts: (/* @__PURE__ */ new Date()).toISOString(),
10381
- usage: ctx.tokenCounter.total()
10382
- });
10383
- await oldSession.close();
10384
- } catch {
10385
- }
10386
- ctx.setSessionStore(newSessionStore);
10387
- const newSession = await newSessionStore.create({
10388
- id: "",
10389
- title: "",
10390
- model: ctx.config.model,
10391
- provider: ctx.config.provider
10392
- });
10393
- ctx.setSession(newSession);
10394
- ctx.context.session = newSession;
10395
- ctx.context.state.replaceMessages([]);
10396
- ctx.context.state.replaceTodos([]);
10397
- ctx.context.readFiles.clear();
10398
- ctx.context.fileMtimes.clear();
10399
- ctx.tokenCounter.reset();
10400
- ctx.setSessionStartedAt(Date.now());
10401
- try {
10402
- const registry = getSessionRegistry2(ctx.wpaths.globalRoot);
10403
- await registry.register({
10404
- sessionId: newSession.id,
10405
- projectSlug: switchSlug,
10406
- projectRoot: resolved,
10407
- projectName: path17.basename(resolved),
10408
- workingDir: resolved,
10409
- clientType: "webui",
10410
- pid: process.pid,
10411
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
10412
- });
10413
- } catch {
10679
+ const payload = msg.payload;
10680
+ const root = typeof payload?.root === "string" ? payload.root : "";
10681
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
10682
+ send(ws, {
10683
+ type: "projects.selected",
10684
+ payload: {
10685
+ root,
10686
+ name: name2,
10687
+ message: "Project switching is disabled in WebUI. Open a project from the launcher or desktop shell instead."
10414
10688
  }
10415
- send(ws, {
10416
- type: "projects.selected",
10417
- payload: {
10418
- root: resolved,
10419
- name: selName || path17.basename(resolved),
10420
- message: `Switched to ${selName || path17.basename(resolved)}`
10421
- }
10422
- });
10423
- broadcast(ctx.clients, {
10424
- type: "subagent.event",
10425
- payload: { kind: "session_stopped", sessionId: oldSessionId }
10426
- });
10427
- broadcast(ctx.clients, {
10428
- type: "session.start",
10429
- payload: {
10430
- ...await ctx.sessionStartPayload(),
10431
- reset: true,
10432
- clearedSessionId: oldSessionId
10433
- }
10434
- });
10435
- } catch (err) {
10436
- send(ws, {
10437
- type: "projects.selected",
10438
- payload: {
10439
- root: selRoot,
10440
- name: selName || path17.basename(selRoot),
10441
- message: errMessage(err)
10442
- }
10443
- });
10444
- }
10689
+ });
10445
10690
  },
10446
10691
  setWorkingDir: async (ws, msg) => {
10447
10692
  const parsed = validateWorkingDirSetPayload(msg.payload);
@@ -10468,9 +10713,9 @@ function createProjectHandlers(ctx) {
10468
10713
  }
10469
10714
 
10470
10715
  // src/server/session-handlers.ts
10471
- import * as path18 from "path";
10716
+ import * as path20 from "path";
10472
10717
  import {
10473
- DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
10718
+ DEFAULT_CONTEXT_WINDOW_MODE_ID,
10474
10719
  repairToolUseAdjacency,
10475
10720
  resolveContextWindowPolicy as resolveContextWindowPolicy3
10476
10721
  } from "@wrongstack/core";
@@ -10552,7 +10797,7 @@ function createSessionHandlers(ctx) {
10552
10797
  type: "context.debug",
10553
10798
  payload: sessionPayload2({
10554
10799
  ...breakdown,
10555
- mode: ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2,
10800
+ mode: ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID,
10556
10801
  policy: ctx.context.meta["contextWindowPolicy"]
10557
10802
  })
10558
10803
  });
@@ -10606,7 +10851,7 @@ function createSessionHandlers(ctx) {
10606
10851
  },
10607
10852
  listContextModes: async (ws, msg) => {
10608
10853
  if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
10609
- const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2);
10854
+ const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
10610
10855
  const allModes = ctx.customModeStore.list().map((m) => ({
10611
10856
  id: m.id,
10612
10857
  name: m.name,
@@ -10696,8 +10941,8 @@ function createSessionHandlers(ctx) {
10696
10941
  }
10697
10942
  const { id } = parsed.value;
10698
10943
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
10699
- ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID2;
10700
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID2);
10944
+ ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
10945
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
10701
10946
  }
10702
10947
  const result = ctx.customModeStore.remove(id);
10703
10948
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -10788,7 +11033,7 @@ function createSessionHandlers(ctx) {
10788
11033
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
10789
11034
  const projectRoot = ctx.getProjectRoot();
10790
11035
  const rewinder = new DefaultSessionRewinder(
10791
- path18.join(projectRoot, ".wrongstack", "sessions"),
11036
+ path20.join(projectRoot, ".wrongstack", "sessions"),
10792
11037
  projectRoot
10793
11038
  );
10794
11039
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
@@ -10804,7 +11049,7 @@ function createSessionHandlers(ctx) {
10804
11049
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
10805
11050
  const projectRoot = ctx.getProjectRoot();
10806
11051
  const rewinder = new DefaultSessionRewinder(
10807
- path18.join(projectRoot, ".wrongstack", "sessions"),
11052
+ path20.join(projectRoot, ".wrongstack", "sessions"),
10808
11053
  projectRoot
10809
11054
  );
10810
11055
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
@@ -10822,6 +11067,29 @@ function createSessionHandlers(ctx) {
10822
11067
  }
10823
11068
 
10824
11069
  // src/server/routes.ts
11070
+ async function enrichProviderModelDescriptors(modelsRegistry, providerId, cfg, models) {
11071
+ return Promise.all(
11072
+ models.map(async (model) => {
11073
+ if (model.contextWindow && model.capabilities.length > 0) return model;
11074
+ const resolved = await resolveProviderModelMetadata(
11075
+ modelsRegistry,
11076
+ providerId,
11077
+ model.id,
11078
+ cfg
11079
+ ).catch(() => void 0);
11080
+ if (!resolved) return model;
11081
+ const capabilities = new Set(model.capabilities);
11082
+ if (resolved.capabilities.tools) capabilities.add("tools");
11083
+ if (resolved.capabilities.reasoning) capabilities.add("reasoning");
11084
+ if (resolved.capabilities.vision) capabilities.add("vision");
11085
+ return {
11086
+ ...model,
11087
+ contextWindow: model.contextWindow || resolved.capabilities.maxContext || void 0,
11088
+ capabilities: [...capabilities]
11089
+ };
11090
+ })
11091
+ );
11092
+ }
10825
11093
  function buildRoutes(state, deps2, cb) {
10826
11094
  const providerHandlers = createProviderHandlers({
10827
11095
  globalConfigPath: deps2.globalConfigPath,
@@ -10863,13 +11131,22 @@ function buildRoutes(state, deps2, cb) {
10863
11131
  const providerId = msg.payload.providerId;
10864
11132
  const saved = await providerHandlers.loadConfigProviders();
10865
11133
  const cfg = saved[providerId];
10866
- const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
10867
- const provider = await deps2.modelsRegistry.getProvider(catalogId);
11134
+ const provider = await resolveProviderCatalogForModels(
11135
+ deps2.modelsRegistry,
11136
+ providerId,
11137
+ cfg
11138
+ );
11139
+ const models = await enrichProviderModelDescriptors(
11140
+ deps2.modelsRegistry,
11141
+ providerId,
11142
+ cfg,
11143
+ resolveProviderModelList(cfg?.models, provider)
11144
+ );
10868
11145
  send(ws, {
10869
11146
  type: "provider.models",
10870
11147
  payload: {
10871
11148
  provider: providerId,
10872
- models: resolveProviderModelList(cfg?.models, provider)
11149
+ models
10873
11150
  }
10874
11151
  });
10875
11152
  },
@@ -10889,7 +11166,7 @@ function buildRoutes(state, deps2, cb) {
10889
11166
  const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
10890
11167
  const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
10891
11168
  deps2.context.provider = newProv;
10892
- await cb.updateAutoCompactionMaxContext(newProv);
11169
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
10893
11170
  await cb.updateGlobalConfig((config) => {
10894
11171
  config.provider = newProvider;
10895
11172
  config.model = newModel;
@@ -10925,7 +11202,12 @@ function buildRoutes(state, deps2, cb) {
10925
11202
  try {
10926
11203
  const history = recentTextTurns(deps2.context.messages);
10927
11204
  const cfg = state.getConfig();
10928
- const resolved = await deps2.modelsRegistry.getModel(cfg.provider ?? "", cfg.model ?? "").catch(() => void 0);
11205
+ const resolved = await resolveProviderModelMetadata(
11206
+ deps2.modelsRegistry,
11207
+ cfg.provider ?? "",
11208
+ cfg.model ?? "",
11209
+ cfg.providers?.[cfg.provider ?? ""]
11210
+ ).catch(() => void 0);
10929
11211
  const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
10930
11212
  const result = await enhanceUserPrompt({
10931
11213
  provider: deps2.context.provider,
@@ -11037,6 +11319,7 @@ function buildRoutes(state, deps2, cb) {
11037
11319
  void cb.persistPrefsToConfig(payload);
11038
11320
  if (typeof payload["yolo"] === "boolean") {
11039
11321
  deps2.permissionPolicy.setYolo?.(payload["yolo"]);
11322
+ if (payload["yolo"] === true) resolveYoloEligiblePendingConfirms(deps2.pendingConfirms);
11040
11323
  }
11041
11324
  const cfg = state.getConfig();
11042
11325
  const features = cfg.features ?? {};
@@ -11107,7 +11390,7 @@ function buildRoutes(state, deps2, cb) {
11107
11390
  }
11108
11391
  return handleMailboxMessages(
11109
11392
  ws,
11110
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11393
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11111
11394
  parsed.value
11112
11395
  );
11113
11396
  },
@@ -11119,13 +11402,13 @@ function buildRoutes(state, deps2, cb) {
11119
11402
  }
11120
11403
  return handleMailboxAgents(
11121
11404
  ws,
11122
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11405
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11123
11406
  parsed.value
11124
11407
  );
11125
11408
  },
11126
11409
  clear: (ws) => handleMailboxClear(
11127
11410
  ws,
11128
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) }
11411
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) }
11129
11412
  ),
11130
11413
  purge: (ws, msg) => {
11131
11414
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -11135,7 +11418,7 @@ function buildRoutes(state, deps2, cb) {
11135
11418
  }
11136
11419
  return handleMailboxPurge(
11137
11420
  ws,
11138
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11421
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11139
11422
  parsed.value
11140
11423
  );
11141
11424
  }
@@ -11281,9 +11564,8 @@ async function startWebUI(opts = {}) {
11281
11564
  let sessionStore = preContext.sessionStore;
11282
11565
  let session = preContext.session;
11283
11566
  let sessionStartedAt = preContext.sessionStartedAt;
11284
- let statusTracker = preContext.statusTracker;
11285
11567
  let modeId = preContext.modeId;
11286
- let needsSetup = preContext.needsSetup;
11568
+ const needsSetup = preContext.needsSetup;
11287
11569
  const prefSnapshot2 = () => prefSnapshot(context.meta);
11288
11570
  const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
11289
11571
  const agentServices = await createAgentServices({
@@ -11306,7 +11588,7 @@ async function startWebUI(opts = {}) {
11306
11588
  skillLoader,
11307
11589
  skillInstaller,
11308
11590
  tokenCounter,
11309
- pipelines: createDefaultPipelines2(),
11591
+ pipelines: createDefaultPipelines(),
11310
11592
  modelCapabilitiesRef,
11311
11593
  sessionGetter: () => session,
11312
11594
  sessionReader,
@@ -11332,13 +11614,16 @@ async function startWebUI(opts = {}) {
11332
11614
  collabHandler,
11333
11615
  updateAutoCompactionMaxContext
11334
11616
  } = agentServices;
11617
+ if (typeof context.meta["yolo"] === "boolean") {
11618
+ permissionPolicy.setYolo?.(context.meta["yolo"]);
11619
+ }
11335
11620
  const sessionStartPayload = createSessionStartPayload({
11336
11621
  getConfig: () => config,
11337
11622
  getSessionId: () => session.id,
11338
11623
  getProjectRoot: () => projectRoot,
11339
11624
  getWorkingDir: () => workingDir,
11340
11625
  getModeId: () => modeId,
11341
- getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID3),
11626
+ getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2),
11342
11627
  getNeedsSetup: () => needsSetup,
11343
11628
  modelsRegistry
11344
11629
  });
@@ -11369,8 +11654,6 @@ async function startWebUI(opts = {}) {
11369
11654
  averageDebounceDelayMs: 0,
11370
11655
  watcherActive: false
11371
11656
  };
11372
- let disposeEvents = null;
11373
- let fleetBroadcast = null;
11374
11657
  const eventArming = armEvents(wssPrimary, wssSecondary, wsHost, wsPort, {
11375
11658
  events,
11376
11659
  broadcast,
@@ -11383,21 +11666,21 @@ async function startWebUI(opts = {}) {
11383
11666
  wpaths
11384
11667
  }, watcherMetricsRef);
11385
11668
  async function touchProjectEntry(root, workDir) {
11386
- const resolved = path20.resolve(root);
11669
+ const resolved = path22.resolve(root);
11387
11670
  const manifest = await loadManifest(globalConfigPath);
11388
11671
  const now = (/* @__PURE__ */ new Date()).toISOString();
11389
- const existing = manifest.projects.find((p) => path20.resolve(p.root) === resolved);
11672
+ const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
11390
11673
  if (existing) {
11391
11674
  existing.lastSeen = now;
11392
- if (workDir) existing.lastWorkingDir = path20.resolve(workDir);
11675
+ if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
11393
11676
  } else {
11394
11677
  manifest.projects.push({
11395
- name: path20.basename(resolved),
11678
+ name: path22.basename(resolved),
11396
11679
  root: resolved,
11397
11680
  slug: generateProjectSlug(resolved),
11398
11681
  createdAt: now,
11399
11682
  lastSeen: now,
11400
- lastWorkingDir: workDir ? path20.resolve(workDir) : void 0
11683
+ lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
11401
11684
  });
11402
11685
  }
11403
11686
  await saveManifest(manifest, globalConfigPath);
@@ -11461,6 +11744,7 @@ async function startWebUI(opts = {}) {
11461
11744
  configStore,
11462
11745
  tokenCounter,
11463
11746
  permissionPolicy,
11747
+ pendingConfirms,
11464
11748
  pipelines,
11465
11749
  logger,
11466
11750
  memoryStore,
@@ -11502,6 +11786,7 @@ async function startWebUI(opts = {}) {
11502
11786
  let lastActiveCfg = JSON.stringify(
11503
11787
  state.getConfig().providers?.[deps2.context.provider.id] ?? null
11504
11788
  );
11789
+ let lastUiLocale = state.getConfig().uiLocale;
11505
11790
  const credentialWatcher = watchProviderConfig(
11506
11791
  globalConfigPath,
11507
11792
  vault,
@@ -11522,6 +11807,14 @@ async function startWebUI(opts = {}) {
11522
11807
  type: "providers.saved",
11523
11808
  payload: { providers: projectSavedProviders(snapshot.providers) }
11524
11809
  });
11810
+ const newUi = snapshot.uiLocale;
11811
+ if (newUi !== lastUiLocale) {
11812
+ lastUiLocale = newUi;
11813
+ if (newUi) {
11814
+ deps2.context.meta["uiLocale"] = newUi;
11815
+ broadcast(clients, { type: "prefs.updated", payload: { uiLocale: newUi } });
11816
+ }
11817
+ }
11525
11818
  const activeId = deps2.context.provider.id;
11526
11819
  const newCfgStr = JSON.stringify(snapshot.providers[activeId] ?? null);
11527
11820
  if (newCfgStr === lastActiveCfg) return;
@@ -11609,7 +11902,7 @@ async function startWebUI(opts = {}) {
11609
11902
  eternalSubscription = null;
11610
11903
  }
11611
11904
  codebaseIndexing.dispose();
11612
- return unregisterInstance(process.pid, path20.dirname(globalConfigPath));
11905
+ return unregisterInstance(process.pid, path22.dirname(globalConfigPath));
11613
11906
  }
11614
11907
  });
11615
11908
  }
@@ -11624,7 +11917,7 @@ function readArg(names) {
11624
11917
  if (current === name2) {
11625
11918
  const next = argv[i + 1];
11626
11919
  if (!next || next.startsWith("-")) {
11627
- throw new ToolValidationError7({ message: `${name2} requires a value`, field: name2 });
11920
+ throw new ToolValidationError6({ message: `${name2} requires a value`, field: name2 });
11628
11921
  }
11629
11922
  return next;
11630
11923
  }
@@ -11637,14 +11930,14 @@ function parsePort(value, fallback, label) {
11637
11930
  if (value === void 0) return fallback;
11638
11931
  const parsed = Number.parseInt(value, 10);
11639
11932
  if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
11640
- throw new ToolValidationError7({
11933
+ throw new ToolValidationError6({
11641
11934
  message: `${label} must be a port between 1 and 65535`,
11642
11935
  field: "port"
11643
11936
  });
11644
11937
  }
11645
11938
  return parsed;
11646
11939
  }
11647
- function envFlag3(name2) {
11940
+ function envFlag2(name2) {
11648
11941
  const value = process.env[name2]?.trim().toLowerCase();
11649
11942
  return value === "1" || value === "true" || value === "yes" || value === "on";
11650
11943
  }
@@ -11703,7 +11996,7 @@ if (argv.includes("--help") || argv.includes("-h")) {
11703
11996
  process.exit(1);
11704
11997
  }
11705
11998
  const open = argv.includes("--open") || argv.includes("-o") || process.env["WEBUI_OPEN"] === "1";
11706
- const requireToken = argv.includes("--require-token") || envFlag3("WEBUI_REQUIRE_TOKEN");
11999
+ const requireToken = argv.includes("--require-token") || envFlag2("WEBUI_REQUIRE_TOKEN");
11707
12000
  console.log(`[WebUI] Starting standalone server on ${wsHost} (http:${httpPort}, ws:${wsPort})...`);
11708
12001
  startWebUI({
11709
12002
  wsPort,