@wrongstack/webui 0.277.1 → 0.280.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/activity-BEjzb11U.js +5 -0
- package/dist/assets/activity-ByEwzh5Q.js +5 -0
- package/dist/assets/activity-CVWYwjgf.js +5 -0
- package/dist/assets/activity-Cjm0UoMi.js +5 -0
- package/dist/assets/activity-CqiCPjKb.js +5 -0
- package/dist/assets/activity-DN6Bmnaw.js +5 -0
- package/dist/assets/chat-CdLXf1yN.js +1 -0
- package/dist/assets/chat-Cfq81NCh.js +1 -0
- package/dist/assets/chat-Cvy-F2d6.js +1 -0
- package/dist/assets/chat-DNIePdaQ.js +1 -0
- package/dist/assets/chat-DhofDrgA.js +1 -0
- package/dist/assets/chat-cxuX4gFa.js +1 -0
- package/dist/assets/commandPalette-2M1hYRuH.js +1 -0
- package/dist/assets/commandPalette-BE_ljQwl.js +1 -0
- package/dist/assets/commandPalette-BYf22WAi.js +1 -0
- package/dist/assets/commandPalette-BZhacjvy.js +1 -0
- package/dist/assets/commandPalette-CTE9ITWg.js +1 -0
- package/dist/assets/commandPalette-RxWSjQIg.js +1 -0
- package/dist/assets/common-BAg8_MKg.js +1 -0
- package/dist/assets/common-BH1mb9Im.js +1 -0
- package/dist/assets/common-C29pCVfm.js +1 -0
- package/dist/assets/common-CrGjglH4.js +1 -0
- package/dist/assets/common-KzRPhobM.js +1 -0
- package/dist/assets/common-tmj0IcQS.js +1 -0
- package/dist/assets/index-BMDGZBnY.css +2 -0
- package/dist/assets/index-Ci_SxQGe.js +157 -0
- package/dist/assets/settings-BVvx94WK.js +1 -0
- package/dist/assets/settings-CQ4x-iFD.js +1 -0
- package/dist/assets/settings-CmHCQRVI.js +1 -0
- package/dist/assets/settings-DgGu17XI.js +1 -0
- package/dist/assets/settings-OOQ-99Tn.js +1 -0
- package/dist/assets/settings-TqprB9wO.js +1 -0
- package/dist/assets/setup-B5TfeR6q.js +1 -0
- package/dist/assets/setup-BBwNpiE3.js +1 -0
- package/dist/assets/setup-BT2iTMm4.js +1 -0
- package/dist/assets/setup-CVMjqrZh.js +1 -0
- package/dist/assets/setup-DOXm3hFc.js +1 -0
- package/dist/assets/setup-Dhb_xOpk.js +1 -0
- package/dist/assets/toasts-BCDGDcyq.js +1 -0
- package/dist/assets/toasts-BwQJwYLe.js +1 -0
- package/dist/assets/toasts-CFzweRH5.js +1 -0
- package/dist/assets/toasts-DDYQsJV8.js +1 -0
- package/dist/assets/toasts-Dm12Nq8t.js +1 -0
- package/dist/assets/toasts-frBt6fvE.js +1 -0
- package/dist/assets/{vendor-C604QguY.js → vendor-B_dTeUDT.js} +252 -252
- package/dist/index.html +3 -3
- package/dist/index.js +26536 -8227
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +875 -582
- package/dist/server/entry.js.map +1 -1
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +870 -577
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +5 -2
- package/package.json +9 -6
- package/dist/assets/index-Bmo3qaVg.js +0 -141
- package/dist/assets/index-D8YeqUJA.css +0 -2
package/dist/server/index.js
CHANGED
|
@@ -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,
|
|
1400
|
+
function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
1401
1401
|
let disposed = false;
|
|
1402
1402
|
const dispose = subscribe((entry) => {
|
|
1403
1403
|
if (disposed) return;
|
|
1404
|
-
|
|
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
|
|
1477
|
-
return
|
|
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
|
|
1481
|
+
let stat2;
|
|
1482
1482
|
try {
|
|
1483
|
-
|
|
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 (!
|
|
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((
|
|
1744
|
+
const git = (args) => new Promise((resolve10) => {
|
|
1745
1745
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
1746
|
-
|
|
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((
|
|
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) =>
|
|
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
|
|
1802
|
-
if (
|
|
1801
|
+
let path23 = m[3] ?? "";
|
|
1802
|
+
if (path23 === "") {
|
|
1803
1803
|
i += 1;
|
|
1804
|
-
|
|
1804
|
+
path23 = parts[i + 1] ?? parts[i] ?? "";
|
|
1805
1805
|
i += 1;
|
|
1806
1806
|
}
|
|
1807
|
-
if (!
|
|
1808
|
-
const prev = counts.get(
|
|
1809
|
-
counts.set(
|
|
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
|
|
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(
|
|
1834
|
-
let deleted = counts.get(
|
|
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:
|
|
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,
|
|
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:
|
|
1853
|
-
if (!
|
|
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:
|
|
1859
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
1860
1860
|
const { join: join14 } = await import("path");
|
|
1861
|
-
const oldText = await git(["show", `HEAD:${
|
|
1861
|
+
const oldText = await git(["show", `HEAD:${path23}`]);
|
|
1862
1862
|
let newText = "";
|
|
1863
1863
|
try {
|
|
1864
|
-
const abs = cwd ? join14(cwd,
|
|
1865
|
-
const buf = await
|
|
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:
|
|
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 =
|
|
2076
|
-
const store = new
|
|
2077
|
-
const reader = new
|
|
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((
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
2158
|
-
const mailbox = new
|
|
2159
|
-
const to = `leader@${
|
|
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:
|
|
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 =
|
|
2184
|
-
const mailbox = new
|
|
2185
|
-
const leaderAddr = `leader@${
|
|
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:
|
|
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 =
|
|
2242
|
-
const mailbox = new
|
|
2243
|
-
const to = `leader@${
|
|
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:
|
|
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 =
|
|
2293
|
+
const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
|
|
2294
2294
|
let mb = mbByDir.get(dir);
|
|
2295
2295
|
if (!mb) {
|
|
2296
|
-
mb = new
|
|
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@${
|
|
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((
|
|
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
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2555
2555
|
}
|
|
2556
2556
|
function injectWsConfig(html, opts) {
|
|
2557
|
-
|
|
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((
|
|
3240
|
+
return new Promise((resolve10) => {
|
|
3241
3241
|
const srv = net.createServer();
|
|
3242
|
-
srv.once("error", () =>
|
|
3242
|
+
srv.once("error", () => resolve10(false));
|
|
3243
3243
|
srv.once("listening", () => {
|
|
3244
|
-
srv.close(() =>
|
|
3244
|
+
srv.close(() => resolve10(true));
|
|
3245
3245
|
});
|
|
3246
3246
|
try {
|
|
3247
3247
|
srv.listen(port, host);
|
|
3248
3248
|
} catch {
|
|
3249
|
-
|
|
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
|
-
|
|
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 (
|
|
4336
|
-
return `prefs.update payload.${key}
|
|
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
|
|
4570
|
-
if (typeof
|
|
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:
|
|
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
|
|
4587
|
-
if (
|
|
4632
|
+
const path23 = payload["path"];
|
|
4633
|
+
if (path23 === void 0 || path23 === null) {
|
|
4588
4634
|
return { ok: true, value: { path: "" } };
|
|
4589
4635
|
}
|
|
4590
|
-
if (typeof
|
|
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:
|
|
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(
|
|
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.
|
|
4881
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
5733
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5947
|
+
broadcast2(clients, { type: "mailbox.received", payload });
|
|
5907
5948
|
});
|
|
5908
5949
|
events.onPattern("mailbox.agent_registered", (_e, payload) => {
|
|
5909
|
-
|
|
5950
|
+
broadcast2(clients, { type: "mailbox.agent_registered", payload });
|
|
5910
5951
|
});
|
|
5911
|
-
const forwardSubagent = (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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
6108
|
+
const scheduleBroadcast = (projectHash, statusData) => {
|
|
6067
6109
|
const now = Date.now();
|
|
6068
|
-
const existing = pendingStatuses.get(
|
|
6110
|
+
const existing = pendingStatuses.get(projectHash);
|
|
6069
6111
|
if (existing && watcherMetrics) {
|
|
6070
6112
|
watcherMetrics.debounceResets++;
|
|
6071
6113
|
}
|
|
6072
|
-
pendingStatuses.set(
|
|
6114
|
+
pendingStatuses.set(projectHash, {
|
|
6073
6115
|
data: statusData,
|
|
6074
6116
|
firstWriteAt: existing ? existing.firstWriteAt : now
|
|
6075
6117
|
});
|
|
6076
|
-
const existingTimer = debounceTimers.get(
|
|
6118
|
+
const existingTimer = debounceTimers.get(projectHash);
|
|
6077
6119
|
if (existingTimer) {
|
|
6078
6120
|
clearTimeout(existingTimer);
|
|
6079
6121
|
}
|
|
6080
6122
|
const timer = setTimeout(() => {
|
|
6081
|
-
debounceTimers.delete(
|
|
6082
|
-
const pending = pendingStatuses.get(
|
|
6123
|
+
debounceTimers.delete(projectHash);
|
|
6124
|
+
const pending = pendingStatuses.get(projectHash);
|
|
6083
6125
|
if (pending) {
|
|
6084
6126
|
const actualDelay = Date.now() - pending.firstWriteAt;
|
|
6085
|
-
broadcastStatus(
|
|
6086
|
-
pendingStatuses.delete(
|
|
6127
|
+
broadcastStatus(projectHash, pending.data, actualDelay);
|
|
6128
|
+
pendingStatuses.delete(projectHash);
|
|
6087
6129
|
}
|
|
6088
6130
|
}, DEBOUNCE_MS);
|
|
6089
|
-
debounceTimers.set(
|
|
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
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
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
|
-
|
|
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 [
|
|
6142
|
-
const timer = debounceTimers.get(
|
|
6174
|
+
for (const [projectHash, pending] of pendingStatuses) {
|
|
6175
|
+
const timer = debounceTimers.get(projectHash);
|
|
6143
6176
|
if (timer) {
|
|
6144
6177
|
clearTimeout(timer);
|
|
6145
|
-
broadcastStatus(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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,
|
|
6585
|
-
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
6730
|
-
projectSkillsDir:
|
|
6731
|
-
globalSkillsDir:
|
|
6732
|
-
projectHash: projectHash
|
|
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
|
|
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
|
|
7129
|
+
import * as path22 from "path";
|
|
6818
7130
|
import {
|
|
6819
|
-
createDefaultPipelines
|
|
7131
|
+
createDefaultPipelines,
|
|
6820
7132
|
createSessionEventBridge,
|
|
6821
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID as
|
|
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
|
|
7596
|
-
import * as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7700
|
-
const normalizedTarget =
|
|
7701
|
-
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot +
|
|
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 {
|
|
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,
|
|
7711
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
7743
|
-
"
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 ${
|
|
8415
|
+
`models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
|
|
7994
8416
|
);
|
|
7995
8417
|
});
|
|
7996
|
-
|
|
8418
|
+
const currentConfig = input.config;
|
|
8419
|
+
let newMaxContext = currentConfig.context?.effectiveMaxContext ?? newProvider.capabilities.maxContext;
|
|
7997
8420
|
try {
|
|
7998
|
-
const m = await
|
|
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
|
-
|
|
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,
|
|
8808
|
+
async function handleGoalGet(projectRoot, broadcast2) {
|
|
8365
8809
|
try {
|
|
8366
8810
|
const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
|
|
8367
|
-
const { readFile:
|
|
8368
|
-
const raw = await
|
|
8811
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
8812
|
+
const raw = await readFile10(goalPath, "utf8");
|
|
8369
8813
|
const goal = JSON.parse(raw);
|
|
8370
|
-
|
|
8814
|
+
broadcast2({ type: "goal.updated", payload: goal });
|
|
8371
8815
|
} catch {
|
|
8372
|
-
|
|
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
|
|
9145
|
-
if (
|
|
9598
|
+
const confirm = pendingConfirms.get(id);
|
|
9599
|
+
if (confirm) {
|
|
9146
9600
|
pendingConfirms.delete(id);
|
|
9147
|
-
|
|
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
|
|
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
|
|
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
|
|
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")
|
|
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
|
|
9697
|
-
import * as
|
|
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 =
|
|
9701
|
-
return
|
|
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
|
|
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
|
|
9715
|
-
await
|
|
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 =
|
|
9722
|
-
const dir =
|
|
9723
|
-
await
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
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
|
-
|
|
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
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
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
|
-
|
|
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
|
|
10720
|
+
import * as path20 from "path";
|
|
10476
10721
|
import {
|
|
10477
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
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"] ??
|
|
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"] ??
|
|
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"] =
|
|
10704
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10871
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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"] ??
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
11679
|
+
if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
|
|
11397
11680
|
} else {
|
|
11398
11681
|
manifest.projects.push({
|
|
11399
|
-
name:
|
|
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 ?
|
|
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,
|
|
11909
|
+
return unregisterInstance(process.pid, path22.dirname(globalConfigPath));
|
|
11617
11910
|
}
|
|
11618
11911
|
});
|
|
11619
11912
|
}
|