@wrongstack/webui 0.273.0 → 0.273.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-CM62rXoC.js → index-D0dNaLPf.js} +54 -54
- package/dist/index.html +1 -1
- package/dist/index.js +108 -42
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +365 -287
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +366 -288
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +6 -6
package/dist/server/index.js
CHANGED
|
@@ -896,7 +896,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
896
896
|
return;
|
|
897
897
|
}
|
|
898
898
|
try {
|
|
899
|
-
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths2, DefaultSessionStore:
|
|
899
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths2, DefaultSessionStore: DefaultSessionStore3, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
|
|
900
900
|
const registry = new SessionRegistry(globalRoot);
|
|
901
901
|
const entry = await registry.get(sessionId);
|
|
902
902
|
if (!entry) {
|
|
@@ -905,7 +905,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
905
905
|
return;
|
|
906
906
|
}
|
|
907
907
|
const paths = resolveWstackPaths2({ projectRoot: entry.projectRoot, globalRoot });
|
|
908
|
-
const store = new
|
|
908
|
+
const store = new DefaultSessionStore3({ dir: paths.projectSessions });
|
|
909
909
|
const reader = new DefaultSessionReader2({ store });
|
|
910
910
|
const rawEntries = [];
|
|
911
911
|
for await (const ev of reader.replay(sessionId)) {
|
|
@@ -1591,8 +1591,8 @@ function isInside(root, target) {
|
|
|
1591
1591
|
}
|
|
1592
1592
|
|
|
1593
1593
|
// src/server/file-handlers.ts
|
|
1594
|
-
import * as
|
|
1595
|
-
import * as
|
|
1594
|
+
import * as fs4 from "fs/promises";
|
|
1595
|
+
import * as path4 from "path";
|
|
1596
1596
|
import { atomicWrite } from "@wrongstack/core";
|
|
1597
1597
|
|
|
1598
1598
|
// src/server/file-picker.ts
|
|
@@ -1643,6 +1643,34 @@ function rankFiles(paths, query, limit) {
|
|
|
1643
1643
|
return scored.slice(0, limit).map((s) => s.path);
|
|
1644
1644
|
}
|
|
1645
1645
|
|
|
1646
|
+
// src/server/path-containment.ts
|
|
1647
|
+
import * as fs3 from "fs/promises";
|
|
1648
|
+
import * as path3 from "path";
|
|
1649
|
+
function isPathInside(root, target) {
|
|
1650
|
+
const relative3 = path3.relative(root, target);
|
|
1651
|
+
return relative3 === "" || !relative3.startsWith("..") && !path3.isAbsolute(relative3);
|
|
1652
|
+
}
|
|
1653
|
+
async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
1654
|
+
const resolved = path3.resolve(projectRoot, inputPath);
|
|
1655
|
+
let stat3;
|
|
1656
|
+
try {
|
|
1657
|
+
stat3 = await fs3.stat(resolved);
|
|
1658
|
+
} catch {
|
|
1659
|
+
throw new Error(`Directory not found or not accessible: ${resolved}`);
|
|
1660
|
+
}
|
|
1661
|
+
if (!stat3.isDirectory()) {
|
|
1662
|
+
throw new Error(`Directory not found or not accessible: ${resolved}`);
|
|
1663
|
+
}
|
|
1664
|
+
const [realProjectRoot, realResolved] = await Promise.all([
|
|
1665
|
+
fs3.realpath(projectRoot),
|
|
1666
|
+
fs3.realpath(resolved)
|
|
1667
|
+
]);
|
|
1668
|
+
if (!isPathInside(realProjectRoot, realResolved)) {
|
|
1669
|
+
throw new Error(`Path must stay inside the project root: ${projectRoot}`);
|
|
1670
|
+
}
|
|
1671
|
+
return resolved;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1646
1674
|
// src/server/ws-utils.ts
|
|
1647
1675
|
import { randomBytes } from "crypto";
|
|
1648
1676
|
import { WebSocket } from "ws";
|
|
@@ -1673,23 +1701,73 @@ function generateAuthToken() {
|
|
|
1673
1701
|
}
|
|
1674
1702
|
|
|
1675
1703
|
// src/server/file-handlers.ts
|
|
1704
|
+
async function resolveFileInsideProject(projectRoot, filePath) {
|
|
1705
|
+
const resolved = path4.resolve(projectRoot, filePath);
|
|
1706
|
+
if (!isPathInside(projectRoot, resolved)) {
|
|
1707
|
+
throw new Error("Path outside project root");
|
|
1708
|
+
}
|
|
1709
|
+
const { parent, base } = splitParentAndBase(resolved);
|
|
1710
|
+
const realProjectRoot = await fs4.realpath(projectRoot);
|
|
1711
|
+
const realParent = await realpathAllowMissing(parent);
|
|
1712
|
+
const realFull = path4.join(realParent, base);
|
|
1713
|
+
if (!isPathInside(realProjectRoot, realFull)) {
|
|
1714
|
+
throw new Error("Path outside project root");
|
|
1715
|
+
}
|
|
1716
|
+
return realFull;
|
|
1717
|
+
}
|
|
1718
|
+
function splitParentAndBase(p) {
|
|
1719
|
+
const base = path4.basename(p);
|
|
1720
|
+
const parent = path4.dirname(p);
|
|
1721
|
+
return { parent, base };
|
|
1722
|
+
}
|
|
1723
|
+
async function realpathAllowMissing(p) {
|
|
1724
|
+
try {
|
|
1725
|
+
return await fs4.realpath(p);
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
if (err.code !== "ENOENT") throw err;
|
|
1728
|
+
}
|
|
1729
|
+
const segments = [];
|
|
1730
|
+
let cursor = p;
|
|
1731
|
+
while (true) {
|
|
1732
|
+
const parent = path4.dirname(cursor);
|
|
1733
|
+
if (parent === cursor) {
|
|
1734
|
+
throw new Error("Path outside project root");
|
|
1735
|
+
}
|
|
1736
|
+
segments.unshift(path4.basename(cursor));
|
|
1737
|
+
try {
|
|
1738
|
+
const realParent = await fs4.realpath(parent);
|
|
1739
|
+
return path4.join(realParent, ...segments);
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
if (err.code !== "ENOENT") throw err;
|
|
1742
|
+
cursor = parent;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1676
1746
|
async function handleFilesTree(ws, msg, projectRoot) {
|
|
1677
1747
|
const payload = msg.payload;
|
|
1678
1748
|
const rawPath = payload?.path?.trim();
|
|
1679
|
-
|
|
1680
|
-
|
|
1749
|
+
let treeRoot;
|
|
1750
|
+
let realProjectRoot;
|
|
1751
|
+
try {
|
|
1752
|
+
if (rawPath && rawPath !== ".") {
|
|
1753
|
+
treeRoot = await resolveWorkingDirInsideProject(projectRoot, rawPath);
|
|
1754
|
+
} else {
|
|
1755
|
+
treeRoot = projectRoot;
|
|
1756
|
+
}
|
|
1757
|
+
realProjectRoot = await fs4.realpath(projectRoot);
|
|
1758
|
+
} catch {
|
|
1681
1759
|
send(ws, {
|
|
1682
1760
|
type: "files.tree",
|
|
1683
1761
|
payload: { root: projectRoot, tree: [], error: "Path outside project root" }
|
|
1684
1762
|
});
|
|
1685
1763
|
return;
|
|
1686
1764
|
}
|
|
1687
|
-
const pathPrefix = treeRoot === projectRoot ? "" : (
|
|
1765
|
+
const pathPrefix = treeRoot === projectRoot ? "" : (path4.relative(projectRoot, treeRoot) + "/").replace(/\\/g, "/");
|
|
1688
1766
|
async function buildTree(dir, rel, depth) {
|
|
1689
1767
|
if (depth > 10) return [];
|
|
1690
1768
|
let entries = [];
|
|
1691
1769
|
try {
|
|
1692
|
-
entries = await
|
|
1770
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1693
1771
|
} catch {
|
|
1694
1772
|
return [];
|
|
1695
1773
|
}
|
|
@@ -1701,11 +1779,20 @@ async function handleFilesTree(ws, msg, projectRoot) {
|
|
|
1701
1779
|
for (const e of entries) {
|
|
1702
1780
|
if (isHiddenEntry(e.name)) continue;
|
|
1703
1781
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
1704
|
-
const childAbs =
|
|
1782
|
+
const childAbs = path4.join(dir, e.name);
|
|
1705
1783
|
const childPath = pathPrefix + childRel;
|
|
1706
1784
|
if (e.isDirectory()) {
|
|
1707
1785
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
1708
|
-
|
|
1786
|
+
let realChild;
|
|
1787
|
+
try {
|
|
1788
|
+
realChild = await fs4.realpath(childAbs);
|
|
1789
|
+
} catch {
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
if (!isPathInside(realProjectRoot, realChild)) {
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
const children = await buildTree(realChild, childRel, depth + 1);
|
|
1709
1796
|
nodes.push({ name: e.name, path: childPath, type: "directory", children });
|
|
1710
1797
|
} else if (e.isFile()) {
|
|
1711
1798
|
nodes.push({ name: e.name, path: childPath, type: "file" });
|
|
@@ -1715,10 +1802,10 @@ async function handleFilesTree(ws, msg, projectRoot) {
|
|
|
1715
1802
|
}
|
|
1716
1803
|
try {
|
|
1717
1804
|
const tree = await buildTree(treeRoot, "", 0);
|
|
1718
|
-
const rootLabel = treeRoot === projectRoot ? projectRoot :
|
|
1805
|
+
const rootLabel = treeRoot === projectRoot ? projectRoot : path4.relative(projectRoot, treeRoot) || ".";
|
|
1719
1806
|
send(ws, { type: "files.tree", payload: { root: rootLabel, tree } });
|
|
1720
1807
|
} catch (err) {
|
|
1721
|
-
const rootLabel = treeRoot === projectRoot ? projectRoot :
|
|
1808
|
+
const rootLabel = treeRoot === projectRoot ? projectRoot : path4.relative(projectRoot, treeRoot) || ".";
|
|
1722
1809
|
send(ws, {
|
|
1723
1810
|
type: "files.tree",
|
|
1724
1811
|
payload: { root: rootLabel, tree: [], error: errMessage(err) }
|
|
@@ -1727,13 +1814,15 @@ async function handleFilesTree(ws, msg, projectRoot) {
|
|
|
1727
1814
|
}
|
|
1728
1815
|
async function handleFilesRead(ws, msg, projectRoot) {
|
|
1729
1816
|
const { filePath } = msg.payload;
|
|
1730
|
-
|
|
1731
|
-
|
|
1817
|
+
let realResolved;
|
|
1818
|
+
try {
|
|
1819
|
+
realResolved = await resolveFileInsideProject(projectRoot, filePath);
|
|
1820
|
+
} catch {
|
|
1732
1821
|
send(ws, { type: "files.read", payload: { filePath, content: "", error: "Forbidden" } });
|
|
1733
1822
|
return;
|
|
1734
1823
|
}
|
|
1735
1824
|
try {
|
|
1736
|
-
const content = await
|
|
1825
|
+
const content = await fs4.readFile(realResolved, "utf8");
|
|
1737
1826
|
send(ws, { type: "files.read", payload: { filePath, content } });
|
|
1738
1827
|
} catch (err) {
|
|
1739
1828
|
send(ws, {
|
|
@@ -1744,16 +1833,18 @@ async function handleFilesRead(ws, msg, projectRoot) {
|
|
|
1744
1833
|
}
|
|
1745
1834
|
async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
|
|
1746
1835
|
const { filePath, content } = msg.payload;
|
|
1747
|
-
|
|
1748
|
-
|
|
1836
|
+
let realResolved;
|
|
1837
|
+
try {
|
|
1838
|
+
realResolved = await resolveFileInsideProject(projectRoot, filePath);
|
|
1839
|
+
} catch {
|
|
1749
1840
|
send(ws, { type: "files.written", payload: { filePath, success: false, error: "Forbidden" } });
|
|
1750
1841
|
return;
|
|
1751
1842
|
}
|
|
1752
1843
|
try {
|
|
1753
|
-
await atomicWrite(
|
|
1844
|
+
await atomicWrite(realResolved, content);
|
|
1754
1845
|
send(ws, { type: "files.written", payload: { filePath, success: true } });
|
|
1755
1846
|
if (opts.onWritten) {
|
|
1756
|
-
void Promise.resolve(opts.onWritten(
|
|
1847
|
+
void Promise.resolve(opts.onWritten(realResolved)).catch(() => void 0);
|
|
1757
1848
|
}
|
|
1758
1849
|
} catch (err) {
|
|
1759
1850
|
send(ws, {
|
|
@@ -1765,8 +1856,16 @@ async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
|
|
|
1765
1856
|
async function handleFilesList(ws, msg, projectRoot) {
|
|
1766
1857
|
const payload = msg.payload ?? {};
|
|
1767
1858
|
const limit = payload.limit ?? 50;
|
|
1768
|
-
|
|
1769
|
-
|
|
1859
|
+
let listRoot;
|
|
1860
|
+
let realProjectRoot;
|
|
1861
|
+
try {
|
|
1862
|
+
if (payload.path) {
|
|
1863
|
+
listRoot = await resolveWorkingDirInsideProject(projectRoot, payload.path);
|
|
1864
|
+
} else {
|
|
1865
|
+
listRoot = projectRoot;
|
|
1866
|
+
}
|
|
1867
|
+
realProjectRoot = await fs4.realpath(projectRoot);
|
|
1868
|
+
} catch {
|
|
1770
1869
|
send(ws, { type: "files.list", payload: { files: [] } });
|
|
1771
1870
|
return;
|
|
1772
1871
|
}
|
|
@@ -1775,7 +1874,7 @@ async function handleFilesList(ws, msg, projectRoot) {
|
|
|
1775
1874
|
if (depth > 8 || results.length >= 600) return;
|
|
1776
1875
|
let entries = [];
|
|
1777
1876
|
try {
|
|
1778
|
-
entries = await
|
|
1877
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1779
1878
|
} catch {
|
|
1780
1879
|
return;
|
|
1781
1880
|
}
|
|
@@ -1785,7 +1884,16 @@ async function handleFilesList(ws, msg, projectRoot) {
|
|
|
1785
1884
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
1786
1885
|
if (e.isDirectory()) {
|
|
1787
1886
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
1788
|
-
|
|
1887
|
+
let realChild;
|
|
1888
|
+
try {
|
|
1889
|
+
realChild = await fs4.realpath(path4.join(dir, e.name));
|
|
1890
|
+
} catch {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
if (!isPathInside(realProjectRoot, realChild)) {
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
await walk(realChild, childRel, depth + 1);
|
|
1789
1897
|
} else if (e.isFile()) {
|
|
1790
1898
|
results.push(childRel);
|
|
1791
1899
|
}
|
|
@@ -1799,7 +1907,7 @@ async function handleFilesList(ws, msg, projectRoot) {
|
|
|
1799
1907
|
}
|
|
1800
1908
|
|
|
1801
1909
|
// src/server/completion-handlers.ts
|
|
1802
|
-
import * as
|
|
1910
|
+
import * as path5 from "path";
|
|
1803
1911
|
import { searchCodebaseIndex } from "@wrongstack/tools/codebase-index/index";
|
|
1804
1912
|
var MAX_PREFIX_CHARS = 12e3;
|
|
1805
1913
|
var MAX_SUFFIX_CHARS = 4e3;
|
|
@@ -1874,8 +1982,8 @@ async function handleCompletionRequest(ws, msg, opts) {
|
|
|
1874
1982
|
return;
|
|
1875
1983
|
}
|
|
1876
1984
|
const payload = parsed.payload;
|
|
1877
|
-
const projectRoot =
|
|
1878
|
-
const resolved =
|
|
1985
|
+
const projectRoot = path5.resolve(opts.projectRoot);
|
|
1986
|
+
const resolved = path5.resolve(projectRoot, payload.filePath);
|
|
1879
1987
|
if (!isInside2(projectRoot, resolved)) {
|
|
1880
1988
|
send(ws, {
|
|
1881
1989
|
type: "completion.result",
|
|
@@ -2274,7 +2382,7 @@ function buildSearchQuery(linePrefix, filePath) {
|
|
|
2274
2382
|
if (memberMatch?.[1]) return memberMatch[1];
|
|
2275
2383
|
const token = linePrefix.match(/([A-Za-z_$][\w$]*)$/)?.[1];
|
|
2276
2384
|
if (token && token.length >= 2) return token;
|
|
2277
|
-
return
|
|
2385
|
+
return path5.basename(filePath, path5.extname(filePath));
|
|
2278
2386
|
}
|
|
2279
2387
|
function currentLinePrefix(prefix) {
|
|
2280
2388
|
const idx = Math.max(prefix.lastIndexOf("\n"), prefix.lastIndexOf("\r"));
|
|
@@ -2304,7 +2412,7 @@ function head(value, max) {
|
|
|
2304
2412
|
return value.length <= max ? value : value.slice(0, max);
|
|
2305
2413
|
}
|
|
2306
2414
|
function isInside2(root, target) {
|
|
2307
|
-
return target === root || target.startsWith(root +
|
|
2415
|
+
return target === root || target.startsWith(root + path5.sep);
|
|
2308
2416
|
}
|
|
2309
2417
|
|
|
2310
2418
|
// src/server/memory-handlers.ts
|
|
@@ -2558,8 +2666,8 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
|
|
|
2558
2666
|
}
|
|
2559
2667
|
|
|
2560
2668
|
// src/server/skills-handlers.ts
|
|
2561
|
-
import { promises as
|
|
2562
|
-
import
|
|
2669
|
+
import { promises as fs5 } from "fs";
|
|
2670
|
+
import path6 from "path";
|
|
2563
2671
|
import JSZip from "jszip";
|
|
2564
2672
|
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
2565
2673
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
@@ -2630,19 +2738,19 @@ async function handleSkillsContent(ws, ctx, msg) {
|
|
|
2630
2738
|
send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
|
|
2631
2739
|
return;
|
|
2632
2740
|
}
|
|
2633
|
-
const body = await
|
|
2634
|
-
const skillDir =
|
|
2741
|
+
const body = await fs5.readFile(entry.path, "utf8");
|
|
2742
|
+
const skillDir = path6.dirname(entry.path);
|
|
2635
2743
|
let relatedFiles = [];
|
|
2636
2744
|
try {
|
|
2637
|
-
const files = await
|
|
2638
|
-
relatedFiles = files.filter((f) => f !==
|
|
2745
|
+
const files = await fs5.readdir(skillDir);
|
|
2746
|
+
relatedFiles = files.filter((f) => f !== path6.basename(entry.path)).map((f) => path6.join(skillDir, f));
|
|
2639
2747
|
} catch {
|
|
2640
2748
|
}
|
|
2641
2749
|
const nameLower = name2.toLowerCase();
|
|
2642
2750
|
const refResults = await Promise.all(
|
|
2643
2751
|
entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
|
|
2644
2752
|
try {
|
|
2645
|
-
const content = await
|
|
2753
|
+
const content = await fs5.readFile(e.path, "utf8");
|
|
2646
2754
|
return [e.name, content.toLowerCase().includes(nameLower)];
|
|
2647
2755
|
} catch {
|
|
2648
2756
|
return [e.name, false];
|
|
@@ -2732,14 +2840,14 @@ async function handleSkillsCreate(ws, ctx, msg) {
|
|
|
2732
2840
|
}
|
|
2733
2841
|
const createPayload = parsed.value;
|
|
2734
2842
|
try {
|
|
2735
|
-
const targetDir = createPayload.scope === "global" ?
|
|
2843
|
+
const targetDir = createPayload.scope === "global" ? path6.join(wstackGlobalRoot(), "skills", createPayload.name.trim()) : path6.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
|
|
2736
2844
|
try {
|
|
2737
|
-
await
|
|
2845
|
+
await fs5.access(targetDir);
|
|
2738
2846
|
send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
|
|
2739
2847
|
return;
|
|
2740
2848
|
} catch {
|
|
2741
2849
|
}
|
|
2742
|
-
await
|
|
2850
|
+
await fs5.mkdir(targetDir, { recursive: true });
|
|
2743
2851
|
const lines = createPayload.description.trim().split("\n");
|
|
2744
2852
|
const firstLine = lines[0].trim();
|
|
2745
2853
|
const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
|
|
@@ -2787,13 +2895,13 @@ ${trigger}
|
|
|
2787
2895
|
"- `bug-hunter` \u2014 for systematic bug detection patterns",
|
|
2788
2896
|
"- `output-standards` \u2014 for standardized `<next_steps>` formatting"
|
|
2789
2897
|
].join("\n");
|
|
2790
|
-
await atomicWrite2(
|
|
2898
|
+
await atomicWrite2(path6.join(targetDir, "SKILL.md"), skillContent);
|
|
2791
2899
|
send(ws, {
|
|
2792
2900
|
type: "skills.created",
|
|
2793
2901
|
payload: {
|
|
2794
2902
|
success: true,
|
|
2795
2903
|
error: null,
|
|
2796
|
-
skill: { name: createPayload.name.trim(), path:
|
|
2904
|
+
skill: { name: createPayload.name.trim(), path: path6.join(targetDir, "SKILL.md"), scope: createPayload.scope }
|
|
2797
2905
|
}
|
|
2798
2906
|
});
|
|
2799
2907
|
} catch (err) {
|
|
@@ -2857,23 +2965,23 @@ import {
|
|
|
2857
2965
|
Agent,
|
|
2858
2966
|
AutoCompactionMiddleware,
|
|
2859
2967
|
Context,
|
|
2860
|
-
DefaultMemoryStore
|
|
2861
|
-
DefaultModeStore
|
|
2968
|
+
DefaultMemoryStore,
|
|
2969
|
+
DefaultModeStore,
|
|
2862
2970
|
DefaultModelsRegistry,
|
|
2863
2971
|
DefaultSessionReader,
|
|
2864
|
-
DefaultSessionStore as
|
|
2865
|
-
DefaultSkillLoader
|
|
2866
|
-
DefaultSystemPromptBuilder as
|
|
2867
|
-
DefaultTokenCounter
|
|
2972
|
+
DefaultSessionStore as DefaultSessionStore2,
|
|
2973
|
+
DefaultSkillLoader,
|
|
2974
|
+
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
|
|
2975
|
+
DefaultTokenCounter,
|
|
2868
2976
|
AnnotationsStore,
|
|
2869
2977
|
CollaborationBus,
|
|
2870
2978
|
collabPauseMiddleware,
|
|
2871
2979
|
collabInjectMiddleware,
|
|
2872
2980
|
estimateRequestTokensCalibrated,
|
|
2873
2981
|
EventBus,
|
|
2874
|
-
createStrategyCompactor
|
|
2982
|
+
createStrategyCompactor,
|
|
2875
2983
|
ProviderRegistry,
|
|
2876
|
-
TOKENS
|
|
2984
|
+
TOKENS,
|
|
2877
2985
|
ToolRegistry,
|
|
2878
2986
|
atomicWrite as atomicWrite6,
|
|
2879
2987
|
createDefaultPipelines,
|
|
@@ -2892,110 +3000,10 @@ import {
|
|
|
2892
3000
|
import { ToolExecutor } from "@wrongstack/core/execution";
|
|
2893
3001
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
2894
3002
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
|
|
2895
|
-
import { builtinToolsPack, forgetTool, rememberTool, searchMemoryTool, relatedMemoryTool } from "@wrongstack/tools";
|
|
3003
|
+
import { builtinToolsPack, configureExecPolicy, ensureSessionShell, forgetTool, rememberTool, searchMemoryTool, relatedMemoryTool } from "@wrongstack/tools";
|
|
2896
3004
|
import { MCPRegistry } from "@wrongstack/mcp";
|
|
2897
3005
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
2898
|
-
|
|
2899
|
-
// ../runtime/src/container.ts
|
|
2900
|
-
import {
|
|
2901
|
-
Container,
|
|
2902
|
-
DefaultConfigStore,
|
|
2903
|
-
DefaultErrorHandler,
|
|
2904
|
-
DefaultMemoryStore,
|
|
2905
|
-
DefaultModeStore,
|
|
2906
|
-
DefaultPermissionPolicy,
|
|
2907
|
-
DefaultRetryPolicy,
|
|
2908
|
-
DefaultSecretScrubber,
|
|
2909
|
-
DefaultSessionStore,
|
|
2910
|
-
DefaultSkillLoader,
|
|
2911
|
-
DefaultSystemPromptBuilder,
|
|
2912
|
-
DefaultTokenCounter,
|
|
2913
|
-
createStrategyCompactor,
|
|
2914
|
-
buildRecoveryStrategies,
|
|
2915
|
-
TOKENS
|
|
2916
|
-
} from "@wrongstack/core";
|
|
2917
|
-
function createDefaultContainer(opts) {
|
|
2918
|
-
const { config, wpaths, logger, modelsRegistry } = opts;
|
|
2919
|
-
const container = new Container();
|
|
2920
|
-
const configStore = new DefaultConfigStore(config);
|
|
2921
|
-
container.bind(TOKENS.ConfigStore, () => configStore);
|
|
2922
|
-
container.bind(TOKENS.Logger, () => logger);
|
|
2923
|
-
container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
|
|
2924
|
-
container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
|
|
2925
|
-
container.bind(
|
|
2926
|
-
TOKENS.ErrorHandler,
|
|
2927
|
-
() => new DefaultErrorHandler(
|
|
2928
|
-
buildRecoveryStrategies({
|
|
2929
|
-
compactor: container.resolve(TOKENS.Compactor),
|
|
2930
|
-
modelsRegistry,
|
|
2931
|
-
getConfig: () => configStore.get()
|
|
2932
|
-
})
|
|
2933
|
-
)
|
|
2934
|
-
);
|
|
2935
|
-
container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
|
|
2936
|
-
container.bind(
|
|
2937
|
-
TOKENS.TokenCounter,
|
|
2938
|
-
() => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider })
|
|
2939
|
-
);
|
|
2940
|
-
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
2941
|
-
container.bind(TOKENS.ModeStore, () => modeStore);
|
|
2942
|
-
container.bind(
|
|
2943
|
-
TOKENS.SessionStore,
|
|
2944
|
-
() => new DefaultSessionStore({
|
|
2945
|
-
dir: wpaths.projectSessions,
|
|
2946
|
-
// Scrub secrets out of persisted user/model turns (F-06). Tool output
|
|
2947
|
-
// is already scrubbed by the executor.
|
|
2948
|
-
secretScrubber: container.resolve(TOKENS.SecretScrubber)
|
|
2949
|
-
})
|
|
2950
|
-
);
|
|
2951
|
-
const memoryStore = new DefaultMemoryStore({ paths: wpaths, events: opts.events });
|
|
2952
|
-
container.bind(TOKENS.MemoryStore, () => memoryStore);
|
|
2953
|
-
const skillLoader = new DefaultSkillLoader({ paths: wpaths, bundledDir: opts.bundledSkillsDir });
|
|
2954
|
-
container.bind(TOKENS.SkillLoader, () => skillLoader);
|
|
2955
|
-
if (opts.systemPrompt) {
|
|
2956
|
-
container.bind(
|
|
2957
|
-
TOKENS.SystemPromptBuilder,
|
|
2958
|
-
() => new DefaultSystemPromptBuilder(opts.systemPrompt)
|
|
2959
|
-
);
|
|
2960
|
-
}
|
|
2961
|
-
container.bind(
|
|
2962
|
-
TOKENS.PermissionPolicy,
|
|
2963
|
-
() => {
|
|
2964
|
-
const policyOptions = {
|
|
2965
|
-
trustFile: wpaths.projectTrust,
|
|
2966
|
-
yolo: opts.permission?.yolo ?? false,
|
|
2967
|
-
yoloDestructive: opts.permission?.yoloDestructive ?? opts.permission?.forceAllYolo ?? false,
|
|
2968
|
-
confirmDestructive: opts.permission?.confirmDestructive ?? false
|
|
2969
|
-
};
|
|
2970
|
-
if (opts.permission?.promptDelegate !== void 0) {
|
|
2971
|
-
policyOptions.promptDelegate = opts.permission.promptDelegate;
|
|
2972
|
-
}
|
|
2973
|
-
return new DefaultPermissionPolicy(policyOptions);
|
|
2974
|
-
}
|
|
2975
|
-
);
|
|
2976
|
-
container.bind(
|
|
2977
|
-
TOKENS.Compactor,
|
|
2978
|
-
() => (
|
|
2979
|
-
// Strategy comes from config.context.strategy: 'hybrid' (default, lossless
|
|
2980
|
-
// rules, no LLM), 'intelligent' (LLM summarization), or 'selective'
|
|
2981
|
-
// (LLM-driven selection). The LLM strategies resolve their provider from
|
|
2982
|
-
// ctx at compact()-time, so binding here (before context.provider exists)
|
|
2983
|
-
// is safe. preserveK / eliseThreshold are class-level fallbacks; the active
|
|
2984
|
-
// ContextWindowPolicy in ctx.meta normally overrides both at runtime.
|
|
2985
|
-
// eliseThreshold is a TOKEN COUNT — a previous value of 0.7 elided
|
|
2986
|
-
// essentially every tool_result (anything > 1 token).
|
|
2987
|
-
createStrategyCompactor({
|
|
2988
|
-
strategy: config.context?.strategy,
|
|
2989
|
-
preserveK: opts.compactor?.preserveK ?? 10,
|
|
2990
|
-
eliseThreshold: opts.compactor?.eliseThreshold ?? 2e3,
|
|
2991
|
-
smart: true,
|
|
2992
|
-
summarizerModel: config.context?.summarizerModel,
|
|
2993
|
-
llmSelector: config.context?.llmSelector
|
|
2994
|
-
})
|
|
2995
|
-
)
|
|
2996
|
-
);
|
|
2997
|
-
return container;
|
|
2998
|
-
}
|
|
3006
|
+
import { createDefaultContainer, makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
2999
3007
|
|
|
3000
3008
|
// src/server/boot.ts
|
|
3001
3009
|
import {
|
|
@@ -3356,6 +3364,13 @@ Type: ${task.type}`;
|
|
|
3356
3364
|
});
|
|
3357
3365
|
const taskItems = activePhase ? Array.from(activePhase.taskGraph.nodes.values()).map(mapTask) : [];
|
|
3358
3366
|
const completedPhases = phases.filter((p) => p.status === "completed").length;
|
|
3367
|
+
const failedPhases = phases.filter((p) => p.status === "failed").length;
|
|
3368
|
+
const failedTasks = phases.reduce(
|
|
3369
|
+
(sum, p) => sum + Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "failed").length,
|
|
3370
|
+
0
|
|
3371
|
+
);
|
|
3372
|
+
const lastFailed = phases.filter((p) => p.status === "failed").sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
|
|
3373
|
+
const lastError = lastFailed ? `${lastFailed.name}: ${lastFailed.metadata?.integrationError ?? "phase failed"}` : null;
|
|
3359
3374
|
return {
|
|
3360
3375
|
title: this.graph.title,
|
|
3361
3376
|
phases: phaseItems,
|
|
@@ -3364,7 +3379,18 @@ Type: ${task.type}`;
|
|
|
3364
3379
|
overallPercent: phases.length > 0 ? Math.round(completedPhases / phases.length * 100) : 0,
|
|
3365
3380
|
autonomous: this.graph.autonomous,
|
|
3366
3381
|
totalTasks,
|
|
3367
|
-
completedTasks
|
|
3382
|
+
completedTasks,
|
|
3383
|
+
// Structured progress + lastError consumed by the autophase store (were
|
|
3384
|
+
// defined client-side but never sent, so they stayed null on the board).
|
|
3385
|
+
progress: {
|
|
3386
|
+
totalPhases: phases.length,
|
|
3387
|
+
completed: completedPhases,
|
|
3388
|
+
failed: failedPhases,
|
|
3389
|
+
totalTasks,
|
|
3390
|
+
completedTasks,
|
|
3391
|
+
failedTasks
|
|
3392
|
+
},
|
|
3393
|
+
lastError
|
|
3368
3394
|
};
|
|
3369
3395
|
}
|
|
3370
3396
|
sendState(client) {
|
|
@@ -3786,7 +3812,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
3786
3812
|
};
|
|
3787
3813
|
|
|
3788
3814
|
// src/server/sdd-wizard-wiring.ts
|
|
3789
|
-
import * as
|
|
3815
|
+
import * as path7 from "path";
|
|
3790
3816
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3791
3817
|
import {
|
|
3792
3818
|
makeCommandVerifier,
|
|
@@ -3822,7 +3848,7 @@ function buildSddWizardDeps(opts) {
|
|
|
3822
3848
|
makeDriver: () => new SddInterviewDriver({
|
|
3823
3849
|
specStore: new SpecStore2({ baseDir: opts.paths.projectSpecs }),
|
|
3824
3850
|
graphStore: new TaskGraphStore2({ baseDir: opts.paths.projectTaskGraphs }),
|
|
3825
|
-
sessionPath:
|
|
3851
|
+
sessionPath: path7.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
3826
3852
|
}),
|
|
3827
3853
|
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
3828
3854
|
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels }) => {
|
|
@@ -3891,9 +3917,6 @@ async function handleSddWizardRoute(_ws, msg, handlers) {
|
|
|
3891
3917
|
return true;
|
|
3892
3918
|
}
|
|
3893
3919
|
|
|
3894
|
-
// src/server/index.ts
|
|
3895
|
-
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
3896
|
-
|
|
3897
3920
|
// src/server/collaboration-ws-handler.ts
|
|
3898
3921
|
import { randomUUID } from "crypto";
|
|
3899
3922
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
@@ -4620,16 +4643,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
4620
4643
|
};
|
|
4621
4644
|
|
|
4622
4645
|
// src/server/projects-manifest.ts
|
|
4623
|
-
import * as
|
|
4624
|
-
import * as
|
|
4646
|
+
import * as fs6 from "fs/promises";
|
|
4647
|
+
import * as path8 from "path";
|
|
4625
4648
|
import { projectSlug } from "@wrongstack/core";
|
|
4626
4649
|
function projectsJsonPath(globalConfigPath) {
|
|
4627
|
-
const base =
|
|
4628
|
-
return
|
|
4650
|
+
const base = path8.dirname(globalConfigPath);
|
|
4651
|
+
return path8.join(base, "projects.json");
|
|
4629
4652
|
}
|
|
4630
4653
|
async function loadManifest(globalConfigPath) {
|
|
4631
4654
|
try {
|
|
4632
|
-
const raw = await
|
|
4655
|
+
const raw = await fs6.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
4633
4656
|
const parsed = JSON.parse(raw);
|
|
4634
4657
|
return { projects: parsed.projects ?? [] };
|
|
4635
4658
|
} catch {
|
|
@@ -4638,16 +4661,16 @@ async function loadManifest(globalConfigPath) {
|
|
|
4638
4661
|
}
|
|
4639
4662
|
async function saveManifest(manifest, globalConfigPath) {
|
|
4640
4663
|
const file = projectsJsonPath(globalConfigPath);
|
|
4641
|
-
await
|
|
4642
|
-
await
|
|
4664
|
+
await fs6.mkdir(path8.dirname(file), { recursive: true });
|
|
4665
|
+
await fs6.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
4643
4666
|
}
|
|
4644
4667
|
function generateProjectSlug(rootPath) {
|
|
4645
4668
|
return projectSlug(rootPath);
|
|
4646
4669
|
}
|
|
4647
4670
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
4648
|
-
const base =
|
|
4649
|
-
const dir =
|
|
4650
|
-
await
|
|
4671
|
+
const base = path8.dirname(globalConfigPath);
|
|
4672
|
+
const dir = path8.join(base, "projects", slug);
|
|
4673
|
+
await fs6.mkdir(dir, { recursive: true });
|
|
4651
4674
|
return dir;
|
|
4652
4675
|
}
|
|
4653
4676
|
|
|
@@ -5073,14 +5096,14 @@ function registerShutdownHandlers(res) {
|
|
|
5073
5096
|
|
|
5074
5097
|
// src/server/instance-registry.ts
|
|
5075
5098
|
import * as os from "os";
|
|
5076
|
-
import * as
|
|
5077
|
-
import * as
|
|
5099
|
+
import * as path9 from "path";
|
|
5100
|
+
import * as fs7 from "fs/promises";
|
|
5078
5101
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
5079
5102
|
function defaultBaseDir() {
|
|
5080
|
-
return
|
|
5103
|
+
return path9.join(os.homedir(), ".wrongstack");
|
|
5081
5104
|
}
|
|
5082
5105
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
5083
|
-
return
|
|
5106
|
+
return path9.join(baseDir, "webui-instances.json");
|
|
5084
5107
|
}
|
|
5085
5108
|
function isPidAlive(pid) {
|
|
5086
5109
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -5093,7 +5116,7 @@ function isPidAlive(pid) {
|
|
|
5093
5116
|
}
|
|
5094
5117
|
async function load(file) {
|
|
5095
5118
|
try {
|
|
5096
|
-
const raw = await
|
|
5119
|
+
const raw = await fs7.readFile(file, "utf8");
|
|
5097
5120
|
const parsed = JSON.parse(raw);
|
|
5098
5121
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
5099
5122
|
return parsed;
|
|
@@ -5238,19 +5261,19 @@ function computeUsageCost(usage, rates) {
|
|
|
5238
5261
|
}
|
|
5239
5262
|
|
|
5240
5263
|
// src/server/provider-handlers.ts
|
|
5241
|
-
import { DefaultSecretScrubber
|
|
5264
|
+
import { DefaultSecretScrubber } from "@wrongstack/core";
|
|
5242
5265
|
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
5243
5266
|
|
|
5244
5267
|
// src/server/provider-config-io.ts
|
|
5245
|
-
import * as
|
|
5246
|
-
import * as
|
|
5268
|
+
import * as fs8 from "fs/promises";
|
|
5269
|
+
import * as path10 from "path";
|
|
5247
5270
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
5248
5271
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
5249
5272
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
5250
5273
|
async function loadSavedProviders(configPath, vault) {
|
|
5251
5274
|
let raw;
|
|
5252
5275
|
try {
|
|
5253
|
-
raw = await
|
|
5276
|
+
raw = await fs8.readFile(configPath, "utf8");
|
|
5254
5277
|
} catch {
|
|
5255
5278
|
return {};
|
|
5256
5279
|
}
|
|
@@ -5267,7 +5290,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
5267
5290
|
let raw;
|
|
5268
5291
|
let fileExists = true;
|
|
5269
5292
|
try {
|
|
5270
|
-
raw = await
|
|
5293
|
+
raw = await fs8.readFile(configPath, "utf8");
|
|
5271
5294
|
} catch (err) {
|
|
5272
5295
|
if (err.code !== "ENOENT") {
|
|
5273
5296
|
throw new Error(
|
|
@@ -5295,7 +5318,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
5295
5318
|
await atomicWrite4(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
5296
5319
|
}
|
|
5297
5320
|
function createProviderConfigIO(configPath) {
|
|
5298
|
-
const keyFile =
|
|
5321
|
+
const keyFile = path10.join(path10.dirname(configPath), ".key");
|
|
5299
5322
|
const vault = new DefaultSecretVault({ keyFile });
|
|
5300
5323
|
return {
|
|
5301
5324
|
load: () => loadSavedProviders(configPath, vault),
|
|
@@ -5424,7 +5447,7 @@ function projectSavedProviders(providers) {
|
|
|
5424
5447
|
return view;
|
|
5425
5448
|
});
|
|
5426
5449
|
}
|
|
5427
|
-
var probeScrubber = new
|
|
5450
|
+
var probeScrubber = new DefaultSecretScrubber();
|
|
5428
5451
|
function createProviderHandlers(deps2) {
|
|
5429
5452
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
5430
5453
|
let configWriteLock = deps2.getConfigWriteLock();
|
|
@@ -5613,7 +5636,7 @@ function createProviderHandlers(deps2) {
|
|
|
5613
5636
|
|
|
5614
5637
|
// src/server/mode-handlers.ts
|
|
5615
5638
|
import {
|
|
5616
|
-
DefaultSystemPromptBuilder
|
|
5639
|
+
DefaultSystemPromptBuilder
|
|
5617
5640
|
} from "@wrongstack/core";
|
|
5618
5641
|
function createModeHandlers(ctx) {
|
|
5619
5642
|
return {
|
|
@@ -5661,7 +5684,7 @@ function createModeHandlers(ctx) {
|
|
|
5661
5684
|
}
|
|
5662
5685
|
ctx.setModeId(id);
|
|
5663
5686
|
const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
|
|
5664
|
-
const freshBuilder = new
|
|
5687
|
+
const freshBuilder = new DefaultSystemPromptBuilder({
|
|
5665
5688
|
memoryStore: ctx.memoryStore,
|
|
5666
5689
|
skillLoader: ctx.skillLoader,
|
|
5667
5690
|
modeStore: ctx.modeStore,
|
|
@@ -5692,40 +5715,10 @@ function createModeHandlers(ctx) {
|
|
|
5692
5715
|
import * as fs9 from "fs/promises";
|
|
5693
5716
|
import * as path11 from "path";
|
|
5694
5717
|
import {
|
|
5695
|
-
DefaultSessionStore
|
|
5696
|
-
DefaultSystemPromptBuilder as
|
|
5718
|
+
DefaultSessionStore,
|
|
5719
|
+
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
|
|
5697
5720
|
getSessionRegistry
|
|
5698
5721
|
} from "@wrongstack/core";
|
|
5699
|
-
|
|
5700
|
-
// src/server/path-containment.ts
|
|
5701
|
-
import * as fs8 from "fs/promises";
|
|
5702
|
-
import * as path10 from "path";
|
|
5703
|
-
function isPathInside(root, target) {
|
|
5704
|
-
const relative3 = path10.relative(root, target);
|
|
5705
|
-
return relative3 === "" || !relative3.startsWith("..") && !path10.isAbsolute(relative3);
|
|
5706
|
-
}
|
|
5707
|
-
async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
5708
|
-
const resolved = path10.resolve(projectRoot, inputPath);
|
|
5709
|
-
let stat3;
|
|
5710
|
-
try {
|
|
5711
|
-
stat3 = await fs8.stat(resolved);
|
|
5712
|
-
} catch {
|
|
5713
|
-
throw new Error(`Directory not found or not accessible: ${resolved}`);
|
|
5714
|
-
}
|
|
5715
|
-
if (!stat3.isDirectory()) {
|
|
5716
|
-
throw new Error(`Directory not found or not accessible: ${resolved}`);
|
|
5717
|
-
}
|
|
5718
|
-
const [realProjectRoot, realResolved] = await Promise.all([
|
|
5719
|
-
fs8.realpath(projectRoot),
|
|
5720
|
-
fs8.realpath(resolved)
|
|
5721
|
-
]);
|
|
5722
|
-
if (!isPathInside(realProjectRoot, realResolved)) {
|
|
5723
|
-
throw new Error(`Path must stay inside the project root: ${projectRoot}`);
|
|
5724
|
-
}
|
|
5725
|
-
return resolved;
|
|
5726
|
-
}
|
|
5727
|
-
|
|
5728
|
-
// src/server/project-handlers.ts
|
|
5729
5722
|
function createProjectHandlers(ctx) {
|
|
5730
5723
|
return {
|
|
5731
5724
|
listProjects: async (ws) => {
|
|
@@ -5837,7 +5830,7 @@ function createProjectHandlers(ctx) {
|
|
|
5837
5830
|
try {
|
|
5838
5831
|
const modeId = ctx.getModeId();
|
|
5839
5832
|
const switchMode = modeId === "default" ? void 0 : await ctx.modeStore.getMode(modeId);
|
|
5840
|
-
const switchBuilder = new
|
|
5833
|
+
const switchBuilder = new DefaultSystemPromptBuilder2({
|
|
5841
5834
|
memoryStore: ctx.memoryStore,
|
|
5842
5835
|
skillLoader: ctx.skillLoader,
|
|
5843
5836
|
modeStore: ctx.modeStore,
|
|
@@ -5861,7 +5854,7 @@ function createProjectHandlers(ctx) {
|
|
|
5861
5854
|
"sessions"
|
|
5862
5855
|
);
|
|
5863
5856
|
await fs9.mkdir(newSessionsDir, { recursive: true });
|
|
5864
|
-
const newSessionStore = new
|
|
5857
|
+
const newSessionStore = new DefaultSessionStore({ dir: newSessionsDir });
|
|
5865
5858
|
const oldSession = ctx.getSession();
|
|
5866
5859
|
const oldSessionId = oldSession.id;
|
|
5867
5860
|
try {
|
|
@@ -6558,6 +6551,22 @@ async function handleModeRoute(ws, msg, handlers) {
|
|
|
6558
6551
|
}
|
|
6559
6552
|
}
|
|
6560
6553
|
|
|
6554
|
+
// src/server/prefs-routes.ts
|
|
6555
|
+
async function handlePrefsRoute(ws, msg, handlers) {
|
|
6556
|
+
switch (msg.type) {
|
|
6557
|
+
case "prefs.get": {
|
|
6558
|
+
await handlers.getPrefs(ws);
|
|
6559
|
+
return true;
|
|
6560
|
+
}
|
|
6561
|
+
case "prefs.update": {
|
|
6562
|
+
await handlers.updatePrefs(ws, msg.payload ?? {});
|
|
6563
|
+
return true;
|
|
6564
|
+
}
|
|
6565
|
+
default:
|
|
6566
|
+
return false;
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
|
|
6561
6570
|
// src/server/shell-git-routes.ts
|
|
6562
6571
|
async function handleShellGitRoute(ws, msg, handlers) {
|
|
6563
6572
|
switch (msg.type) {
|
|
@@ -6598,6 +6607,44 @@ async function handleMailboxRoute(ws, msg, handlers) {
|
|
|
6598
6607
|
}
|
|
6599
6608
|
}
|
|
6600
6609
|
|
|
6610
|
+
// src/server/mcp-routes.ts
|
|
6611
|
+
async function handleMcpRoute(ws, msg, handlers) {
|
|
6612
|
+
switch (msg.type) {
|
|
6613
|
+
case "mcp.list":
|
|
6614
|
+
await handlers.list(ws, msg);
|
|
6615
|
+
return true;
|
|
6616
|
+
case "mcp.add":
|
|
6617
|
+
await handlers.add(ws, msg);
|
|
6618
|
+
return true;
|
|
6619
|
+
case "mcp.update":
|
|
6620
|
+
await handlers.update(ws, msg);
|
|
6621
|
+
return true;
|
|
6622
|
+
case "mcp.remove":
|
|
6623
|
+
await handlers.remove(ws, msg);
|
|
6624
|
+
return true;
|
|
6625
|
+
case "mcp.enable":
|
|
6626
|
+
await handlers.enable(ws, msg);
|
|
6627
|
+
return true;
|
|
6628
|
+
case "mcp.disable":
|
|
6629
|
+
await handlers.disable(ws, msg);
|
|
6630
|
+
return true;
|
|
6631
|
+
case "mcp.sleep":
|
|
6632
|
+
await handlers.sleep(ws, msg);
|
|
6633
|
+
return true;
|
|
6634
|
+
case "mcp.wake":
|
|
6635
|
+
await handlers.wake(ws, msg);
|
|
6636
|
+
return true;
|
|
6637
|
+
case "mcp.restart":
|
|
6638
|
+
await handlers.restart(ws, msg);
|
|
6639
|
+
return true;
|
|
6640
|
+
case "mcp.discover":
|
|
6641
|
+
await handlers.discover(ws, msg);
|
|
6642
|
+
return true;
|
|
6643
|
+
default:
|
|
6644
|
+
return false;
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
|
|
6601
6648
|
// src/server/brain-routes.ts
|
|
6602
6649
|
async function handleBrainRoute(ws, msg, handlers) {
|
|
6603
6650
|
switch (msg.type) {
|
|
@@ -7069,11 +7116,13 @@ function setupEvents(deps2) {
|
|
|
7069
7116
|
events.on("provider.response", (e) => {
|
|
7070
7117
|
if (e.usage?.input != null) {
|
|
7071
7118
|
const maxCtx = context.provider.capabilities.maxContext;
|
|
7072
|
-
const
|
|
7119
|
+
const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
|
|
7120
|
+
const load2 = Math.max(0, Math.min(1, rawLoad));
|
|
7073
7121
|
const costUsd = context.tokenCounter.estimateCost().total;
|
|
7074
7122
|
forwardSubagent("ctx_pct", {
|
|
7075
7123
|
subagentId: "leader",
|
|
7076
|
-
load:
|
|
7124
|
+
load: load2,
|
|
7125
|
+
rawLoad,
|
|
7077
7126
|
tokens: e.usage.input,
|
|
7078
7127
|
maxContext: maxCtx,
|
|
7079
7128
|
costUsd
|
|
@@ -7724,6 +7773,7 @@ async function handleGoalGet(projectRoot, broadcast2) {
|
|
|
7724
7773
|
|
|
7725
7774
|
// src/server/index.ts
|
|
7726
7775
|
async function startWebUI(opts = {}) {
|
|
7776
|
+
ensureSessionShell();
|
|
7727
7777
|
const requestedWsPort = opts.wsPort ?? 3457;
|
|
7728
7778
|
const wsHost = opts.wsHost ?? "127.0.0.1";
|
|
7729
7779
|
const requestedHttpPort = Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
@@ -7805,7 +7855,7 @@ async function startWebUI(opts = {}) {
|
|
|
7805
7855
|
ttlSeconds: 24 * 3600
|
|
7806
7856
|
});
|
|
7807
7857
|
const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
|
|
7808
|
-
const configStore = opts.services?.configStore ?? container.resolve(
|
|
7858
|
+
const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
|
|
7809
7859
|
const providerRegistry = new ProviderRegistry();
|
|
7810
7860
|
try {
|
|
7811
7861
|
const factories = await buildProviderFactoriesFromRegistry({
|
|
@@ -7827,7 +7877,7 @@ async function startWebUI(opts = {}) {
|
|
|
7827
7877
|
r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
7828
7878
|
return r;
|
|
7829
7879
|
})();
|
|
7830
|
-
const memoryStore = new
|
|
7880
|
+
const memoryStore = new DefaultMemoryStore({ paths: wpaths });
|
|
7831
7881
|
if (config.features.memory) {
|
|
7832
7882
|
toolRegistry.register(rememberTool(memoryStore));
|
|
7833
7883
|
toolRegistry.register(forgetTool(memoryStore));
|
|
@@ -7840,6 +7890,7 @@ async function startWebUI(opts = {}) {
|
|
|
7840
7890
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
7841
7891
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
7842
7892
|
applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
|
|
7893
|
+
configureExecPolicy(config.tools?.exec ?? {});
|
|
7843
7894
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7844
7895
|
const mcpRegistry = new MCPRegistry({
|
|
7845
7896
|
toolRegistry,
|
|
@@ -7856,7 +7907,7 @@ async function startWebUI(opts = {}) {
|
|
|
7856
7907
|
});
|
|
7857
7908
|
}
|
|
7858
7909
|
}
|
|
7859
|
-
let sessionStore = opts.services?.session ?? new
|
|
7910
|
+
let sessionStore = opts.services?.session ?? new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
7860
7911
|
if (!opts.services?.session) {
|
|
7861
7912
|
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
7862
7913
|
if (count > 0) logger.info(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
|
|
@@ -7944,11 +7995,11 @@ async function startWebUI(opts = {}) {
|
|
|
7944
7995
|
});
|
|
7945
7996
|
} catch {
|
|
7946
7997
|
}
|
|
7947
|
-
const tokenCounter = new
|
|
7998
|
+
const tokenCounter = new DefaultTokenCounter({
|
|
7948
7999
|
registry: modelsRegistry,
|
|
7949
8000
|
providerId: config.provider
|
|
7950
8001
|
});
|
|
7951
|
-
const modeStore = new
|
|
8002
|
+
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
7952
8003
|
const activeMode = await modeStore.getActiveMode();
|
|
7953
8004
|
let modeId = activeMode?.id ?? "default";
|
|
7954
8005
|
const modePrompt = activeMode?.prompt ?? "";
|
|
@@ -7969,7 +8020,7 @@ async function startWebUI(opts = {}) {
|
|
|
7969
8020
|
const modelCapabilitiesRef = {
|
|
7970
8021
|
current: modelCapabilities
|
|
7971
8022
|
};
|
|
7972
|
-
const skillLoader = config.features.skills ? new
|
|
8023
|
+
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7973
8024
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7974
8025
|
manifestPath: path16.join(wstackGlobalRoot2(), "installed-skills.json"),
|
|
7975
8026
|
projectSkillsDir: path16.join(projectRoot, ".wrongstack", "skills"),
|
|
@@ -7977,7 +8028,7 @@ async function startWebUI(opts = {}) {
|
|
|
7977
8028
|
projectHash: projectHash(projectRoot),
|
|
7978
8029
|
skillLoader
|
|
7979
8030
|
}) : void 0;
|
|
7980
|
-
const systemPromptBuilder = new
|
|
8031
|
+
const systemPromptBuilder = new DefaultSystemPromptBuilder3({
|
|
7981
8032
|
memoryStore,
|
|
7982
8033
|
skillLoader,
|
|
7983
8034
|
modeStore,
|
|
@@ -8267,7 +8318,7 @@ async function startWebUI(opts = {}) {
|
|
|
8267
8318
|
projectRoot,
|
|
8268
8319
|
logger
|
|
8269
8320
|
});
|
|
8270
|
-
const compactor =
|
|
8321
|
+
const compactor = createStrategyCompactor({
|
|
8271
8322
|
strategy: config.context?.strategy,
|
|
8272
8323
|
preserveK: config.context?.preserveK ?? 10,
|
|
8273
8324
|
eliseThreshold: config.context?.eliseThreshold ?? 2e3,
|
|
@@ -8343,9 +8394,9 @@ async function startWebUI(opts = {}) {
|
|
|
8343
8394
|
maxContext: newMaxContext
|
|
8344
8395
|
});
|
|
8345
8396
|
}
|
|
8346
|
-
const secretScrubber = container.resolve(
|
|
8347
|
-
const renderer = container.has(
|
|
8348
|
-
const permissionPolicy = container.resolve(
|
|
8397
|
+
const secretScrubber = container.resolve(TOKENS.SecretScrubber);
|
|
8398
|
+
const renderer = container.has(TOKENS.Renderer) ? container.resolve(TOKENS.Renderer) : void 0;
|
|
8399
|
+
const permissionPolicy = container.resolve(TOKENS.PermissionPolicy);
|
|
8349
8400
|
const toolExecutor = new ToolExecutor(toolRegistry, {
|
|
8350
8401
|
permissionPolicy,
|
|
8351
8402
|
secretScrubber,
|
|
@@ -8388,7 +8439,7 @@ async function startWebUI(opts = {}) {
|
|
|
8388
8439
|
}),
|
|
8389
8440
|
events
|
|
8390
8441
|
);
|
|
8391
|
-
container.bind(
|
|
8442
|
+
container.bind(TOKENS.BrainArbiter, () => brain);
|
|
8392
8443
|
const brainMailbox = new GlobalMailbox2(wpaths.projectDir, events);
|
|
8393
8444
|
const brainMonitor = new BrainMonitor({
|
|
8394
8445
|
events,
|
|
@@ -8768,8 +8819,10 @@ async function startWebUI(opts = {}) {
|
|
|
8768
8819
|
let sessionRoutes;
|
|
8769
8820
|
let projectRoutes;
|
|
8770
8821
|
let modeRoutes;
|
|
8822
|
+
let prefsRoutes;
|
|
8771
8823
|
let shellGitRoutes;
|
|
8772
8824
|
let mailboxRoutes;
|
|
8825
|
+
let mcpRoutes;
|
|
8773
8826
|
let brainRoutes;
|
|
8774
8827
|
let autoPhaseRoutes;
|
|
8775
8828
|
let specsRoutes;
|
|
@@ -8780,8 +8833,10 @@ async function startWebUI(opts = {}) {
|
|
|
8780
8833
|
if (await handleSessionRoute(ws, msg, sessionRoutes)) return;
|
|
8781
8834
|
if (await handleProjectRoute(ws, msg, projectRoutes)) return;
|
|
8782
8835
|
if (await handleModeRoute(ws, msg, modeRoutes)) return;
|
|
8836
|
+
if (await handlePrefsRoute(ws, msg, prefsRoutes)) return;
|
|
8783
8837
|
if (await handleShellGitRoute(ws, msg, shellGitRoutes)) return;
|
|
8784
8838
|
if (await handleMailboxRoute(ws, msg, mailboxRoutes)) return;
|
|
8839
|
+
if (await handleMcpRoute(ws, msg, mcpRoutes)) return;
|
|
8785
8840
|
if (await handleBrainRoute(ws, msg, brainRoutes)) return;
|
|
8786
8841
|
if (await handleAutoPhaseRoute(ws, msg, autoPhaseRoutes)) return;
|
|
8787
8842
|
if (await handleSpecsRoute(ws, msg, specsRoutes)) return;
|
|
@@ -8892,27 +8947,31 @@ async function startWebUI(opts = {}) {
|
|
|
8892
8947
|
case "memory.forget":
|
|
8893
8948
|
return handleMemoryForget(ws, msg, memoryStore);
|
|
8894
8949
|
// ── MCP operations — delegated to shared handlers (mcp-handlers.ts),
|
|
8895
|
-
// backed by the live MCPRegistry constructed above.
|
|
8950
|
+
// backed by the live MCPRegistry constructed above. Routed via
|
|
8951
|
+
// handleMcpRoute (see mcpRoutes = { ... } below). These case arms
|
|
8952
|
+
// are unreachable but left as tripwires for any future regression
|
|
8953
|
+
// where the route chain stops claiming 'mcp.*'. If you see one
|
|
8954
|
+
// fire, fix the dispatch order in the handleMessage chain above.
|
|
8896
8955
|
case "mcp.list":
|
|
8897
|
-
|
|
8956
|
+
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
8898
8957
|
case "mcp.add":
|
|
8899
|
-
|
|
8900
|
-
case "mcp.remove":
|
|
8901
|
-
return handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry);
|
|
8958
|
+
throw new Error("handleMcpRoute did not claim mcp.add \u2014 check chain order");
|
|
8902
8959
|
case "mcp.update":
|
|
8903
|
-
|
|
8904
|
-
case "mcp.
|
|
8905
|
-
|
|
8906
|
-
case "mcp.sleep":
|
|
8907
|
-
return handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry);
|
|
8908
|
-
case "mcp.discover":
|
|
8909
|
-
return handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry);
|
|
8960
|
+
throw new Error("handleMcpRoute did not claim mcp.update \u2014 check chain order");
|
|
8961
|
+
case "mcp.remove":
|
|
8962
|
+
throw new Error("handleMcpRoute did not claim mcp.remove \u2014 check chain order");
|
|
8910
8963
|
case "mcp.enable":
|
|
8911
|
-
|
|
8964
|
+
throw new Error("handleMcpRoute did not claim mcp.enable \u2014 check chain order");
|
|
8912
8965
|
case "mcp.disable":
|
|
8913
|
-
|
|
8966
|
+
throw new Error("handleMcpRoute did not claim mcp.disable \u2014 check chain order");
|
|
8967
|
+
case "mcp.sleep":
|
|
8968
|
+
throw new Error("handleMcpRoute did not claim mcp.sleep \u2014 check chain order");
|
|
8969
|
+
case "mcp.wake":
|
|
8970
|
+
throw new Error("handleMcpRoute did not claim mcp.wake \u2014 check chain order");
|
|
8914
8971
|
case "mcp.restart":
|
|
8915
|
-
|
|
8972
|
+
throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
|
|
8973
|
+
case "mcp.discover":
|
|
8974
|
+
throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
|
|
8916
8975
|
// Skills — full request→response cycle lives in skills-handlers.ts
|
|
8917
8976
|
// (shared with the CLI's embedded server). skillsCtx is the closed-over
|
|
8918
8977
|
// loader/installer/projectRoot the handlers need.
|
|
@@ -9060,53 +9119,11 @@ async function startWebUI(opts = {}) {
|
|
|
9060
9119
|
break;
|
|
9061
9120
|
}
|
|
9062
9121
|
case "prefs.update": {
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
sendResult2(ws, false, parsed.message);
|
|
9066
|
-
break;
|
|
9067
|
-
}
|
|
9068
|
-
const payload = parsed.value.prefs;
|
|
9069
|
-
for (const [key, val] of Object.entries(payload)) {
|
|
9070
|
-
context.meta[key] = val;
|
|
9071
|
-
}
|
|
9072
|
-
void persistPrefsToConfig(payload);
|
|
9073
|
-
if (typeof payload["yolo"] === "boolean") {
|
|
9074
|
-
permissionPolicy.setYolo?.(payload["yolo"]);
|
|
9075
|
-
}
|
|
9076
|
-
if (typeof payload["featureMcp"] === "boolean")
|
|
9077
|
-
config.features.mcp = payload["featureMcp"];
|
|
9078
|
-
if (typeof payload["featurePlugins"] === "boolean")
|
|
9079
|
-
config.features.plugins = payload["featurePlugins"];
|
|
9080
|
-
if (typeof payload["featureMemory"] === "boolean")
|
|
9081
|
-
config.features.memory = payload["featureMemory"];
|
|
9082
|
-
if (typeof payload["featureSkills"] === "boolean")
|
|
9083
|
-
config.features.skills = payload["featureSkills"];
|
|
9084
|
-
if (typeof payload["featureModelsRegistry"] === "boolean")
|
|
9085
|
-
config.features.modelsRegistry = payload["featureModelsRegistry"];
|
|
9086
|
-
if (Array.isArray(payload["fallbackModels"]))
|
|
9087
|
-
config.fallbackModels = payload["fallbackModels"];
|
|
9088
|
-
if (typeof payload["fallbackAuto"] === "boolean")
|
|
9089
|
-
config.fallbackAuto = payload["fallbackAuto"];
|
|
9090
|
-
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
9091
|
-
if (payload["contextAutoCompact"] && autoCompactor) {
|
|
9092
|
-
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
9093
|
-
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
9094
|
-
} else {
|
|
9095
|
-
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
9096
|
-
}
|
|
9097
|
-
}
|
|
9098
|
-
if (typeof payload["logLevel"] === "string") {
|
|
9099
|
-
const valid = ["debug", "info", "warn", "error"];
|
|
9100
|
-
if (valid.includes(payload["logLevel"])) {
|
|
9101
|
-
logger.level = payload["logLevel"];
|
|
9102
|
-
}
|
|
9103
|
-
}
|
|
9104
|
-
broadcast(clients, { type: "prefs.updated", payload: prefSnapshot() });
|
|
9105
|
-
break;
|
|
9122
|
+
void ws;
|
|
9123
|
+
throw new Error("handlePrefsRoute did not claim prefs.update \u2014 check chain order");
|
|
9106
9124
|
}
|
|
9107
9125
|
case "prefs.get": {
|
|
9108
|
-
|
|
9109
|
-
break;
|
|
9126
|
+
throw new Error("handlePrefsRoute did not claim prefs.get \u2014 check chain order");
|
|
9110
9127
|
}
|
|
9111
9128
|
default:
|
|
9112
9129
|
send(ws, {
|
|
@@ -9327,6 +9344,55 @@ async function startWebUI(opts = {}) {
|
|
|
9327
9344
|
},
|
|
9328
9345
|
sessionStartPayload
|
|
9329
9346
|
});
|
|
9347
|
+
prefsRoutes = {
|
|
9348
|
+
getPrefs: async (ws) => {
|
|
9349
|
+
send(ws, { type: "prefs.updated", payload: prefSnapshot() });
|
|
9350
|
+
},
|
|
9351
|
+
updatePrefs: async (ws, msgPayload) => {
|
|
9352
|
+
const parsed = validatePrefsUpdatePayload(msgPayload);
|
|
9353
|
+
if (!parsed.ok) {
|
|
9354
|
+
sendResult2(ws, false, parsed.message);
|
|
9355
|
+
return;
|
|
9356
|
+
}
|
|
9357
|
+
const payload = parsed.value.prefs;
|
|
9358
|
+
for (const [key, val] of Object.entries(payload)) {
|
|
9359
|
+
context.meta[key] = val;
|
|
9360
|
+
}
|
|
9361
|
+
void persistPrefsToConfig(payload);
|
|
9362
|
+
if (typeof payload["yolo"] === "boolean") {
|
|
9363
|
+
permissionPolicy.setYolo?.(payload["yolo"]);
|
|
9364
|
+
}
|
|
9365
|
+
if (typeof payload["featureMcp"] === "boolean")
|
|
9366
|
+
config.features.mcp = payload["featureMcp"];
|
|
9367
|
+
if (typeof payload["featurePlugins"] === "boolean")
|
|
9368
|
+
config.features.plugins = payload["featurePlugins"];
|
|
9369
|
+
if (typeof payload["featureMemory"] === "boolean")
|
|
9370
|
+
config.features.memory = payload["featureMemory"];
|
|
9371
|
+
if (typeof payload["featureSkills"] === "boolean")
|
|
9372
|
+
config.features.skills = payload["featureSkills"];
|
|
9373
|
+
if (typeof payload["featureModelsRegistry"] === "boolean")
|
|
9374
|
+
config.features.modelsRegistry = payload["featureModelsRegistry"];
|
|
9375
|
+
if (Array.isArray(payload["fallbackModels"]))
|
|
9376
|
+
config.fallbackModels = payload["fallbackModels"];
|
|
9377
|
+
if (typeof payload["fallbackAuto"] === "boolean")
|
|
9378
|
+
config.fallbackAuto = payload["fallbackAuto"];
|
|
9379
|
+
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
9380
|
+
if (payload["contextAutoCompact"] && autoCompactor) {
|
|
9381
|
+
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
9382
|
+
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
9383
|
+
} else {
|
|
9384
|
+
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
9385
|
+
}
|
|
9386
|
+
}
|
|
9387
|
+
if (typeof payload["logLevel"] === "string") {
|
|
9388
|
+
const valid = ["debug", "info", "warn", "error"];
|
|
9389
|
+
if (valid.includes(payload["logLevel"])) {
|
|
9390
|
+
logger.level = payload["logLevel"];
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
broadcast(clients, { type: "prefs.updated", payload: prefSnapshot() });
|
|
9394
|
+
}
|
|
9395
|
+
};
|
|
9330
9396
|
shellGitRoutes = {
|
|
9331
9397
|
gitInfo: async (ws) => {
|
|
9332
9398
|
await handleGitInfo(ws, projectRoot);
|
|
@@ -9379,6 +9445,18 @@ async function startWebUI(opts = {}) {
|
|
|
9379
9445
|
return handleMailboxPurge(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
9380
9446
|
}
|
|
9381
9447
|
};
|
|
9448
|
+
mcpRoutes = {
|
|
9449
|
+
list: (ws, msg) => handleMcpList(ws, msg, globalConfigPath, mcpRegistry),
|
|
9450
|
+
add: (ws, msg) => handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry),
|
|
9451
|
+
update: (ws, msg) => handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry),
|
|
9452
|
+
remove: (ws, msg) => handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry),
|
|
9453
|
+
enable: (ws, msg) => handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry),
|
|
9454
|
+
disable: (ws, msg) => handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry),
|
|
9455
|
+
sleep: (ws, msg) => handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry),
|
|
9456
|
+
wake: (ws, msg) => handleMcpWake(ws, msg, globalConfigPath, mcpRegistry),
|
|
9457
|
+
restart: (ws, msg) => handleMcpRestart(ws, msg, globalConfigPath, mcpRegistry),
|
|
9458
|
+
discover: (ws, msg) => handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry)
|
|
9459
|
+
};
|
|
9382
9460
|
brainRoutes = {
|
|
9383
9461
|
status: (ws) => {
|
|
9384
9462
|
send(ws, {
|