@wrongstack/webui 0.277.2 → 0.280.1

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
@@ -1397,11 +1397,11 @@ async function handleDesignVerify(ws, ctx) {
1397
1397
  }
1398
1398
 
1399
1399
  // src/server/eternal-iteration-broadcast.ts
1400
- function createEternalSubscription(subscribe, broadcast3, clientsRef) {
1400
+ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
1401
1401
  let disposed = false;
1402
1402
  const dispose = subscribe((entry) => {
1403
1403
  if (disposed) return;
1404
- broadcast3(clientsRef(), {
1404
+ broadcast2(clientsRef(), {
1405
1405
  type: "eternal.iteration",
1406
1406
  payload: { entry }
1407
1407
  });
@@ -1473,21 +1473,21 @@ import * as fs3 from "fs/promises";
1473
1473
  import * as path4 from "path";
1474
1474
  import { ToolValidationError } from "@wrongstack/core";
1475
1475
  function isPathInside(root, target) {
1476
- const relative3 = path4.relative(root, target);
1477
- return relative3 === "" || !relative3.startsWith("..") && !path4.isAbsolute(relative3);
1476
+ const relative4 = path4.relative(root, target);
1477
+ return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
1478
1478
  }
1479
1479
  async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1480
1480
  const resolved = path4.resolve(projectRoot, inputPath);
1481
- let stat3;
1481
+ let stat2;
1482
1482
  try {
1483
- stat3 = await fs3.stat(resolved);
1483
+ stat2 = await fs3.stat(resolved);
1484
1484
  } catch {
1485
1485
  throw new ToolValidationError({
1486
1486
  message: `Directory not found or not accessible: ${resolved}`,
1487
1487
  field: "path"
1488
1488
  });
1489
1489
  }
1490
- if (!stat3.isDirectory()) {
1490
+ if (!stat2.isDirectory()) {
1491
1491
  throw new ToolValidationError({
1492
1492
  message: `Directory not found or not accessible: ${resolved}`,
1493
1493
  field: "path"
@@ -1741,9 +1741,9 @@ async function handleGitInfo(ws, projectRoot) {
1741
1741
  const cwd = projectRoot || void 0;
1742
1742
  try {
1743
1743
  const { execFile: ef } = await import("child_process");
1744
- const git = (args) => new Promise((resolve11) => {
1744
+ const git = (args) => new Promise((resolve10) => {
1745
1745
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1746
- resolve11(err ? "" : stdout.trim());
1746
+ resolve10(err ? "" : stdout.trim());
1747
1747
  });
1748
1748
  });
1749
1749
  const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
@@ -1769,12 +1769,12 @@ async function handleGitInfo(ws, projectRoot) {
1769
1769
  function makeGit(cwd) {
1770
1770
  return async (args) => {
1771
1771
  const { execFile: ef } = await import("child_process");
1772
- return new Promise((resolve11) => {
1772
+ return new Promise((resolve10) => {
1773
1773
  ef(
1774
1774
  "git",
1775
1775
  args,
1776
1776
  { cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
1777
- (err, stdout) => resolve11(err ? "" : stdout)
1777
+ (err, stdout) => resolve10(err ? "" : stdout)
1778
1778
  );
1779
1779
  });
1780
1780
  };
@@ -1798,15 +1798,15 @@ async function handleGitChanges(ws, projectRoot) {
1798
1798
  if (!m) continue;
1799
1799
  const added = m[1] === "-" ? 0 : Number(m[1]);
1800
1800
  const deleted = m[2] === "-" ? 0 : Number(m[2]);
1801
- let path21 = m[3] ?? "";
1802
- if (path21 === "") {
1801
+ let path23 = m[3] ?? "";
1802
+ if (path23 === "") {
1803
1803
  i += 1;
1804
- path21 = parts[i + 1] ?? parts[i] ?? "";
1804
+ path23 = parts[i + 1] ?? parts[i] ?? "";
1805
1805
  i += 1;
1806
1806
  }
1807
- if (!path21) continue;
1808
- const prev = counts.get(path21) ?? { added: 0, deleted: 0 };
1809
- counts.set(path21, { added: prev.added + added, deleted: prev.deleted + deleted });
1807
+ if (!path23) continue;
1808
+ const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
1809
+ counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
1810
1810
  }
1811
1811
  };
1812
1812
  parseNumstat(unstagedNumstat);
@@ -1818,7 +1818,7 @@ async function handleGitChanges(ws, projectRoot) {
1818
1818
  if (!rec || rec.length < 3) continue;
1819
1819
  const x = rec[0] ?? " ";
1820
1820
  const y = rec[1] ?? " ";
1821
- const path21 = rec.slice(3);
1821
+ const path23 = rec.slice(3);
1822
1822
  const isRename = x === "R" || x === "C" || y === "R" || y === "C";
1823
1823
  if (isRename) i += 1;
1824
1824
  let status;
@@ -1830,13 +1830,13 @@ async function handleGitChanges(ws, projectRoot) {
1830
1830
  else if (x === "D" || y === "D") status = "D";
1831
1831
  else status = "M";
1832
1832
  const staged = x !== " " && x !== "?";
1833
- let added = counts.get(path21)?.added ?? 0;
1834
- let deleted = counts.get(path21)?.deleted ?? 0;
1833
+ let added = counts.get(path23)?.added ?? 0;
1834
+ let deleted = counts.get(path23)?.deleted ?? 0;
1835
1835
  if (status === "?") {
1836
1836
  added = 0;
1837
1837
  deleted = 0;
1838
1838
  }
1839
- files.push({ path: path21, status, added, deleted, staged });
1839
+ files.push({ path: path23, status, added, deleted, staged });
1840
1840
  }
1841
1841
  send(ws, { type: "git.changes", payload: { files } });
1842
1842
  } catch (err) {
@@ -1847,22 +1847,22 @@ async function handleGitChanges(ws, projectRoot) {
1847
1847
  }
1848
1848
  }
1849
1849
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1850
- async function handleGitDiff(ws, projectRoot, path21) {
1850
+ async function handleGitDiff(ws, projectRoot, path23) {
1851
1851
  const cwd = projectRoot || void 0;
1852
- const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path21, ...extra } });
1853
- if (!path21 || path21.includes("\0") || path21.includes("..") || nodePath.isAbsolute(path21)) {
1852
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1853
+ if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
1854
1854
  reply({ oldText: "", newText: "", error: "invalid path" });
1855
1855
  return;
1856
1856
  }
1857
1857
  try {
1858
1858
  const git = makeGit(cwd);
1859
- const { readFile: readFile9 } = await import("fs/promises");
1859
+ const { readFile: readFile10 } = await import("fs/promises");
1860
1860
  const { join: join14 } = await import("path");
1861
- const oldText = await git(["show", `HEAD:${path21}`]);
1861
+ const oldText = await git(["show", `HEAD:${path23}`]);
1862
1862
  let newText = "";
1863
1863
  try {
1864
- const abs = cwd ? join14(cwd, path21) : path21;
1865
- const buf = await readFile9(abs);
1864
+ const abs = cwd ? join14(cwd, path23) : path23;
1865
+ const buf = await readFile10(abs);
1866
1866
  if (buf.includes(0)) {
1867
1867
  reply({ oldText: "", newText: "", binary: true });
1868
1868
  return;
@@ -2064,7 +2064,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2064
2064
  return;
2065
2065
  }
2066
2066
  try {
2067
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore4, DefaultSessionReader: DefaultSessionReader3 } = await import("@wrongstack/core");
2067
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2068
2068
  const registry = new SessionRegistry(globalRoot);
2069
2069
  const entry = await registry.get(sessionId);
2070
2070
  if (!entry) {
@@ -2072,9 +2072,9 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2072
2072
  res.end(JSON.stringify({ error: "Session not found" }));
2073
2073
  return;
2074
2074
  }
2075
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2076
- const store = new DefaultSessionStore4({ dir: paths.projectSessions });
2077
- const reader = new DefaultSessionReader3({ store });
2075
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2076
+ const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2077
+ const reader = new DefaultSessionReader2({ store });
2078
2078
  const rawEntries = [];
2079
2079
  for await (const ev of reader.replay(sessionId)) {
2080
2080
  const mapped = mapWatchEntry(ev);
@@ -2099,7 +2099,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2099
2099
  }
2100
2100
  }
2101
2101
  function readJsonBody(req) {
2102
- return new Promise((resolve11, reject) => {
2102
+ return new Promise((resolve10, reject) => {
2103
2103
  let data = "";
2104
2104
  req.on("data", (chunk) => {
2105
2105
  data += chunk;
@@ -2110,7 +2110,7 @@ function readJsonBody(req) {
2110
2110
  });
2111
2111
  req.on("end", () => {
2112
2112
  try {
2113
- resolve11(data ? JSON.parse(data) : {});
2113
+ resolve10(data ? JSON.parse(data) : {});
2114
2114
  } catch (err) {
2115
2115
  reject(err instanceof Error ? err : new Error(String(err)));
2116
2116
  }
@@ -2146,7 +2146,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2146
2146
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2147
2147
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2148
2148
  try {
2149
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2149
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2150
2150
  const registry = new SessionRegistry(globalRoot);
2151
2151
  const entry = await registry.get(sessionId);
2152
2152
  if (!entry) {
@@ -2154,9 +2154,9 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2154
2154
  res.end(JSON.stringify({ error: "Session not found" }));
2155
2155
  return;
2156
2156
  }
2157
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2158
- const mailbox = new GlobalMailbox5(paths.projectDir);
2159
- const to = `leader@${mailboxSessionTag3(sessionId)}`;
2157
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2158
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2159
+ const to = `leader@${mailboxSessionTag2(sessionId)}`;
2160
2160
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
2161
2161
  res.writeHead(200, { "Content-Type": "application/json" });
2162
2162
  res.end(JSON.stringify({ ok: true, id: sent.id, to, type, delivered: entry.status }));
@@ -2172,7 +2172,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2172
2172
  return;
2173
2173
  }
2174
2174
  try {
2175
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2175
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2176
2176
  const registry = new SessionRegistry(globalRoot);
2177
2177
  const entry = await registry.get(sessionId);
2178
2178
  if (!entry) {
@@ -2180,9 +2180,9 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2180
2180
  res.end(JSON.stringify({ error: "Session not found" }));
2181
2181
  return;
2182
2182
  }
2183
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2184
- const mailbox = new GlobalMailbox5(paths.projectDir);
2185
- const leaderAddr = `leader@${mailboxSessionTag3(sessionId)}`;
2183
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2184
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2185
+ const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2186
2186
  const [inbound, outbound] = await Promise.all([
2187
2187
  mailbox.query({ to: leaderAddr, limit: 50 }),
2188
2188
  mailbox.query({ from: leaderAddr, limit: 50 })
@@ -2230,7 +2230,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2230
2230
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2231
2231
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2232
2232
  try {
2233
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2233
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2234
2234
  const registry = new SessionRegistry(globalRoot);
2235
2235
  const entry = await registry.get(sessionId);
2236
2236
  if (!entry) {
@@ -2238,9 +2238,9 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2238
2238
  res.end(JSON.stringify({ error: "Session not found" }));
2239
2239
  return;
2240
2240
  }
2241
- const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2242
- const mailbox = new GlobalMailbox5(paths.projectDir);
2243
- const to = `leader@${mailboxSessionTag3(sessionId)}`;
2241
+ const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2242
+ const mailbox = new GlobalMailbox4(paths.projectDir);
2243
+ const to = `leader@${mailboxSessionTag2(sessionId)}`;
2244
2244
  const sent = await mailbox.send({
2245
2245
  from,
2246
2246
  to,
@@ -2278,7 +2278,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2278
2278
  }
2279
2279
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2280
2280
  try {
2281
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox5, mailboxSessionTag: mailboxSessionTag3 } = await import("@wrongstack/core");
2281
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2282
2282
  const registry = new SessionRegistry(globalRoot);
2283
2283
  const all = await registry.list();
2284
2284
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2290,10 +2290,10 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2290
2290
  }
2291
2291
  const mbByDir = /* @__PURE__ */ new Map();
2292
2292
  const mailboxFor = (projectRoot) => {
2293
- const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2293
+ const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2294
2294
  let mb = mbByDir.get(dir);
2295
2295
  if (!mb) {
2296
- mb = new GlobalMailbox5(dir);
2296
+ mb = new GlobalMailbox4(dir);
2297
2297
  mbByDir.set(dir, mb);
2298
2298
  }
2299
2299
  return mb;
@@ -2305,7 +2305,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2305
2305
  const mb = mailboxFor(s.projectRoot);
2306
2306
  await mb.send({
2307
2307
  from,
2308
- to: `leader@${mailboxSessionTag3(s.sessionId)}`,
2308
+ to: `leader@${mailboxSessionTag2(s.sessionId)}`,
2309
2309
  type: "steer",
2310
2310
  subject: "Broadcast from Fleet HQ",
2311
2311
  body: text,
@@ -2353,14 +2353,14 @@ function pushEvent(event) {
2353
2353
  }
2354
2354
  }
2355
2355
  function parseBody(req) {
2356
- return new Promise((resolve11, reject) => {
2356
+ return new Promise((resolve10, reject) => {
2357
2357
  let body = "";
2358
2358
  req.on("data", (chunk) => {
2359
2359
  body += chunk.toString("utf-8");
2360
2360
  });
2361
2361
  req.on("end", () => {
2362
2362
  try {
2363
- resolve11(JSON.parse(body));
2363
+ resolve10(JSON.parse(body));
2364
2364
  } catch {
2365
2365
  reject(new Error("Invalid JSON"));
2366
2366
  }
@@ -2554,7 +2554,7 @@ function escapeHtmlAttr(value) {
2554
2554
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2555
2555
  }
2556
2556
  function injectWsConfig(html, opts) {
2557
- let out = injectWsPort(html, opts.wsPort);
2557
+ const out = injectWsPort(html, opts.wsPort);
2558
2558
  if (!opts.publicWsUrl || out.includes('name="wrongstack-ws-url"')) return out;
2559
2559
  const tag = `<meta name="wrongstack-ws-url" content="${escapeHtmlAttr(opts.publicWsUrl)}" />`;
2560
2560
  if (out.includes("</head>")) {
@@ -3237,16 +3237,16 @@ function openBrowser(url, platform = process.platform) {
3237
3237
  import * as net from "net";
3238
3238
  import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3239
3239
  function isPortFree(host, port) {
3240
- return new Promise((resolve11) => {
3240
+ return new Promise((resolve10) => {
3241
3241
  const srv = net.createServer();
3242
- srv.once("error", () => resolve11(false));
3242
+ srv.once("error", () => resolve10(false));
3243
3243
  srv.once("listening", () => {
3244
- srv.close(() => resolve11(true));
3244
+ srv.close(() => resolve10(true));
3245
3245
  });
3246
3246
  try {
3247
3247
  srv.listen(port, host);
3248
3248
  } catch {
3249
- resolve11(false);
3249
+ resolve10(false);
3250
3250
  }
3251
3251
  });
3252
3252
  }
@@ -4132,6 +4132,7 @@ function validateMailboxMessagesPayload(payload) {
4132
4132
  const limit = payload["limit"];
4133
4133
  const agentId = payload["agentId"];
4134
4134
  const unreadOnly = payload["unreadOnly"];
4135
+ const incompleteOnly = payload["incompleteOnly"];
4135
4136
  if (limit !== void 0 && (typeof limit !== "number" || !Number.isFinite(limit) || limit < 1)) {
4136
4137
  return {
4137
4138
  ok: false,
@@ -4150,7 +4151,13 @@ function validateMailboxMessagesPayload(payload) {
4150
4151
  message: "mailbox.messages payload.unreadOnly must be a boolean when provided"
4151
4152
  };
4152
4153
  }
4153
- return { ok: true, value: { limit, agentId, unreadOnly } };
4154
+ if (incompleteOnly !== void 0 && typeof incompleteOnly !== "boolean") {
4155
+ return {
4156
+ ok: false,
4157
+ message: "mailbox.messages payload.incompleteOnly must be a boolean when provided"
4158
+ };
4159
+ }
4160
+ return { ok: true, value: { limit, agentId, unreadOnly, incompleteOnly } };
4154
4161
  }
4155
4162
  function validateMailboxAgentsPayload(payload) {
4156
4163
  if (payload === void 0) return { ok: true, value: void 0 };
@@ -4285,7 +4292,7 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4285
4292
  "enhanceDelayMs",
4286
4293
  "tgLongToolMs"
4287
4294
  ]);
4288
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken"]);
4295
+ var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4289
4296
  var ENUM_PREF_KEYS = {
4290
4297
  autonomy: AUTONOMY_VALUES,
4291
4298
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4298,6 +4305,37 @@ var ENUM_PREF_KEYS = {
4298
4305
  reasoningEffort: REASONING_EFFORT_VALUES,
4299
4306
  cacheTtl: CACHE_TTL_VALUES
4300
4307
  };
4308
+ function validateModelRuntimeValue(modelRuntime, path23) {
4309
+ const reasoning = modelRuntime["reasoning"];
4310
+ if (reasoning !== void 0) {
4311
+ if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
4312
+ const mode = reasoning["mode"];
4313
+ const effort = reasoning["effort"];
4314
+ const preserve = reasoning["preserve"];
4315
+ if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
4316
+ return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4317
+ }
4318
+ if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
4319
+ return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4320
+ }
4321
+ if (preserve !== void 0 && typeof preserve !== "boolean") {
4322
+ return `${path23}.reasoning.preserve must be a boolean when provided`;
4323
+ }
4324
+ }
4325
+ const cache = modelRuntime["cache"];
4326
+ if (cache !== void 0) {
4327
+ if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
4328
+ const ttl = cache["ttl"];
4329
+ if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
4330
+ return `${path23}.cache.ttl must be one of: 5m, 1h`;
4331
+ }
4332
+ }
4333
+ const parameters = modelRuntime["parameters"];
4334
+ if (parameters !== void 0 && !isRecord(parameters)) {
4335
+ return `${path23}.parameters must be an object when provided`;
4336
+ }
4337
+ return null;
4338
+ }
4301
4339
  function validatePreferenceValue(key, value) {
4302
4340
  if (BOOLEAN_PREF_KEYS.has(key)) {
4303
4341
  return typeof value === "boolean" ? null : `prefs.update payload.${key} must be a boolean`;
@@ -4323,6 +4361,7 @@ function validatePreferenceValue(key, value) {
4323
4361
  const provider = entry["provider"];
4324
4362
  const model = entry["model"];
4325
4363
  const fallbackProfile = entry["fallbackProfile"];
4364
+ const modelRuntime = entry["modelRuntime"];
4326
4365
  if (provider !== void 0 && typeof provider !== "string") {
4327
4366
  return `prefs.update payload.${key}.provider must be a string when provided`;
4328
4367
  }
@@ -4332,8 +4371,15 @@ function validatePreferenceValue(key, value) {
4332
4371
  if (fallbackProfile !== void 0 && typeof fallbackProfile !== "string") {
4333
4372
  return `prefs.update payload.${key}.fallbackProfile must be a string when provided`;
4334
4373
  }
4335
- if (model === void 0 && fallbackProfile === void 0) {
4336
- return `prefs.update payload.${key} entries require model or fallbackProfile`;
4374
+ if (modelRuntime !== void 0 && !isRecord(modelRuntime)) {
4375
+ return `prefs.update payload.${key}.modelRuntime must be an object when provided`;
4376
+ }
4377
+ if (isRecord(modelRuntime)) {
4378
+ const runtimeError = validateModelRuntimeValue(modelRuntime, `prefs.update payload.${key}.modelRuntime`);
4379
+ if (runtimeError) return runtimeError;
4380
+ }
4381
+ if (model === void 0 && fallbackProfile === void 0 && modelRuntime === void 0) {
4382
+ return `prefs.update payload.${key} entries require model, fallbackProfile, or modelRuntime`;
4337
4383
  }
4338
4384
  }
4339
4385
  return null;
@@ -4566,8 +4612,8 @@ function validateShellOpenPayload(payload) {
4566
4612
  if (!isRecord(payload)) {
4567
4613
  return { ok: false, message: "shell.open payload must be an object with string path" };
4568
4614
  }
4569
- const path21 = payload["path"];
4570
- if (typeof path21 !== "string" || path21.trim().length === 0) {
4615
+ const path23 = payload["path"];
4616
+ if (typeof path23 !== "string" || path23.trim().length === 0) {
4571
4617
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4572
4618
  }
4573
4619
  const target = payload["target"];
@@ -4577,48 +4623,20 @@ function validateShellOpenPayload(payload) {
4577
4623
  message: 'shell.open payload.target must be "file" or "terminal" when provided'
4578
4624
  };
4579
4625
  }
4580
- return { ok: true, value: { path: path21, target } };
4626
+ return { ok: true, value: { path: path23, target } };
4581
4627
  }
4582
4628
  function validateGitDiffPayload(payload) {
4583
4629
  if (!isRecord(payload)) {
4584
4630
  return { ok: false, message: "git.diff payload must be an object" };
4585
4631
  }
4586
- const path21 = payload["path"];
4587
- if (path21 === void 0 || path21 === null) {
4632
+ const path23 = payload["path"];
4633
+ if (path23 === void 0 || path23 === null) {
4588
4634
  return { ok: true, value: { path: "" } };
4589
4635
  }
4590
- if (typeof path21 !== "string") {
4636
+ if (typeof path23 !== "string") {
4591
4637
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
4592
4638
  }
4593
- return { ok: true, value: { path: path21 } };
4594
- }
4595
- function validateProjectsAddPayload(payload) {
4596
- if (!isRecord(payload)) {
4597
- return { ok: false, message: "projects.add payload must be an object with string root" };
4598
- }
4599
- const root = payload["root"];
4600
- if (typeof root !== "string" || root.trim().length === 0) {
4601
- return { ok: false, message: "projects.add payload.root must be a non-empty string" };
4602
- }
4603
- const name2 = payload["name"];
4604
- if (name2 !== void 0 && typeof name2 !== "string") {
4605
- return { ok: false, message: "projects.add payload.name must be a string when provided" };
4606
- }
4607
- return { ok: true, value: { root, name: typeof name2 === "string" ? name2 : void 0 } };
4608
- }
4609
- function validateProjectsSelectPayload(payload) {
4610
- if (!isRecord(payload)) {
4611
- return { ok: false, message: "projects.select payload must be an object with string root" };
4612
- }
4613
- const root = payload["root"];
4614
- if (typeof root !== "string" || root.trim().length === 0) {
4615
- return { ok: false, message: "projects.select payload.root must be a non-empty string" };
4616
- }
4617
- const name2 = payload["name"];
4618
- if (name2 !== void 0 && typeof name2 !== "string") {
4619
- return { ok: false, message: "projects.select payload.name must be a string when provided" };
4620
- }
4621
- return { ok: true, value: { root, name: typeof name2 === "string" ? name2 : void 0 } };
4639
+ return { ok: true, value: { path: path23 } };
4622
4640
  }
4623
4641
 
4624
4642
  // src/server/skills-handlers.ts
@@ -4791,7 +4809,13 @@ async function handleSkillsCreate(ws, ctx, msg) {
4791
4809
  }
4792
4810
  const createPayload = parsed.value;
4793
4811
  try {
4794
- const targetDir = createPayload.scope === "global" ? path11.join(wstackGlobalRoot(), "skills", createPayload.name.trim()) : path11.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
4812
+ const targetDir = createPayload.scope === "global" ? path11.join(
4813
+ ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
4814
+ createPayload.name.trim()
4815
+ ) : path11.join(
4816
+ ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
4817
+ createPayload.name.trim()
4818
+ );
4795
4819
  try {
4796
4820
  await fs9.access(targetDir);
4797
4821
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
@@ -4877,8 +4901,9 @@ async function handleSkillsEdit(ws, ctx, msg) {
4877
4901
  send(ws, { type: "skills.edited", payload: { success: false, error: `Skill "${editPayload.name}" not found` } });
4878
4902
  return;
4879
4903
  }
4880
- if (entry.scope.includes("bundled")) {
4881
- send(ws, { type: "skills.edited", payload: { success: false, error: "Bundled skills cannot be edited" } });
4904
+ if (entry.source !== "project" && entry.source !== "user") {
4905
+ const label = entry.source === "bundled" ? "Bundled" : "Foreign (read-only)";
4906
+ send(ws, { type: "skills.edited", payload: { success: false, error: `${label} skills cannot be edited` } });
4882
4907
  return;
4883
4908
  }
4884
4909
  await atomicWrite5(entry.path, editPayload.body);
@@ -5503,8 +5528,20 @@ function registerShutdownHandlers(res) {
5503
5528
  import * as fs10 from "fs/promises";
5504
5529
  import { watch as fsWatch } from "fs";
5505
5530
  import * as path12 from "path";
5531
+ function statusProjectHashFromWatchFilename(projectsDir, filename) {
5532
+ const raw = String(filename);
5533
+ const relative4 = path12.isAbsolute(raw) ? path12.relative(projectsDir, raw) : raw;
5534
+ const parts = relative4.split(/[\\/]+/).filter(Boolean);
5535
+ if (parts.length < 2) return null;
5536
+ if (parts[parts.length - 1] !== "status.json") return null;
5537
+ return parts[parts.length - 2] ?? null;
5538
+ }
5539
+ function shouldLogWatcherStats() {
5540
+ const value = process.env["WRONGSTACK_WEBUI_WATCHER_STATS"]?.trim().toLowerCase();
5541
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5542
+ }
5506
5543
  function setupEvents(deps2) {
5507
- const { events, broadcast: broadcast3, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5544
+ const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5508
5545
  const disposers = [];
5509
5546
  const currentSessionId = () => context.session?.id ?? "";
5510
5547
  const sessionPayload2 = (payload) => {
@@ -5523,19 +5560,19 @@ function setupEvents(deps2) {
5523
5560
  };
5524
5561
  events.on("iteration.started", (e) => {
5525
5562
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5526
- broadcast3(clients, {
5563
+ broadcast2(clients, {
5527
5564
  type: "iteration.started",
5528
5565
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5529
5566
  });
5530
5567
  });
5531
5568
  events.on("iteration.completed", (e) => {
5532
- broadcast3(clients, {
5569
+ broadcast2(clients, {
5533
5570
  type: "iteration.completed",
5534
5571
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5535
5572
  });
5536
5573
  });
5537
5574
  events.on("iteration.limit_reached", (e) => {
5538
- broadcast3(clients, {
5575
+ broadcast2(clients, {
5539
5576
  type: "iteration.limit_reached",
5540
5577
  payload: sessionPayload2({
5541
5578
  sessionId: e.sessionId,
@@ -5545,19 +5582,19 @@ function setupEvents(deps2) {
5545
5582
  });
5546
5583
  });
5547
5584
  events.on("provider.text_delta", (e) => {
5548
- broadcast3(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5585
+ broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5549
5586
  });
5550
5587
  events.on("provider.thinking_delta", (e) => {
5551
- broadcast3(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5588
+ broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5552
5589
  });
5553
5590
  events.on("provider.stream_error", (e) => {
5554
- broadcast3(clients, {
5591
+ broadcast2(clients, {
5555
5592
  type: "provider.stream_error",
5556
5593
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5557
5594
  });
5558
5595
  });
5559
5596
  events.on("tool.started", (e) => {
5560
- broadcast3(clients, {
5597
+ broadcast2(clients, {
5561
5598
  type: "tool.started",
5562
5599
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
5563
5600
  });
@@ -5570,7 +5607,7 @@ function setupEvents(deps2) {
5570
5607
  });
5571
5608
  });
5572
5609
  events.on("tool.progress", (e) => {
5573
- broadcast3(clients, {
5610
+ broadcast2(clients, {
5574
5611
  type: "tool.progress",
5575
5612
  // Nested `event` shape — the client handler reads `payload.event?.text`
5576
5613
  // and early-returns on a falsy text, so a flat { eventType, text } payload
@@ -5587,7 +5624,7 @@ function setupEvents(deps2) {
5587
5624
  });
5588
5625
  });
5589
5626
  events.on("tool.executed", (e) => {
5590
- broadcast3(clients, {
5627
+ broadcast2(clients, {
5591
5628
  type: "tool.executed",
5592
5629
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
5593
5630
  });
@@ -5603,10 +5640,10 @@ function setupEvents(deps2) {
5603
5640
  outputTokens: e.outputTokens,
5604
5641
  outputLines: e.outputLines
5605
5642
  });
5606
- broadcast3(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
5643
+ broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
5607
5644
  const sideEffects = context.sideEffects ?? [];
5608
5645
  if (sideEffects.length > 0) {
5609
- broadcast3(clients, {
5646
+ broadcast2(clients, {
5610
5647
  type: "side_effects",
5611
5648
  payload: sessionPayload2({
5612
5649
  sessionId: e.sessionId,
@@ -5628,7 +5665,7 @@ function setupEvents(deps2) {
5628
5665
  if (typeof taskPath === "string" && taskPath) {
5629
5666
  const { loadTasks } = await import("@wrongstack/core");
5630
5667
  const file = await loadTasks(taskPath);
5631
- broadcast3(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5668
+ broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5632
5669
  }
5633
5670
  } catch {
5634
5671
  }
@@ -5637,7 +5674,7 @@ function setupEvents(deps2) {
5637
5674
  if (typeof planPath === "string" && planPath) {
5638
5675
  const { loadPlan } = await import("@wrongstack/core");
5639
5676
  const plan = await loadPlan(planPath);
5640
- 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: [] } }) });
5677
+ 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: [] } }) });
5641
5678
  }
5642
5679
  } catch {
5643
5680
  }
@@ -5645,7 +5682,7 @@ function setupEvents(deps2) {
5645
5682
  }
5646
5683
  });
5647
5684
  events.on("tool.loop_detected", (e) => {
5648
- broadcast3(clients, {
5685
+ broadcast2(clients, {
5649
5686
  type: "tool.loop_detected",
5650
5687
  payload: sessionPayload2({
5651
5688
  sessionId: e.sessionId,
@@ -5657,19 +5694,19 @@ function setupEvents(deps2) {
5657
5694
  });
5658
5695
  });
5659
5696
  events.on("trust.persisted", (e) => {
5660
- broadcast3(clients, {
5697
+ broadcast2(clients, {
5661
5698
  type: "trust.persisted",
5662
5699
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5663
5700
  });
5664
5701
  });
5665
5702
  events.on("delegate.started", (e) => {
5666
- broadcast3(clients, {
5703
+ broadcast2(clients, {
5667
5704
  type: "delegate.started",
5668
5705
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5669
5706
  });
5670
5707
  });
5671
5708
  events.on("delegate.completed", (e) => {
5672
- broadcast3(clients, {
5709
+ broadcast2(clients, {
5673
5710
  type: "delegate.completed",
5674
5711
  payload: sessionPayload2({
5675
5712
  sessionId: e.sessionId,
@@ -5687,14 +5724,14 @@ function setupEvents(deps2) {
5687
5724
  });
5688
5725
  });
5689
5726
  events.on("provider.response", (e) => {
5690
- broadcast3(clients, { type: "provider.response", payload: sessionPayload2({ sessionId: e.sessionId, usage: e.usage, stopReason: e.stopReason, messageId: "current" }) });
5727
+ broadcast2(clients, { type: "provider.response", payload: sessionPayload2({ sessionId: e.sessionId, usage: e.usage, stopReason: e.stopReason, messageId: "current" }) });
5691
5728
  });
5692
5729
  events.on("ctx.pct", (e) => {
5693
- broadcast3(clients, {
5730
+ broadcast2(clients, {
5694
5731
  type: "ctx.pct",
5695
5732
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
5696
5733
  });
5697
- broadcast3(clients, {
5734
+ broadcast2(clients, {
5698
5735
  type: "subagent.event",
5699
5736
  payload: sessionPayload2({
5700
5737
  sessionId: e.sessionId,
@@ -5707,33 +5744,37 @@ function setupEvents(deps2) {
5707
5744
  });
5708
5745
  });
5709
5746
  events.on("ctx.max_context", (e) => {
5710
- broadcast3(clients, {
5747
+ broadcast2(clients, {
5711
5748
  type: "ctx.max_context",
5712
5749
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5713
5750
  });
5714
5751
  });
5715
5752
  events.on("token.threshold", (e) => {
5716
- broadcast3(clients, {
5753
+ broadcast2(clients, {
5717
5754
  type: "token.threshold",
5718
5755
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5719
5756
  });
5720
5757
  });
5721
5758
  events.on("token.cost_estimate_unavailable", (e) => {
5722
- broadcast3(clients, {
5759
+ broadcast2(clients, {
5723
5760
  type: "token.cost_estimate_unavailable",
5724
5761
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5725
5762
  });
5726
5763
  });
5727
5764
  events.on("context.repaired", (e) => {
5728
- broadcast3(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5765
+ broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5729
5766
  });
5730
5767
  events.on("tool.confirm_needed", (e) => {
5731
5768
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5732
- pendingConfirms.set(id, e.resolve);
5733
- broadcast3(clients, { type: "tool.confirm_needed", payload: sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern }) });
5769
+ pendingConfirms.set(id, {
5770
+ resolve: e.resolve,
5771
+ decisionSource: e.decisionSource,
5772
+ riskTier: e.riskTier
5773
+ });
5774
+ 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 }) });
5734
5775
  });
5735
5776
  events.on("error", (e) => {
5736
- broadcast3(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5777
+ broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5737
5778
  appendForCurrentSession(e.sessionId, {
5738
5779
  type: "error",
5739
5780
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5742,13 +5783,13 @@ function setupEvents(deps2) {
5742
5783
  });
5743
5784
  });
5744
5785
  events.on("session.damaged", (e) => {
5745
- broadcast3(clients, {
5786
+ broadcast2(clients, {
5746
5787
  type: "session.damaged",
5747
5788
  payload: { sessionId: e.sessionId, detail: e.detail }
5748
5789
  });
5749
5790
  });
5750
5791
  events.on("session.rewound", (e) => {
5751
- broadcast3(clients, {
5792
+ broadcast2(clients, {
5752
5793
  type: "session.rewound",
5753
5794
  payload: sessionPayload2({
5754
5795
  sessionId: e.sessionId,
@@ -5759,7 +5800,7 @@ function setupEvents(deps2) {
5759
5800
  });
5760
5801
  });
5761
5802
  events.on("checkpoint.written", (e) => {
5762
- broadcast3(clients, {
5803
+ broadcast2(clients, {
5763
5804
  type: "checkpoint.written",
5764
5805
  payload: sessionPayload2({
5765
5806
  sessionId: e.sessionId,
@@ -5771,19 +5812,19 @@ function setupEvents(deps2) {
5771
5812
  });
5772
5813
  });
5773
5814
  events.on("in_flight.started", (e) => {
5774
- broadcast3(clients, {
5815
+ broadcast2(clients, {
5775
5816
  type: "in_flight.started",
5776
5817
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
5777
5818
  });
5778
5819
  });
5779
5820
  events.on("in_flight.ended", (e) => {
5780
- broadcast3(clients, {
5821
+ broadcast2(clients, {
5781
5822
  type: "in_flight.ended",
5782
5823
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
5783
5824
  });
5784
5825
  });
5785
5826
  events.on("provider.retry", (e) => {
5786
- broadcast3(clients, {
5827
+ broadcast2(clients, {
5787
5828
  type: "provider.retry",
5788
5829
  payload: sessionPayload2({
5789
5830
  sessionId: e.sessionId,
@@ -5805,7 +5846,7 @@ function setupEvents(deps2) {
5805
5846
  });
5806
5847
  });
5807
5848
  events.on("provider.error", (e) => {
5808
- broadcast3(clients, {
5849
+ broadcast2(clients, {
5809
5850
  type: "provider.error",
5810
5851
  payload: sessionPayload2({
5811
5852
  sessionId: e.sessionId,
@@ -5825,7 +5866,7 @@ function setupEvents(deps2) {
5825
5866
  });
5826
5867
  });
5827
5868
  events.on("provider.fallback", (e) => {
5828
- broadcast3(clients, {
5869
+ broadcast2(clients, {
5829
5870
  type: "provider.fallback",
5830
5871
  payload: sessionPayload2({
5831
5872
  sessionId: e.sessionId,
@@ -5837,7 +5878,7 @@ function setupEvents(deps2) {
5837
5878
  });
5838
5879
  });
5839
5880
  events.on("compaction.fired", (e) => {
5840
- broadcast3(clients, {
5881
+ broadcast2(clients, {
5841
5882
  type: "context.compacted",
5842
5883
  payload: sessionPayload2({
5843
5884
  sessionId: e.sessionId,
@@ -5849,7 +5890,7 @@ function setupEvents(deps2) {
5849
5890
  });
5850
5891
  });
5851
5892
  events.on("compaction.failed", (e) => {
5852
- broadcast3(clients, {
5893
+ broadcast2(clients, {
5853
5894
  type: "compaction.failed",
5854
5895
  payload: sessionPayload2({
5855
5896
  sessionId: e.sessionId,
@@ -5864,25 +5905,25 @@ function setupEvents(deps2) {
5864
5905
  });
5865
5906
  });
5866
5907
  events.on("mcp.server.connected", (e) => {
5867
- broadcast3(clients, {
5908
+ broadcast2(clients, {
5868
5909
  type: "mcp.server.connected",
5869
5910
  payload: { name: e.name, toolCount: e.toolCount }
5870
5911
  });
5871
5912
  });
5872
5913
  events.on("mcp.server.reconnected", (e) => {
5873
- broadcast3(clients, {
5914
+ broadcast2(clients, {
5874
5915
  type: "mcp.server.reconnected",
5875
5916
  payload: { name: e.name, toolCount: e.toolCount }
5876
5917
  });
5877
5918
  });
5878
5919
  events.on("mcp.server.disconnected", (e) => {
5879
- broadcast3(clients, {
5920
+ broadcast2(clients, {
5880
5921
  type: "mcp.server.disconnected",
5881
5922
  payload: { name: e.name, reason: e.reason }
5882
5923
  });
5883
5924
  });
5884
5925
  events.on("coordinator.stats", (e) => {
5885
- broadcast3(clients, {
5926
+ broadcast2(clients, {
5886
5927
  type: "coordinator.stats",
5887
5928
  payload: sessionPayload2({
5888
5929
  sessionId: e.sessionId,
@@ -5903,12 +5944,12 @@ function setupEvents(deps2) {
5903
5944
  });
5904
5945
  });
5905
5946
  events.onPattern("mailbox.received", (_e, payload) => {
5906
- broadcast3(clients, { type: "mailbox.received", payload });
5947
+ broadcast2(clients, { type: "mailbox.received", payload });
5907
5948
  });
5908
5949
  events.onPattern("mailbox.agent_registered", (_e, payload) => {
5909
- broadcast3(clients, { type: "mailbox.agent_registered", payload });
5950
+ broadcast2(clients, { type: "mailbox.agent_registered", payload });
5910
5951
  });
5911
- const forwardSubagent = (kind, payload) => broadcast3(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5952
+ const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5912
5953
  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 }));
5913
5954
  events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
5914
5955
  events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
@@ -5918,7 +5959,7 @@ function setupEvents(deps2) {
5918
5959
  events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
5919
5960
  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 }));
5920
5961
  events.on("agent.timeline.message", (e) => {
5921
- broadcast3(clients, {
5962
+ broadcast2(clients, {
5922
5963
  type: "agent.timeline.message",
5923
5964
  payload: sessionPayload2({
5924
5965
  sessionId: e.sessionId,
@@ -5934,7 +5975,7 @@ function setupEvents(deps2) {
5934
5975
  });
5935
5976
  });
5936
5977
  events.on("agent.status_changed", (e) => {
5937
- broadcast3(clients, {
5978
+ broadcast2(clients, {
5938
5979
  type: "agent.status_changed",
5939
5980
  payload: sessionPayload2({
5940
5981
  sessionId: e.sessionId,
@@ -6003,19 +6044,19 @@ function setupEvents(deps2) {
6003
6044
  }
6004
6045
  });
6005
6046
  events.onPattern("mailbox.*", (eventName, payload) => {
6006
- broadcast3(clients, {
6047
+ broadcast2(clients, {
6007
6048
  type: "mailbox.event",
6008
6049
  payload: sessionPayload2({ event: eventName, ...payload })
6009
6050
  });
6010
6051
  });
6011
6052
  events.onPattern("brain.*", (eventName, payload) => {
6012
- broadcast3(clients, {
6053
+ broadcast2(clients, {
6013
6054
  type: "brain.event",
6014
6055
  payload: sessionPayload2({ event: eventName, ...payload })
6015
6056
  });
6016
6057
  });
6017
6058
  events.on("client.status", async (e) => {
6018
- broadcast3(clients, { type: "client.status_update", payload: e });
6059
+ broadcast2(clients, { type: "client.status_update", payload: e });
6019
6060
  if (wpaths?.projectStatus) {
6020
6061
  try {
6021
6062
  const statusFile = wpaths.projectStatus(e.projectHash);
@@ -6047,79 +6088,71 @@ function setupEvents(deps2) {
6047
6088
  if (!watcherMetrics || watcherMetrics.broadcastsSent === 0) return 0;
6048
6089
  return watcherMetrics.totalDebounceDelayMs / watcherMetrics.broadcastsSent;
6049
6090
  };
6091
+ const logWatcherMetricsEnabled = shouldLogWatcherStats();
6050
6092
  const logWatcherMetrics = () => {
6051
- if (!watcherMetrics) return;
6093
+ if (!watcherMetrics || !logWatcherMetricsEnabled) return;
6052
6094
  watcherMetrics.averageDebounceDelayMs = getAverageDebounceDelay();
6053
6095
  console.log(
6054
6096
  `[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`
6055
6097
  );
6056
6098
  };
6057
- const metricsInterval = setInterval(logWatcherMetrics, 6e4);
6099
+ const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
6058
6100
  const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
6059
- broadcast3(clients, { type: "client.status_update", payload: statusData });
6101
+ broadcast2(clients, { type: "client.status_update", payload: statusData });
6060
6102
  if (watcherMetrics) {
6061
6103
  watcherMetrics.broadcastsSent++;
6062
6104
  watcherMetrics.totalDebounceDelayMs += actualDelayMs;
6063
6105
  watcherMetrics.averageDebounceDelayMs = getAverageDebounceDelay();
6064
6106
  }
6065
6107
  };
6066
- const scheduleBroadcast = (projectHash3, statusData) => {
6108
+ const scheduleBroadcast = (projectHash, statusData) => {
6067
6109
  const now = Date.now();
6068
- const existing = pendingStatuses.get(projectHash3);
6110
+ const existing = pendingStatuses.get(projectHash);
6069
6111
  if (existing && watcherMetrics) {
6070
6112
  watcherMetrics.debounceResets++;
6071
6113
  }
6072
- pendingStatuses.set(projectHash3, {
6114
+ pendingStatuses.set(projectHash, {
6073
6115
  data: statusData,
6074
6116
  firstWriteAt: existing ? existing.firstWriteAt : now
6075
6117
  });
6076
- const existingTimer = debounceTimers.get(projectHash3);
6118
+ const existingTimer = debounceTimers.get(projectHash);
6077
6119
  if (existingTimer) {
6078
6120
  clearTimeout(existingTimer);
6079
6121
  }
6080
6122
  const timer = setTimeout(() => {
6081
- debounceTimers.delete(projectHash3);
6082
- const pending = pendingStatuses.get(projectHash3);
6123
+ debounceTimers.delete(projectHash);
6124
+ const pending = pendingStatuses.get(projectHash);
6083
6125
  if (pending) {
6084
6126
  const actualDelay = Date.now() - pending.firstWriteAt;
6085
- broadcastStatus(projectHash3, pending.data, actualDelay);
6086
- pendingStatuses.delete(projectHash3);
6127
+ broadcastStatus(projectHash, pending.data, actualDelay);
6128
+ pendingStatuses.delete(projectHash);
6087
6129
  }
6088
6130
  }, DEBOUNCE_MS);
6089
- debounceTimers.set(projectHash3, timer);
6131
+ debounceTimers.set(projectHash, timer);
6090
6132
  };
6091
6133
  let watcher;
6092
6134
  const startWatcher = async () => {
6093
6135
  try {
6094
6136
  await fs10.mkdir(projectsDir, { recursive: true });
6095
6137
  watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6096
- if (eventType === "change") {
6097
- if (filename == null) return;
6098
- if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6099
- const targetFile = path12.join(projectsDir, String(filename));
6100
- if (targetFile.endsWith("status.json")) {
6101
- const projectHash3 = path12.basename(path12.dirname(targetFile));
6102
- if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash3)) {
6103
- return;
6104
- }
6105
- if (watcherMetrics) watcherMetrics.filesProcessed++;
6106
- try {
6107
- const content = await fs10.readFile(targetFile, "utf-8");
6108
- const statusData = JSON.parse(content);
6109
- if (statusData.projectHash) {
6110
- const hash = String(statusData.projectHash);
6111
- if (!knownProjectHashes.has(hash)) {
6112
- knownProjectHashes.add(hash);
6113
- if (watcherMetrics) watcherMetrics.activeProjects = knownProjectHashes.size;
6114
- }
6115
- }
6116
- scheduleBroadcast(projectHash3, statusData);
6117
- } catch {
6118
- }
6119
- }
6138
+ if (eventType !== "change" && eventType !== "rename") return;
6139
+ if (filename == null) return;
6140
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
6141
+ if (!projectHash) return;
6142
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6143
+ if (!knownProjectHashes.has(projectHash)) return;
6144
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
6145
+ try {
6146
+ const targetFile = path12.join(projectsDir, projectHash, "status.json");
6147
+ const content = await fs10.readFile(targetFile, "utf-8");
6148
+ const statusData = JSON.parse(content);
6149
+ scheduleBroadcast(projectHash, statusData);
6150
+ } catch {
6120
6151
  }
6121
6152
  });
6122
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
6153
+ if (logWatcherMetricsEnabled) {
6154
+ console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
6155
+ }
6123
6156
  } catch (err) {
6124
6157
  console.error("[setup-events] Failed to start status file watcher:", err);
6125
6158
  }
@@ -6135,14 +6168,14 @@ function setupEvents(deps2) {
6135
6168
  });
6136
6169
  startWatcher();
6137
6170
  disposers.push(() => {
6138
- clearInterval(metricsInterval);
6171
+ if (metricsInterval) clearInterval(metricsInterval);
6139
6172
  logWatcherMetrics();
6140
6173
  if (watcherMetrics) watcherMetrics.watcherActive = false;
6141
- for (const [projectHash3, pending] of pendingStatuses) {
6142
- const timer = debounceTimers.get(projectHash3);
6174
+ for (const [projectHash, pending] of pendingStatuses) {
6175
+ const timer = debounceTimers.get(projectHash);
6143
6176
  if (timer) {
6144
6177
  clearTimeout(timer);
6145
- broadcastStatus(projectHash3, pending.data, 0);
6178
+ broadcastStatus(projectHash, pending.data, 0);
6146
6179
  }
6147
6180
  }
6148
6181
  for (const timer of debounceTimers.values()) {
@@ -6152,7 +6185,7 @@ function setupEvents(deps2) {
6152
6185
  pendingStatuses.clear();
6153
6186
  if (watcher) {
6154
6187
  watcher.close();
6155
- console.log("[setup-events] Closed status file watcher");
6188
+ if (logWatcherMetricsEnabled) console.log("[setup-events] Closed status file watcher");
6156
6189
  }
6157
6190
  });
6158
6191
  }
@@ -6193,7 +6226,7 @@ function setupEvents(deps2) {
6193
6226
  lastActivityAt: a.lastActivityAt
6194
6227
  }))
6195
6228
  }));
6196
- broadcast3(clients, { type: "sessions.status_update", payload: { sessions: live } });
6229
+ broadcast2(clients, { type: "sessions.status_update", payload: { sessions: live } });
6197
6230
  } catch {
6198
6231
  }
6199
6232
  };
@@ -6240,6 +6273,115 @@ function computeUsageCost(usage, rates) {
6240
6273
  return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
6241
6274
  }
6242
6275
 
6276
+ // src/server/model-catalog.ts
6277
+ import { capabilitiesFor } from "@wrongstack/providers";
6278
+ var SIBLING_CATALOG = {
6279
+ "anthropic-oauth": "anthropic",
6280
+ "openai-codex": "openai",
6281
+ "github-copilot": "openai"
6282
+ };
6283
+ async function resolveProviderCatalogForModels(modelsRegistry, providerId, cfg) {
6284
+ return await modelsRegistry.getProvider(providerId) ?? (cfg?.type && cfg.type !== providerId ? await modelsRegistry.getProvider(cfg.type) : void 0);
6285
+ }
6286
+ async function resolveProviderModelMetadata(modelsRegistry, providerId, modelId, cfg) {
6287
+ const direct = await modelsRegistry.getModel(providerId, modelId).catch(() => void 0);
6288
+ if (direct) return overlayConfiguredCapabilities(direct, cfg);
6289
+ if (cfg?.type && cfg.type !== providerId) {
6290
+ const typed = await modelsRegistry.getModel(cfg.type, modelId).catch(() => void 0);
6291
+ if (typed) return overlayConfiguredCapabilities(typed, cfg);
6292
+ }
6293
+ const sibling = cfg?.family ? SIBLING_CATALOG[cfg.family] : void 0;
6294
+ if (sibling) {
6295
+ const siblingModel = await modelsRegistry.getModel(sibling, modelId).catch(() => void 0);
6296
+ if (siblingModel) return overlayConfiguredCapabilities(siblingModel, cfg);
6297
+ const caps2 = await capabilitiesFor(
6298
+ modelsRegistry,
6299
+ sibling,
6300
+ modelId,
6301
+ cfg?.customModels
6302
+ ).catch(() => void 0);
6303
+ if (caps2 && positiveNumber(caps2.maxContext)) {
6304
+ return syntheticModel(providerId, modelId, cfg, caps2);
6305
+ }
6306
+ }
6307
+ const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
6308
+ const caps = await capabilitiesFor(
6309
+ modelsRegistry,
6310
+ catalogId,
6311
+ modelId,
6312
+ cfg?.customModels
6313
+ ).catch(() => void 0);
6314
+ if (caps && positiveNumber(caps.maxContext)) {
6315
+ return syntheticModel(providerId, modelId, cfg, caps);
6316
+ }
6317
+ const crossCatalog = await findModelAcrossCatalog(modelsRegistry, modelId);
6318
+ if (crossCatalog) return overlayConfiguredCapabilities(crossCatalog, cfg);
6319
+ return syntheticModel(providerId, modelId, cfg);
6320
+ }
6321
+ async function findModelAcrossCatalog(modelsRegistry, modelId) {
6322
+ if (typeof modelsRegistry.listProviders !== "function" || typeof modelsRegistry.getModel !== "function") {
6323
+ return void 0;
6324
+ }
6325
+ const providers = await modelsRegistry.listProviders().catch(() => []);
6326
+ for (const provider of providers) {
6327
+ if (!provider.models.some((model) => model.id === modelId)) continue;
6328
+ const resolved = await modelsRegistry.getModel(provider.id, modelId).catch(() => void 0);
6329
+ if (resolved?.capabilities?.maxContext) return resolved;
6330
+ }
6331
+ return void 0;
6332
+ }
6333
+ function syntheticModel(providerId, modelId, cfg, capabilities) {
6334
+ const custom = cfg?.customModels?.[modelId];
6335
+ const configuredMax = positiveNumber(readConfiguredMaxContext(cfg));
6336
+ const customCaps = custom?.capabilities;
6337
+ const maxContext = positiveNumber(customCaps?.maxContext) ?? positiveNumber(capabilities?.maxContext) ?? configuredMax ?? 0;
6338
+ const isKnown = Boolean(custom) || Boolean(cfg?.models?.includes(modelId)) || maxContext > 0 || Boolean(capabilities);
6339
+ if (!isKnown) return void 0;
6340
+ return {
6341
+ providerId,
6342
+ modelId,
6343
+ capabilities: {
6344
+ tools: readConfiguredBool(cfg, "tools") ?? capabilities?.tools ?? customCaps?.tools ?? false,
6345
+ vision: readConfiguredBool(cfg, "vision") ?? capabilities?.vision ?? customCaps?.vision ?? false,
6346
+ reasoning: readConfiguredBool(cfg, "reasoning") ?? capabilities?.reasoning ?? customCaps?.reasoning ?? false,
6347
+ maxContext,
6348
+ maxOutput: custom?.maxOutput ?? customCaps?.maxOutput ?? capabilities?.maxOutput
6349
+ },
6350
+ cost: void 0
6351
+ };
6352
+ }
6353
+ function overlayConfiguredCapabilities(model, cfg) {
6354
+ const custom = cfg?.customModels?.[model.modelId];
6355
+ const configuredMax = positiveNumber(readConfiguredMaxContext(cfg));
6356
+ const customCaps = custom?.capabilities;
6357
+ if (!configuredMax && !customCaps && custom?.maxOutput === void 0) return model;
6358
+ return {
6359
+ ...model,
6360
+ capabilities: {
6361
+ ...model.capabilities,
6362
+ ...customCaps,
6363
+ maxContext: customCaps?.maxContext ?? configuredMax ?? model.capabilities.maxContext,
6364
+ maxOutput: custom?.maxOutput ?? customCaps?.maxOutput ?? model.capabilities.maxOutput
6365
+ }
6366
+ };
6367
+ }
6368
+ function readConfiguredMaxContext(providerConfig) {
6369
+ if (!providerConfig || typeof providerConfig !== "object") return void 0;
6370
+ const capabilities = providerConfig.capabilities;
6371
+ if (!capabilities || typeof capabilities !== "object") return void 0;
6372
+ return capabilities.maxContext;
6373
+ }
6374
+ function readConfiguredBool(providerConfig, key) {
6375
+ if (!providerConfig || typeof providerConfig !== "object") return void 0;
6376
+ const capabilities = providerConfig.capabilities;
6377
+ if (!capabilities || typeof capabilities !== "object") return void 0;
6378
+ const value = capabilities[key];
6379
+ return typeof value === "boolean" ? value : void 0;
6380
+ }
6381
+ function positiveNumber(value) {
6382
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
6383
+ }
6384
+
6243
6385
  // src/server/server-runtime.ts
6244
6386
  import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6245
6387
  async function resolvePorts(opts) {
@@ -6272,7 +6414,12 @@ function createSessionStartPayload(g) {
6272
6414
  let outputCost = 0;
6273
6415
  let cacheReadCost = 0;
6274
6416
  try {
6275
- const m = await g.modelsRegistry.getModel(config.provider, config.model);
6417
+ const m = await resolveProviderModelMetadata(
6418
+ g.modelsRegistry,
6419
+ config.provider,
6420
+ config.model,
6421
+ config.providers?.[config.provider]
6422
+ );
6276
6423
  maxContext = m?.capabilities?.maxContext ?? 0;
6277
6424
  if (!maxContext) {
6278
6425
  try {
@@ -6415,7 +6562,7 @@ function registerShutdown(deps2) {
6415
6562
  }
6416
6563
 
6417
6564
  // src/server/pre-context-services.ts
6418
- import * as path14 from "path";
6565
+ import * as path15 from "path";
6419
6566
  import { createRequire } from "module";
6420
6567
  import { WebSocket as WebSocket2 } from "ws";
6421
6568
  import {
@@ -6453,13 +6600,14 @@ import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
6453
6600
  import { createDefaultContainer } from "@wrongstack/runtime";
6454
6601
  import {
6455
6602
  builtinToolsPack,
6603
+ configureDangerBypass,
6456
6604
  configureExecPolicy,
6457
6605
  forgetTool,
6458
6606
  relatedMemoryTool,
6459
6607
  rememberTool,
6460
6608
  searchMemoryTool
6461
6609
  } from "@wrongstack/tools";
6462
- import { projectHash, toErrorMessage as toErrorMessage5, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
6610
+ import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6463
6611
 
6464
6612
  // src/server/setup-screen.ts
6465
6613
  import { expectDefined as expectDefined2 } from "@wrongstack/core";
@@ -6541,6 +6689,7 @@ function seedContextMeta(config, context) {
6541
6689
  meta["favoriteModelsOnly"] = config.favoriteModelsOnly === true;
6542
6690
  meta["modelMatrix"] = config.modelMatrix ?? {};
6543
6691
  meta["fallbackAuto"] = config.fallbackAuto !== false;
6692
+ if (typeof config.uiLocale === "string" && config.uiLocale) meta["uiLocale"] = config.uiLocale;
6544
6693
  meta["featureMcp"] = config.features.mcp !== false;
6545
6694
  meta["featurePlugins"] = config.features.plugins !== false;
6546
6695
  meta["featureMemory"] = config.features.memory !== false;
@@ -6579,10 +6728,117 @@ function seedContextMeta(config, context) {
6579
6728
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
6580
6729
  }
6581
6730
 
6731
+ // src/server/model-auto-discovery.ts
6732
+ import * as fs11 from "fs/promises";
6733
+ import * as path14 from "path";
6734
+ import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
6735
+ function isOverlayRegistry(value) {
6736
+ return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
6737
+ }
6738
+ function resolveKey(cfg) {
6739
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
6740
+ const active = cfg.activeKey ? cfg.apiKeys.find((key) => key.label === cfg.activeKey) : void 0;
6741
+ return (active ?? cfg.apiKeys[0])?.apiKey;
6742
+ }
6743
+ return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
6744
+ }
6745
+ function eligibleProviders(config) {
6746
+ const out = [];
6747
+ for (const [id, cfg] of Object.entries(config.providers ?? {})) {
6748
+ const preset = COMPATIBLE_PRESETS[id];
6749
+ const enabled = cfg.autoDiscoverModels ?? preset?.autoDiscover ?? false;
6750
+ if (!enabled) continue;
6751
+ const baseUrl = cfg.baseUrl ?? preset?.defaultBaseUrl;
6752
+ if (!baseUrl) continue;
6753
+ out.push({ id, cfg, baseUrl, apiKey: resolveKey(cfg) });
6754
+ }
6755
+ return out;
6756
+ }
6757
+ async function readCache(file) {
6758
+ try {
6759
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6760
+ } catch {
6761
+ return {};
6762
+ }
6763
+ }
6764
+ async function discoverAndMergeWebuiProviders(opts) {
6765
+ const registry = opts.registry;
6766
+ if (!isOverlayRegistry(registry)) return;
6767
+ const targets = eligibleProviders(opts.config);
6768
+ if (targets.length === 0) return;
6769
+ const cacheFile = path14.join(opts.cacheDir, "discovered-models-cache.json");
6770
+ const cache = await readCache(cacheFile);
6771
+ let cacheDirty = false;
6772
+ await Promise.all(
6773
+ targets.map(async ({ id, cfg, baseUrl, apiKey }) => {
6774
+ const cacheKey = `${id}\0${baseUrl}`;
6775
+ const provider = await discoverOpenAICompatibleModels(id, {
6776
+ baseUrl,
6777
+ apiKey,
6778
+ headers: cfg.headers,
6779
+ providerName: id,
6780
+ fetchImpl: opts.fetchImpl
6781
+ });
6782
+ if (provider) {
6783
+ cache[cacheKey] = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), provider };
6784
+ cacheDirty = true;
6785
+ registry.mergeOverlay({ [id]: provider });
6786
+ opts.logger?.info?.(
6787
+ `auto-discovered ${Object.keys(provider.models).length} models for "${id}" from ${baseUrl}`
6788
+ );
6789
+ return;
6790
+ }
6791
+ const cached = cache[cacheKey];
6792
+ if (cached) {
6793
+ registry.mergeOverlay({ [id]: cached.provider });
6794
+ opts.logger?.warn?.(
6795
+ `auto-discovery for "${id}" failed; using ${Object.keys(cached.provider.models).length} cached models from ${cached.fetchedAt}`
6796
+ );
6797
+ } else {
6798
+ opts.logger?.warn?.(
6799
+ `auto-discovery for "${id}" failed and no cache available (server at ${baseUrl} unreachable?)`
6800
+ );
6801
+ }
6802
+ })
6803
+ );
6804
+ if (cacheDirty) {
6805
+ try {
6806
+ await fs11.mkdir(path14.dirname(cacheFile), { recursive: true });
6807
+ await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
6808
+ } catch {
6809
+ opts.logger?.debug?.("provider auto-discovery cache write failed");
6810
+ }
6811
+ }
6812
+ }
6813
+
6582
6814
  // src/server/pre-context-services.ts
6815
+ var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
6583
6816
  async function createPreContextServices(input) {
6584
- const { config, wpaths, logger, opts, globalConfigPath, projectRoot, workingDir, needsProvider } = input;
6585
- const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({ cacheFile: wpaths.modelsCache, ttlSeconds: 24 * 3600 });
6817
+ const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
6818
+ const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
6819
+ cacheFile: wpaths.modelsCache,
6820
+ ttlSeconds: 0,
6821
+ overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
6822
+ overlayCacheFile: wpaths.modelsOverlayCache
6823
+ });
6824
+ if (!opts.services?.modelsRegistry) {
6825
+ try {
6826
+ await modelsRegistry.refresh();
6827
+ logger.info("models.dev catalog refreshed");
6828
+ } catch (err) {
6829
+ logger.warn(`models.dev refresh failed (${toErrorMessage5(err)}); using cached catalog`);
6830
+ }
6831
+ }
6832
+ try {
6833
+ await discoverAndMergeWebuiProviders({
6834
+ config,
6835
+ registry: modelsRegistry,
6836
+ cacheDir: path15.dirname(wpaths.modelsCache),
6837
+ logger
6838
+ });
6839
+ } catch (err) {
6840
+ logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
6841
+ }
6586
6842
  const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
6587
6843
  const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
6588
6844
  const providerRegistry = new ProviderRegistry();
@@ -6613,6 +6869,7 @@ async function createPreContextServices(input) {
6613
6869
  applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
6614
6870
  applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
6615
6871
  configureExecPolicy(config.tools?.exec ?? {});
6872
+ configureDangerBypass(config.tools?.exec?.danger ?? {});
6616
6873
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
6617
6874
  const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
6618
6875
  if (config.features.mcp && config.mcpServers) {
@@ -6623,7 +6880,7 @@ async function createPreContextServices(input) {
6623
6880
  });
6624
6881
  }
6625
6882
  }
6626
- let sessionStore = opts.services?.session ?? new DefaultSessionStore({ dir: wpaths.projectSessions });
6883
+ const sessionStore = opts.services?.session ?? new DefaultSessionStore({ dir: wpaths.projectSessions });
6627
6884
  if (!opts.services?.session) {
6628
6885
  sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
6629
6886
  if (count > 0) logger.info(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
@@ -6645,7 +6902,7 @@ async function createPreContextServices(input) {
6645
6902
  sessionId: session.id,
6646
6903
  projectSlug: wpaths.projectSlug,
6647
6904
  projectRoot,
6648
- projectName: path14.basename(projectRoot),
6905
+ projectName: path15.basename(projectRoot),
6649
6906
  workingDir,
6650
6907
  clientType: "webui",
6651
6908
  pid: process.pid,
@@ -6661,12 +6918,21 @@ async function createPreContextServices(input) {
6661
6918
  statusTracker.start();
6662
6919
  let stopHqSessionBridge;
6663
6920
  let hqTelemetryPublisher;
6921
+ const stopHqAuxBridges = [];
6664
6922
  try {
6665
- const { createHqPublisherFromEnv, startSessionTelemetryBridge } = await import("@wrongstack/core");
6923
+ const {
6924
+ createHqPublisherFromEnv,
6925
+ startSessionTelemetryBridge,
6926
+ startFleetTelemetryBridge,
6927
+ startBrainTelemetryBridge,
6928
+ startWorktreeTelemetryBridge,
6929
+ startToolTelemetryBridge,
6930
+ startCostTelemetryBridge
6931
+ } = await import("@wrongstack/core");
6666
6932
  const hqTelemetry = createHqPublisherFromEnv({
6667
6933
  clientKind: "webui",
6668
6934
  projectRoot,
6669
- projectName: path14.basename(projectRoot),
6935
+ projectName: path15.basename(projectRoot),
6670
6936
  appConfig: config,
6671
6937
  socketFactory: (url) => new WebSocket2(url)
6672
6938
  });
@@ -6678,11 +6944,41 @@ async function createPreContextServices(input) {
6678
6944
  events,
6679
6945
  sessionId: session.id,
6680
6946
  projectRoot,
6681
- projectName: path14.basename(projectRoot),
6947
+ projectName: path15.basename(projectRoot),
6682
6948
  globalRoot: wpaths.globalRoot,
6683
6949
  initialAgents: statusTracker?.getAgents(),
6684
6950
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
6685
6951
  });
6952
+ try {
6953
+ stopHqAuxBridges.push(
6954
+ startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
6955
+ );
6956
+ } catch {
6957
+ }
6958
+ try {
6959
+ stopHqAuxBridges.push(
6960
+ startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6961
+ );
6962
+ } catch {
6963
+ }
6964
+ try {
6965
+ stopHqAuxBridges.push(
6966
+ startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6967
+ );
6968
+ } catch {
6969
+ }
6970
+ try {
6971
+ stopHqAuxBridges.push(
6972
+ startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
6973
+ );
6974
+ } catch {
6975
+ }
6976
+ try {
6977
+ stopHqAuxBridges.push(
6978
+ startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6979
+ );
6980
+ } catch {
6981
+ }
6686
6982
  }
6687
6983
  } catch {
6688
6984
  }
@@ -6692,6 +6988,12 @@ async function createPreContextServices(input) {
6692
6988
  await registry.markClosing();
6693
6989
  statusTracker?.stop();
6694
6990
  stopHqSessionBridge?.();
6991
+ for (const stop of stopHqAuxBridges) {
6992
+ try {
6993
+ stop();
6994
+ } catch {
6995
+ }
6996
+ }
6695
6997
  hqTelemetryPublisher?.close();
6696
6998
  } catch {
6697
6999
  }
@@ -6721,22 +7023,27 @@ async function createPreContextServices(input) {
6721
7023
  const customModeStore = createCustomModeStore(wpaths.configDir);
6722
7024
  await customModeStore.load();
6723
7025
  console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
6724
- const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
7026
+ const resolvedModel = await resolveProviderModelMetadata(
7027
+ modelsRegistry,
7028
+ config.provider,
7029
+ config.model,
7030
+ config.providers?.[config.provider]
7031
+ );
6725
7032
  const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
6726
7033
  const modelCapabilitiesRef = { current: modelCapabilities };
6727
7034
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
6728
7035
  const skillInstaller = config.features.skills ? new SkillInstaller({
6729
- manifestPath: path14.join(wstackGlobalRoot2(), "installed-skills.json"),
6730
- projectSkillsDir: path14.join(projectRoot, ".wrongstack", "skills"),
6731
- globalSkillsDir: path14.join(wstackGlobalRoot2(), "skills"),
6732
- projectHash: projectHash(projectRoot),
7036
+ manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
7037
+ projectSkillsDir: wpaths.inProjectSkills,
7038
+ globalSkillsDir: wpaths.globalSkills,
7039
+ projectHash: wpaths.projectHash,
6733
7040
  skillLoader
6734
7041
  }) : void 0;
6735
7042
  const promptsEnabled = config.features.prompts !== false;
6736
7043
  const bundledPromptsDir = promptsEnabled ? (() => {
6737
7044
  try {
6738
7045
  const req = createRequire(import.meta.url);
6739
- return path14.join(path14.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7046
+ return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
6740
7047
  } catch {
6741
7048
  return void 0;
6742
7049
  }
@@ -6753,6 +7060,11 @@ async function createPreContextServices(input) {
6753
7060
  modelCapabilities: () => modelCapabilitiesRef.current,
6754
7061
  instructionPaths: { globalDir: wpaths.globalInstructions, projectDir: wpaths.inProjectInstructions }
6755
7062
  });
7063
+ if (container.has(TOKENS.SystemPromptBuilder)) {
7064
+ container.override(TOKENS.SystemPromptBuilder, () => systemPromptBuilder, { owner: "webui" });
7065
+ } else {
7066
+ container.bind(TOKENS.SystemPromptBuilder, () => systemPromptBuilder, { owner: "webui" });
7067
+ }
6756
7068
  let onlineAgents = [];
6757
7069
  try {
6758
7070
  const systemMailbox = new GlobalMailbox(wpaths.projectDir);
@@ -6814,11 +7126,11 @@ async function createPreContextServices(input) {
6814
7126
  }
6815
7127
 
6816
7128
  // src/server/start-webui.ts
6817
- import * as path20 from "path";
7129
+ import * as path22 from "path";
6818
7130
  import {
6819
- createDefaultPipelines as createDefaultPipelines2,
7131
+ createDefaultPipelines,
6820
7132
  createSessionEventBridge,
6821
- DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID3,
7133
+ DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
6822
7134
  expectDefined as expectDefined3,
6823
7135
  resolveSessionLoggingConfig,
6824
7136
  watchProviderConfig
@@ -7592,8 +7904,8 @@ var CollaborationWebSocketHandler = class {
7592
7904
  };
7593
7905
 
7594
7906
  // src/server/codebase-indexing.ts
7595
- import * as fs11 from "fs";
7596
- import * as path15 from "path";
7907
+ import * as fs12 from "fs";
7908
+ import * as path16 from "path";
7597
7909
  import {
7598
7910
  cancelPendingReindexes,
7599
7911
  enqueueReindex,
@@ -7642,11 +7954,11 @@ function setupWebUICodebaseIndexing(deps2) {
7642
7954
  let watcher;
7643
7955
  if (idx.watchExternal) {
7644
7956
  try {
7645
- watcher = fs11.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
7957
+ watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
7646
7958
  if (!filename) return;
7647
7959
  const rel = filename.toString();
7648
7960
  if (isIgnored(rel)) return;
7649
- const abs = path15.resolve(deps2.projectRoot, rel);
7961
+ const abs = path16.resolve(deps2.projectRoot, rel);
7650
7962
  enqueueFile(abs);
7651
7963
  });
7652
7964
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -7659,7 +7971,7 @@ function setupWebUICodebaseIndexing(deps2) {
7659
7971
  }
7660
7972
  function enqueueFile(filePath) {
7661
7973
  if (!idx.onEdit && !idx.watchExternal) return;
7662
- const abs = path15.isAbsolute(filePath) ? path15.normalize(filePath) : path15.resolve(deps2.projectRoot, filePath);
7974
+ const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
7663
7975
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
7664
7976
  enqueueReindex({
7665
7977
  projectRoot: deps2.projectRoot,
@@ -7696,75 +8008,150 @@ function isIgnored(rel) {
7696
8008
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
7697
8009
  }
7698
8010
  function isInside2(root, target) {
7699
- const normalizedRoot = path15.resolve(root);
7700
- const normalizedTarget = path15.resolve(target);
7701
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path15.sep);
8011
+ const normalizedRoot = path16.resolve(root);
8012
+ const normalizedTarget = path16.resolve(target);
8013
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
7702
8014
  }
7703
8015
 
7704
8016
  // src/server/discover-mailbox-bridge.ts
7705
- import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core";
8017
+ import { spawn as spawn3 } from "child_process";
8018
+ import { createRequire as createRequire2 } from "module";
8019
+ import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
7706
8020
  import { readLiveLock } from "@wrongstack/core/coordination";
8021
+ var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
7707
8022
  async function discoverMailboxBridgeForWebui(params) {
7708
8023
  const mode = params.config?.features?.mailboxBridge ?? "auto";
7709
8024
  if (mode === "off") return;
7710
- const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot3());
7711
- const result = await readLiveLock(projectDir);
8025
+ const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot2());
8026
+ let result = await readLiveLock(projectDir);
8027
+ let spawnedPid = null;
8028
+ if (result.kind !== "live") {
8029
+ spawnedPid = spawnMailboxBridge(params.projectRoot, params.logger);
8030
+ if (spawnedPid !== null) {
8031
+ const live = await waitForLiveMailboxBridge(projectDir, MAILBOX_BRIDGE_BOOT_TIMEOUT_MS);
8032
+ if (live) {
8033
+ stashBridge(params, live, projectDir, "spawned", spawnedPid);
8034
+ params.logger.debug("webui spawned mailbox bridge", {
8035
+ url: live.url,
8036
+ lockPath: projectDir,
8037
+ childPid: spawnedPid
8038
+ });
8039
+ return;
8040
+ }
8041
+ result = await readLiveLock(projectDir);
8042
+ }
8043
+ }
7712
8044
  switch (result.kind) {
7713
8045
  case "live": {
7714
8046
  params.logger.debug("webui joined existing mailbox bridge", {
7715
8047
  url: result.lock.url,
7716
8048
  lockPath: projectDir
7717
8049
  });
7718
- params.ctx.meta["mailboxBridge"] = {
7719
- url: result.lock.url,
7720
- token: result.lock.token,
7721
- lockPath: projectDir,
7722
- childPid: null,
7723
- source: "joined"
7724
- };
8050
+ stashBridge(params, result.lock, projectDir, "joined", null);
7725
8051
  break;
7726
8052
  }
7727
8053
  case "probe-failed": {
7728
8054
  params.logger.warn(
7729
8055
  "mailbox bridge present but /healthz unreachable; webui will start without external-agent connectivity",
7730
- { url: result.lock.url, lockPath: projectDir }
8056
+ { url: result.lock.url, lockPath: projectDir, spawnedPid }
7731
8057
  );
7732
- params.ctx.meta["mailboxBridge"] = {
7733
- url: result.lock.url,
7734
- token: result.lock.token,
7735
- lockPath: projectDir,
7736
- childPid: null,
7737
- source: "unhealthy"
7738
- };
8058
+ stashBridge(params, result.lock, projectDir, "unhealthy", null);
7739
8059
  break;
7740
8060
  }
7741
8061
  case "absent": {
7742
- params.logger.info(
7743
- "no mailbox bridge running; webui will start without external-agent connectivity. Run `wstack mailbox serve` or a CLI surface to bring one up.",
7744
- { projectDir }
8062
+ params.logger.warn(
8063
+ "mailbox bridge unavailable; webui will start without external-agent connectivity. Run `wstack mailbox serve` or a CLI surface to bring one up.",
8064
+ { projectDir, spawnedPid }
7745
8065
  );
7746
8066
  break;
7747
8067
  }
7748
8068
  }
7749
8069
  }
8070
+ function stashBridge(params, lock, lockPath, source, childPid) {
8071
+ params.ctx.meta["mailboxBridge"] = {
8072
+ url: lock.url,
8073
+ token: lock.token,
8074
+ lockPath,
8075
+ childPid,
8076
+ source
8077
+ };
8078
+ }
8079
+ async function waitForLiveMailboxBridge(projectDir, timeoutMs) {
8080
+ const deadline = Date.now() + timeoutMs;
8081
+ while (Date.now() < deadline) {
8082
+ await sleep(120);
8083
+ const result = await readLiveLock(projectDir);
8084
+ if (result.kind === "live") return result.lock;
8085
+ }
8086
+ return null;
8087
+ }
8088
+ function spawnMailboxBridge(projectRoot, logger) {
8089
+ const invocation = mailboxServeInvocation();
8090
+ try {
8091
+ const child = spawn3(invocation.command, invocation.args, {
8092
+ cwd: projectRoot,
8093
+ detached: process.platform !== "win32",
8094
+ env: process.env,
8095
+ stdio: "ignore",
8096
+ windowsHide: true,
8097
+ ...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {}
8098
+ });
8099
+ child.once("error", (err) => {
8100
+ logger.warn("failed to spawn mailbox bridge for webui", {
8101
+ command: invocation.command,
8102
+ err: err instanceof Error ? err.message : String(err)
8103
+ });
8104
+ });
8105
+ child.unref();
8106
+ return child.pid ?? null;
8107
+ } catch (err) {
8108
+ logger.warn("failed to spawn mailbox bridge for webui", {
8109
+ command: invocation.command,
8110
+ err: err instanceof Error ? err.message : String(err)
8111
+ });
8112
+ return null;
8113
+ }
8114
+ }
8115
+ function mailboxServeInvocation() {
8116
+ const explicitCliEntry = process.env["WRONGSTACK_CLI_ENTRY"];
8117
+ if (explicitCliEntry) {
8118
+ return { command: process.execPath, args: [explicitCliEntry, "mailbox", "serve"] };
8119
+ }
8120
+ try {
8121
+ const require2 = createRequire2(import.meta.url);
8122
+ const cliEntry = require2.resolve("@wrongstack/cli");
8123
+ return { command: process.execPath, args: [cliEntry, "mailbox", "serve"] };
8124
+ } catch {
8125
+ return {
8126
+ command: process.platform === "win32" ? "wstack.cmd" : "wstack",
8127
+ args: ["mailbox", "serve"]
8128
+ };
8129
+ }
8130
+ }
8131
+ function sleep(ms) {
8132
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
8133
+ }
7750
8134
 
7751
8135
  // src/server/terminal-ws-handler.ts
7752
- import { createRequire as createRequire2 } from "module";
8136
+ import { spawn as spawnChild } from "child_process";
8137
+ import { createRequire as createRequire3 } from "module";
7753
8138
  import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
7754
8139
  var MAX_SESSIONS_PER_CLIENT = 8;
7755
8140
  var DEFAULT_COLS = 80;
7756
8141
  var DEFAULT_ROWS = 24;
7757
- var requireFromHere = createRequire2(import.meta.url);
8142
+ var requireFromHere = createRequire3(import.meta.url);
7758
8143
  var cachedNodePty;
7759
8144
  var TerminalWebSocketHandler = class {
7760
- constructor(getCwd, logger, loadNodePty = defaultLoadNodePty) {
8145
+ constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
7761
8146
  this.getCwd = getCwd;
7762
8147
  this.logger = logger;
7763
8148
  this.loadNodePty = loadNodePty;
8149
+ this.killProcessTree = killProcessTree;
7764
8150
  }
7765
8151
  getCwd;
7766
8152
  logger;
7767
8153
  loadNodePty;
8154
+ killProcessTree;
7768
8155
  /** ws → (terminalId → pty). */
7769
8156
  sessions = /* @__PURE__ */ new Map();
7770
8157
  addClient(ws) {
@@ -7826,10 +8213,14 @@ var TerminalWebSocketHandler = class {
7826
8213
  cols: clampDim(payload.cols, DEFAULT_COLS),
7827
8214
  rows: clampDim(payload.rows, DEFAULT_ROWS),
7828
8215
  cwd: this.getCwd(),
7829
- env: process.env
8216
+ env: process.env,
8217
+ ...windowsPtyOptions()
7830
8218
  });
7831
8219
  } catch (err) {
8220
+ const msg = `Integrated terminal failed to start: ${toErrorMessage7(err)}`;
7832
8221
  this.logger.warn?.(`terminal spawn failed: ${toErrorMessage7(err)}`);
8222
+ this.send(ws, { type: "terminal.output", payload: { id: payload.id, data: `${msg}\r
8223
+ ` } });
7833
8224
  this.send(ws, { type: "terminal.exit", payload: { id: payload.id, exitCode: -1 } });
7834
8225
  return;
7835
8226
  }
@@ -7862,21 +8253,38 @@ var TerminalWebSocketHandler = class {
7862
8253
  const pty = map?.get(id);
7863
8254
  if (!pty) return;
7864
8255
  map?.delete(id);
7865
- try {
7866
- pty.kill();
7867
- } catch {
7868
- }
8256
+ this.killPty(pty, "terminal close");
7869
8257
  }
7870
8258
  disposeClient(ws) {
7871
8259
  const map = this.sessions.get(ws);
7872
8260
  if (!map) return;
7873
8261
  for (const pty of map.values()) {
8262
+ this.killPty(pty, "terminal client dispose");
8263
+ }
8264
+ this.sessions.delete(ws);
8265
+ }
8266
+ killPty(pty, reason) {
8267
+ if (process.platform === "win32" && isPositivePid(pty.pid)) {
8268
+ this.killWindowsProcessTree(pty, reason);
8269
+ return;
8270
+ }
8271
+ try {
8272
+ pty.kill();
8273
+ } catch (err) {
8274
+ this.logger.warn?.(`${reason} failed: ${toErrorMessage7(err)}`);
8275
+ }
8276
+ }
8277
+ killWindowsProcessTree(pty, reason) {
8278
+ try {
8279
+ this.killProcessTree(pty.pid);
7874
8280
  try {
7875
- pty.kill();
8281
+ pty.write("");
8282
+ pty.write("exit\r");
7876
8283
  } catch {
7877
8284
  }
8285
+ } catch (err) {
8286
+ this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage7(err)}`);
7878
8287
  }
7879
- this.sessions.delete(ws);
7880
8288
  }
7881
8289
  send(ws, msg) {
7882
8290
  try {
@@ -7900,6 +8308,21 @@ function isStr(v) {
7900
8308
  function numOrUndef(v) {
7901
8309
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
7902
8310
  }
8311
+ function windowsPtyOptions() {
8312
+ if (process.platform !== "win32") return {};
8313
+ if (process.env.WRONGSTACK_TERMINAL_USE_CONPTY_DLL !== "1") return {};
8314
+ return { useConptyDll: true };
8315
+ }
8316
+ function defaultKillProcessTree(pid) {
8317
+ const child = spawnChild("taskkill", ["/PID", String(pid), "/T", "/F"], {
8318
+ stdio: "ignore",
8319
+ windowsHide: true
8320
+ });
8321
+ child.once("error", () => void 0);
8322
+ }
8323
+ function isPositivePid(value) {
8324
+ return Number.isInteger(value) && Number(value) > 0;
8325
+ }
7903
8326
  function clampDim(value, fallback) {
7904
8327
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
7905
8328
  return Math.max(1, Math.min(1e3, Math.floor(value)));
@@ -7922,20 +8345,14 @@ async function createAgentServices(input) {
7922
8345
  providerRegistry,
7923
8346
  modelsRegistry,
7924
8347
  events,
7925
- mcpRegistry,
7926
- skillLoader,
7927
- skillInstaller,
7928
- tokenCounter,
7929
8348
  modelCapabilitiesRef
7930
8349
  } = input;
7931
8350
  const collabBus = new CollaborationBusCtor();
7932
8351
  const pipelines = input.pipelines;
7933
- const collabPause = collabPauseMiddleware(collabBus, { logger });
7934
- Object.defineProperty(collabPause, "name", { value: "collab-pause" });
7935
- pipelines.toolCall.prepend(collabPause);
7936
8352
  const collabInject = collabInjectMiddleware(collabBus, { logger });
7937
- Object.defineProperty(collabInject, "name", { value: "collab-inject" });
7938
8353
  pipelines.toolCall.prepend(collabInject);
8354
+ const collabPause = collabPauseMiddleware(collabBus, { logger });
8355
+ pipelines.toolCall.prepend(collabPause);
7939
8356
  installDesignStudioMiddleware({ pipelines, ctx: context });
7940
8357
  const codebaseIndexing = setupWebUICodebaseIndexing({
7941
8358
  config,
@@ -7956,7 +8373,12 @@ async function createAgentServices(input) {
7956
8373
  let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
7957
8374
  if (!effectiveMaxContext) {
7958
8375
  try {
7959
- const m = await modelsRegistry.getModel(provider.id, context.model);
8376
+ const m = await resolveProviderModelMetadata(
8377
+ modelsRegistry,
8378
+ config.provider,
8379
+ context.model,
8380
+ config.providers?.[config.provider]
8381
+ );
7960
8382
  effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
7961
8383
  } catch {
7962
8384
  }
@@ -7987,15 +8409,21 @@ async function createAgentServices(input) {
7987
8409
  );
7988
8410
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
7989
8411
  }
7990
- const updateAutoCompactionMaxContext = async (newProvider) => {
8412
+ const updateAutoCompactionMaxContext = async (newProvider, providerId = newProvider.id, providerCfg) => {
7991
8413
  await modelsRegistry.refresh().catch((err) => {
7992
8414
  logger.warn(
7993
- `models.dev refresh failed for ${newProvider.id}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
8415
+ `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
7994
8416
  );
7995
8417
  });
7996
- let newMaxContext = config.context?.effectiveMaxContext ?? newProvider.capabilities.maxContext;
8418
+ const currentConfig = input.config;
8419
+ let newMaxContext = currentConfig.context?.effectiveMaxContext ?? newProvider.capabilities.maxContext;
7997
8420
  try {
7998
- const m = await modelsRegistry.getModel(newProvider.id, context.model);
8421
+ const m = await resolveProviderModelMetadata(
8422
+ modelsRegistry,
8423
+ providerId,
8424
+ context.model,
8425
+ providerCfg ?? currentConfig.providers?.[providerId]
8426
+ );
7999
8427
  newMaxContext = m?.capabilities?.maxContext ?? newMaxContext;
8000
8428
  } catch {
8001
8429
  }
@@ -8204,6 +8632,24 @@ async function createAgentServices(input) {
8204
8632
  };
8205
8633
  }
8206
8634
 
8635
+ // src/server/pending-confirms.ts
8636
+ function isDestructivePendingConfirm(confirm) {
8637
+ return confirm.riskTier === "destructive" || confirm.decisionSource === "yolo_destructive";
8638
+ }
8639
+ function resolveYoloEligiblePendingConfirms(pendingConfirms) {
8640
+ for (const [id, confirm] of pendingConfirms) {
8641
+ if (isDestructivePendingConfirm(confirm)) continue;
8642
+ pendingConfirms.delete(id);
8643
+ confirm.resolve("yes");
8644
+ }
8645
+ }
8646
+ function resolveAllPendingConfirms(pendingConfirms, decision) {
8647
+ for (const [id, confirm] of pendingConfirms) {
8648
+ pendingConfirms.delete(id);
8649
+ confirm.resolve(decision);
8650
+ }
8651
+ }
8652
+
8207
8653
  // src/server/connection-handler.ts
8208
8654
  var REPLAY_MESSAGE_CAP = 2e3;
8209
8655
  var RATE_LIMIT_MESSAGES = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
@@ -8315,12 +8761,7 @@ function createConnectionHandler(opts) {
8315
8761
  const closing = opts.clients.get(ws);
8316
8762
  opts.clients.delete(ws);
8317
8763
  if (closing) rateLimits.delete(closing.connId);
8318
- if (opts.pendingConfirms.size > 0) {
8319
- for (const [, resolve11] of opts.pendingConfirms) {
8320
- resolve11("no");
8321
- }
8322
- opts.pendingConfirms.clear();
8323
- }
8764
+ resolveAllPendingConfirms(opts.pendingConfirms, "no");
8324
8765
  });
8325
8766
  ws.on("error", (err) => {
8326
8767
  console.warn(
@@ -8335,6 +8776,9 @@ function createConnectionHandler(opts) {
8335
8776
  };
8336
8777
  }
8337
8778
 
8779
+ // src/server/message-dispatcher.ts
8780
+ import path17 from "path";
8781
+
8338
8782
  // src/server/autophase-routes.ts
8339
8783
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
8340
8784
  if (!msg.type.startsWith("autophase.")) return false;
@@ -8361,15 +8805,15 @@ async function handleBrainRoute(ws, msg, handlers) {
8361
8805
 
8362
8806
  // src/server/goal-handlers.ts
8363
8807
  import { resolveWstackPaths } from "@wrongstack/core/utils";
8364
- async function handleGoalGet(projectRoot, broadcast3) {
8808
+ async function handleGoalGet(projectRoot, broadcast2) {
8365
8809
  try {
8366
8810
  const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
8367
- const { readFile: readFile9 } = await import("fs/promises");
8368
- const raw = await readFile9(goalPath, "utf8");
8811
+ const { readFile: readFile10 } = await import("fs/promises");
8812
+ const raw = await readFile10(goalPath, "utf8");
8369
8813
  const goal = JSON.parse(raw);
8370
- broadcast3({ type: "goal.updated", payload: goal });
8814
+ broadcast2({ type: "goal.updated", payload: goal });
8371
8815
  } catch {
8372
- broadcast3({ type: "goal.updated", payload: null });
8816
+ broadcast2({ type: "goal.updated", payload: null });
8373
8817
  }
8374
8818
  }
8375
8819
 
@@ -9029,6 +9473,16 @@ function createMessageDispatcher(opts) {
9029
9473
  broadcast: (m) => broadcast(state.getClients(), m)
9030
9474
  };
9031
9475
  }
9476
+ function makeSkillsContext() {
9477
+ const projectRoot = state.getProjectRoot();
9478
+ return {
9479
+ skillLoader: deps2.skillLoader,
9480
+ skillInstaller: deps2.skillInstaller,
9481
+ projectRoot,
9482
+ projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
9483
+ globalSkillsDir: deps2.wpaths.globalSkills
9484
+ };
9485
+ }
9032
9486
  function messageSessionId(msg) {
9033
9487
  const payload = msg.payload;
9034
9488
  return payload && typeof payload === "object" && typeof payload.sessionId === "string" ? payload.sessionId : void 0;
@@ -9141,10 +9595,10 @@ function createMessageDispatcher(opts) {
9141
9595
  case "tool.confirm_result": {
9142
9596
  if (!ensureCurrentSession(ws, msg, "tool.confirm_result")) return;
9143
9597
  const { id, decision } = msg.payload;
9144
- const resolve11 = pendingConfirms.get(id);
9145
- if (resolve11) {
9598
+ const confirm = pendingConfirms.get(id);
9599
+ if (confirm) {
9146
9600
  pendingConfirms.delete(id);
9147
- resolve11(decision);
9601
+ confirm.resolve(decision);
9148
9602
  }
9149
9603
  break;
9150
9604
  }
@@ -9202,60 +9656,28 @@ function createMessageDispatcher(opts) {
9202
9656
  throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
9203
9657
  // Skills — full request→response cycle lives in skills-handlers.ts.
9204
9658
  case "skills.list":
9205
- await handleSkillsList(ws, {
9206
- skillLoader: deps2.skillLoader,
9207
- skillInstaller: deps2.skillInstaller,
9208
- projectRoot: state.getProjectRoot()
9209
- });
9659
+ await handleSkillsList(ws, makeSkillsContext());
9210
9660
  break;
9211
9661
  case "skills.content":
9212
- await handleSkillsContent(ws, {
9213
- skillLoader: deps2.skillLoader,
9214
- skillInstaller: deps2.skillInstaller,
9215
- projectRoot: state.getProjectRoot()
9216
- }, msg);
9662
+ await handleSkillsContent(ws, makeSkillsContext(), msg);
9217
9663
  break;
9218
9664
  case "skills.install":
9219
- await handleSkillsInstall(ws, {
9220
- skillLoader: deps2.skillLoader,
9221
- skillInstaller: deps2.skillInstaller,
9222
- projectRoot: state.getProjectRoot()
9223
- }, msg);
9665
+ await handleSkillsInstall(ws, makeSkillsContext(), msg);
9224
9666
  break;
9225
9667
  case "skills.uninstall":
9226
- await handleSkillsUninstall(ws, {
9227
- skillLoader: deps2.skillLoader,
9228
- skillInstaller: deps2.skillInstaller,
9229
- projectRoot: state.getProjectRoot()
9230
- }, msg);
9668
+ await handleSkillsUninstall(ws, makeSkillsContext(), msg);
9231
9669
  break;
9232
9670
  case "skills.update":
9233
- await handleSkillsUpdate(ws, {
9234
- skillLoader: deps2.skillLoader,
9235
- skillInstaller: deps2.skillInstaller,
9236
- projectRoot: state.getProjectRoot()
9237
- }, msg);
9671
+ await handleSkillsUpdate(ws, makeSkillsContext(), msg);
9238
9672
  break;
9239
9673
  case "skills.create":
9240
- await handleSkillsCreate(ws, {
9241
- skillLoader: deps2.skillLoader,
9242
- skillInstaller: deps2.skillInstaller,
9243
- projectRoot: state.getProjectRoot()
9244
- }, msg);
9674
+ await handleSkillsCreate(ws, makeSkillsContext(), msg);
9245
9675
  break;
9246
9676
  case "skills.edit":
9247
- await handleSkillsEdit(ws, {
9248
- skillLoader: deps2.skillLoader,
9249
- skillInstaller: deps2.skillInstaller,
9250
- projectRoot: state.getProjectRoot()
9251
- }, msg);
9677
+ await handleSkillsEdit(ws, makeSkillsContext(), msg);
9252
9678
  break;
9253
9679
  case "skills.export":
9254
- await handleSkillsExport(ws, {
9255
- skillLoader: deps2.skillLoader,
9256
- skillInstaller: deps2.skillInstaller,
9257
- projectRoot: state.getProjectRoot()
9258
- });
9680
+ await handleSkillsExport(ws, makeSkillsContext());
9259
9681
  break;
9260
9682
  // Prompt library — shared handlers (prompts-handlers.ts).
9261
9683
  case "prompts.list":
@@ -9369,7 +9791,12 @@ function createMessageDispatcher(opts) {
9369
9791
  const session = state.getSession();
9370
9792
  const usage = deps2.tokenCounter.total();
9371
9793
  const cacheStats = deps2.tokenCounter.cacheStats();
9372
- const m = await deps2.modelsRegistry.getModel(config.provider, config.model).catch(() => null);
9794
+ const m = await resolveProviderModelMetadata(
9795
+ deps2.modelsRegistry,
9796
+ config.provider,
9797
+ config.model,
9798
+ config.providers?.[config.provider]
9799
+ ).catch(() => null);
9373
9800
  const cost = computeUsageCost(usage, getCostRates(m));
9374
9801
  send(ws, {
9375
9802
  type: "stats.get",
@@ -9460,7 +9887,7 @@ function createMessageDispatcher(opts) {
9460
9887
  // src/server/pref-helpers.ts
9461
9888
  import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
9462
9889
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
9463
- import * as fs12 from "fs/promises";
9890
+ import * as fs13 from "fs/promises";
9464
9891
  var PREF_KEYS = [
9465
9892
  "autonomy",
9466
9893
  "autonomyDelayMs",
@@ -9486,6 +9913,7 @@ var PREF_KEYS = [
9486
9913
  "tokenSavingTier",
9487
9914
  "maxConcurrent",
9488
9915
  "titleAnimation",
9916
+ "uiLocale",
9489
9917
  "logLevel",
9490
9918
  "auditLevel",
9491
9919
  "hqEnabled",
@@ -9519,7 +9947,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
9519
9947
  const write = async () => {
9520
9948
  let raw;
9521
9949
  try {
9522
- raw = await fs12.readFile(globalConfigPath, "utf8");
9950
+ raw = await fs13.readFile(globalConfigPath, "utf8");
9523
9951
  } catch {
9524
9952
  raw = "{}";
9525
9953
  }
@@ -9561,7 +9989,10 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9561
9989
  setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
9562
9990
  if (typeof payload["autoProceedMaxIterations"] === "number")
9563
9991
  setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
9564
- if (typeof payload["yolo"] === "boolean") setAutonomy("yolo", payload["yolo"]);
9992
+ if (typeof payload["yolo"] === "boolean") {
9993
+ setAutonomy("yolo", payload["yolo"]);
9994
+ decrypted.yolo = payload["yolo"];
9995
+ }
9565
9996
  if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
9566
9997
  if (typeof payload["confirmExit"] === "boolean")
9567
9998
  setAutonomy("confirmExit", payload["confirmExit"]);
@@ -9576,6 +10007,7 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9576
10007
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
9577
10008
  if (typeof payload["nextPrediction"] === "boolean")
9578
10009
  decrypted.nextPrediction = payload["nextPrediction"];
10010
+ if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
9579
10011
  if (Array.isArray(payload["fallbackModels"]))
9580
10012
  decrypted.fallbackModels = payload["fallbackModels"];
9581
10013
  if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
@@ -9693,16 +10125,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
9693
10125
  }
9694
10126
 
9695
10127
  // src/server/projects-manifest.ts
9696
- import * as fs13 from "fs/promises";
9697
- import * as path16 from "path";
10128
+ import * as fs14 from "fs/promises";
10129
+ import * as path18 from "path";
9698
10130
  import { projectSlug } from "@wrongstack/core";
9699
10131
  function projectsJsonPath(globalConfigPath) {
9700
- const base = path16.dirname(globalConfigPath);
9701
- return path16.join(base, "projects.json");
10132
+ const base = path18.dirname(globalConfigPath);
10133
+ return path18.join(base, "projects.json");
9702
10134
  }
9703
10135
  async function loadManifest(globalConfigPath) {
9704
10136
  try {
9705
- const raw = await fs13.readFile(projectsJsonPath(globalConfigPath), "utf8");
10137
+ const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
9706
10138
  const parsed = JSON.parse(raw);
9707
10139
  return { projects: parsed.projects ?? [] };
9708
10140
  } catch {
@@ -9711,16 +10143,16 @@ async function loadManifest(globalConfigPath) {
9711
10143
  }
9712
10144
  async function saveManifest(manifest, globalConfigPath) {
9713
10145
  const file = projectsJsonPath(globalConfigPath);
9714
- await fs13.mkdir(path16.dirname(file), { recursive: true });
9715
- await fs13.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
10146
+ await fs14.mkdir(path18.dirname(file), { recursive: true });
10147
+ await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
9716
10148
  }
9717
10149
  function generateProjectSlug(rootPath) {
9718
10150
  return projectSlug(rootPath);
9719
10151
  }
9720
10152
  async function ensureProjectDataDir(slug, globalConfigPath) {
9721
- const base = path16.dirname(globalConfigPath);
9722
- const dir = path16.join(base, "projects", slug);
9723
- await fs13.mkdir(dir, { recursive: true });
10153
+ const base = path18.dirname(globalConfigPath);
10154
+ const dir = path18.join(base, "projects", slug);
10155
+ await fs14.mkdir(dir, { recursive: true });
9724
10156
  return dir;
9725
10157
  }
9726
10158
 
@@ -9754,7 +10186,7 @@ function projectSavedProviders(providers) {
9754
10186
  }
9755
10187
  var probeScrubber = new DefaultSecretScrubber();
9756
10188
  function createProviderHandlers(deps2) {
9757
- const { globalConfigPath, vault, broadcast: broadcast3, clients } = deps2;
10189
+ const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
9758
10190
  let configWriteLock = deps2.getConfigWriteLock();
9759
10191
  async function loadConfigProviders() {
9760
10192
  return loadSavedProviders(globalConfigPath, vault);
@@ -9842,7 +10274,7 @@ function createProviderHandlers(deps2) {
9842
10274
  }
9843
10275
  }
9844
10276
  function broadcastSaved(providers) {
9845
- broadcast3(clients, {
10277
+ broadcast2(clients, {
9846
10278
  type: "providers.saved",
9847
10279
  payload: { providers: projectSavedProviders(providers) }
9848
10280
  });
@@ -10035,7 +10467,7 @@ function createProviderHandlers(deps2) {
10035
10467
  }
10036
10468
 
10037
10469
  // src/server/routes.ts
10038
- import path19 from "path";
10470
+ import path21 from "path";
10039
10471
  import {
10040
10472
  enhanceUserPrompt,
10041
10473
  gatedEnhancerReasoning,
@@ -10050,14 +10482,19 @@ async function handleMailboxMessages(ws, deps2, payload) {
10050
10482
  try {
10051
10483
  const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
10052
10484
  const mb = new GlobalMailbox3(dir);
10485
+ const limit = payload?.limit ?? 30;
10486
+ const unreadForAgent = payload?.unreadOnly === true && payload.agentId !== void 0;
10053
10487
  const messages = await mb.query({
10054
- limit: payload?.limit ?? 30,
10488
+ limit: payload?.unreadOnly === true && payload.agentId === void 0 ? Math.max(limit * 5, 100) : limit,
10489
+ to: payload?.agentId,
10490
+ unreadBy: unreadForAgent ? payload.agentId : void 0,
10055
10491
  incompleteOnly: payload?.incompleteOnly ?? false
10056
10492
  });
10493
+ const visibleMessages = payload?.unreadOnly === true && payload.agentId === void 0 ? messages.filter((m) => Object.keys(m.readBy).length === 0).slice(0, limit) : messages;
10057
10494
  send(ws, {
10058
10495
  type: "mailbox.messages",
10059
10496
  payload: {
10060
- messages: messages.map((m) => ({
10497
+ messages: visibleMessages.map((m) => ({
10061
10498
  id: m.id,
10062
10499
  from: m.from,
10063
10500
  to: m.to,
@@ -10219,15 +10656,7 @@ function createModeHandlers(ctx) {
10219
10656
  }
10220
10657
 
10221
10658
  // src/server/project-handlers.ts
10222
- import * as fs14 from "fs/promises";
10223
- import * as path17 from "path";
10224
- import {
10225
- DefaultSessionStore as DefaultSessionStore2,
10226
- DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
10227
- getSessionRegistry as getSessionRegistry2,
10228
- resolveWstackPaths as resolveWstackPaths3,
10229
- ToolValidationError as ToolValidationError6
10230
- } from "@wrongstack/core";
10659
+ import * as path19 from "path";
10231
10660
  function createProjectHandlers(ctx) {
10232
10661
  return {
10233
10662
  listProjects: async (ws) => {
@@ -10239,213 +10668,29 @@ function createProjectHandlers(ctx) {
10239
10668
  }
10240
10669
  },
10241
10670
  addProject: async (ws, msg) => {
10242
- const parsed = validateProjectsAddPayload(msg.payload);
10243
- if (!parsed.ok) {
10244
- send(ws, {
10245
- type: "projects.added",
10246
- payload: { name: "", root: "", slug: "", message: parsed.message }
10247
- });
10248
- return;
10249
- }
10250
- const { root: addRoot, name: displayName } = parsed.value;
10251
- try {
10252
- const resolved = path17.resolve(addRoot);
10253
- await fs14.access(resolved);
10254
- const stat3 = await fs14.stat(resolved);
10255
- if (!stat3.isDirectory()) {
10256
- throw new ToolValidationError6({ message: `Not a directory: ${resolved}`, field: "path" });
10257
- }
10258
- const manifest = await loadManifest(ctx.globalConfigPath);
10259
- const existing = manifest.projects.find((p) => p.root === resolved);
10260
- if (existing) {
10261
- send(ws, {
10262
- type: "projects.added",
10263
- payload: {
10264
- name: existing.name,
10265
- root: existing.root,
10266
- slug: existing.slug,
10267
- message: `Already registered as "${existing.name}"`
10268
- }
10269
- });
10270
- return;
10671
+ const payload = msg.payload;
10672
+ send(ws, {
10673
+ type: "projects.added",
10674
+ payload: {
10675
+ name: typeof payload?.name === "string" ? payload.name : "",
10676
+ root: typeof payload?.root === "string" ? payload.root : "",
10677
+ slug: "",
10678
+ message: "Project registration is disabled in WebUI. Open/register projects from the launcher or desktop shell."
10271
10679
  }
10272
- const name2 = displayName?.trim() || path17.basename(resolved);
10273
- const slug = generateProjectSlug(resolved);
10274
- await ensureProjectDataDir(slug, ctx.globalConfigPath);
10275
- const now = (/* @__PURE__ */ new Date()).toISOString();
10276
- manifest.projects.push({ name: name2, root: resolved, slug, lastSeen: now, createdAt: now });
10277
- await saveManifest(manifest, ctx.globalConfigPath);
10278
- send(ws, {
10279
- type: "projects.added",
10280
- payload: { name: name2, root: resolved, slug, message: `Registered project "${name2}"` }
10281
- });
10282
- } catch (err) {
10283
- send(ws, {
10284
- type: "projects.added",
10285
- payload: { name: path17.basename(addRoot), root: addRoot, slug: "", message: errMessage(err) }
10286
- });
10287
- }
10680
+ });
10288
10681
  },
10289
10682
  selectProject: async (ws, msg) => {
10290
- const parsed = validateProjectsSelectPayload(msg.payload);
10291
- if (!parsed.ok) {
10292
- send(ws, {
10293
- type: "projects.selected",
10294
- payload: { root: "", name: "", message: parsed.message }
10295
- });
10296
- return;
10297
- }
10298
- const { root: selRoot, name: selName } = parsed.value;
10299
- try {
10300
- const resolved = path17.resolve(selRoot);
10301
- try {
10302
- await fs14.access(resolved);
10303
- const stat3 = await fs14.stat(resolved);
10304
- if (!stat3.isDirectory()) {
10305
- throw new ToolValidationError6({ message: `Not a directory: ${resolved}`, field: "path" });
10306
- }
10307
- } catch (err) {
10308
- send(ws, {
10309
- type: "projects.selected",
10310
- payload: {
10311
- root: selRoot,
10312
- name: selName || path17.basename(selRoot),
10313
- message: `Cannot switch: ${errMessage(err)}`
10314
- }
10315
- });
10316
- return;
10317
- }
10318
- const manifest = await loadManifest(ctx.globalConfigPath);
10319
- const entry = manifest.projects.find((p) => p.root === resolved);
10320
- if (entry) {
10321
- entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
10322
- entry.lastWorkingDir = resolved;
10323
- } else {
10324
- const name2 = selName?.trim() || path17.basename(resolved);
10325
- const slug = generateProjectSlug(resolved);
10326
- manifest.projects.push({
10327
- name: name2,
10328
- root: resolved,
10329
- slug,
10330
- lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
10331
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
10332
- lastWorkingDir: resolved
10333
- });
10334
- await ensureProjectDataDir(slug, ctx.globalConfigPath);
10335
- }
10336
- await saveManifest(manifest, ctx.globalConfigPath);
10337
- ctx.abortRunLock();
10338
- ctx.setProjectRoot(resolved);
10339
- ctx.setWorkingDir(resolved);
10340
- ctx.context.cwd = resolved;
10341
- ctx.context.projectRoot = resolved;
10342
- const switchSlug = entry?.slug ?? generateProjectSlug(resolved);
10343
- try {
10344
- const modeId = ctx.getModeId();
10345
- const switchMode = modeId === "default" ? void 0 : await ctx.modeStore.getMode(modeId);
10346
- const switchPaths = resolveWstackPaths3({
10347
- projectRoot: resolved,
10348
- globalRoot: ctx.wpaths.globalRoot
10349
- });
10350
- const switchBuilder = new DefaultSystemPromptBuilder3({
10351
- memoryStore: ctx.memoryStore,
10352
- skillLoader: ctx.skillLoader,
10353
- modeStore: ctx.modeStore,
10354
- modeId,
10355
- modePrompt: switchMode?.prompt ?? "",
10356
- modelCapabilities: ctx.modelCapabilities,
10357
- instructionPaths: {
10358
- globalDir: switchPaths.globalInstructions,
10359
- projectDir: switchPaths.inProjectInstructions
10360
- }
10361
- });
10362
- ctx.context.systemPrompt = await switchBuilder.build({
10363
- cwd: resolved,
10364
- projectRoot: resolved,
10365
- tools: ctx.toolRegistry.list(),
10366
- provider: ctx.config.provider,
10367
- model: ctx.config.model
10368
- });
10369
- } catch {
10370
- }
10371
- const newSessionsDir = path17.join(
10372
- path17.dirname(ctx.globalConfigPath),
10373
- "projects",
10374
- switchSlug,
10375
- "sessions"
10376
- );
10377
- await fs14.mkdir(newSessionsDir, { recursive: true });
10378
- const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
10379
- const oldSession = ctx.getSession();
10380
- const oldSessionId = oldSession.id;
10381
- try {
10382
- await oldSession.append({
10383
- type: "session_end",
10384
- ts: (/* @__PURE__ */ new Date()).toISOString(),
10385
- usage: ctx.tokenCounter.total()
10386
- });
10387
- await oldSession.close();
10388
- } catch {
10389
- }
10390
- ctx.setSessionStore(newSessionStore);
10391
- const newSession = await newSessionStore.create({
10392
- id: "",
10393
- title: "",
10394
- model: ctx.config.model,
10395
- provider: ctx.config.provider
10396
- });
10397
- ctx.setSession(newSession);
10398
- ctx.context.session = newSession;
10399
- ctx.context.state.replaceMessages([]);
10400
- ctx.context.state.replaceTodos([]);
10401
- ctx.context.readFiles.clear();
10402
- ctx.context.fileMtimes.clear();
10403
- ctx.tokenCounter.reset();
10404
- ctx.setSessionStartedAt(Date.now());
10405
- try {
10406
- const registry = getSessionRegistry2(ctx.wpaths.globalRoot);
10407
- await registry.register({
10408
- sessionId: newSession.id,
10409
- projectSlug: switchSlug,
10410
- projectRoot: resolved,
10411
- projectName: path17.basename(resolved),
10412
- workingDir: resolved,
10413
- clientType: "webui",
10414
- pid: process.pid,
10415
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
10416
- });
10417
- } catch {
10683
+ const payload = msg.payload;
10684
+ const root = typeof payload?.root === "string" ? payload.root : "";
10685
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
10686
+ send(ws, {
10687
+ type: "projects.selected",
10688
+ payload: {
10689
+ root,
10690
+ name: name2,
10691
+ message: "Project switching is disabled in WebUI. Open a project from the launcher or desktop shell instead."
10418
10692
  }
10419
- send(ws, {
10420
- type: "projects.selected",
10421
- payload: {
10422
- root: resolved,
10423
- name: selName || path17.basename(resolved),
10424
- message: `Switched to ${selName || path17.basename(resolved)}`
10425
- }
10426
- });
10427
- broadcast(ctx.clients, {
10428
- type: "subagent.event",
10429
- payload: { kind: "session_stopped", sessionId: oldSessionId }
10430
- });
10431
- broadcast(ctx.clients, {
10432
- type: "session.start",
10433
- payload: {
10434
- ...await ctx.sessionStartPayload(),
10435
- reset: true,
10436
- clearedSessionId: oldSessionId
10437
- }
10438
- });
10439
- } catch (err) {
10440
- send(ws, {
10441
- type: "projects.selected",
10442
- payload: {
10443
- root: selRoot,
10444
- name: selName || path17.basename(selRoot),
10445
- message: errMessage(err)
10446
- }
10447
- });
10448
- }
10693
+ });
10449
10694
  },
10450
10695
  setWorkingDir: async (ws, msg) => {
10451
10696
  const parsed = validateWorkingDirSetPayload(msg.payload);
@@ -10472,9 +10717,9 @@ function createProjectHandlers(ctx) {
10472
10717
  }
10473
10718
 
10474
10719
  // src/server/session-handlers.ts
10475
- import * as path18 from "path";
10720
+ import * as path20 from "path";
10476
10721
  import {
10477
- DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
10722
+ DEFAULT_CONTEXT_WINDOW_MODE_ID,
10478
10723
  repairToolUseAdjacency,
10479
10724
  resolveContextWindowPolicy as resolveContextWindowPolicy3
10480
10725
  } from "@wrongstack/core";
@@ -10556,7 +10801,7 @@ function createSessionHandlers(ctx) {
10556
10801
  type: "context.debug",
10557
10802
  payload: sessionPayload2({
10558
10803
  ...breakdown,
10559
- mode: ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2,
10804
+ mode: ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID,
10560
10805
  policy: ctx.context.meta["contextWindowPolicy"]
10561
10806
  })
10562
10807
  });
@@ -10610,7 +10855,7 @@ function createSessionHandlers(ctx) {
10610
10855
  },
10611
10856
  listContextModes: async (ws, msg) => {
10612
10857
  if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
10613
- const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2);
10858
+ const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
10614
10859
  const allModes = ctx.customModeStore.list().map((m) => ({
10615
10860
  id: m.id,
10616
10861
  name: m.name,
@@ -10700,8 +10945,8 @@ function createSessionHandlers(ctx) {
10700
10945
  }
10701
10946
  const { id } = parsed.value;
10702
10947
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
10703
- ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID2;
10704
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID2);
10948
+ ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
10949
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
10705
10950
  }
10706
10951
  const result = ctx.customModeStore.remove(id);
10707
10952
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -10792,7 +11037,7 @@ function createSessionHandlers(ctx) {
10792
11037
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
10793
11038
  const projectRoot = ctx.getProjectRoot();
10794
11039
  const rewinder = new DefaultSessionRewinder(
10795
- path18.join(projectRoot, ".wrongstack", "sessions"),
11040
+ path20.join(projectRoot, ".wrongstack", "sessions"),
10796
11041
  projectRoot
10797
11042
  );
10798
11043
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
@@ -10808,7 +11053,7 @@ function createSessionHandlers(ctx) {
10808
11053
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
10809
11054
  const projectRoot = ctx.getProjectRoot();
10810
11055
  const rewinder = new DefaultSessionRewinder(
10811
- path18.join(projectRoot, ".wrongstack", "sessions"),
11056
+ path20.join(projectRoot, ".wrongstack", "sessions"),
10812
11057
  projectRoot
10813
11058
  );
10814
11059
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
@@ -10826,6 +11071,29 @@ function createSessionHandlers(ctx) {
10826
11071
  }
10827
11072
 
10828
11073
  // src/server/routes.ts
11074
+ async function enrichProviderModelDescriptors(modelsRegistry, providerId, cfg, models) {
11075
+ return Promise.all(
11076
+ models.map(async (model) => {
11077
+ if (model.contextWindow && model.capabilities.length > 0) return model;
11078
+ const resolved = await resolveProviderModelMetadata(
11079
+ modelsRegistry,
11080
+ providerId,
11081
+ model.id,
11082
+ cfg
11083
+ ).catch(() => void 0);
11084
+ if (!resolved) return model;
11085
+ const capabilities = new Set(model.capabilities);
11086
+ if (resolved.capabilities.tools) capabilities.add("tools");
11087
+ if (resolved.capabilities.reasoning) capabilities.add("reasoning");
11088
+ if (resolved.capabilities.vision) capabilities.add("vision");
11089
+ return {
11090
+ ...model,
11091
+ contextWindow: model.contextWindow || resolved.capabilities.maxContext || void 0,
11092
+ capabilities: [...capabilities]
11093
+ };
11094
+ })
11095
+ );
11096
+ }
10829
11097
  function buildRoutes(state, deps2, cb) {
10830
11098
  const providerHandlers = createProviderHandlers({
10831
11099
  globalConfigPath: deps2.globalConfigPath,
@@ -10867,13 +11135,22 @@ function buildRoutes(state, deps2, cb) {
10867
11135
  const providerId = msg.payload.providerId;
10868
11136
  const saved = await providerHandlers.loadConfigProviders();
10869
11137
  const cfg = saved[providerId];
10870
- const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
10871
- const provider = await deps2.modelsRegistry.getProvider(catalogId);
11138
+ const provider = await resolveProviderCatalogForModels(
11139
+ deps2.modelsRegistry,
11140
+ providerId,
11141
+ cfg
11142
+ );
11143
+ const models = await enrichProviderModelDescriptors(
11144
+ deps2.modelsRegistry,
11145
+ providerId,
11146
+ cfg,
11147
+ resolveProviderModelList(cfg?.models, provider)
11148
+ );
10872
11149
  send(ws, {
10873
11150
  type: "provider.models",
10874
11151
  payload: {
10875
11152
  provider: providerId,
10876
- models: resolveProviderModelList(cfg?.models, provider)
11153
+ models
10877
11154
  }
10878
11155
  });
10879
11156
  },
@@ -10893,7 +11170,7 @@ function buildRoutes(state, deps2, cb) {
10893
11170
  const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
10894
11171
  const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
10895
11172
  deps2.context.provider = newProv;
10896
- await cb.updateAutoCompactionMaxContext(newProv);
11173
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
10897
11174
  await cb.updateGlobalConfig((config) => {
10898
11175
  config.provider = newProvider;
10899
11176
  config.model = newModel;
@@ -10929,7 +11206,12 @@ function buildRoutes(state, deps2, cb) {
10929
11206
  try {
10930
11207
  const history = recentTextTurns(deps2.context.messages);
10931
11208
  const cfg = state.getConfig();
10932
- const resolved = await deps2.modelsRegistry.getModel(cfg.provider ?? "", cfg.model ?? "").catch(() => void 0);
11209
+ const resolved = await resolveProviderModelMetadata(
11210
+ deps2.modelsRegistry,
11211
+ cfg.provider ?? "",
11212
+ cfg.model ?? "",
11213
+ cfg.providers?.[cfg.provider ?? ""]
11214
+ ).catch(() => void 0);
10933
11215
  const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
10934
11216
  const result = await enhanceUserPrompt({
10935
11217
  provider: deps2.context.provider,
@@ -11041,6 +11323,7 @@ function buildRoutes(state, deps2, cb) {
11041
11323
  void cb.persistPrefsToConfig(payload);
11042
11324
  if (typeof payload["yolo"] === "boolean") {
11043
11325
  deps2.permissionPolicy.setYolo?.(payload["yolo"]);
11326
+ if (payload["yolo"] === true) resolveYoloEligiblePendingConfirms(deps2.pendingConfirms);
11044
11327
  }
11045
11328
  const cfg = state.getConfig();
11046
11329
  const features = cfg.features ?? {};
@@ -11111,7 +11394,7 @@ function buildRoutes(state, deps2, cb) {
11111
11394
  }
11112
11395
  return handleMailboxMessages(
11113
11396
  ws,
11114
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11397
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11115
11398
  parsed.value
11116
11399
  );
11117
11400
  },
@@ -11123,13 +11406,13 @@ function buildRoutes(state, deps2, cb) {
11123
11406
  }
11124
11407
  return handleMailboxAgents(
11125
11408
  ws,
11126
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11409
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11127
11410
  parsed.value
11128
11411
  );
11129
11412
  },
11130
11413
  clear: (ws) => handleMailboxClear(
11131
11414
  ws,
11132
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) }
11415
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) }
11133
11416
  ),
11134
11417
  purge: (ws, msg) => {
11135
11418
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -11139,7 +11422,7 @@ function buildRoutes(state, deps2, cb) {
11139
11422
  }
11140
11423
  return handleMailboxPurge(
11141
11424
  ws,
11142
- { projectRoot: state.getProjectRoot(), globalRoot: path19.dirname(deps2.globalConfigPath) },
11425
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11143
11426
  parsed.value
11144
11427
  );
11145
11428
  }
@@ -11285,9 +11568,8 @@ async function startWebUI(opts = {}) {
11285
11568
  let sessionStore = preContext.sessionStore;
11286
11569
  let session = preContext.session;
11287
11570
  let sessionStartedAt = preContext.sessionStartedAt;
11288
- let statusTracker = preContext.statusTracker;
11289
11571
  let modeId = preContext.modeId;
11290
- let needsSetup = preContext.needsSetup;
11572
+ const needsSetup = preContext.needsSetup;
11291
11573
  const prefSnapshot2 = () => prefSnapshot(context.meta);
11292
11574
  const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
11293
11575
  const agentServices = await createAgentServices({
@@ -11310,7 +11592,7 @@ async function startWebUI(opts = {}) {
11310
11592
  skillLoader,
11311
11593
  skillInstaller,
11312
11594
  tokenCounter,
11313
- pipelines: createDefaultPipelines2(),
11595
+ pipelines: createDefaultPipelines(),
11314
11596
  modelCapabilitiesRef,
11315
11597
  sessionGetter: () => session,
11316
11598
  sessionReader,
@@ -11336,13 +11618,16 @@ async function startWebUI(opts = {}) {
11336
11618
  collabHandler,
11337
11619
  updateAutoCompactionMaxContext
11338
11620
  } = agentServices;
11621
+ if (typeof context.meta["yolo"] === "boolean") {
11622
+ permissionPolicy.setYolo?.(context.meta["yolo"]);
11623
+ }
11339
11624
  const sessionStartPayload = createSessionStartPayload({
11340
11625
  getConfig: () => config,
11341
11626
  getSessionId: () => session.id,
11342
11627
  getProjectRoot: () => projectRoot,
11343
11628
  getWorkingDir: () => workingDir,
11344
11629
  getModeId: () => modeId,
11345
- getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID3),
11630
+ getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2),
11346
11631
  getNeedsSetup: () => needsSetup,
11347
11632
  modelsRegistry
11348
11633
  });
@@ -11373,8 +11658,6 @@ async function startWebUI(opts = {}) {
11373
11658
  averageDebounceDelayMs: 0,
11374
11659
  watcherActive: false
11375
11660
  };
11376
- let disposeEvents = null;
11377
- let fleetBroadcast = null;
11378
11661
  const eventArming = armEvents(wssPrimary, wssSecondary, wsHost, wsPort, {
11379
11662
  events,
11380
11663
  broadcast,
@@ -11387,21 +11670,21 @@ async function startWebUI(opts = {}) {
11387
11670
  wpaths
11388
11671
  }, watcherMetricsRef);
11389
11672
  async function touchProjectEntry(root, workDir) {
11390
- const resolved = path20.resolve(root);
11673
+ const resolved = path22.resolve(root);
11391
11674
  const manifest = await loadManifest(globalConfigPath);
11392
11675
  const now = (/* @__PURE__ */ new Date()).toISOString();
11393
- const existing = manifest.projects.find((p) => path20.resolve(p.root) === resolved);
11676
+ const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
11394
11677
  if (existing) {
11395
11678
  existing.lastSeen = now;
11396
- if (workDir) existing.lastWorkingDir = path20.resolve(workDir);
11679
+ if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
11397
11680
  } else {
11398
11681
  manifest.projects.push({
11399
- name: path20.basename(resolved),
11682
+ name: path22.basename(resolved),
11400
11683
  root: resolved,
11401
11684
  slug: generateProjectSlug(resolved),
11402
11685
  createdAt: now,
11403
11686
  lastSeen: now,
11404
- lastWorkingDir: workDir ? path20.resolve(workDir) : void 0
11687
+ lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
11405
11688
  });
11406
11689
  }
11407
11690
  await saveManifest(manifest, globalConfigPath);
@@ -11465,6 +11748,7 @@ async function startWebUI(opts = {}) {
11465
11748
  configStore,
11466
11749
  tokenCounter,
11467
11750
  permissionPolicy,
11751
+ pendingConfirms,
11468
11752
  pipelines,
11469
11753
  logger,
11470
11754
  memoryStore,
@@ -11506,6 +11790,7 @@ async function startWebUI(opts = {}) {
11506
11790
  let lastActiveCfg = JSON.stringify(
11507
11791
  state.getConfig().providers?.[deps2.context.provider.id] ?? null
11508
11792
  );
11793
+ let lastUiLocale = state.getConfig().uiLocale;
11509
11794
  const credentialWatcher = watchProviderConfig(
11510
11795
  globalConfigPath,
11511
11796
  vault,
@@ -11526,6 +11811,14 @@ async function startWebUI(opts = {}) {
11526
11811
  type: "providers.saved",
11527
11812
  payload: { providers: projectSavedProviders(snapshot.providers) }
11528
11813
  });
11814
+ const newUi = snapshot.uiLocale;
11815
+ if (newUi !== lastUiLocale) {
11816
+ lastUiLocale = newUi;
11817
+ if (newUi) {
11818
+ deps2.context.meta["uiLocale"] = newUi;
11819
+ broadcast(clients, { type: "prefs.updated", payload: { uiLocale: newUi } });
11820
+ }
11821
+ }
11529
11822
  const activeId = deps2.context.provider.id;
11530
11823
  const newCfgStr = JSON.stringify(snapshot.providers[activeId] ?? null);
11531
11824
  if (newCfgStr === lastActiveCfg) return;
@@ -11613,7 +11906,7 @@ async function startWebUI(opts = {}) {
11613
11906
  eternalSubscription = null;
11614
11907
  }
11615
11908
  codebaseIndexing.dispose();
11616
- return unregisterInstance(process.pid, path20.dirname(globalConfigPath));
11909
+ return unregisterInstance(process.pid, path22.dirname(globalConfigPath));
11617
11910
  }
11618
11911
  });
11619
11912
  }