@wrongstack/webui 0.274.0 → 0.275.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/index-BG4jUAmc.js +141 -0
- package/dist/assets/index-Cw32ELnp.css +2 -0
- package/dist/assets/{vendor-CzID01pz.js → vendor-Cl_sFcw4.js} +255 -255
- package/dist/index.html +3 -3
- package/dist/index.js +8251 -5753
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +1134 -169
- package/dist/server/entry.js.map +1 -1
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/index.d.ts +133 -4
- package/dist/server/index.js +1148 -170
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +261 -4
- package/package.json +6 -6
- package/dist/assets/index-CwMm5VgW.js +0 -140
- package/dist/assets/index-DqD59GFW.css +0 -2
package/dist/server/entry.js
CHANGED
|
@@ -171,9 +171,12 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
171
171
|
"reasoningPreserve",
|
|
172
172
|
"hqEnabled",
|
|
173
173
|
"hqRawContent",
|
|
174
|
-
"fallbackAuto"
|
|
174
|
+
"fallbackAuto",
|
|
175
|
+
"favoriteModelsOnly"
|
|
175
176
|
]);
|
|
176
|
-
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels"]);
|
|
177
|
+
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
|
|
178
|
+
var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
|
|
179
|
+
var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
|
|
177
180
|
var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
178
181
|
"autonomyDelayMs",
|
|
179
182
|
"autoProceedMaxIterations",
|
|
@@ -208,6 +211,33 @@ function validatePreferenceValue(key, value) {
|
|
|
208
211
|
if (STRING_ARRAY_PREF_KEYS.has(key)) {
|
|
209
212
|
return Array.isArray(value) && value.every((v) => typeof v === "string") ? null : `prefs.update payload.${key} must be an array of strings`;
|
|
210
213
|
}
|
|
214
|
+
if (STRING_ARRAY_RECORD_PREF_KEYS.has(key)) {
|
|
215
|
+
return isRecord(value) && Object.values(value).every(
|
|
216
|
+
(v) => Array.isArray(v) && v.every((item) => typeof item === "string")
|
|
217
|
+
) ? null : `prefs.update payload.${key} must be an object of string arrays`;
|
|
218
|
+
}
|
|
219
|
+
if (MODEL_MATRIX_PREF_KEYS.has(key)) {
|
|
220
|
+
if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
|
|
221
|
+
for (const entry of Object.values(value)) {
|
|
222
|
+
if (!isRecord(entry)) return `prefs.update payload.${key} entries must be objects`;
|
|
223
|
+
const provider = entry["provider"];
|
|
224
|
+
const model = entry["model"];
|
|
225
|
+
const fallbackProfile = entry["fallbackProfile"];
|
|
226
|
+
if (provider !== void 0 && typeof provider !== "string") {
|
|
227
|
+
return `prefs.update payload.${key}.provider must be a string when provided`;
|
|
228
|
+
}
|
|
229
|
+
if (model !== void 0 && typeof model !== "string") {
|
|
230
|
+
return `prefs.update payload.${key}.model must be a string when provided`;
|
|
231
|
+
}
|
|
232
|
+
if (fallbackProfile !== void 0 && typeof fallbackProfile !== "string") {
|
|
233
|
+
return `prefs.update payload.${key}.fallbackProfile must be a string when provided`;
|
|
234
|
+
}
|
|
235
|
+
if (model === void 0 && fallbackProfile === void 0) {
|
|
236
|
+
return `prefs.update payload.${key} entries require model or fallbackProfile`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
211
241
|
const allowed = ENUM_PREF_KEYS[key];
|
|
212
242
|
if (allowed) {
|
|
213
243
|
return typeof value === "string" && allowed.has(value) ? null : `prefs.update payload.${key} must be one of: ${Array.from(allowed).join(", ")}`;
|
|
@@ -436,8 +466,8 @@ function validateShellOpenPayload(payload) {
|
|
|
436
466
|
if (!isRecord(payload)) {
|
|
437
467
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
438
468
|
}
|
|
439
|
-
const
|
|
440
|
-
if (typeof
|
|
469
|
+
const path18 = payload["path"];
|
|
470
|
+
if (typeof path18 !== "string" || path18.trim().length === 0) {
|
|
441
471
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
442
472
|
}
|
|
443
473
|
const target = payload["target"];
|
|
@@ -447,20 +477,20 @@ function validateShellOpenPayload(payload) {
|
|
|
447
477
|
message: 'shell.open payload.target must be "file" or "terminal" when provided'
|
|
448
478
|
};
|
|
449
479
|
}
|
|
450
|
-
return { ok: true, value: { path:
|
|
480
|
+
return { ok: true, value: { path: path18, target } };
|
|
451
481
|
}
|
|
452
482
|
function validateGitDiffPayload(payload) {
|
|
453
483
|
if (!isRecord(payload)) {
|
|
454
484
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
455
485
|
}
|
|
456
|
-
const
|
|
457
|
-
if (
|
|
486
|
+
const path18 = payload["path"];
|
|
487
|
+
if (path18 === void 0 || path18 === null) {
|
|
458
488
|
return { ok: true, value: { path: "" } };
|
|
459
489
|
}
|
|
460
|
-
if (typeof
|
|
490
|
+
if (typeof path18 !== "string") {
|
|
461
491
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
462
492
|
}
|
|
463
|
-
return { ok: true, value: { path:
|
|
493
|
+
return { ok: true, value: { path: path18 } };
|
|
464
494
|
}
|
|
465
495
|
function validateProjectsAddPayload(payload) {
|
|
466
496
|
if (!isRecord(payload)) {
|
|
@@ -710,8 +740,57 @@ async function handleWorklistMessage(ctx, ws, msg) {
|
|
|
710
740
|
|
|
711
741
|
// src/server/index.ts
|
|
712
742
|
import { makeMailboxTool, makeMailSendTool, makeMailInboxTool, mailboxSessionTag } from "@wrongstack/core";
|
|
713
|
-
import { toErrorMessage as toErrorMessage6, wstackGlobalRoot as
|
|
743
|
+
import { toErrorMessage as toErrorMessage6, wstackGlobalRoot as wstackGlobalRoot3, projectHash } from "@wrongstack/core/utils";
|
|
714
744
|
import { SkillInstaller } from "@wrongstack/core/skills";
|
|
745
|
+
|
|
746
|
+
// src/server/discover-mailbox-bridge.ts
|
|
747
|
+
import { resolveProjectDir, wstackGlobalRoot } from "@wrongstack/core";
|
|
748
|
+
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
749
|
+
async function discoverMailboxBridgeForWebui(params) {
|
|
750
|
+
const mode = params.config?.features?.mailboxBridge ?? "auto";
|
|
751
|
+
if (mode === "off") return;
|
|
752
|
+
const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot());
|
|
753
|
+
const result = await readLiveLock(projectDir);
|
|
754
|
+
switch (result.kind) {
|
|
755
|
+
case "live": {
|
|
756
|
+
params.logger.debug("webui joined existing mailbox bridge", {
|
|
757
|
+
url: result.lock.url,
|
|
758
|
+
lockPath: projectDir
|
|
759
|
+
});
|
|
760
|
+
params.ctx.meta["mailboxBridge"] = {
|
|
761
|
+
url: result.lock.url,
|
|
762
|
+
token: result.lock.token,
|
|
763
|
+
lockPath: projectDir,
|
|
764
|
+
childPid: null,
|
|
765
|
+
source: "joined"
|
|
766
|
+
};
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
case "probe-failed": {
|
|
770
|
+
params.logger.warn(
|
|
771
|
+
"mailbox bridge present but /healthz unreachable; webui will start without external-agent connectivity",
|
|
772
|
+
{ url: result.lock.url, lockPath: projectDir }
|
|
773
|
+
);
|
|
774
|
+
params.ctx.meta["mailboxBridge"] = {
|
|
775
|
+
url: result.lock.url,
|
|
776
|
+
token: result.lock.token,
|
|
777
|
+
lockPath: projectDir,
|
|
778
|
+
childPid: null,
|
|
779
|
+
source: "unhealthy"
|
|
780
|
+
};
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
case "absent": {
|
|
784
|
+
params.logger.info(
|
|
785
|
+
"no mailbox bridge running; webui will start without external-agent connectivity. Run `wstack mailbox serve` or a CLI surface to bring one up.",
|
|
786
|
+
{ projectDir }
|
|
787
|
+
);
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/server/index.ts
|
|
715
794
|
import {
|
|
716
795
|
BrainMonitor,
|
|
717
796
|
DefaultBrainArbiter,
|
|
@@ -719,8 +798,9 @@ import {
|
|
|
719
798
|
createAutonomyBrain,
|
|
720
799
|
createTieredBrainArbiter
|
|
721
800
|
} from "@wrongstack/core";
|
|
722
|
-
import * as
|
|
723
|
-
import * as
|
|
801
|
+
import * as fs14 from "fs/promises";
|
|
802
|
+
import * as path17 from "path";
|
|
803
|
+
import { createRequire as createRequire2 } from "module";
|
|
724
804
|
|
|
725
805
|
// src/server/http-server.ts
|
|
726
806
|
import * as fs from "fs/promises";
|
|
@@ -897,7 +977,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
897
977
|
return;
|
|
898
978
|
}
|
|
899
979
|
try {
|
|
900
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
980
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore3, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
|
|
901
981
|
const registry = new SessionRegistry(globalRoot);
|
|
902
982
|
const entry = await registry.get(sessionId);
|
|
903
983
|
if (!entry) {
|
|
@@ -905,7 +985,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
905
985
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
906
986
|
return;
|
|
907
987
|
}
|
|
908
|
-
const paths =
|
|
988
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
909
989
|
const store = new DefaultSessionStore3({ dir: paths.projectSessions });
|
|
910
990
|
const reader = new DefaultSessionReader2({ store });
|
|
911
991
|
const rawEntries = [];
|
|
@@ -932,7 +1012,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
932
1012
|
}
|
|
933
1013
|
}
|
|
934
1014
|
function readJsonBody(req) {
|
|
935
|
-
return new Promise((
|
|
1015
|
+
return new Promise((resolve10, reject) => {
|
|
936
1016
|
let data = "";
|
|
937
1017
|
req.on("data", (chunk) => {
|
|
938
1018
|
data += chunk;
|
|
@@ -943,7 +1023,7 @@ function readJsonBody(req) {
|
|
|
943
1023
|
});
|
|
944
1024
|
req.on("end", () => {
|
|
945
1025
|
try {
|
|
946
|
-
|
|
1026
|
+
resolve10(data ? JSON.parse(data) : {});
|
|
947
1027
|
} catch (err) {
|
|
948
1028
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
949
1029
|
}
|
|
@@ -979,7 +1059,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
979
1059
|
const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
|
|
980
1060
|
const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
|
|
981
1061
|
try {
|
|
982
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
1062
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox3, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
983
1063
|
const registry = new SessionRegistry(globalRoot);
|
|
984
1064
|
const entry = await registry.get(sessionId);
|
|
985
1065
|
if (!entry) {
|
|
@@ -987,7 +1067,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
987
1067
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
988
1068
|
return;
|
|
989
1069
|
}
|
|
990
|
-
const paths =
|
|
1070
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
991
1071
|
const mailbox = new GlobalMailbox3(paths.projectDir);
|
|
992
1072
|
const to = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
993
1073
|
const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
|
|
@@ -1005,7 +1085,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
1005
1085
|
return;
|
|
1006
1086
|
}
|
|
1007
1087
|
try {
|
|
1008
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
1088
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox3, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
1009
1089
|
const registry = new SessionRegistry(globalRoot);
|
|
1010
1090
|
const entry = await registry.get(sessionId);
|
|
1011
1091
|
if (!entry) {
|
|
@@ -1013,7 +1093,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
1013
1093
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1014
1094
|
return;
|
|
1015
1095
|
}
|
|
1016
|
-
const paths =
|
|
1096
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
1017
1097
|
const mailbox = new GlobalMailbox3(paths.projectDir);
|
|
1018
1098
|
const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
1019
1099
|
const [inbound, outbound] = await Promise.all([
|
|
@@ -1063,7 +1143,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
1063
1143
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
1064
1144
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
1065
1145
|
try {
|
|
1066
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
1146
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox3, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
1067
1147
|
const registry = new SessionRegistry(globalRoot);
|
|
1068
1148
|
const entry = await registry.get(sessionId);
|
|
1069
1149
|
if (!entry) {
|
|
@@ -1071,7 +1151,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
1071
1151
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1072
1152
|
return;
|
|
1073
1153
|
}
|
|
1074
|
-
const paths =
|
|
1154
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
1075
1155
|
const mailbox = new GlobalMailbox3(paths.projectDir);
|
|
1076
1156
|
const to = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
1077
1157
|
const sent = await mailbox.send({
|
|
@@ -1111,7 +1191,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
1111
1191
|
}
|
|
1112
1192
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
1113
1193
|
try {
|
|
1114
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
1194
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox3, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
1115
1195
|
const registry = new SessionRegistry(globalRoot);
|
|
1116
1196
|
const all = await registry.list();
|
|
1117
1197
|
const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
|
|
@@ -1123,7 +1203,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
1123
1203
|
}
|
|
1124
1204
|
const mbByDir = /* @__PURE__ */ new Map();
|
|
1125
1205
|
const mailboxFor = (projectRoot) => {
|
|
1126
|
-
const dir =
|
|
1206
|
+
const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
|
|
1127
1207
|
let mb = mbByDir.get(dir);
|
|
1128
1208
|
if (!mb) {
|
|
1129
1209
|
mb = new GlobalMailbox3(dir);
|
|
@@ -1334,7 +1414,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
1334
1414
|
`ws://127.0.0.1:${wsPort}`,
|
|
1335
1415
|
`wss://127.0.0.1:${wsPort}`
|
|
1336
1416
|
]);
|
|
1337
|
-
if (requestHost && requestHost !== "127.0.0.1") {
|
|
1417
|
+
if (requestHost && requestHost !== "127.0.0.1" && requestHost !== "::1" && requestHost !== "[::1]") {
|
|
1338
1418
|
const host = formatCspHostname(requestHost);
|
|
1339
1419
|
connect.add(`ws://${host}:${wsPort}`);
|
|
1340
1420
|
connect.add(`wss://${host}:${wsPort}`);
|
|
@@ -1859,6 +1939,16 @@ async function realpathAllowMissing(p) {
|
|
|
1859
1939
|
}
|
|
1860
1940
|
}
|
|
1861
1941
|
}
|
|
1942
|
+
function validatedPayload(msg, label) {
|
|
1943
|
+
if (msg == null || typeof msg !== "object") {
|
|
1944
|
+
throw new TypeError(`Expected object for ${label}, got ${msg}`);
|
|
1945
|
+
}
|
|
1946
|
+
const payload = msg.payload;
|
|
1947
|
+
if (payload == null || typeof payload !== "object") {
|
|
1948
|
+
throw new TypeError(`Expected payload object for ${label}, got ${payload}`);
|
|
1949
|
+
}
|
|
1950
|
+
return payload;
|
|
1951
|
+
}
|
|
1862
1952
|
async function handleFilesTree(ws, msg, projectRoot) {
|
|
1863
1953
|
const payload = msg.payload;
|
|
1864
1954
|
const rawPath = payload?.path?.trim();
|
|
@@ -1929,7 +2019,13 @@ async function handleFilesTree(ws, msg, projectRoot) {
|
|
|
1929
2019
|
}
|
|
1930
2020
|
}
|
|
1931
2021
|
async function handleFilesRead(ws, msg, projectRoot) {
|
|
1932
|
-
|
|
2022
|
+
let filePath;
|
|
2023
|
+
try {
|
|
2024
|
+
({ filePath } = validatedPayload(msg, "files.read"));
|
|
2025
|
+
} catch {
|
|
2026
|
+
send(ws, { type: "files.read", payload: { filePath: "", content: "", error: "Malformed request" } });
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
1933
2029
|
let realResolved;
|
|
1934
2030
|
try {
|
|
1935
2031
|
realResolved = await resolveFileInsideProject(projectRoot, filePath);
|
|
@@ -1948,7 +2044,14 @@ async function handleFilesRead(ws, msg, projectRoot) {
|
|
|
1948
2044
|
}
|
|
1949
2045
|
}
|
|
1950
2046
|
async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
|
|
1951
|
-
|
|
2047
|
+
let filePath;
|
|
2048
|
+
let content;
|
|
2049
|
+
try {
|
|
2050
|
+
({ filePath, content } = validatedPayload(msg, "files.write"));
|
|
2051
|
+
} catch {
|
|
2052
|
+
send(ws, { type: "files.written", payload: { filePath: "", success: false, error: "Malformed request" } });
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
1952
2055
|
let realResolved;
|
|
1953
2056
|
try {
|
|
1954
2057
|
realResolved = await resolveFileInsideProject(projectRoot, filePath);
|
|
@@ -2786,7 +2889,7 @@ import { promises as fs5 } from "fs";
|
|
|
2786
2889
|
import path6 from "path";
|
|
2787
2890
|
import JSZip from "jszip";
|
|
2788
2891
|
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
2789
|
-
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
2892
|
+
import { wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
2790
2893
|
async function handleSkillsList(ws, ctx) {
|
|
2791
2894
|
if (!ctx.skillLoader) {
|
|
2792
2895
|
send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
|
|
@@ -2956,7 +3059,7 @@ async function handleSkillsCreate(ws, ctx, msg) {
|
|
|
2956
3059
|
}
|
|
2957
3060
|
const createPayload = parsed.value;
|
|
2958
3061
|
try {
|
|
2959
|
-
const targetDir = createPayload.scope === "global" ? path6.join(
|
|
3062
|
+
const targetDir = createPayload.scope === "global" ? path6.join(wstackGlobalRoot2(), "skills", createPayload.name.trim()) : path6.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
|
|
2960
3063
|
try {
|
|
2961
3064
|
await fs5.access(targetDir);
|
|
2962
3065
|
send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
|
|
@@ -3076,6 +3179,416 @@ async function handleSkillsExport(ws, ctx) {
|
|
|
3076
3179
|
}
|
|
3077
3180
|
}
|
|
3078
3181
|
|
|
3182
|
+
// src/server/prompts-handlers.ts
|
|
3183
|
+
function parseVariablesPayload(raw) {
|
|
3184
|
+
if (!Array.isArray(raw)) return void 0;
|
|
3185
|
+
const out = [];
|
|
3186
|
+
for (const item of raw) {
|
|
3187
|
+
if (!item || typeof item !== "object") continue;
|
|
3188
|
+
const o = item;
|
|
3189
|
+
if (typeof o["name"] !== "string" || !o["name"].trim()) continue;
|
|
3190
|
+
const enumVals = Array.isArray(o["enum"]) && o["enum"].every((x) => typeof x === "string") ? o["enum"].map((s) => s.trim()).filter(Boolean) : void 0;
|
|
3191
|
+
const v = { name: o["name"].trim() };
|
|
3192
|
+
if (typeof o["description"] === "string" && o["description"].trim()) {
|
|
3193
|
+
v.description = o["description"].trim();
|
|
3194
|
+
}
|
|
3195
|
+
if (o["required"] === true) v.required = true;
|
|
3196
|
+
if (o["multiline"] === true) v.multiline = true;
|
|
3197
|
+
if (enumVals && enumVals.length > 0) v.enum = enumVals;
|
|
3198
|
+
out.push(v);
|
|
3199
|
+
}
|
|
3200
|
+
return out.length > 0 ? out : void 0;
|
|
3201
|
+
}
|
|
3202
|
+
function toMeta(e) {
|
|
3203
|
+
return {
|
|
3204
|
+
id: e.id,
|
|
3205
|
+
slug: e.slug,
|
|
3206
|
+
title: e.title,
|
|
3207
|
+
description: e.description,
|
|
3208
|
+
category: e.category,
|
|
3209
|
+
tags: e.tags,
|
|
3210
|
+
source: e.source,
|
|
3211
|
+
favorite: e.favorite,
|
|
3212
|
+
variables: e.variables ?? []
|
|
3213
|
+
};
|
|
3214
|
+
}
|
|
3215
|
+
async function handlePromptsList(ws, ctx) {
|
|
3216
|
+
if (!ctx.promptLoader) {
|
|
3217
|
+
send(ws, { type: "prompts.list", payload: { enabled: false, prompts: [], categories: [] } });
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
try {
|
|
3221
|
+
const [all, categories] = await Promise.all([
|
|
3222
|
+
ctx.promptLoader.list(),
|
|
3223
|
+
ctx.promptLoader.categories()
|
|
3224
|
+
]);
|
|
3225
|
+
send(ws, {
|
|
3226
|
+
type: "prompts.list",
|
|
3227
|
+
payload: { enabled: true, prompts: all.map(toMeta), categories }
|
|
3228
|
+
});
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
send(ws, {
|
|
3231
|
+
type: "prompts.list",
|
|
3232
|
+
payload: { enabled: true, prompts: [], categories: [], error: errMessage(err) }
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
async function handlePromptsSearch(ws, ctx, msg) {
|
|
3237
|
+
if (!ctx.promptLoader) {
|
|
3238
|
+
send(ws, { type: "prompts.search", payload: { enabled: false, prompts: [] } });
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
const payload = msg.payload ?? {};
|
|
3242
|
+
try {
|
|
3243
|
+
const results = await ctx.promptLoader.search(payload.query ?? "", {
|
|
3244
|
+
...payload.category ? { category: payload.category } : {},
|
|
3245
|
+
limit: 50
|
|
3246
|
+
});
|
|
3247
|
+
send(ws, { type: "prompts.search", payload: { enabled: true, prompts: results.map(toMeta) } });
|
|
3248
|
+
} catch (err) {
|
|
3249
|
+
send(ws, {
|
|
3250
|
+
type: "prompts.search",
|
|
3251
|
+
payload: { enabled: true, prompts: [], error: errMessage(err) }
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
async function handlePromptsContent(ws, ctx, msg) {
|
|
3256
|
+
const slug = msg.payload?.slug;
|
|
3257
|
+
if (!ctx.promptLoader || !slug) {
|
|
3258
|
+
send(ws, {
|
|
3259
|
+
type: "prompts.content",
|
|
3260
|
+
payload: { slug: slug ?? "", found: false, content: "", variables: [] }
|
|
3261
|
+
});
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
try {
|
|
3265
|
+
const entry = await ctx.promptLoader.find(slug);
|
|
3266
|
+
if (!entry) {
|
|
3267
|
+
send(ws, {
|
|
3268
|
+
type: "prompts.content",
|
|
3269
|
+
payload: { slug, found: false, content: "", variables: [] }
|
|
3270
|
+
});
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
send(ws, {
|
|
3274
|
+
type: "prompts.content",
|
|
3275
|
+
payload: {
|
|
3276
|
+
slug: entry.slug,
|
|
3277
|
+
found: true,
|
|
3278
|
+
title: entry.title,
|
|
3279
|
+
content: entry.content,
|
|
3280
|
+
variables: entry.variables ?? [],
|
|
3281
|
+
category: entry.category,
|
|
3282
|
+
source: entry.source
|
|
3283
|
+
}
|
|
3284
|
+
});
|
|
3285
|
+
} catch (err) {
|
|
3286
|
+
send(ws, {
|
|
3287
|
+
type: "prompts.content",
|
|
3288
|
+
payload: { slug, found: false, content: "", variables: [], error: errMessage(err) }
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
async function handlePromptsFavorite(ws, ctx, msg) {
|
|
3293
|
+
const payload = msg.payload;
|
|
3294
|
+
if (!ctx.promptLoader || !payload?.slug) {
|
|
3295
|
+
send(ws, {
|
|
3296
|
+
type: "prompts.favorite",
|
|
3297
|
+
payload: { success: false, error: "Prompt library unavailable" }
|
|
3298
|
+
});
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
try {
|
|
3302
|
+
const updated = await ctx.promptLoader.setFavorite(payload.slug, payload.favorite !== false);
|
|
3303
|
+
if (!updated) {
|
|
3304
|
+
send(ws, {
|
|
3305
|
+
type: "prompts.favorite",
|
|
3306
|
+
payload: { success: false, error: "Prompt not found" }
|
|
3307
|
+
});
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
send(ws, {
|
|
3311
|
+
type: "prompts.favorite",
|
|
3312
|
+
payload: { success: true, slug: updated.slug, favorite: updated.favorite }
|
|
3313
|
+
});
|
|
3314
|
+
} catch (err) {
|
|
3315
|
+
send(ws, { type: "prompts.favorite", payload: { success: false, error: errMessage(err) } });
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
async function handlePromptsCreate(ws, ctx, msg) {
|
|
3319
|
+
const p = msg.payload;
|
|
3320
|
+
if (!ctx.promptLoader || !p) {
|
|
3321
|
+
send(ws, {
|
|
3322
|
+
type: "prompts.created",
|
|
3323
|
+
payload: { success: false, error: "Prompt library unavailable" }
|
|
3324
|
+
});
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const title = typeof p["title"] === "string" ? p["title"].trim() : "";
|
|
3328
|
+
const content = typeof p["content"] === "string" ? p["content"] : "";
|
|
3329
|
+
if (!title || !content) {
|
|
3330
|
+
send(ws, {
|
|
3331
|
+
type: "prompts.created",
|
|
3332
|
+
payload: { success: false, error: "Title and content are required" }
|
|
3333
|
+
});
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
try {
|
|
3337
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3338
|
+
const tags = Array.isArray(p["tags"]) ? p["tags"].filter((t) => typeof t === "string") : [];
|
|
3339
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "prompt";
|
|
3340
|
+
const variables = parseVariablesPayload(p["variables"]);
|
|
3341
|
+
const entry = {
|
|
3342
|
+
id: slug,
|
|
3343
|
+
slug,
|
|
3344
|
+
title,
|
|
3345
|
+
description: typeof p["description"] === "string" ? p["description"] : "",
|
|
3346
|
+
content,
|
|
3347
|
+
category: typeof p["category"] === "string" && p["category"] ? p["category"] : "uncategorized",
|
|
3348
|
+
tags,
|
|
3349
|
+
source: "user",
|
|
3350
|
+
favorite: false,
|
|
3351
|
+
...variables ? { variables } : {},
|
|
3352
|
+
createdAt: now,
|
|
3353
|
+
updatedAt: now
|
|
3354
|
+
};
|
|
3355
|
+
await ctx.promptLoader.save(entry);
|
|
3356
|
+
send(ws, { type: "prompts.created", payload: { success: true, slug } });
|
|
3357
|
+
} catch (err) {
|
|
3358
|
+
send(ws, { type: "prompts.created", payload: { success: false, error: errMessage(err) } });
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
async function handlePromptsUsed(ws, ctx, msg) {
|
|
3362
|
+
const slug = msg.payload?.slug;
|
|
3363
|
+
if (!ctx.promptUsage || !slug) {
|
|
3364
|
+
send(ws, { type: "prompts.used", payload: { success: false } });
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
try {
|
|
3368
|
+
await ctx.promptUsage.record(slug);
|
|
3369
|
+
send(ws, { type: "prompts.used", payload: { success: true, slug } });
|
|
3370
|
+
} catch {
|
|
3371
|
+
send(ws, { type: "prompts.used", payload: { success: false } });
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
async function handlePromptsRecent(ws, ctx) {
|
|
3375
|
+
if (!ctx.promptUsage) {
|
|
3376
|
+
send(ws, { type: "prompts.recent", payload: { slugs: [] } });
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
try {
|
|
3380
|
+
const recent = await ctx.promptUsage.recent(50);
|
|
3381
|
+
send(ws, { type: "prompts.recent", payload: { slugs: recent.map((r) => r.slug) } });
|
|
3382
|
+
} catch (err) {
|
|
3383
|
+
send(ws, { type: "prompts.recent", payload: { slugs: [], error: errMessage(err) } });
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// src/server/design-handlers.ts
|
|
3388
|
+
import * as fs6 from "fs/promises";
|
|
3389
|
+
import * as path7 from "path";
|
|
3390
|
+
import {
|
|
3391
|
+
applyTokenOverrides,
|
|
3392
|
+
getDesignKitLoader,
|
|
3393
|
+
getDesignState,
|
|
3394
|
+
isDesignStack,
|
|
3395
|
+
loadActiveKit,
|
|
3396
|
+
materializeTokens,
|
|
3397
|
+
recordKitChoice,
|
|
3398
|
+
recordOverrides,
|
|
3399
|
+
runDesignVerify,
|
|
3400
|
+
setActiveKit,
|
|
3401
|
+
setDesignOverrides
|
|
3402
|
+
} from "@wrongstack/core";
|
|
3403
|
+
function readOverrides(value) {
|
|
3404
|
+
const out = {};
|
|
3405
|
+
if (value && typeof value === "object") {
|
|
3406
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3407
|
+
if (typeof v === "string") out[k] = v;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return out;
|
|
3411
|
+
}
|
|
3412
|
+
var FOUNDATIONS_ID = "_foundations";
|
|
3413
|
+
async function buildListPayload(ctx) {
|
|
3414
|
+
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
3415
|
+
const manifests = (await loader.list()).filter((k) => k.id !== FOUNDATIONS_ID);
|
|
3416
|
+
const kits = [];
|
|
3417
|
+
for (const m of manifests) {
|
|
3418
|
+
const tokens = await loader.readTokens(m.id);
|
|
3419
|
+
kits.push({
|
|
3420
|
+
id: m.id,
|
|
3421
|
+
name: m.name,
|
|
3422
|
+
aesthetic: m.aesthetic,
|
|
3423
|
+
bestFor: m.bestFor,
|
|
3424
|
+
stacks: m.stacks,
|
|
3425
|
+
tags: m.tags,
|
|
3426
|
+
light: tokens?.light ?? {},
|
|
3427
|
+
dark: tokens?.dark ?? {}
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
const state = ctx.agentMeta ? getDesignState(ctx.agentMeta) : void 0;
|
|
3431
|
+
const persisted = await loadActiveKit(ctx.projectRoot).catch(() => void 0);
|
|
3432
|
+
return {
|
|
3433
|
+
kits,
|
|
3434
|
+
activeKit: state?.activeKit ?? persisted?.kit ?? null,
|
|
3435
|
+
stack: state?.stack ?? persisted?.stack ?? null,
|
|
3436
|
+
overrides: state?.overrides ?? persisted?.overrides ?? {}
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
async function handleDesignList(ws, ctx) {
|
|
3440
|
+
try {
|
|
3441
|
+
send(ws, { type: "design.list", payload: await buildListPayload(ctx) });
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
send(ws, {
|
|
3444
|
+
type: "design.list",
|
|
3445
|
+
payload: { kits: [], activeKit: null, stack: null, error: String(err) }
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
async function handleDesignState(ws, ctx) {
|
|
3450
|
+
const state = ctx.agentMeta ? getDesignState(ctx.agentMeta) : void 0;
|
|
3451
|
+
send(ws, {
|
|
3452
|
+
type: "design.state",
|
|
3453
|
+
payload: {
|
|
3454
|
+
activeKit: state?.activeKit ?? null,
|
|
3455
|
+
stack: state?.stack ?? null,
|
|
3456
|
+
overrides: state?.overrides ?? {}
|
|
3457
|
+
}
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
async function handleDesignUse(ws, ctx, msg) {
|
|
3461
|
+
const payload = msg.payload ?? {};
|
|
3462
|
+
const kitId = typeof payload.kit === "string" ? payload.kit.trim() : "";
|
|
3463
|
+
if (!kitId) {
|
|
3464
|
+
send(ws, { type: "design.use", payload: { ok: false, error: "No kit id provided" } });
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
try {
|
|
3468
|
+
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
3469
|
+
const kit = await loader.find(kitId);
|
|
3470
|
+
if (!kit) {
|
|
3471
|
+
send(ws, { type: "design.use", payload: { ok: false, kit: kitId, error: "Kit not found" } });
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
const stackArg = typeof payload.stack === "string" ? payload.stack : void 0;
|
|
3475
|
+
const stack = stackArg && isDesignStack(stackArg) ? stackArg : kit.stacks[0] ?? "web";
|
|
3476
|
+
const persisted = await loadActiveKit(ctx.projectRoot).catch(() => void 0);
|
|
3477
|
+
const keep = persisted?.kit === kit.id ? persisted.overrides ?? {} : {};
|
|
3478
|
+
const overrides = { ...keep, ...readOverrides(payload.overrides) };
|
|
3479
|
+
if (ctx.agentMeta) setActiveKit(ctx.agentMeta, kit.id, stack, overrides);
|
|
3480
|
+
await recordKitChoice(
|
|
3481
|
+
ctx.projectRoot,
|
|
3482
|
+
kit.id,
|
|
3483
|
+
stack,
|
|
3484
|
+
"webui",
|
|
3485
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
3486
|
+
Object.keys(overrides).length ? overrides : void 0
|
|
3487
|
+
);
|
|
3488
|
+
const body = await loader.readBody(kit.id, stack);
|
|
3489
|
+
const rawTokens = await loader.readTokens(kit.id);
|
|
3490
|
+
const tokens = rawTokens ? applyTokenOverrides(rawTokens, overrides) : rawTokens;
|
|
3491
|
+
send(ws, {
|
|
3492
|
+
type: "design.use",
|
|
3493
|
+
payload: {
|
|
3494
|
+
ok: true,
|
|
3495
|
+
kit: kit.id,
|
|
3496
|
+
name: kit.name,
|
|
3497
|
+
aesthetic: kit.aesthetic,
|
|
3498
|
+
stack,
|
|
3499
|
+
body,
|
|
3500
|
+
overrides,
|
|
3501
|
+
light: tokens?.light ?? {},
|
|
3502
|
+
dark: tokens?.dark ?? {}
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
} catch (err) {
|
|
3506
|
+
send(ws, { type: "design.use", payload: { ok: false, kit: kitId, error: String(err) } });
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
async function handleDesignSet(ws, ctx, msg) {
|
|
3510
|
+
const patch = readOverrides(msg.payload?.overrides);
|
|
3511
|
+
if (Object.keys(patch).length === 0) {
|
|
3512
|
+
send(ws, { type: "design.set", payload: { ok: false, error: "No overrides provided" } });
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
try {
|
|
3516
|
+
const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
|
|
3517
|
+
if (!merged) {
|
|
3518
|
+
send(ws, { type: "design.set", payload: { ok: false, error: "No active kit" } });
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
if (ctx.agentMeta) setDesignOverrides(ctx.agentMeta, merged);
|
|
3522
|
+
send(ws, { type: "design.set", payload: { ok: true, overrides: merged } });
|
|
3523
|
+
} catch (err) {
|
|
3524
|
+
send(ws, { type: "design.set", payload: { ok: false, error: String(err) } });
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
async function handleDesignMaterialize(ws, ctx, msg) {
|
|
3528
|
+
const payload = msg.payload ?? {};
|
|
3529
|
+
try {
|
|
3530
|
+
const active = await loadActiveKit(ctx.projectRoot);
|
|
3531
|
+
if (!active) {
|
|
3532
|
+
send(ws, { type: "design.materialize", payload: { ok: false, error: "No active kit" } });
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
3536
|
+
const stackArg = typeof payload.stack === "string" ? payload.stack : void 0;
|
|
3537
|
+
const stack = stackArg && isDesignStack(stackArg) ? stackArg : active.stack && isDesignStack(active.stack) ? active.stack : "web";
|
|
3538
|
+
const raw = await loader.readTokens(active.kit);
|
|
3539
|
+
if (!raw) {
|
|
3540
|
+
send(ws, { type: "design.materialize", payload: { ok: false, error: "Kit has no tokens" } });
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
const tokens = applyTokenOverrides(raw, active.overrides);
|
|
3544
|
+
const result = materializeTokens({
|
|
3545
|
+
tokens,
|
|
3546
|
+
stack,
|
|
3547
|
+
kitId: active.kit,
|
|
3548
|
+
outPath: typeof payload.out === "string" ? payload.out : void 0
|
|
3549
|
+
});
|
|
3550
|
+
const abs = path7.join(ctx.projectRoot, result.path);
|
|
3551
|
+
await fs6.mkdir(path7.dirname(abs), { recursive: true });
|
|
3552
|
+
await fs6.writeFile(abs, result.content);
|
|
3553
|
+
send(ws, {
|
|
3554
|
+
type: "design.materialize",
|
|
3555
|
+
payload: { ok: true, path: result.path, format: result.format, stack }
|
|
3556
|
+
});
|
|
3557
|
+
} catch (err) {
|
|
3558
|
+
send(ws, { type: "design.materialize", payload: { ok: false, error: String(err) } });
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
async function handleDesignVerify(ws, ctx) {
|
|
3562
|
+
try {
|
|
3563
|
+
const active = await loadActiveKit(ctx.projectRoot);
|
|
3564
|
+
if (!active) {
|
|
3565
|
+
send(ws, { type: "design.verify", payload: { ok: false, error: "No active kit" } });
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
3569
|
+
const raw = await loader.readTokens(active.kit);
|
|
3570
|
+
if (!raw) {
|
|
3571
|
+
send(ws, { type: "design.verify", payload: { ok: false, error: "Kit has no tokens" } });
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
const tokens = applyTokenOverrides(raw, active.overrides);
|
|
3575
|
+
const report = await runDesignVerify(ctx.projectRoot, tokens);
|
|
3576
|
+
send(ws, {
|
|
3577
|
+
type: "design.verify",
|
|
3578
|
+
payload: {
|
|
3579
|
+
ok: true,
|
|
3580
|
+
kit: active.kit,
|
|
3581
|
+
filesScanned: report.filesScanned,
|
|
3582
|
+
score: report.score,
|
|
3583
|
+
violations: report.violations.slice(0, 50),
|
|
3584
|
+
violationCount: report.violations.length
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
} catch (err) {
|
|
3588
|
+
send(ws, { type: "design.verify", payload: { ok: false, error: String(err) } });
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3079
3592
|
// src/server/index.ts
|
|
3080
3593
|
import {
|
|
3081
3594
|
Agent,
|
|
@@ -3087,6 +3600,8 @@ import {
|
|
|
3087
3600
|
DefaultSessionReader,
|
|
3088
3601
|
DefaultSessionStore as DefaultSessionStore2,
|
|
3089
3602
|
DefaultSkillLoader,
|
|
3603
|
+
DefaultPromptLoader,
|
|
3604
|
+
PromptUsageStore,
|
|
3090
3605
|
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
|
|
3091
3606
|
DefaultTokenCounter,
|
|
3092
3607
|
AnnotationsStore,
|
|
@@ -3101,17 +3616,20 @@ import {
|
|
|
3101
3616
|
ToolRegistry,
|
|
3102
3617
|
atomicWrite as atomicWrite6,
|
|
3103
3618
|
createDefaultPipelines,
|
|
3619
|
+
installDesignStudioMiddleware,
|
|
3104
3620
|
createSessionEventBridge,
|
|
3105
3621
|
resolveSessionLoggingConfig,
|
|
3106
3622
|
DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
|
|
3107
3623
|
DEFAULT_SESSION_PRUNE_DAYS,
|
|
3108
3624
|
DEFAULT_TOOLS_CONFIG,
|
|
3109
3625
|
applyToolDescriptionModes,
|
|
3626
|
+
applyToolResultRenderModes,
|
|
3110
3627
|
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
3111
3628
|
enhanceUserPrompt,
|
|
3112
3629
|
gatedEnhancerReasoning,
|
|
3113
3630
|
recentTextTurns,
|
|
3114
|
-
resolveProviderModelList
|
|
3631
|
+
resolveProviderModelList,
|
|
3632
|
+
cleanupStaleSddWorktrees as cleanupStaleSddWorktrees3
|
|
3115
3633
|
} from "@wrongstack/core";
|
|
3116
3634
|
import { ToolExecutor } from "@wrongstack/core/execution";
|
|
3117
3635
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
@@ -3794,7 +4312,7 @@ var SpecsWebSocketHandler = class {
|
|
|
3794
4312
|
};
|
|
3795
4313
|
|
|
3796
4314
|
// src/server/sdd-board-ws-handler.ts
|
|
3797
|
-
import { SddBoardStore } from "@wrongstack/core";
|
|
4315
|
+
import { applySddLifecycle, SddBoardStore } from "@wrongstack/core";
|
|
3798
4316
|
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
3799
4317
|
"pause",
|
|
3800
4318
|
"resume",
|
|
@@ -3808,19 +4326,19 @@ var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
|
3808
4326
|
"set_task_verification",
|
|
3809
4327
|
"cancel_task",
|
|
3810
4328
|
"delete_task",
|
|
3811
|
-
"split_task"
|
|
3812
|
-
// Lifecycle (pair with a prior `stop`): sweep worktrees / revert merged commits.
|
|
3813
|
-
"cleanup_worktrees",
|
|
3814
|
-
"rollback"
|
|
4329
|
+
"split_task"
|
|
3815
4330
|
]);
|
|
4331
|
+
var LIFECYCLE_TYPES = /* @__PURE__ */ new Set(["cleanup_worktrees", "rollback", "destroy"]);
|
|
3816
4332
|
var SddBoardWebSocketHandler = class {
|
|
3817
4333
|
store;
|
|
3818
4334
|
clients = /* @__PURE__ */ new Set();
|
|
4335
|
+
lifecycle;
|
|
3819
4336
|
latest = null;
|
|
3820
4337
|
poll = null;
|
|
3821
4338
|
unsub = null;
|
|
3822
|
-
constructor(boardsDir, events) {
|
|
4339
|
+
constructor(boardsDir, events, lifecycle) {
|
|
3823
4340
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
4341
|
+
this.lifecycle = lifecycle;
|
|
3824
4342
|
if (events) {
|
|
3825
4343
|
const handler = (e) => {
|
|
3826
4344
|
this.latest = e.snapshot;
|
|
@@ -3849,6 +4367,10 @@ var SddBoardWebSocketHandler = class {
|
|
|
3849
4367
|
return;
|
|
3850
4368
|
}
|
|
3851
4369
|
const action = msg.type.replace(/^sdd\.board\./, "");
|
|
4370
|
+
if (LIFECYCLE_TYPES.has(action)) {
|
|
4371
|
+
await this.applyLifecycle(action, msg.payload);
|
|
4372
|
+
return;
|
|
4373
|
+
}
|
|
3852
4374
|
if (CONTROL_TYPES.has(action)) {
|
|
3853
4375
|
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
3854
4376
|
if (runId) {
|
|
@@ -3860,6 +4382,40 @@ var SddBoardWebSocketHandler = class {
|
|
|
3860
4382
|
}
|
|
3861
4383
|
}
|
|
3862
4384
|
}
|
|
4385
|
+
/**
|
|
4386
|
+
* Apply a cleanup/rollback/destroy from disk and broadcast a structured
|
|
4387
|
+
* `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
|
|
4388
|
+
* the user must stop it first; the UI gates the buttons on `!active` and the
|
|
4389
|
+
* Destroy flow auto-stops then waits before sending `destroy`.
|
|
4390
|
+
*/
|
|
4391
|
+
async applyLifecycle(op, payload) {
|
|
4392
|
+
if (!this.lifecycle) {
|
|
4393
|
+
this.broadcast({
|
|
4394
|
+
type: "sdd.board.lifecycle_result",
|
|
4395
|
+
payload: { op, ok: false, reason: "Lifecycle operations are not available in this session." }
|
|
4396
|
+
});
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4399
|
+
if (this.latest && (this.latest.status === "running" || this.latest.status === "paused")) {
|
|
4400
|
+
this.broadcast({
|
|
4401
|
+
type: "sdd.board.lifecycle_result",
|
|
4402
|
+
payload: { op, ok: false, reason: "Stop the run first, then retry." }
|
|
4403
|
+
});
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const runId = payload?.runId ?? this.latest?.runId;
|
|
4407
|
+
const result = await applySddLifecycle(op, {
|
|
4408
|
+
projectRoot: this.lifecycle.projectRoot,
|
|
4409
|
+
paths: this.lifecycle.paths,
|
|
4410
|
+
runId,
|
|
4411
|
+
revertMerged: payload?.revertMerged === true
|
|
4412
|
+
});
|
|
4413
|
+
this.broadcast({ type: "sdd.board.lifecycle_result", payload: result });
|
|
4414
|
+
if (op === "destroy" && result.ok) {
|
|
4415
|
+
this.latest = null;
|
|
4416
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: null });
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
3863
4419
|
dispose() {
|
|
3864
4420
|
if (this.poll) clearInterval(this.poll);
|
|
3865
4421
|
this.unsub?.();
|
|
@@ -4039,9 +4595,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
4039
4595
|
};
|
|
4040
4596
|
|
|
4041
4597
|
// src/server/sdd-wizard-wiring.ts
|
|
4042
|
-
import * as
|
|
4598
|
+
import * as path8 from "path";
|
|
4043
4599
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4044
4600
|
import {
|
|
4601
|
+
cleanupStaleSddWorktrees,
|
|
4045
4602
|
makeCommandVerifier,
|
|
4046
4603
|
makeLlmSubtaskGenerator,
|
|
4047
4604
|
SddBoardStore as SddBoardStore2,
|
|
@@ -4076,7 +4633,7 @@ function buildSddWizardDeps(opts) {
|
|
|
4076
4633
|
makeDriver: () => new SddInterviewDriver({
|
|
4077
4634
|
specStore: new SpecStore2({ baseDir: opts.paths.projectSpecs }),
|
|
4078
4635
|
graphStore: new TaskGraphStore2({ baseDir: opts.paths.projectTaskGraphs }),
|
|
4079
|
-
sessionPath:
|
|
4636
|
+
sessionPath: path8.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
4080
4637
|
}),
|
|
4081
4638
|
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
4082
4639
|
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
|
|
@@ -4093,7 +4650,13 @@ function buildSddWizardDeps(opts) {
|
|
|
4093
4650
|
encoding: "utf8",
|
|
4094
4651
|
windowsHide: true
|
|
4095
4652
|
}).stdout?.trim() === "true";
|
|
4096
|
-
if (inGit)
|
|
4653
|
+
if (inGit) {
|
|
4654
|
+
await cleanupStaleSddWorktrees({
|
|
4655
|
+
projectRoot: opts.projectRoot,
|
|
4656
|
+
boardsDir: opts.paths.projectSddBoards
|
|
4657
|
+
}).catch(() => void 0);
|
|
4658
|
+
worktrees = new WorktreeManager2({ projectRoot: opts.projectRoot, events: opts.events });
|
|
4659
|
+
}
|
|
4097
4660
|
}
|
|
4098
4661
|
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
4099
4662
|
const verifyTask = makeCommandVerifier();
|
|
@@ -4872,16 +5435,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
4872
5435
|
};
|
|
4873
5436
|
|
|
4874
5437
|
// src/server/projects-manifest.ts
|
|
4875
|
-
import * as
|
|
4876
|
-
import * as
|
|
5438
|
+
import * as fs7 from "fs/promises";
|
|
5439
|
+
import * as path9 from "path";
|
|
4877
5440
|
import { projectSlug } from "@wrongstack/core";
|
|
4878
5441
|
function projectsJsonPath(globalConfigPath) {
|
|
4879
|
-
const base =
|
|
4880
|
-
return
|
|
5442
|
+
const base = path9.dirname(globalConfigPath);
|
|
5443
|
+
return path9.join(base, "projects.json");
|
|
4881
5444
|
}
|
|
4882
5445
|
async function loadManifest(globalConfigPath) {
|
|
4883
5446
|
try {
|
|
4884
|
-
const raw = await
|
|
5447
|
+
const raw = await fs7.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
4885
5448
|
const parsed = JSON.parse(raw);
|
|
4886
5449
|
return { projects: parsed.projects ?? [] };
|
|
4887
5450
|
} catch {
|
|
@@ -4890,16 +5453,16 @@ async function loadManifest(globalConfigPath) {
|
|
|
4890
5453
|
}
|
|
4891
5454
|
async function saveManifest(manifest, globalConfigPath) {
|
|
4892
5455
|
const file = projectsJsonPath(globalConfigPath);
|
|
4893
|
-
await
|
|
4894
|
-
await
|
|
5456
|
+
await fs7.mkdir(path9.dirname(file), { recursive: true });
|
|
5457
|
+
await fs7.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
4895
5458
|
}
|
|
4896
5459
|
function generateProjectSlug(rootPath) {
|
|
4897
5460
|
return projectSlug(rootPath);
|
|
4898
5461
|
}
|
|
4899
5462
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
4900
|
-
const base =
|
|
4901
|
-
const dir =
|
|
4902
|
-
await
|
|
5463
|
+
const base = path9.dirname(globalConfigPath);
|
|
5464
|
+
const dir = path9.join(base, "projects", slug);
|
|
5465
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
4903
5466
|
return dir;
|
|
4904
5467
|
}
|
|
4905
5468
|
|
|
@@ -5061,16 +5624,22 @@ function clampDim(value, fallback) {
|
|
|
5061
5624
|
}
|
|
5062
5625
|
|
|
5063
5626
|
// src/server/worktree-ws-handler.ts
|
|
5627
|
+
import { join as join6, resolve as resolve6, sep as sep4 } from "path";
|
|
5628
|
+
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2, WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
|
|
5064
5629
|
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
5065
5630
|
var MAX_ACTIVITY = 6;
|
|
5631
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
5632
|
+
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
5066
5633
|
var WorktreeWebSocketHandler = class {
|
|
5067
|
-
constructor(events, logger) {
|
|
5634
|
+
constructor(events, logger, management) {
|
|
5068
5635
|
this.events = events;
|
|
5069
5636
|
this.logger = logger;
|
|
5637
|
+
this.management = management;
|
|
5070
5638
|
this.subscribe();
|
|
5071
5639
|
}
|
|
5072
5640
|
events;
|
|
5073
5641
|
logger;
|
|
5642
|
+
management;
|
|
5074
5643
|
clients = /* @__PURE__ */ new Set();
|
|
5075
5644
|
handles = /* @__PURE__ */ new Map();
|
|
5076
5645
|
baseBranch = "";
|
|
@@ -5081,12 +5650,197 @@ var WorktreeWebSocketHandler = class {
|
|
|
5081
5650
|
ws.on("close", () => this.clients.delete(ws));
|
|
5082
5651
|
ws.on("error", () => this.clients.delete(ws));
|
|
5083
5652
|
this.send(ws, this.stateMessage());
|
|
5653
|
+
void this.scanAndBroadcast();
|
|
5654
|
+
}
|
|
5655
|
+
/** Handle worktree-panel control messages (scan / clean / per-row ops). */
|
|
5656
|
+
async handleMessage(msg) {
|
|
5657
|
+
if (msg.type === "worktree.scan") {
|
|
5658
|
+
await this.scanAndBroadcast();
|
|
5659
|
+
return true;
|
|
5660
|
+
}
|
|
5661
|
+
if (msg.type === "worktree.cleanup") {
|
|
5662
|
+
await this.cleanupOrphans();
|
|
5663
|
+
return true;
|
|
5664
|
+
}
|
|
5665
|
+
if (msg.type === "worktree.remove") {
|
|
5666
|
+
await this.removeOne(msg.payload?.["dir"], msg.payload?.["branch"]);
|
|
5667
|
+
return true;
|
|
5668
|
+
}
|
|
5669
|
+
if (msg.type === "worktree.merge") {
|
|
5670
|
+
await this.mergeBranch(msg.payload?.["branch"]);
|
|
5671
|
+
return true;
|
|
5672
|
+
}
|
|
5673
|
+
if (msg.type === "worktree.diff") {
|
|
5674
|
+
await this.diffOne(msg.payload?.["dir"], msg.payload?.["baseBranch"]);
|
|
5675
|
+
return true;
|
|
5676
|
+
}
|
|
5677
|
+
return false;
|
|
5084
5678
|
}
|
|
5085
5679
|
dispose() {
|
|
5086
5680
|
for (const off of this.offs) off();
|
|
5087
5681
|
this.offs.length = 0;
|
|
5088
5682
|
this.stopBroadcast();
|
|
5089
5683
|
}
|
|
5684
|
+
// ── orphan management ─────────────────────────────────────────────────────
|
|
5685
|
+
/** Absolute managed-worktrees root for this project. */
|
|
5686
|
+
worktreesRoot() {
|
|
5687
|
+
return resolve6(join6(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
5688
|
+
}
|
|
5689
|
+
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
5690
|
+
underRoot(dir) {
|
|
5691
|
+
const abs = resolve6(dir);
|
|
5692
|
+
const root = this.worktreesRoot();
|
|
5693
|
+
return abs !== root && abs.startsWith(root + sep4);
|
|
5694
|
+
}
|
|
5695
|
+
/** Branches of worktrees a live in-session run currently owns. */
|
|
5696
|
+
liveActiveBranches() {
|
|
5697
|
+
const live = /* @__PURE__ */ new Set();
|
|
5698
|
+
for (const h of this.handles.values()) {
|
|
5699
|
+
if (ACTIVE_STATUSES.has(h.status) && h.branch) live.add(h.branch);
|
|
5700
|
+
}
|
|
5701
|
+
return live;
|
|
5702
|
+
}
|
|
5703
|
+
/**
|
|
5704
|
+
* Scan the disk for managed worktrees/branches NOT owned by a live in-session
|
|
5705
|
+
* run and broadcast them as orphans, with whether it is safe to clean now.
|
|
5706
|
+
* No-op (empty inventory) when management deps were not wired.
|
|
5707
|
+
*/
|
|
5708
|
+
async scanAndBroadcast() {
|
|
5709
|
+
if (!this.management) {
|
|
5710
|
+
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
5711
|
+
return;
|
|
5712
|
+
}
|
|
5713
|
+
try {
|
|
5714
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5715
|
+
const { worktrees, branches } = await wt.listManaged();
|
|
5716
|
+
const live = this.liveActiveBranches();
|
|
5717
|
+
const orphans = [];
|
|
5718
|
+
const seenBranches = /* @__PURE__ */ new Set();
|
|
5719
|
+
for (const w of worktrees) {
|
|
5720
|
+
if (w.branch && live.has(w.branch)) continue;
|
|
5721
|
+
if (w.branch) seenBranches.add(w.branch);
|
|
5722
|
+
orphans.push({ kind: "worktree", dir: w.dir, branch: w.branch });
|
|
5723
|
+
}
|
|
5724
|
+
for (const b of branches) {
|
|
5725
|
+
if (live.has(b) || seenBranches.has(b)) continue;
|
|
5726
|
+
orphans.push({ kind: "branch", branch: b });
|
|
5727
|
+
}
|
|
5728
|
+
const canClean = this.liveActiveBranches().size === 0;
|
|
5729
|
+
this.broadcast({
|
|
5730
|
+
type: "worktree.orphans",
|
|
5731
|
+
payload: {
|
|
5732
|
+
orphans,
|
|
5733
|
+
canClean,
|
|
5734
|
+
reason: canClean ? void 0 : "a run is live in this session"
|
|
5735
|
+
}
|
|
5736
|
+
});
|
|
5737
|
+
} catch (err) {
|
|
5738
|
+
this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage4(err)}`);
|
|
5739
|
+
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
/**
|
|
5743
|
+
* Force-remove every orphaned worktree + branch. Refused while a run is live —
|
|
5744
|
+
* in this session (active handles) OR another process (the SDD board liveness
|
|
5745
|
+
* guard inside cleanupStaleSddWorktrees). Best-effort; reports the outcome.
|
|
5746
|
+
*/
|
|
5747
|
+
async cleanupOrphans() {
|
|
5748
|
+
if (!this.management) {
|
|
5749
|
+
this.broadcast({
|
|
5750
|
+
type: "worktree.cleanup_result",
|
|
5751
|
+
payload: { ok: false, removed: 0, reason: "cleanup is not available in this session" }
|
|
5752
|
+
});
|
|
5753
|
+
return;
|
|
5754
|
+
}
|
|
5755
|
+
if (this.liveActiveBranches().size > 0) {
|
|
5756
|
+
this.broadcast({
|
|
5757
|
+
type: "worktree.cleanup_result",
|
|
5758
|
+
payload: { ok: false, removed: 0, reason: "a run is live in this session \u2014 stop it first" }
|
|
5759
|
+
});
|
|
5760
|
+
return;
|
|
5761
|
+
}
|
|
5762
|
+
const res = await cleanupStaleSddWorktrees2({
|
|
5763
|
+
projectRoot: this.management.projectRoot,
|
|
5764
|
+
boardsDir: this.management.boardsDir
|
|
5765
|
+
});
|
|
5766
|
+
if (res.skippedReason) {
|
|
5767
|
+
this.broadcast({
|
|
5768
|
+
type: "worktree.cleanup_result",
|
|
5769
|
+
payload: { ok: false, removed: 0, reason: res.skippedReason }
|
|
5770
|
+
});
|
|
5771
|
+
await this.scanAndBroadcast();
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5774
|
+
for (const [id, h] of [...this.handles]) {
|
|
5775
|
+
if (!ACTIVE_STATUSES.has(h.status)) this.handles.delete(id);
|
|
5776
|
+
}
|
|
5777
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: true, removed: res.removed } });
|
|
5778
|
+
this.broadcastState();
|
|
5779
|
+
await this.scanAndBroadcast();
|
|
5780
|
+
}
|
|
5781
|
+
/** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
|
|
5782
|
+
async removeOne(dir, branch) {
|
|
5783
|
+
if (!this.management || !dir && !branch) {
|
|
5784
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: false, removed: 0, reason: "nothing to remove" } });
|
|
5785
|
+
return;
|
|
5786
|
+
}
|
|
5787
|
+
if (branch && !MANAGED_BRANCH_RE.test(branch)) {
|
|
5788
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: false, removed: 0, reason: "not a managed worktree branch" } });
|
|
5789
|
+
return;
|
|
5790
|
+
}
|
|
5791
|
+
if (dir && !this.underRoot(dir)) {
|
|
5792
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: false, removed: 0, reason: "path is outside the managed worktrees root" } });
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5795
|
+
if (branch && this.liveActiveBranches().has(branch)) {
|
|
5796
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: false, removed: 0, reason: "a run is live on this worktree \u2014 stop it first" } });
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
let removed = false;
|
|
5800
|
+
if (dir) {
|
|
5801
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5802
|
+
({ removed } = await wt.removeOne(dir, branch));
|
|
5803
|
+
}
|
|
5804
|
+
for (const [id, h] of [...this.handles]) {
|
|
5805
|
+
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId)) this.handles.delete(id);
|
|
5806
|
+
}
|
|
5807
|
+
this.broadcast({ type: "worktree.cleanup_result", payload: { ok: removed, removed: removed ? 1 : 0, reason: removed ? void 0 : "remove failed (not a managed worktree?)" } });
|
|
5808
|
+
this.broadcastState();
|
|
5809
|
+
await this.scanAndBroadcast();
|
|
5810
|
+
}
|
|
5811
|
+
/** Squash-merge ONE branch into base. Refused while a live run owns it. */
|
|
5812
|
+
async mergeBranch(branch) {
|
|
5813
|
+
if (!this.management || !branch) {
|
|
5814
|
+
this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch: branch ?? "", reason: "no branch" } });
|
|
5815
|
+
return;
|
|
5816
|
+
}
|
|
5817
|
+
if (!MANAGED_BRANCH_RE.test(branch)) {
|
|
5818
|
+
this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch, reason: "not a managed worktree branch" } });
|
|
5819
|
+
return;
|
|
5820
|
+
}
|
|
5821
|
+
if (this.liveActiveBranches().has(branch)) {
|
|
5822
|
+
this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" } });
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5825
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5826
|
+
const res = await wt.mergeBranch(branch);
|
|
5827
|
+
this.broadcast({
|
|
5828
|
+
type: "worktree.merge_result",
|
|
5829
|
+
payload: { ok: res.ok, branch, conflict: res.conflict, conflictFiles: res.conflictFiles, reason: res.reason }
|
|
5830
|
+
});
|
|
5831
|
+
await this.scanAndBroadcast();
|
|
5832
|
+
}
|
|
5833
|
+
/** Compact change summary for one worktree checkout. */
|
|
5834
|
+
async diffOne(dir, baseBranch) {
|
|
5835
|
+
if (!this.management || !dir || !this.underRoot(dir)) {
|
|
5836
|
+
this.broadcast({ type: "worktree.diff_result", payload: { dir: dir ?? "", summary: null } });
|
|
5837
|
+
return;
|
|
5838
|
+
}
|
|
5839
|
+
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
5840
|
+
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
5841
|
+
const summary = await wt.diffSummary(resolve6(dir), base);
|
|
5842
|
+
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
5843
|
+
}
|
|
5090
5844
|
// ── internals ───────────────────────────────────────────────────────────
|
|
5091
5845
|
subscribe() {
|
|
5092
5846
|
const on = this.events.on.bind(this.events);
|
|
@@ -5098,6 +5852,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5098
5852
|
handleId: e.handleId,
|
|
5099
5853
|
ownerId: e.ownerId,
|
|
5100
5854
|
ownerLabel: e.ownerLabel,
|
|
5855
|
+
dir: e.dir,
|
|
5101
5856
|
branch: e.branch,
|
|
5102
5857
|
baseBranch: e.baseBranch,
|
|
5103
5858
|
status: "active",
|
|
@@ -5200,10 +5955,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
5200
5955
|
};
|
|
5201
5956
|
|
|
5202
5957
|
// src/server/mailbox-handlers.ts
|
|
5203
|
-
import { GlobalMailbox, resolveProjectDir } from "@wrongstack/core";
|
|
5958
|
+
import { GlobalMailbox, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core";
|
|
5204
5959
|
async function handleMailboxMessages(ws, deps2, payload) {
|
|
5205
5960
|
try {
|
|
5206
|
-
const dir =
|
|
5961
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
5207
5962
|
const mb = new GlobalMailbox(dir);
|
|
5208
5963
|
const messages = await mb.query({
|
|
5209
5964
|
limit: payload?.limit ?? 30,
|
|
@@ -5239,7 +5994,7 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
5239
5994
|
}
|
|
5240
5995
|
async function handleMailboxAgents(ws, deps2, payload) {
|
|
5241
5996
|
try {
|
|
5242
|
-
const dir =
|
|
5997
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
5243
5998
|
const mb = new GlobalMailbox(dir);
|
|
5244
5999
|
const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
|
|
5245
6000
|
send(ws, {
|
|
@@ -5268,7 +6023,7 @@ async function handleMailboxAgents(ws, deps2, payload) {
|
|
|
5268
6023
|
}
|
|
5269
6024
|
async function handleMailboxClear(ws, deps2) {
|
|
5270
6025
|
try {
|
|
5271
|
-
const dir =
|
|
6026
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
5272
6027
|
const mb = new GlobalMailbox(dir);
|
|
5273
6028
|
await mb.clearAll();
|
|
5274
6029
|
send(ws, { type: "mailbox.cleared", payload: {} });
|
|
@@ -5278,7 +6033,7 @@ async function handleMailboxClear(ws, deps2) {
|
|
|
5278
6033
|
}
|
|
5279
6034
|
async function handleMailboxPurge(ws, deps2, opts) {
|
|
5280
6035
|
try {
|
|
5281
|
-
const dir =
|
|
6036
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
5282
6037
|
const mb = new GlobalMailbox(dir);
|
|
5283
6038
|
const result = await mb.purgeStale(opts);
|
|
5284
6039
|
send(ws, { type: "mailbox.purged", payload: result });
|
|
@@ -5325,14 +6080,14 @@ function registerShutdownHandlers(res) {
|
|
|
5325
6080
|
|
|
5326
6081
|
// src/server/instance-registry.ts
|
|
5327
6082
|
import * as os from "os";
|
|
5328
|
-
import * as
|
|
5329
|
-
import * as
|
|
6083
|
+
import * as path10 from "path";
|
|
6084
|
+
import * as fs8 from "fs/promises";
|
|
5330
6085
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
5331
6086
|
function defaultBaseDir() {
|
|
5332
|
-
return
|
|
6087
|
+
return path10.join(os.homedir(), ".wrongstack");
|
|
5333
6088
|
}
|
|
5334
6089
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
5335
|
-
return
|
|
6090
|
+
return path10.join(baseDir, "webui-instances.json");
|
|
5336
6091
|
}
|
|
5337
6092
|
function isPidAlive(pid) {
|
|
5338
6093
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -5345,7 +6100,7 @@ function isPidAlive(pid) {
|
|
|
5345
6100
|
}
|
|
5346
6101
|
async function load(file) {
|
|
5347
6102
|
try {
|
|
5348
|
-
const raw = await
|
|
6103
|
+
const raw = await fs8.readFile(file, "utf8");
|
|
5349
6104
|
const parsed = JSON.parse(raw);
|
|
5350
6105
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
5351
6106
|
return parsed;
|
|
@@ -5404,16 +6159,16 @@ function formatInstances(instances) {
|
|
|
5404
6159
|
// src/server/port-utils.ts
|
|
5405
6160
|
import * as net from "net";
|
|
5406
6161
|
function isPortFree(host, port) {
|
|
5407
|
-
return new Promise((
|
|
6162
|
+
return new Promise((resolve10) => {
|
|
5408
6163
|
const srv = net.createServer();
|
|
5409
|
-
srv.once("error", () =>
|
|
6164
|
+
srv.once("error", () => resolve10(false));
|
|
5410
6165
|
srv.once("listening", () => {
|
|
5411
|
-
srv.close(() =>
|
|
6166
|
+
srv.close(() => resolve10(true));
|
|
5412
6167
|
});
|
|
5413
6168
|
try {
|
|
5414
6169
|
srv.listen(port, host);
|
|
5415
6170
|
} catch {
|
|
5416
|
-
|
|
6171
|
+
resolve10(false);
|
|
5417
6172
|
}
|
|
5418
6173
|
});
|
|
5419
6174
|
}
|
|
@@ -5494,15 +6249,15 @@ import { DefaultSecretScrubber } from "@wrongstack/core";
|
|
|
5494
6249
|
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
5495
6250
|
|
|
5496
6251
|
// src/server/provider-config-io.ts
|
|
5497
|
-
import * as
|
|
5498
|
-
import * as
|
|
6252
|
+
import * as fs9 from "fs/promises";
|
|
6253
|
+
import * as path11 from "path";
|
|
5499
6254
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
5500
6255
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
5501
6256
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
5502
6257
|
async function loadSavedProviders(configPath, vault) {
|
|
5503
6258
|
let raw;
|
|
5504
6259
|
try {
|
|
5505
|
-
raw = await
|
|
6260
|
+
raw = await fs9.readFile(configPath, "utf8");
|
|
5506
6261
|
} catch {
|
|
5507
6262
|
return {};
|
|
5508
6263
|
}
|
|
@@ -5519,7 +6274,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
5519
6274
|
let raw;
|
|
5520
6275
|
let fileExists = true;
|
|
5521
6276
|
try {
|
|
5522
|
-
raw = await
|
|
6277
|
+
raw = await fs9.readFile(configPath, "utf8");
|
|
5523
6278
|
} catch (err) {
|
|
5524
6279
|
if (err.code !== "ENOENT") {
|
|
5525
6280
|
throw new Error(
|
|
@@ -5857,7 +6612,8 @@ function createProviderHandlers(deps2) {
|
|
|
5857
6612
|
|
|
5858
6613
|
// src/server/mode-handlers.ts
|
|
5859
6614
|
import {
|
|
5860
|
-
DefaultSystemPromptBuilder
|
|
6615
|
+
DefaultSystemPromptBuilder,
|
|
6616
|
+
resolveWstackPaths
|
|
5861
6617
|
} from "@wrongstack/core";
|
|
5862
6618
|
function createModeHandlers(ctx) {
|
|
5863
6619
|
return {
|
|
@@ -5905,13 +6661,18 @@ function createModeHandlers(ctx) {
|
|
|
5905
6661
|
}
|
|
5906
6662
|
ctx.setModeId(id);
|
|
5907
6663
|
const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
|
|
6664
|
+
const paths = resolveWstackPaths({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
5908
6665
|
const freshBuilder = new DefaultSystemPromptBuilder({
|
|
5909
6666
|
memoryStore: ctx.memoryStore,
|
|
5910
6667
|
skillLoader: ctx.skillLoader,
|
|
5911
6668
|
modeStore: ctx.modeStore,
|
|
5912
6669
|
modeId: id,
|
|
5913
6670
|
modePrompt,
|
|
5914
|
-
modelCapabilities: ctx.modelCapabilities
|
|
6671
|
+
modelCapabilities: ctx.modelCapabilities,
|
|
6672
|
+
instructionPaths: {
|
|
6673
|
+
globalDir: paths.globalInstructions,
|
|
6674
|
+
projectDir: paths.inProjectInstructions
|
|
6675
|
+
}
|
|
5915
6676
|
});
|
|
5916
6677
|
ctx.context.systemPrompt = await freshBuilder.build({
|
|
5917
6678
|
cwd: ctx.projectRoot,
|
|
@@ -5933,12 +6694,13 @@ function createModeHandlers(ctx) {
|
|
|
5933
6694
|
}
|
|
5934
6695
|
|
|
5935
6696
|
// src/server/project-handlers.ts
|
|
5936
|
-
import * as
|
|
5937
|
-
import * as
|
|
6697
|
+
import * as fs10 from "fs/promises";
|
|
6698
|
+
import * as path12 from "path";
|
|
5938
6699
|
import {
|
|
5939
6700
|
DefaultSessionStore,
|
|
5940
6701
|
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
|
|
5941
|
-
getSessionRegistry
|
|
6702
|
+
getSessionRegistry,
|
|
6703
|
+
resolveWstackPaths as resolveWstackPaths2
|
|
5942
6704
|
} from "@wrongstack/core";
|
|
5943
6705
|
function createProjectHandlers(ctx) {
|
|
5944
6706
|
return {
|
|
@@ -5961,9 +6723,9 @@ function createProjectHandlers(ctx) {
|
|
|
5961
6723
|
}
|
|
5962
6724
|
const { root: addRoot, name: displayName } = parsed.value;
|
|
5963
6725
|
try {
|
|
5964
|
-
const resolved =
|
|
5965
|
-
await
|
|
5966
|
-
const stat3 = await
|
|
6726
|
+
const resolved = path12.resolve(addRoot);
|
|
6727
|
+
await fs10.access(resolved);
|
|
6728
|
+
const stat3 = await fs10.stat(resolved);
|
|
5967
6729
|
if (!stat3.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
5968
6730
|
const manifest = await loadManifest(ctx.globalConfigPath);
|
|
5969
6731
|
const existing = manifest.projects.find((p) => p.root === resolved);
|
|
@@ -5979,7 +6741,7 @@ function createProjectHandlers(ctx) {
|
|
|
5979
6741
|
});
|
|
5980
6742
|
return;
|
|
5981
6743
|
}
|
|
5982
|
-
const name2 = displayName?.trim() ||
|
|
6744
|
+
const name2 = displayName?.trim() || path12.basename(resolved);
|
|
5983
6745
|
const slug = generateProjectSlug(resolved);
|
|
5984
6746
|
await ensureProjectDataDir(slug, ctx.globalConfigPath);
|
|
5985
6747
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5992,7 +6754,7 @@ function createProjectHandlers(ctx) {
|
|
|
5992
6754
|
} catch (err) {
|
|
5993
6755
|
send(ws, {
|
|
5994
6756
|
type: "projects.added",
|
|
5995
|
-
payload: { name:
|
|
6757
|
+
payload: { name: path12.basename(addRoot), root: addRoot, slug: "", message: errMessage(err) }
|
|
5996
6758
|
});
|
|
5997
6759
|
}
|
|
5998
6760
|
},
|
|
@@ -6007,17 +6769,17 @@ function createProjectHandlers(ctx) {
|
|
|
6007
6769
|
}
|
|
6008
6770
|
const { root: selRoot, name: selName } = parsed.value;
|
|
6009
6771
|
try {
|
|
6010
|
-
const resolved =
|
|
6772
|
+
const resolved = path12.resolve(selRoot);
|
|
6011
6773
|
try {
|
|
6012
|
-
await
|
|
6013
|
-
const stat3 = await
|
|
6774
|
+
await fs10.access(resolved);
|
|
6775
|
+
const stat3 = await fs10.stat(resolved);
|
|
6014
6776
|
if (!stat3.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
6015
6777
|
} catch (err) {
|
|
6016
6778
|
send(ws, {
|
|
6017
6779
|
type: "projects.selected",
|
|
6018
6780
|
payload: {
|
|
6019
6781
|
root: selRoot,
|
|
6020
|
-
name: selName ||
|
|
6782
|
+
name: selName || path12.basename(selRoot),
|
|
6021
6783
|
message: `Cannot switch: ${errMessage(err)}`
|
|
6022
6784
|
}
|
|
6023
6785
|
});
|
|
@@ -6029,7 +6791,7 @@ function createProjectHandlers(ctx) {
|
|
|
6029
6791
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
6030
6792
|
entry.lastWorkingDir = resolved;
|
|
6031
6793
|
} else {
|
|
6032
|
-
const name2 = selName?.trim() ||
|
|
6794
|
+
const name2 = selName?.trim() || path12.basename(resolved);
|
|
6033
6795
|
const slug = generateProjectSlug(resolved);
|
|
6034
6796
|
manifest.projects.push({
|
|
6035
6797
|
name: name2,
|
|
@@ -6051,13 +6813,21 @@ function createProjectHandlers(ctx) {
|
|
|
6051
6813
|
try {
|
|
6052
6814
|
const modeId = ctx.getModeId();
|
|
6053
6815
|
const switchMode = modeId === "default" ? void 0 : await ctx.modeStore.getMode(modeId);
|
|
6816
|
+
const switchPaths = resolveWstackPaths2({
|
|
6817
|
+
projectRoot: resolved,
|
|
6818
|
+
globalRoot: ctx.wpaths.globalRoot
|
|
6819
|
+
});
|
|
6054
6820
|
const switchBuilder = new DefaultSystemPromptBuilder2({
|
|
6055
6821
|
memoryStore: ctx.memoryStore,
|
|
6056
6822
|
skillLoader: ctx.skillLoader,
|
|
6057
6823
|
modeStore: ctx.modeStore,
|
|
6058
6824
|
modeId,
|
|
6059
6825
|
modePrompt: switchMode?.prompt ?? "",
|
|
6060
|
-
modelCapabilities: ctx.modelCapabilities
|
|
6826
|
+
modelCapabilities: ctx.modelCapabilities,
|
|
6827
|
+
instructionPaths: {
|
|
6828
|
+
globalDir: switchPaths.globalInstructions,
|
|
6829
|
+
projectDir: switchPaths.inProjectInstructions
|
|
6830
|
+
}
|
|
6061
6831
|
});
|
|
6062
6832
|
ctx.context.systemPrompt = await switchBuilder.build({
|
|
6063
6833
|
cwd: resolved,
|
|
@@ -6068,13 +6838,13 @@ function createProjectHandlers(ctx) {
|
|
|
6068
6838
|
});
|
|
6069
6839
|
} catch {
|
|
6070
6840
|
}
|
|
6071
|
-
const newSessionsDir =
|
|
6072
|
-
|
|
6841
|
+
const newSessionsDir = path12.join(
|
|
6842
|
+
path12.dirname(ctx.globalConfigPath),
|
|
6073
6843
|
"projects",
|
|
6074
6844
|
switchSlug,
|
|
6075
6845
|
"sessions"
|
|
6076
6846
|
);
|
|
6077
|
-
await
|
|
6847
|
+
await fs10.mkdir(newSessionsDir, { recursive: true });
|
|
6078
6848
|
const newSessionStore = new DefaultSessionStore({ dir: newSessionsDir });
|
|
6079
6849
|
const oldSession = ctx.getSession();
|
|
6080
6850
|
const oldSessionId = oldSession.id;
|
|
@@ -6108,7 +6878,7 @@ function createProjectHandlers(ctx) {
|
|
|
6108
6878
|
sessionId: newSession.id,
|
|
6109
6879
|
projectSlug: switchSlug,
|
|
6110
6880
|
projectRoot: resolved,
|
|
6111
|
-
projectName:
|
|
6881
|
+
projectName: path12.basename(resolved),
|
|
6112
6882
|
workingDir: resolved,
|
|
6113
6883
|
clientType: "webui",
|
|
6114
6884
|
pid: process.pid,
|
|
@@ -6120,8 +6890,8 @@ function createProjectHandlers(ctx) {
|
|
|
6120
6890
|
type: "projects.selected",
|
|
6121
6891
|
payload: {
|
|
6122
6892
|
root: resolved,
|
|
6123
|
-
name: selName ||
|
|
6124
|
-
message: `Switched to ${selName ||
|
|
6893
|
+
name: selName || path12.basename(resolved),
|
|
6894
|
+
message: `Switched to ${selName || path12.basename(resolved)}`
|
|
6125
6895
|
}
|
|
6126
6896
|
});
|
|
6127
6897
|
broadcast(ctx.clients, {
|
|
@@ -6141,7 +6911,7 @@ function createProjectHandlers(ctx) {
|
|
|
6141
6911
|
type: "projects.selected",
|
|
6142
6912
|
payload: {
|
|
6143
6913
|
root: selRoot,
|
|
6144
|
-
name: selName ||
|
|
6914
|
+
name: selName || path12.basename(selRoot),
|
|
6145
6915
|
message: errMessage(err)
|
|
6146
6916
|
}
|
|
6147
6917
|
});
|
|
@@ -6172,7 +6942,7 @@ function createProjectHandlers(ctx) {
|
|
|
6172
6942
|
}
|
|
6173
6943
|
|
|
6174
6944
|
// src/server/session-handlers.ts
|
|
6175
|
-
import * as
|
|
6945
|
+
import * as path13 from "path";
|
|
6176
6946
|
import {
|
|
6177
6947
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
6178
6948
|
repairToolUseAdjacency,
|
|
@@ -6514,7 +7284,7 @@ function createSessionHandlers(ctx) {
|
|
|
6514
7284
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
6515
7285
|
const projectRoot = ctx.getProjectRoot();
|
|
6516
7286
|
const rewinder = new DefaultSessionRewinder(
|
|
6517
|
-
|
|
7287
|
+
path13.join(projectRoot, ".wrongstack", "sessions"),
|
|
6518
7288
|
projectRoot
|
|
6519
7289
|
);
|
|
6520
7290
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
@@ -6529,7 +7299,7 @@ function createSessionHandlers(ctx) {
|
|
|
6529
7299
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
6530
7300
|
const projectRoot = ctx.getProjectRoot();
|
|
6531
7301
|
const rewinder = new DefaultSessionRewinder(
|
|
6532
|
-
|
|
7302
|
+
path13.join(projectRoot, ".wrongstack", "sessions"),
|
|
6533
7303
|
projectRoot
|
|
6534
7304
|
);
|
|
6535
7305
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
@@ -6905,9 +7675,9 @@ async function handleSddBoardRoute(_ws, msg, handlers) {
|
|
|
6905
7675
|
}
|
|
6906
7676
|
|
|
6907
7677
|
// src/server/setup-events.ts
|
|
6908
|
-
import * as
|
|
7678
|
+
import * as fs11 from "fs/promises";
|
|
6909
7679
|
import { watch as fsWatch } from "fs";
|
|
6910
|
-
import * as
|
|
7680
|
+
import * as path14 from "path";
|
|
6911
7681
|
function setupEvents(deps2) {
|
|
6912
7682
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
6913
7683
|
const disposers = [];
|
|
@@ -6996,6 +7766,22 @@ function setupEvents(deps2) {
|
|
|
6996
7766
|
}).catch(() => {
|
|
6997
7767
|
});
|
|
6998
7768
|
broadcast2(clients, { type: "todos.updated", payload: { todos: [...context.todos] } });
|
|
7769
|
+
const sideEffects = context.sideEffects ?? [];
|
|
7770
|
+
if (sideEffects.length > 0) {
|
|
7771
|
+
broadcast2(clients, {
|
|
7772
|
+
type: "side_effects",
|
|
7773
|
+
payload: {
|
|
7774
|
+
sideEffects: sideEffects.slice(-50).map((se) => ({
|
|
7775
|
+
toolUseId: se.toolUseId,
|
|
7776
|
+
toolName: se.toolName,
|
|
7777
|
+
ts: se.ts,
|
|
7778
|
+
input: se.input,
|
|
7779
|
+
outcome: se.outcome,
|
|
7780
|
+
risk: se.risk
|
|
7781
|
+
}))
|
|
7782
|
+
}
|
|
7783
|
+
});
|
|
7784
|
+
}
|
|
6999
7785
|
if (e.name === "task" || e.name === "plan" || e.name === "todo") {
|
|
7000
7786
|
void (async () => {
|
|
7001
7787
|
try {
|
|
@@ -7374,16 +8160,16 @@ function setupEvents(deps2) {
|
|
|
7374
8160
|
if (wpaths?.projectStatus) {
|
|
7375
8161
|
try {
|
|
7376
8162
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
7377
|
-
const dir =
|
|
7378
|
-
await
|
|
7379
|
-
await
|
|
8163
|
+
const dir = path14.dirname(statusFile);
|
|
8164
|
+
await fs11.mkdir(dir, { recursive: true });
|
|
8165
|
+
await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
7380
8166
|
} catch (err) {
|
|
7381
8167
|
console.error("[setup-events] Failed to write status.json:", err);
|
|
7382
8168
|
}
|
|
7383
8169
|
}
|
|
7384
8170
|
});
|
|
7385
8171
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
7386
|
-
const projectsDir =
|
|
8172
|
+
const projectsDir = path14.join(wpaths.configDir, "projects");
|
|
7387
8173
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
7388
8174
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
7389
8175
|
const DEBOUNCE_MS = 150;
|
|
@@ -7446,20 +8232,20 @@ function setupEvents(deps2) {
|
|
|
7446
8232
|
let watcher;
|
|
7447
8233
|
const startWatcher = async () => {
|
|
7448
8234
|
try {
|
|
7449
|
-
await
|
|
8235
|
+
await fs11.mkdir(projectsDir, { recursive: true });
|
|
7450
8236
|
watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
|
|
7451
8237
|
if (eventType === "change") {
|
|
7452
8238
|
if (filename == null) return;
|
|
7453
8239
|
if (watcherMetrics) watcherMetrics.fileChangesDetected++;
|
|
7454
|
-
const targetFile =
|
|
8240
|
+
const targetFile = path14.join(projectsDir, String(filename));
|
|
7455
8241
|
if (targetFile.endsWith("status.json")) {
|
|
7456
|
-
const projectHash2 =
|
|
8242
|
+
const projectHash2 = path14.basename(path14.dirname(targetFile));
|
|
7457
8243
|
if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash2)) {
|
|
7458
8244
|
return;
|
|
7459
8245
|
}
|
|
7460
8246
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
7461
8247
|
try {
|
|
7462
|
-
const content = await
|
|
8248
|
+
const content = await fs11.readFile(targetFile, "utf-8");
|
|
7463
8249
|
const statusData = JSON.parse(content);
|
|
7464
8250
|
if (statusData.projectHash) {
|
|
7465
8251
|
const hash = String(statusData.projectHash);
|
|
@@ -7511,7 +8297,7 @@ function setupEvents(deps2) {
|
|
|
7511
8297
|
}
|
|
7512
8298
|
});
|
|
7513
8299
|
}
|
|
7514
|
-
const globalRoot = globalConfigPath ?
|
|
8300
|
+
const globalRoot = globalConfigPath ? path14.dirname(globalConfigPath) : void 0;
|
|
7515
8301
|
if (globalRoot) {
|
|
7516
8302
|
const broadcastSessions = async () => {
|
|
7517
8303
|
try {
|
|
@@ -7584,11 +8370,11 @@ function setupEvents(deps2) {
|
|
|
7584
8370
|
|
|
7585
8371
|
// src/server/custom-context-modes.ts
|
|
7586
8372
|
import { listContextWindowModes, atomicWrite as atomicWrite5 } from "@wrongstack/core";
|
|
7587
|
-
import * as
|
|
7588
|
-
import * as
|
|
8373
|
+
import * as fs12 from "fs/promises";
|
|
8374
|
+
import * as path15 from "path";
|
|
7589
8375
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
7590
8376
|
function storePath(wrongstackDir) {
|
|
7591
|
-
return
|
|
8377
|
+
return path15.join(wrongstackDir, STORE_FILENAME);
|
|
7592
8378
|
}
|
|
7593
8379
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
7594
8380
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -7596,7 +8382,7 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
7596
8382
|
const load2 = async () => {
|
|
7597
8383
|
modes.clear();
|
|
7598
8384
|
try {
|
|
7599
|
-
const raw = await
|
|
8385
|
+
const raw = await fs12.readFile(storePath(wrongstackDir), "utf8");
|
|
7600
8386
|
const parsed = JSON.parse(raw);
|
|
7601
8387
|
if (Array.isArray(parsed.modes)) {
|
|
7602
8388
|
for (const m of parsed.modes) {
|
|
@@ -7642,7 +8428,8 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
7642
8428
|
custom: true
|
|
7643
8429
|
};
|
|
7644
8430
|
modes.set(mode.id, entry);
|
|
7645
|
-
void save2()
|
|
8431
|
+
void save2().catch(() => {
|
|
8432
|
+
});
|
|
7646
8433
|
return { ok: true };
|
|
7647
8434
|
};
|
|
7648
8435
|
const update = (id, patch) => {
|
|
@@ -7668,7 +8455,8 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
7668
8455
|
if (patch.targetLoad !== void 0) next.targetLoad = patch.targetLoad;
|
|
7669
8456
|
if (patch.aggressiveOn !== void 0) next.aggressiveOn = patch.aggressiveOn;
|
|
7670
8457
|
modes.set(id, next);
|
|
7671
|
-
void save2()
|
|
8458
|
+
void save2().catch(() => {
|
|
8459
|
+
});
|
|
7672
8460
|
return { ok: true };
|
|
7673
8461
|
};
|
|
7674
8462
|
const remove = (id) => {
|
|
@@ -7678,7 +8466,8 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
7678
8466
|
if (!modes.delete(id)) {
|
|
7679
8467
|
return { ok: false, error: `Mode "${id}" not found` };
|
|
7680
8468
|
}
|
|
7681
|
-
void save2()
|
|
8469
|
+
void save2().catch(() => {
|
|
8470
|
+
});
|
|
7682
8471
|
return { ok: true };
|
|
7683
8472
|
};
|
|
7684
8473
|
const list = () => {
|
|
@@ -7719,14 +8508,17 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
7719
8508
|
}
|
|
7720
8509
|
|
|
7721
8510
|
// src/server/shell-open.ts
|
|
7722
|
-
import * as
|
|
7723
|
-
import * as
|
|
8511
|
+
import * as fs13 from "fs/promises";
|
|
8512
|
+
import * as path16 from "path";
|
|
7724
8513
|
import { spawn as spawn2 } from "child_process";
|
|
7725
|
-
var METACHAR_REGEX = /[&|<>^"'
|
|
8514
|
+
var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
|
|
8515
|
+
function shellQuote(s) {
|
|
8516
|
+
return s.replaceAll("'", `'"'"'`);
|
|
8517
|
+
}
|
|
7726
8518
|
async function handleShellOpen(req, logger) {
|
|
7727
8519
|
try {
|
|
7728
|
-
const resolved =
|
|
7729
|
-
await
|
|
8520
|
+
const resolved = path16.resolve(req.path);
|
|
8521
|
+
await fs13.access(resolved);
|
|
7730
8522
|
if (METACHAR_REGEX.test(resolved)) {
|
|
7731
8523
|
return { success: false, message: "Path contains unsupported characters." };
|
|
7732
8524
|
}
|
|
@@ -7759,7 +8551,11 @@ async function handleShellOpen(req, logger) {
|
|
|
7759
8551
|
() => launch(
|
|
7760
8552
|
"gnome-terminal",
|
|
7761
8553
|
[`--working-directory=${resolved}`],
|
|
7762
|
-
() =>
|
|
8554
|
+
() => (
|
|
8555
|
+
// Pass argv array so sh -c sees a literal string, not an interpolated one.
|
|
8556
|
+
// shellQuote() guards against paths that somehow slipped the METACHAR_REGEX.
|
|
8557
|
+
launch("xterm", ["-e", "sh", "-c", `cd ${shellQuote(resolved)} && ${process.env["SHELL"] ?? "sh"}`])
|
|
8558
|
+
)
|
|
7763
8559
|
)
|
|
7764
8560
|
);
|
|
7765
8561
|
}
|
|
@@ -7778,9 +8574,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
7778
8574
|
const cwd = projectRoot || void 0;
|
|
7779
8575
|
try {
|
|
7780
8576
|
const { execFile: ef } = await import("child_process");
|
|
7781
|
-
const git = (args) => new Promise((
|
|
8577
|
+
const git = (args) => new Promise((resolve10) => {
|
|
7782
8578
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
7783
|
-
|
|
8579
|
+
resolve10(err ? "" : stdout.trim());
|
|
7784
8580
|
});
|
|
7785
8581
|
});
|
|
7786
8582
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -7806,12 +8602,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
7806
8602
|
function makeGit(cwd) {
|
|
7807
8603
|
return async (args) => {
|
|
7808
8604
|
const { execFile: ef } = await import("child_process");
|
|
7809
|
-
return new Promise((
|
|
8605
|
+
return new Promise((resolve10) => {
|
|
7810
8606
|
ef(
|
|
7811
8607
|
"git",
|
|
7812
8608
|
args,
|
|
7813
8609
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
7814
|
-
(err, stdout) =>
|
|
8610
|
+
(err, stdout) => resolve10(err ? "" : stdout)
|
|
7815
8611
|
);
|
|
7816
8612
|
});
|
|
7817
8613
|
};
|
|
@@ -7835,15 +8631,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
7835
8631
|
if (!m) continue;
|
|
7836
8632
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
7837
8633
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
7838
|
-
let
|
|
7839
|
-
if (
|
|
8634
|
+
let path18 = m[3] ?? "";
|
|
8635
|
+
if (path18 === "") {
|
|
7840
8636
|
i += 1;
|
|
7841
|
-
|
|
8637
|
+
path18 = parts[i + 1] ?? parts[i] ?? "";
|
|
7842
8638
|
i += 1;
|
|
7843
8639
|
}
|
|
7844
|
-
if (!
|
|
7845
|
-
const prev = counts.get(
|
|
7846
|
-
counts.set(
|
|
8640
|
+
if (!path18) continue;
|
|
8641
|
+
const prev = counts.get(path18) ?? { added: 0, deleted: 0 };
|
|
8642
|
+
counts.set(path18, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
7847
8643
|
}
|
|
7848
8644
|
};
|
|
7849
8645
|
parseNumstat(unstagedNumstat);
|
|
@@ -7855,7 +8651,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
7855
8651
|
if (!rec || rec.length < 3) continue;
|
|
7856
8652
|
const x = rec[0] ?? " ";
|
|
7857
8653
|
const y = rec[1] ?? " ";
|
|
7858
|
-
const
|
|
8654
|
+
const path18 = rec.slice(3);
|
|
7859
8655
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
7860
8656
|
if (isRename) i += 1;
|
|
7861
8657
|
let status;
|
|
@@ -7867,13 +8663,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
7867
8663
|
else if (x === "D" || y === "D") status = "D";
|
|
7868
8664
|
else status = "M";
|
|
7869
8665
|
const staged = x !== " " && x !== "?";
|
|
7870
|
-
let added = counts.get(
|
|
7871
|
-
let deleted = counts.get(
|
|
8666
|
+
let added = counts.get(path18)?.added ?? 0;
|
|
8667
|
+
let deleted = counts.get(path18)?.deleted ?? 0;
|
|
7872
8668
|
if (status === "?") {
|
|
7873
8669
|
added = 0;
|
|
7874
8670
|
deleted = 0;
|
|
7875
8671
|
}
|
|
7876
|
-
files.push({ path:
|
|
8672
|
+
files.push({ path: path18, status, added, deleted, staged });
|
|
7877
8673
|
}
|
|
7878
8674
|
send(ws, { type: "git.changes", payload: { files } });
|
|
7879
8675
|
} catch (err) {
|
|
@@ -7884,21 +8680,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
7884
8680
|
}
|
|
7885
8681
|
}
|
|
7886
8682
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
7887
|
-
async function handleGitDiff(ws, projectRoot,
|
|
8683
|
+
async function handleGitDiff(ws, projectRoot, path18) {
|
|
7888
8684
|
const cwd = projectRoot || void 0;
|
|
7889
|
-
const reply = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
7890
|
-
if (!
|
|
8685
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path18, ...extra } });
|
|
8686
|
+
if (!path18 || path18.includes("\0") || path18.includes("..") || nodePath.isAbsolute(path18)) {
|
|
7891
8687
|
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
7892
8688
|
return;
|
|
7893
8689
|
}
|
|
7894
8690
|
try {
|
|
7895
8691
|
const git = makeGit(cwd);
|
|
7896
8692
|
const { readFile: readFile9 } = await import("fs/promises");
|
|
7897
|
-
const { join:
|
|
7898
|
-
const oldText = await git(["show", `HEAD:${
|
|
8693
|
+
const { join: join14 } = await import("path");
|
|
8694
|
+
const oldText = await git(["show", `HEAD:${path18}`]);
|
|
7899
8695
|
let newText = "";
|
|
7900
8696
|
try {
|
|
7901
|
-
const abs = cwd ?
|
|
8697
|
+
const abs = cwd ? join14(cwd, path18) : path18;
|
|
7902
8698
|
const buf = await readFile9(abs);
|
|
7903
8699
|
if (buf.includes(0)) {
|
|
7904
8700
|
reply({ oldText: "", newText: "", binary: true });
|
|
@@ -7979,10 +8775,10 @@ async function handleProcessKillAll(ws) {
|
|
|
7979
8775
|
}
|
|
7980
8776
|
|
|
7981
8777
|
// src/server/goal-handlers.ts
|
|
7982
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
8778
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
7983
8779
|
async function handleGoalGet(projectRoot, broadcast2) {
|
|
7984
8780
|
try {
|
|
7985
|
-
const goalPath =
|
|
8781
|
+
const goalPath = resolveWstackPaths3({ projectRoot }).projectGoal;
|
|
7986
8782
|
const { readFile: readFile9 } = await import("fs/promises");
|
|
7987
8783
|
const raw = await readFile9(goalPath, "utf8");
|
|
7988
8784
|
const goal = JSON.parse(raw);
|
|
@@ -8040,7 +8836,7 @@ async function startWebUI(opts = {}) {
|
|
|
8040
8836
|
const write = async () => {
|
|
8041
8837
|
let raw;
|
|
8042
8838
|
try {
|
|
8043
|
-
raw = await
|
|
8839
|
+
raw = await fs14.readFile(globalConfigPath, "utf8");
|
|
8044
8840
|
} catch {
|
|
8045
8841
|
raw = "{}";
|
|
8046
8842
|
}
|
|
@@ -8114,6 +8910,7 @@ async function startWebUI(opts = {}) {
|
|
|
8114
8910
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
8115
8911
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
8116
8912
|
applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
|
|
8913
|
+
applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
|
|
8117
8914
|
configureExecPolicy(config.tools?.exec ?? {});
|
|
8118
8915
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
8119
8916
|
const mcpRegistry = new MCPRegistry({
|
|
@@ -8158,7 +8955,7 @@ async function startWebUI(opts = {}) {
|
|
|
8158
8955
|
sessionId: session.id,
|
|
8159
8956
|
projectSlug: wpaths.projectSlug,
|
|
8160
8957
|
projectRoot,
|
|
8161
|
-
projectName:
|
|
8958
|
+
projectName: path17.basename(projectRoot),
|
|
8162
8959
|
workingDir,
|
|
8163
8960
|
clientType: "webui",
|
|
8164
8961
|
pid: process.pid,
|
|
@@ -8178,7 +8975,7 @@ async function startWebUI(opts = {}) {
|
|
|
8178
8975
|
const hqTelemetry = createHqPublisherFromEnv({
|
|
8179
8976
|
clientKind: "webui",
|
|
8180
8977
|
projectRoot,
|
|
8181
|
-
projectName:
|
|
8978
|
+
projectName: path17.basename(projectRoot),
|
|
8182
8979
|
appConfig: config,
|
|
8183
8980
|
socketFactory: (url) => new WebSocket2(url)
|
|
8184
8981
|
});
|
|
@@ -8190,7 +8987,7 @@ async function startWebUI(opts = {}) {
|
|
|
8190
8987
|
events,
|
|
8191
8988
|
sessionId: session.id,
|
|
8192
8989
|
projectRoot,
|
|
8193
|
-
projectName:
|
|
8990
|
+
projectName: path17.basename(projectRoot),
|
|
8194
8991
|
globalRoot: wpaths.globalRoot,
|
|
8195
8992
|
initialAgents: statusTracker?.getAgents(),
|
|
8196
8993
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8246,19 +9043,39 @@ async function startWebUI(opts = {}) {
|
|
|
8246
9043
|
};
|
|
8247
9044
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
8248
9045
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
8249
|
-
manifestPath:
|
|
8250
|
-
projectSkillsDir:
|
|
8251
|
-
globalSkillsDir:
|
|
9046
|
+
manifestPath: path17.join(wstackGlobalRoot3(), "installed-skills.json"),
|
|
9047
|
+
projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
|
|
9048
|
+
globalSkillsDir: path17.join(wstackGlobalRoot3(), "skills"),
|
|
8252
9049
|
projectHash: projectHash(projectRoot),
|
|
8253
9050
|
skillLoader
|
|
8254
9051
|
}) : void 0;
|
|
9052
|
+
const promptsEnabled = config.features.prompts !== false;
|
|
9053
|
+
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
9054
|
+
try {
|
|
9055
|
+
const req = createRequire2(import.meta.url);
|
|
9056
|
+
return path17.join(
|
|
9057
|
+
path17.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
9058
|
+
"data",
|
|
9059
|
+
"prompts"
|
|
9060
|
+
);
|
|
9061
|
+
} catch {
|
|
9062
|
+
return void 0;
|
|
9063
|
+
}
|
|
9064
|
+
})() : void 0;
|
|
9065
|
+
const promptLoader = promptsEnabled ? new DefaultPromptLoader({ paths: wpaths, bundledDir: bundledPromptsDir }) : void 0;
|
|
9066
|
+
const promptUsage = new PromptUsageStore(wpaths.promptUsage);
|
|
9067
|
+
const promptsCtx = { promptLoader, promptUsage };
|
|
8255
9068
|
const systemPromptBuilder = new DefaultSystemPromptBuilder3({
|
|
8256
9069
|
memoryStore,
|
|
8257
9070
|
skillLoader,
|
|
8258
9071
|
modeStore,
|
|
8259
9072
|
modeId,
|
|
8260
9073
|
modePrompt,
|
|
8261
|
-
modelCapabilities: () => modelCapabilitiesRef.current
|
|
9074
|
+
modelCapabilities: () => modelCapabilitiesRef.current,
|
|
9075
|
+
instructionPaths: {
|
|
9076
|
+
globalDir: wpaths.globalInstructions,
|
|
9077
|
+
projectDir: wpaths.inProjectInstructions
|
|
9078
|
+
}
|
|
8262
9079
|
});
|
|
8263
9080
|
let onlineAgents = [];
|
|
8264
9081
|
try {
|
|
@@ -8353,6 +9170,10 @@ async function startWebUI(opts = {}) {
|
|
|
8353
9170
|
context.meta["enhanceLanguage"] = autonomyCfg["enhanceLanguage"] ?? "original";
|
|
8354
9171
|
context.meta["nextPrediction"] = config.nextPrediction ?? false;
|
|
8355
9172
|
context.meta["fallbackModels"] = config.fallbackModels ?? [];
|
|
9173
|
+
context.meta["fallbackProfiles"] = config.fallbackProfiles ?? {};
|
|
9174
|
+
context.meta["favoriteModels"] = config.favoriteModels ?? [];
|
|
9175
|
+
context.meta["favoriteModelsOnly"] = config.favoriteModelsOnly === true;
|
|
9176
|
+
context.meta["modelMatrix"] = config.modelMatrix ?? {};
|
|
8356
9177
|
context.meta["fallbackAuto"] = config.fallbackAuto !== false;
|
|
8357
9178
|
context.meta["featureMcp"] = config.features.mcp !== false;
|
|
8358
9179
|
context.meta["featurePlugins"] = config.features.plugins !== false;
|
|
@@ -8365,6 +9186,20 @@ async function startWebUI(opts = {}) {
|
|
|
8365
9186
|
context.meta["logLevel"] = config.log?.level ?? "info";
|
|
8366
9187
|
context.meta["auditLevel"] = config.session?.auditLevel ?? "standard";
|
|
8367
9188
|
context.meta["maxIterations"] = config.tools?.maxIterations ?? 500;
|
|
9189
|
+
context.meta["contextMode"] = config.context?.mode ?? "balanced";
|
|
9190
|
+
{
|
|
9191
|
+
const tsm = config.features?.tokenSavingMode;
|
|
9192
|
+
context.meta["tokenSavingTier"] = typeof tsm === "string" ? tsm : tsm ? "medium" : "off";
|
|
9193
|
+
}
|
|
9194
|
+
context.meta["maxConcurrent"] = typeof config.maxConcurrent === "number" ? config.maxConcurrent : 10;
|
|
9195
|
+
context.meta["titleAnimation"] = autonomyCfg["terminalTitleAnimation"] !== false;
|
|
9196
|
+
{
|
|
9197
|
+
const mr = config.modelRuntime ?? {};
|
|
9198
|
+
context.meta["reasoningMode"] = mr.reasoning?.mode ?? "auto";
|
|
9199
|
+
context.meta["reasoningEffort"] = mr.reasoning?.effort ?? "high";
|
|
9200
|
+
context.meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
|
|
9201
|
+
context.meta["cacheTtl"] = mr.cache?.ttl ?? "default";
|
|
9202
|
+
}
|
|
8368
9203
|
const hqConfig = config.hq;
|
|
8369
9204
|
context.meta["hqEnabled"] = hqConfig?.enabled === true;
|
|
8370
9205
|
context.meta["hqUrl"] = hqConfig?.url ?? "";
|
|
@@ -8398,6 +9233,10 @@ async function startWebUI(opts = {}) {
|
|
|
8398
9233
|
"indexOnStart",
|
|
8399
9234
|
"contextAutoCompact",
|
|
8400
9235
|
"contextStrategy",
|
|
9236
|
+
"contextMode",
|
|
9237
|
+
"tokenSavingTier",
|
|
9238
|
+
"maxConcurrent",
|
|
9239
|
+
"titleAnimation",
|
|
8401
9240
|
"logLevel",
|
|
8402
9241
|
"auditLevel",
|
|
8403
9242
|
"hqEnabled",
|
|
@@ -8413,6 +9252,10 @@ async function startWebUI(opts = {}) {
|
|
|
8413
9252
|
"reasoningPreserve",
|
|
8414
9253
|
"cacheTtl",
|
|
8415
9254
|
"fallbackModels",
|
|
9255
|
+
"fallbackProfiles",
|
|
9256
|
+
"favoriteModels",
|
|
9257
|
+
"favoriteModelsOnly",
|
|
9258
|
+
"modelMatrix",
|
|
8416
9259
|
"fallbackAuto"
|
|
8417
9260
|
];
|
|
8418
9261
|
const prefSnapshot = () => {
|
|
@@ -8445,6 +9288,15 @@ async function startWebUI(opts = {}) {
|
|
|
8445
9288
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
8446
9289
|
if (typeof payload["nextPrediction"] === "boolean") decrypted.nextPrediction = payload["nextPrediction"];
|
|
8447
9290
|
if (Array.isArray(payload["fallbackModels"])) decrypted.fallbackModels = payload["fallbackModels"];
|
|
9291
|
+
if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
|
|
9292
|
+
decrypted.fallbackProfiles = payload["fallbackProfiles"];
|
|
9293
|
+
}
|
|
9294
|
+
if (Array.isArray(payload["favoriteModels"])) decrypted.favoriteModels = payload["favoriteModels"];
|
|
9295
|
+
if (typeof payload["favoriteModelsOnly"] === "boolean")
|
|
9296
|
+
decrypted.favoriteModelsOnly = payload["favoriteModelsOnly"];
|
|
9297
|
+
if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
|
|
9298
|
+
decrypted.modelMatrix = payload["modelMatrix"];
|
|
9299
|
+
}
|
|
8448
9300
|
if (typeof payload["fallbackAuto"] === "boolean") decrypted.fallbackAuto = payload["fallbackAuto"];
|
|
8449
9301
|
const FEATURE_MAP = {
|
|
8450
9302
|
featureMcp: "mcp",
|
|
@@ -8460,12 +9312,26 @@ async function startWebUI(opts = {}) {
|
|
|
8460
9312
|
decrypted.features = feats;
|
|
8461
9313
|
}
|
|
8462
9314
|
}
|
|
8463
|
-
if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string") {
|
|
9315
|
+
if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string" || typeof payload["contextMode"] === "string") {
|
|
8464
9316
|
const ctxCfg = decrypted.context ?? {};
|
|
8465
9317
|
if (typeof payload["contextAutoCompact"] === "boolean") ctxCfg.autoCompact = payload["contextAutoCompact"];
|
|
8466
9318
|
if (typeof payload["contextStrategy"] === "string") ctxCfg.strategy = payload["contextStrategy"];
|
|
9319
|
+
if (typeof payload["contextMode"] === "string") ctxCfg.mode = payload["contextMode"];
|
|
8467
9320
|
decrypted.context = ctxCfg;
|
|
8468
9321
|
}
|
|
9322
|
+
if (typeof payload["tokenSavingTier"] === "string") {
|
|
9323
|
+
const featsCfg = decrypted.features ?? {};
|
|
9324
|
+
featsCfg.tokenSavingMode = payload["tokenSavingTier"];
|
|
9325
|
+
decrypted.features = featsCfg;
|
|
9326
|
+
}
|
|
9327
|
+
if (typeof payload["maxConcurrent"] === "number") {
|
|
9328
|
+
decrypted.maxConcurrent = payload["maxConcurrent"];
|
|
9329
|
+
}
|
|
9330
|
+
if (typeof payload["titleAnimation"] === "boolean") {
|
|
9331
|
+
const autoCfg = decrypted.autonomy ?? {};
|
|
9332
|
+
autoCfg.terminalTitleAnimation = payload["titleAnimation"];
|
|
9333
|
+
decrypted.autonomy = autoCfg;
|
|
9334
|
+
}
|
|
8469
9335
|
if (typeof payload["logLevel"] === "string") {
|
|
8470
9336
|
const logCfg = decrypted.log ?? {};
|
|
8471
9337
|
logCfg.level = payload["logLevel"];
|
|
@@ -8536,6 +9402,7 @@ async function startWebUI(opts = {}) {
|
|
|
8536
9402
|
const collabInject = collabInjectMiddleware(collabBus, { logger });
|
|
8537
9403
|
Object.defineProperty(collabInject, "name", { value: "collab-inject" });
|
|
8538
9404
|
pipelines.toolCall.prepend(collabInject);
|
|
9405
|
+
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
8539
9406
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
8540
9407
|
config,
|
|
8541
9408
|
context,
|
|
@@ -8631,6 +9498,17 @@ async function startWebUI(opts = {}) {
|
|
|
8631
9498
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
8632
9499
|
tracer: void 0
|
|
8633
9500
|
});
|
|
9501
|
+
const webuiLogger = container.resolve(TOKENS.Logger);
|
|
9502
|
+
void discoverMailboxBridgeForWebui({
|
|
9503
|
+
projectRoot,
|
|
9504
|
+
config,
|
|
9505
|
+
logger: webuiLogger,
|
|
9506
|
+
ctx: context
|
|
9507
|
+
}).catch((err) => {
|
|
9508
|
+
webuiLogger.warn("mailbox bridge discovery threw on webui boot", {
|
|
9509
|
+
err: err instanceof Error ? err.message : String(err)
|
|
9510
|
+
});
|
|
9511
|
+
});
|
|
8634
9512
|
const agent = new Agent({
|
|
8635
9513
|
container,
|
|
8636
9514
|
tools: toolRegistry,
|
|
@@ -8727,7 +9605,18 @@ async function startWebUI(opts = {}) {
|
|
|
8727
9605
|
projectRoot
|
|
8728
9606
|
);
|
|
8729
9607
|
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
8730
|
-
const sddBoardHandler = new SddBoardWebSocketHandler(wpaths.projectSddBoards
|
|
9608
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(wpaths.projectSddBoards, void 0, {
|
|
9609
|
+
projectRoot,
|
|
9610
|
+
paths: {
|
|
9611
|
+
projectSpecs: wpaths.projectSpecs,
|
|
9612
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
9613
|
+
projectSddSession: wpaths.projectSddSession,
|
|
9614
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
9615
|
+
}
|
|
9616
|
+
});
|
|
9617
|
+
void cleanupStaleSddWorktrees3({ projectRoot, boardsDir: wpaths.projectSddBoards }).catch(
|
|
9618
|
+
() => void 0
|
|
9619
|
+
);
|
|
8731
9620
|
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
8732
9621
|
buildSddWizardDeps({
|
|
8733
9622
|
agent,
|
|
@@ -8749,7 +9638,10 @@ async function startWebUI(opts = {}) {
|
|
|
8749
9638
|
}
|
|
8750
9639
|
})
|
|
8751
9640
|
);
|
|
8752
|
-
const worktreeHandler = new WorktreeWebSocketHandler(events, logger
|
|
9641
|
+
const worktreeHandler = new WorktreeWebSocketHandler(events, logger, {
|
|
9642
|
+
projectRoot,
|
|
9643
|
+
boardsDir: wpaths.projectSddBoards
|
|
9644
|
+
});
|
|
8753
9645
|
const terminalHandler = new TerminalWebSocketHandler(() => workingDir, logger);
|
|
8754
9646
|
const collabHandler = new CollaborationWebSocketHandler(
|
|
8755
9647
|
events,
|
|
@@ -8788,7 +9680,7 @@ async function startWebUI(opts = {}) {
|
|
|
8788
9680
|
inputCost,
|
|
8789
9681
|
outputCost,
|
|
8790
9682
|
cacheReadCost,
|
|
8791
|
-
projectName:
|
|
9683
|
+
projectName: path17.basename(projectRoot) || projectRoot,
|
|
8792
9684
|
projectRoot,
|
|
8793
9685
|
cwd: workingDir,
|
|
8794
9686
|
mode: modeId,
|
|
@@ -8937,8 +9829,8 @@ async function startWebUI(opts = {}) {
|
|
|
8937
9829
|
clients.delete(ws);
|
|
8938
9830
|
if (closing) rateLimits.delete(closing.connId);
|
|
8939
9831
|
if (pendingConfirms.size > 0) {
|
|
8940
|
-
for (const [id,
|
|
8941
|
-
|
|
9832
|
+
for (const [id, resolve10] of pendingConfirms) {
|
|
9833
|
+
resolve10("no");
|
|
8942
9834
|
pendingConfirms.delete(id);
|
|
8943
9835
|
}
|
|
8944
9836
|
}
|
|
@@ -9018,21 +9910,21 @@ async function startWebUI(opts = {}) {
|
|
|
9018
9910
|
});
|
|
9019
9911
|
}
|
|
9020
9912
|
async function touchProjectEntry(root, workDir) {
|
|
9021
|
-
const resolved =
|
|
9913
|
+
const resolved = path17.resolve(root);
|
|
9022
9914
|
const manifest = await loadManifest(globalConfigPath);
|
|
9023
9915
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9024
|
-
const existing = manifest.projects.find((p) =>
|
|
9916
|
+
const existing = manifest.projects.find((p) => path17.resolve(p.root) === resolved);
|
|
9025
9917
|
if (existing) {
|
|
9026
9918
|
existing.lastSeen = now;
|
|
9027
|
-
if (workDir) existing.lastWorkingDir =
|
|
9919
|
+
if (workDir) existing.lastWorkingDir = path17.resolve(workDir);
|
|
9028
9920
|
} else {
|
|
9029
9921
|
manifest.projects.push({
|
|
9030
|
-
name:
|
|
9922
|
+
name: path17.basename(resolved),
|
|
9031
9923
|
root: resolved,
|
|
9032
9924
|
slug: generateProjectSlug(resolved),
|
|
9033
9925
|
createdAt: now,
|
|
9034
9926
|
lastSeen: now,
|
|
9035
|
-
lastWorkingDir: workDir ?
|
|
9927
|
+
lastWorkingDir: workDir ? path17.resolve(workDir) : void 0
|
|
9036
9928
|
});
|
|
9037
9929
|
}
|
|
9038
9930
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -9077,6 +9969,8 @@ async function startWebUI(opts = {}) {
|
|
|
9077
9969
|
if (await handleSpecsRoute(ws, msg, specsRoutes)) return;
|
|
9078
9970
|
if (await handleSddBoardRoute(ws, msg, sddBoardRoutes)) return;
|
|
9079
9971
|
if (await handleSddWizardRoute(ws, msg, sddWizardRoutes)) return;
|
|
9972
|
+
if (msg.type.startsWith("worktree.") && await worktreeHandler.handleMessage(msg))
|
|
9973
|
+
return;
|
|
9080
9974
|
switch (msg.type) {
|
|
9081
9975
|
// Collaboration messages short-circuit the user/agent flow.
|
|
9082
9976
|
// They don't touch runLock, the agent loop, or the message queue —
|
|
@@ -9147,10 +10041,10 @@ async function startWebUI(opts = {}) {
|
|
|
9147
10041
|
}
|
|
9148
10042
|
case "tool.confirm_result": {
|
|
9149
10043
|
const { id, decision } = msg.payload;
|
|
9150
|
-
const
|
|
9151
|
-
if (
|
|
10044
|
+
const resolve10 = pendingConfirms.get(id);
|
|
10045
|
+
if (resolve10) {
|
|
9152
10046
|
pendingConfirms.delete(id);
|
|
9153
|
-
|
|
10047
|
+
resolve10(decision);
|
|
9154
10048
|
}
|
|
9155
10049
|
break;
|
|
9156
10050
|
}
|
|
@@ -9234,6 +10128,48 @@ async function startWebUI(opts = {}) {
|
|
|
9234
10128
|
case "skills.export":
|
|
9235
10129
|
await handleSkillsExport(ws, { skillLoader, skillInstaller, projectRoot });
|
|
9236
10130
|
break;
|
|
10131
|
+
// Prompt library — shared handlers (prompts-handlers.ts).
|
|
10132
|
+
case "prompts.list":
|
|
10133
|
+
await handlePromptsList(ws, promptsCtx);
|
|
10134
|
+
break;
|
|
10135
|
+
case "prompts.search":
|
|
10136
|
+
await handlePromptsSearch(ws, promptsCtx, msg);
|
|
10137
|
+
break;
|
|
10138
|
+
case "prompts.content":
|
|
10139
|
+
await handlePromptsContent(ws, promptsCtx, msg);
|
|
10140
|
+
break;
|
|
10141
|
+
case "prompts.favorite":
|
|
10142
|
+
await handlePromptsFavorite(ws, promptsCtx, msg);
|
|
10143
|
+
break;
|
|
10144
|
+
case "prompts.create":
|
|
10145
|
+
await handlePromptsCreate(ws, promptsCtx, msg);
|
|
10146
|
+
break;
|
|
10147
|
+
case "prompts.used":
|
|
10148
|
+
await handlePromptsUsed(ws, promptsCtx, msg);
|
|
10149
|
+
break;
|
|
10150
|
+
case "prompts.recent":
|
|
10151
|
+
await handlePromptsRecent(ws, promptsCtx);
|
|
10152
|
+
break;
|
|
10153
|
+
// Design Studio — shared handlers (design-handlers.ts). agentMeta is the
|
|
10154
|
+
// live context so design.use pins the active kit for the next turn.
|
|
10155
|
+
case "design.list":
|
|
10156
|
+
await handleDesignList(ws, { projectRoot, agentMeta: context });
|
|
10157
|
+
break;
|
|
10158
|
+
case "design.use":
|
|
10159
|
+
await handleDesignUse(ws, { projectRoot, agentMeta: context }, msg);
|
|
10160
|
+
break;
|
|
10161
|
+
case "design.state":
|
|
10162
|
+
await handleDesignState(ws, { projectRoot, agentMeta: context });
|
|
10163
|
+
break;
|
|
10164
|
+
case "design.set":
|
|
10165
|
+
await handleDesignSet(ws, { projectRoot, agentMeta: context }, msg);
|
|
10166
|
+
break;
|
|
10167
|
+
case "design.materialize":
|
|
10168
|
+
await handleDesignMaterialize(ws, { projectRoot, agentMeta: context }, msg);
|
|
10169
|
+
break;
|
|
10170
|
+
case "design.verify":
|
|
10171
|
+
await handleDesignVerify(ws, { projectRoot, agentMeta: context });
|
|
10172
|
+
break;
|
|
9237
10173
|
case "diag.get": {
|
|
9238
10174
|
const usage = tokenCounter.total();
|
|
9239
10175
|
send(ws, {
|
|
@@ -9314,11 +10250,29 @@ async function startWebUI(opts = {}) {
|
|
|
9314
10250
|
messages: context.messages.length,
|
|
9315
10251
|
readFiles: context.readFiles.size,
|
|
9316
10252
|
tools: toolRegistry.list().length,
|
|
10253
|
+
sideEffectCount: context.sideEffects?.length ?? 0,
|
|
9317
10254
|
elapsedMs: Date.now() - sessionStartedAt
|
|
9318
10255
|
}
|
|
9319
10256
|
});
|
|
9320
10257
|
break;
|
|
9321
10258
|
}
|
|
10259
|
+
case "side_effects.list": {
|
|
10260
|
+
const sideEffects = context.sideEffects ?? [];
|
|
10261
|
+
send(ws, {
|
|
10262
|
+
type: "side_effects",
|
|
10263
|
+
payload: {
|
|
10264
|
+
sideEffects: sideEffects.slice(-50).map((se) => ({
|
|
10265
|
+
toolUseId: se.toolUseId,
|
|
10266
|
+
toolName: se.toolName,
|
|
10267
|
+
ts: se.ts,
|
|
10268
|
+
input: se.input,
|
|
10269
|
+
outcome: se.outcome,
|
|
10270
|
+
risk: se.risk
|
|
10271
|
+
}))
|
|
10272
|
+
}
|
|
10273
|
+
});
|
|
10274
|
+
break;
|
|
10275
|
+
}
|
|
9322
10276
|
case "process.list": {
|
|
9323
10277
|
await handleProcessList(ws);
|
|
9324
10278
|
break;
|
|
@@ -9573,6 +10527,7 @@ async function startWebUI(opts = {}) {
|
|
|
9573
10527
|
toolRegistry,
|
|
9574
10528
|
config,
|
|
9575
10529
|
projectRoot,
|
|
10530
|
+
globalRoot: wpaths.globalRoot,
|
|
9576
10531
|
clients,
|
|
9577
10532
|
setModeId: (id) => {
|
|
9578
10533
|
modeId = id;
|
|
@@ -9609,6 +10564,16 @@ async function startWebUI(opts = {}) {
|
|
|
9609
10564
|
config.features.modelsRegistry = payload["featureModelsRegistry"];
|
|
9610
10565
|
if (Array.isArray(payload["fallbackModels"]))
|
|
9611
10566
|
config.fallbackModels = payload["fallbackModels"];
|
|
10567
|
+
if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
|
|
10568
|
+
config.fallbackProfiles = payload["fallbackProfiles"];
|
|
10569
|
+
}
|
|
10570
|
+
if (Array.isArray(payload["favoriteModels"]))
|
|
10571
|
+
config.favoriteModels = payload["favoriteModels"];
|
|
10572
|
+
if (typeof payload["favoriteModelsOnly"] === "boolean")
|
|
10573
|
+
config.favoriteModelsOnly = payload["favoriteModelsOnly"];
|
|
10574
|
+
if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
|
|
10575
|
+
config.modelMatrix = payload["modelMatrix"];
|
|
10576
|
+
}
|
|
9612
10577
|
if (typeof payload["fallbackAuto"] === "boolean")
|
|
9613
10578
|
config.fallbackAuto = payload["fallbackAuto"];
|
|
9614
10579
|
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
@@ -9660,7 +10625,7 @@ async function startWebUI(opts = {}) {
|
|
|
9660
10625
|
sendResult2(ws, false, parsed.message);
|
|
9661
10626
|
return;
|
|
9662
10627
|
}
|
|
9663
|
-
return handleMailboxMessages(ws, { projectRoot, globalRoot:
|
|
10628
|
+
return handleMailboxMessages(ws, { projectRoot, globalRoot: path17.dirname(globalConfigPath) }, parsed.value);
|
|
9664
10629
|
},
|
|
9665
10630
|
agents: (ws, msg) => {
|
|
9666
10631
|
const parsed = validateMailboxAgentsPayload(msg.payload);
|
|
@@ -9668,16 +10633,16 @@ async function startWebUI(opts = {}) {
|
|
|
9668
10633
|
sendResult2(ws, false, parsed.message);
|
|
9669
10634
|
return;
|
|
9670
10635
|
}
|
|
9671
|
-
return handleMailboxAgents(ws, { projectRoot, globalRoot:
|
|
10636
|
+
return handleMailboxAgents(ws, { projectRoot, globalRoot: path17.dirname(globalConfigPath) }, parsed.value);
|
|
9672
10637
|
},
|
|
9673
|
-
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot:
|
|
10638
|
+
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot: path17.dirname(globalConfigPath) }),
|
|
9674
10639
|
purge: (ws, msg) => {
|
|
9675
10640
|
const parsed = validateMailboxPurgePayload(msg.payload);
|
|
9676
10641
|
if (!parsed.ok) {
|
|
9677
10642
|
sendResult2(ws, false, parsed.message);
|
|
9678
10643
|
return;
|
|
9679
10644
|
}
|
|
9680
|
-
return handleMailboxPurge(ws, { projectRoot, globalRoot:
|
|
10645
|
+
return handleMailboxPurge(ws, { projectRoot, globalRoot: path17.dirname(globalConfigPath) }, parsed.value);
|
|
9681
10646
|
}
|
|
9682
10647
|
};
|
|
9683
10648
|
mcpRoutes = {
|
|
@@ -9757,7 +10722,7 @@ async function startWebUI(opts = {}) {
|
|
|
9757
10722
|
};
|
|
9758
10723
|
const httpServer = createHttpServer({
|
|
9759
10724
|
host: wsHost,
|
|
9760
|
-
distDir:
|
|
10725
|
+
distDir: path17.resolve(import.meta.dirname, "../../dist"),
|
|
9761
10726
|
wsPort,
|
|
9762
10727
|
publicWsUrl,
|
|
9763
10728
|
globalRoot: wpaths.globalRoot,
|
|
@@ -9768,7 +10733,7 @@ async function startWebUI(opts = {}) {
|
|
|
9768
10733
|
void fleetBroadcast?.();
|
|
9769
10734
|
}
|
|
9770
10735
|
});
|
|
9771
|
-
const registryBaseDir =
|
|
10736
|
+
const registryBaseDir = path17.dirname(globalConfigPath);
|
|
9772
10737
|
httpServer.listen(httpPort, wsHost, () => {
|
|
9773
10738
|
const openUrl = buildWebUIAccessUrl({
|
|
9774
10739
|
host: wsHost,
|
|
@@ -9785,7 +10750,7 @@ async function startWebUI(opts = {}) {
|
|
|
9785
10750
|
wsPort,
|
|
9786
10751
|
host: wsHost,
|
|
9787
10752
|
projectRoot,
|
|
9788
|
-
projectName:
|
|
10753
|
+
projectName: path17.basename(projectRoot) || projectRoot,
|
|
9789
10754
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9790
10755
|
url: buildWebUIAccessUrl({ host: wsHost, port: httpPort, publicUrl })
|
|
9791
10756
|
},
|