@wrongstack/webui 0.265.1 → 0.267.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-UZtAQH-v.css +2 -0
- package/dist/assets/index-q6W9UOEF.js +121 -0
- package/dist/assets/{vendor-BX-wfDR9.js → vendor-HxGAEBey.js} +230 -230
- package/dist/index.html +3 -3
- package/dist/index.js +3331 -2562
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +493 -365
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.d.ts +50 -24
- package/dist/server/index.js +512 -382
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +29 -0
- package/package.json +6 -5
- package/dist/assets/index-CC3KdGXG.js +0 -118
- package/dist/assets/index-CFHDQU3T.css +0 -2
package/dist/server/entry.js
CHANGED
|
@@ -182,8 +182,8 @@ import {
|
|
|
182
182
|
createAutonomyBrain,
|
|
183
183
|
createTieredBrainArbiter
|
|
184
184
|
} from "@wrongstack/core";
|
|
185
|
-
import * as
|
|
186
|
-
import * as
|
|
185
|
+
import * as fs9 from "fs/promises";
|
|
186
|
+
import * as path9 from "path";
|
|
187
187
|
|
|
188
188
|
// src/server/http-server.ts
|
|
189
189
|
import * as fs from "fs/promises";
|
|
@@ -225,8 +225,8 @@ function extractTokenFromCookie(cookieHeader) {
|
|
|
225
225
|
for (const part of raw.split(";")) {
|
|
226
226
|
const eq = part.indexOf("=");
|
|
227
227
|
if (eq < 0) continue;
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
228
|
+
const name2 = part.slice(0, eq).trim();
|
|
229
|
+
if (name2 === "ws_token") {
|
|
230
230
|
try {
|
|
231
231
|
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
232
232
|
} catch {
|
|
@@ -931,8 +931,8 @@ var KEEP_DOTFILES = /* @__PURE__ */ new Set([
|
|
|
931
931
|
".eslintrc",
|
|
932
932
|
".prettierrc"
|
|
933
933
|
]);
|
|
934
|
-
function isHiddenEntry(
|
|
935
|
-
return
|
|
934
|
+
function isHiddenEntry(name2) {
|
|
935
|
+
return name2.startsWith(".") && !KEEP_DOTFILES.has(name2);
|
|
936
936
|
}
|
|
937
937
|
function rankFiles(paths, query, limit) {
|
|
938
938
|
const q = query.toLowerCase();
|
|
@@ -1144,245 +1144,218 @@ async function handleMemoryForget(ws, msg, memoryStore) {
|
|
|
1144
1144
|
}
|
|
1145
1145
|
|
|
1146
1146
|
// src/server/mcp-handlers.ts
|
|
1147
|
-
import
|
|
1148
|
-
import
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1147
|
+
import { allServers } from "@wrongstack/core";
|
|
1148
|
+
import {
|
|
1149
|
+
addMcp,
|
|
1150
|
+
disableMcp,
|
|
1151
|
+
discoverMcp,
|
|
1152
|
+
enableMcp,
|
|
1153
|
+
listMcp,
|
|
1154
|
+
removeMcp,
|
|
1155
|
+
restartMcp,
|
|
1156
|
+
updateMcp
|
|
1157
|
+
} from "@wrongstack/mcp";
|
|
1158
|
+
function mapStatus(raw) {
|
|
1159
|
+
switch (raw) {
|
|
1160
|
+
case "connected":
|
|
1161
|
+
return "connected";
|
|
1162
|
+
case "connecting":
|
|
1163
|
+
case "reconnecting":
|
|
1164
|
+
return "connecting";
|
|
1165
|
+
case "failed":
|
|
1166
|
+
return "error";
|
|
1167
|
+
case "dormant":
|
|
1168
|
+
return "sleeping";
|
|
1169
|
+
default:
|
|
1170
|
+
return "stopped";
|
|
1171
|
+
}
|
|
1152
1172
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
name,
|
|
1156
|
-
transport:
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1173
|
+
function toView(info) {
|
|
1174
|
+
const view = {
|
|
1175
|
+
name: info.name,
|
|
1176
|
+
transport: info.transport,
|
|
1177
|
+
// A dormant lazy server is "asleep", not stopped — preserve that even when
|
|
1178
|
+
// it's enabled in config.
|
|
1179
|
+
status: info.status === "dormant" ? "sleeping" : info.enabled === false ? "stopped" : mapStatus(info.status),
|
|
1180
|
+
enabled: info.enabled,
|
|
1181
|
+
tools: info.tools
|
|
1161
1182
|
};
|
|
1183
|
+
if (info.description !== void 0) view.description = info.description;
|
|
1184
|
+
if (info.lazy !== void 0) view.lazy = info.lazy;
|
|
1185
|
+
return view;
|
|
1162
1186
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1187
|
+
function deps(ws, globalConfigPath, registry) {
|
|
1188
|
+
if (!registry || !globalConfigPath) {
|
|
1189
|
+
send(ws, {
|
|
1190
|
+
type: "mcp.operation_result",
|
|
1191
|
+
payload: { success: false, message: "MCP registry is not available in this session." }
|
|
1192
|
+
});
|
|
1193
|
+
return null;
|
|
1169
1194
|
}
|
|
1195
|
+
return { configPath: globalConfigPath, registry, presets: allServers() };
|
|
1170
1196
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
await fs3.mkdir(dir, { recursive: true });
|
|
1174
|
-
await fs3.writeFile(configPath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
1197
|
+
function name(msg) {
|
|
1198
|
+
return msg.payload?.name ?? "";
|
|
1175
1199
|
}
|
|
1176
|
-
async function
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
servers.push(projectServer(name, cfg));
|
|
1200
|
+
async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
|
|
1201
|
+
if (!mcpRegistry || !globalConfigPath) {
|
|
1202
|
+
send(ws, { type: "mcp.list", payload: { servers: [] } });
|
|
1203
|
+
return;
|
|
1181
1204
|
}
|
|
1182
|
-
|
|
1205
|
+
const servers = await listMcp({
|
|
1206
|
+
configPath: globalConfigPath,
|
|
1207
|
+
registry: mcpRegistry,
|
|
1208
|
+
presets: allServers()
|
|
1209
|
+
});
|
|
1210
|
+
send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
|
|
1183
1211
|
}
|
|
1184
|
-
function
|
|
1185
|
-
const
|
|
1186
|
-
if (!
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1212
|
+
async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1213
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1214
|
+
if (!d) return;
|
|
1215
|
+
const result = await addMcp(msg.payload, d);
|
|
1216
|
+
if (result.ok && result.server) {
|
|
1217
|
+
send(ws, { type: "mcp.server.added", payload: { server: toView(result.server) } });
|
|
1218
|
+
if (result.registryError) {
|
|
1219
|
+
send(ws, {
|
|
1220
|
+
type: "mcp.server.error",
|
|
1221
|
+
payload: { name: result.server.name, error: result.registryError }
|
|
1222
|
+
});
|
|
1223
|
+
} else if (result.server.enabled) {
|
|
1224
|
+
send(ws, { type: "mcp.server.connected", payload: { name: result.server.name } });
|
|
1191
1225
|
}
|
|
1192
|
-
} catch {
|
|
1193
1226
|
}
|
|
1194
|
-
|
|
1227
|
+
send(ws, {
|
|
1228
|
+
type: "mcp.operation_result",
|
|
1229
|
+
payload: { success: result.ok, message: result.message }
|
|
1230
|
+
});
|
|
1195
1231
|
}
|
|
1196
|
-
async function
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
server.status = registryState.state;
|
|
1203
|
-
server.tools = Array.from({ length: registryState.toolCount }, (_, i) => `tool-${i + 1}`);
|
|
1204
|
-
}
|
|
1232
|
+
async function handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1233
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1234
|
+
if (!d) return;
|
|
1235
|
+
const result = await updateMcp(msg.payload, d);
|
|
1236
|
+
if (result.ok && result.server) {
|
|
1237
|
+
send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
|
|
1205
1238
|
}
|
|
1206
|
-
send(ws, {
|
|
1239
|
+
send(ws, {
|
|
1240
|
+
type: "mcp.operation_result",
|
|
1241
|
+
payload: { success: result.ok, message: result.message }
|
|
1242
|
+
});
|
|
1207
1243
|
}
|
|
1208
|
-
async function
|
|
1209
|
-
const
|
|
1210
|
-
if (!
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
try {
|
|
1215
|
-
const diskConfig = await readConfig(globalConfigPath);
|
|
1216
|
-
const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
|
|
1217
|
-
if (mcpServers[payload.name]) {
|
|
1218
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" already exists` } });
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
mcpServers[payload.name] = {
|
|
1222
|
-
transport: payload.transport,
|
|
1223
|
-
description: payload.description,
|
|
1224
|
-
enabled: payload.enabled ?? true,
|
|
1225
|
-
command: payload.command,
|
|
1226
|
-
args: payload.args,
|
|
1227
|
-
env: payload.env,
|
|
1228
|
-
allowedTools: payload.allowedTools
|
|
1229
|
-
};
|
|
1230
|
-
diskConfig.mcpServers = mcpServers;
|
|
1231
|
-
await writeConfig(globalConfigPath, diskConfig);
|
|
1232
|
-
const newServer = projectServer(payload.name, mcpServers[payload.name]);
|
|
1233
|
-
send(ws, { type: "mcp.server.added", payload: { server: newServer } });
|
|
1234
|
-
if (mcpRegistry && (payload.enabled ?? true)) {
|
|
1235
|
-
const serverConfig = mcpServers[payload.name];
|
|
1236
|
-
try {
|
|
1237
|
-
await mcpRegistry.start({
|
|
1238
|
-
name: payload.name,
|
|
1239
|
-
transport: payload.transport,
|
|
1240
|
-
command: payload.command,
|
|
1241
|
-
args: payload.args,
|
|
1242
|
-
env: payload.env,
|
|
1243
|
-
allowedTools: payload.allowedTools,
|
|
1244
|
-
enabled: true
|
|
1245
|
-
});
|
|
1246
|
-
} catch (err) {
|
|
1247
|
-
send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" added` } });
|
|
1251
|
-
} catch (err) {
|
|
1252
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to add server: ${err}` } });
|
|
1244
|
+
async function handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1245
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1246
|
+
if (!d) return;
|
|
1247
|
+
const result = await removeMcp(name(msg), d);
|
|
1248
|
+
if (result.ok) {
|
|
1249
|
+
send(ws, { type: "mcp.server.removed", payload: { name: name(msg) } });
|
|
1253
1250
|
}
|
|
1251
|
+
send(ws, {
|
|
1252
|
+
type: "mcp.operation_result",
|
|
1253
|
+
payload: { success: result.ok, message: result.message }
|
|
1254
|
+
});
|
|
1254
1255
|
}
|
|
1255
|
-
async function
|
|
1256
|
-
const
|
|
1257
|
-
if (!
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
const diskConfig = await readConfig(globalConfigPath);
|
|
1269
|
-
const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
|
|
1270
|
-
if (!mcpServers[payload.name]) {
|
|
1271
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" not found` } });
|
|
1272
|
-
return;
|
|
1256
|
+
async function handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1257
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1258
|
+
if (!d) return;
|
|
1259
|
+
const result = await enableMcp(name(msg), d);
|
|
1260
|
+
if (result.ok && result.server) {
|
|
1261
|
+
send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
|
|
1262
|
+
if (result.registryError) {
|
|
1263
|
+
send(ws, {
|
|
1264
|
+
type: "mcp.server.error",
|
|
1265
|
+
payload: { name: name(msg), error: result.registryError }
|
|
1266
|
+
});
|
|
1267
|
+
} else {
|
|
1268
|
+
send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
|
|
1273
1269
|
}
|
|
1274
|
-
delete mcpServers[payload.name];
|
|
1275
|
-
diskConfig.mcpServers = mcpServers;
|
|
1276
|
-
await writeConfig(globalConfigPath, diskConfig);
|
|
1277
|
-
send(ws, { type: "mcp.server.removed", payload: { name: payload.name } });
|
|
1278
|
-
send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" removed` } });
|
|
1279
|
-
} catch (err) {
|
|
1280
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to remove server: ${err}` } });
|
|
1281
1270
|
}
|
|
1271
|
+
send(ws, {
|
|
1272
|
+
type: "mcp.operation_result",
|
|
1273
|
+
payload: { success: result.ok, message: result.message }
|
|
1274
|
+
});
|
|
1282
1275
|
}
|
|
1283
|
-
async function
|
|
1284
|
-
const
|
|
1285
|
-
if (!
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
|
|
1292
|
-
if (!mcpServers[payload.name]) {
|
|
1293
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" not found` } });
|
|
1294
|
-
return;
|
|
1276
|
+
async function handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1277
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1278
|
+
if (!d) return;
|
|
1279
|
+
const result = await disableMcp(name(msg), d);
|
|
1280
|
+
if (result.ok) {
|
|
1281
|
+
send(ws, { type: "mcp.server.sleeping", payload: { name: name(msg) } });
|
|
1282
|
+
if (result.server) {
|
|
1283
|
+
send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
|
|
1295
1284
|
}
|
|
1296
|
-
const existing = mcpServers[payload.name];
|
|
1297
|
-
mcpServers[payload.name] = {
|
|
1298
|
-
transport: payload.transport ?? existing.transport,
|
|
1299
|
-
description: payload.description ?? existing.description,
|
|
1300
|
-
enabled: payload.enabled ?? existing.enabled,
|
|
1301
|
-
command: payload.command ?? existing.command,
|
|
1302
|
-
args: payload.args ?? existing.args,
|
|
1303
|
-
env: payload.env ?? existing.env,
|
|
1304
|
-
allowedTools: payload.allowedTools ?? existing.allowedTools
|
|
1305
|
-
};
|
|
1306
|
-
diskConfig.mcpServers = mcpServers;
|
|
1307
|
-
await writeConfig(globalConfigPath, diskConfig);
|
|
1308
|
-
const updatedServer = projectServer(payload.name, mcpServers[payload.name]);
|
|
1309
|
-
send(ws, { type: "mcp.server.updated", payload: { server: updatedServer } });
|
|
1310
|
-
send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" updated` } });
|
|
1311
|
-
} catch (err) {
|
|
1312
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to update server: ${err}` } });
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
async function handleMcpWake(ws, msg, _config, _globalConfigPath, mcpRegistry) {
|
|
1316
|
-
const payload = msg.payload;
|
|
1317
|
-
if (!payload.name) {
|
|
1318
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
if (!mcpRegistry) {
|
|
1322
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: "MCP registry not available" } });
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
try {
|
|
1326
|
-
send(ws, { type: "mcp.server.waking", payload: { name: payload.name } });
|
|
1327
|
-
await mcpRegistry.restart(payload.name);
|
|
1328
|
-
send(ws, { type: "mcp.server.connected", payload: { name: payload.name } });
|
|
1329
|
-
send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" restarted` } });
|
|
1330
|
-
} catch (err) {
|
|
1331
|
-
send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
|
|
1332
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to restart "${payload.name}": ${err}` } });
|
|
1333
1285
|
}
|
|
1286
|
+
send(ws, {
|
|
1287
|
+
type: "mcp.operation_result",
|
|
1288
|
+
payload: { success: result.ok, message: result.message }
|
|
1289
|
+
});
|
|
1334
1290
|
}
|
|
1335
|
-
async function handleMcpSleep(ws, msg,
|
|
1336
|
-
const
|
|
1337
|
-
if (!
|
|
1338
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
if (!mcpRegistry) {
|
|
1342
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: "MCP registry not available" } });
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1291
|
+
async function handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1292
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1293
|
+
if (!d) return;
|
|
1345
1294
|
try {
|
|
1346
|
-
await
|
|
1347
|
-
send(ws, { type: "mcp.server.sleeping", payload: { name:
|
|
1348
|
-
send(ws, {
|
|
1295
|
+
await d.registry.stop(name(msg));
|
|
1296
|
+
send(ws, { type: "mcp.server.sleeping", payload: { name: name(msg) } });
|
|
1297
|
+
send(ws, {
|
|
1298
|
+
type: "mcp.operation_result",
|
|
1299
|
+
payload: { success: true, message: `Server "${name(msg)}" stopped` }
|
|
1300
|
+
});
|
|
1349
1301
|
} catch (err) {
|
|
1350
|
-
|
|
1351
|
-
send(ws, { type: "mcp.
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
if (!payload.name) {
|
|
1357
|
-
send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
|
|
1358
|
-
return;
|
|
1302
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
1303
|
+
send(ws, { type: "mcp.server.error", payload: { name: name(msg), error } });
|
|
1304
|
+
send(ws, {
|
|
1305
|
+
type: "mcp.operation_result",
|
|
1306
|
+
payload: { success: false, message: `Failed to stop "${name(msg)}": ${error}` }
|
|
1307
|
+
});
|
|
1359
1308
|
}
|
|
1360
|
-
send(ws, { type: "mcp.server.discovered", payload: { name: payload.name, tools: [] } });
|
|
1361
|
-
send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" tools were discovered on connect` } });
|
|
1362
1309
|
}
|
|
1363
|
-
async function
|
|
1364
|
-
const
|
|
1365
|
-
if (!
|
|
1366
|
-
|
|
1367
|
-
|
|
1310
|
+
async function handleMcpWake(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1311
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1312
|
+
if (!d) return;
|
|
1313
|
+
send(ws, { type: "mcp.server.waking", payload: { name: name(msg) } });
|
|
1314
|
+
const result = await restartMcp(name(msg), d);
|
|
1315
|
+
if (result.ok && !result.registryError) {
|
|
1316
|
+
send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
|
|
1317
|
+
} else if (result.registryError) {
|
|
1318
|
+
send(ws, {
|
|
1319
|
+
type: "mcp.server.error",
|
|
1320
|
+
payload: { name: name(msg), error: result.registryError }
|
|
1321
|
+
});
|
|
1368
1322
|
}
|
|
1369
|
-
send(ws, {
|
|
1323
|
+
send(ws, {
|
|
1324
|
+
type: "mcp.operation_result",
|
|
1325
|
+
payload: { success: result.ok, message: result.message }
|
|
1326
|
+
});
|
|
1370
1327
|
}
|
|
1371
|
-
async function
|
|
1372
|
-
const
|
|
1373
|
-
if (!
|
|
1374
|
-
|
|
1375
|
-
|
|
1328
|
+
async function handleMcpRestart(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1329
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1330
|
+
if (!d) return;
|
|
1331
|
+
const result = await restartMcp(name(msg), d);
|
|
1332
|
+
if (result.ok && !result.registryError) {
|
|
1333
|
+
send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
|
|
1334
|
+
} else if (result.registryError) {
|
|
1335
|
+
send(ws, {
|
|
1336
|
+
type: "mcp.server.error",
|
|
1337
|
+
payload: { name: name(msg), error: result.registryError }
|
|
1338
|
+
});
|
|
1376
1339
|
}
|
|
1377
|
-
send(ws, {
|
|
1340
|
+
send(ws, {
|
|
1341
|
+
type: "mcp.operation_result",
|
|
1342
|
+
payload: { success: result.ok, message: result.message }
|
|
1343
|
+
});
|
|
1378
1344
|
}
|
|
1379
|
-
async function
|
|
1380
|
-
const
|
|
1381
|
-
if (!
|
|
1382
|
-
|
|
1383
|
-
|
|
1345
|
+
async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
|
|
1346
|
+
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
1347
|
+
if (!d) return;
|
|
1348
|
+
const result = await discoverMcp(name(msg), d);
|
|
1349
|
+
if (result.ok) {
|
|
1350
|
+
send(ws, {
|
|
1351
|
+
type: "mcp.server.discovered",
|
|
1352
|
+
payload: { name: name(msg), tools: result.tools ?? [] }
|
|
1353
|
+
});
|
|
1384
1354
|
}
|
|
1385
|
-
send(ws, {
|
|
1355
|
+
send(ws, {
|
|
1356
|
+
type: "mcp.operation_result",
|
|
1357
|
+
payload: { success: result.ok, message: result.message }
|
|
1358
|
+
});
|
|
1386
1359
|
}
|
|
1387
1360
|
|
|
1388
1361
|
// src/server/index.ts
|
|
@@ -1418,12 +1391,14 @@ import {
|
|
|
1418
1391
|
repairToolUseAdjacency,
|
|
1419
1392
|
resolveContextWindowPolicy,
|
|
1420
1393
|
enhanceUserPrompt,
|
|
1421
|
-
recentTextTurns
|
|
1394
|
+
recentTextTurns,
|
|
1395
|
+
resolveProviderModelList
|
|
1422
1396
|
} from "@wrongstack/core";
|
|
1423
1397
|
import { ToolExecutor } from "@wrongstack/core/execution";
|
|
1424
1398
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
1425
1399
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
|
|
1426
1400
|
import { builtinToolsPack, forgetTool, rememberTool, searchMemoryTool, relatedMemoryTool } from "@wrongstack/tools";
|
|
1401
|
+
import { MCPRegistry } from "@wrongstack/mcp";
|
|
1427
1402
|
import { WebSocketServer } from "ws";
|
|
1428
1403
|
|
|
1429
1404
|
// ../runtime/src/container.ts
|
|
@@ -2670,9 +2645,9 @@ var WorktreeWebSocketHandler = class {
|
|
|
2670
2645
|
|
|
2671
2646
|
// src/server/mailbox-handlers.ts
|
|
2672
2647
|
import { GlobalMailbox, resolveProjectDir } from "@wrongstack/core";
|
|
2673
|
-
async function handleMailboxMessages(ws,
|
|
2648
|
+
async function handleMailboxMessages(ws, deps2, payload) {
|
|
2674
2649
|
try {
|
|
2675
|
-
const dir = resolveProjectDir(
|
|
2650
|
+
const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
|
|
2676
2651
|
const mb = new GlobalMailbox(dir);
|
|
2677
2652
|
const messages = await mb.query({
|
|
2678
2653
|
limit: payload?.limit ?? 30,
|
|
@@ -2706,9 +2681,9 @@ async function handleMailboxMessages(ws, deps, payload) {
|
|
|
2706
2681
|
send(ws, { type: "mailbox.messages", payload: { messages: [], error: errMessage(err) } });
|
|
2707
2682
|
}
|
|
2708
2683
|
}
|
|
2709
|
-
async function handleMailboxAgents(ws,
|
|
2684
|
+
async function handleMailboxAgents(ws, deps2, payload) {
|
|
2710
2685
|
try {
|
|
2711
|
-
const dir = resolveProjectDir(
|
|
2686
|
+
const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
|
|
2712
2687
|
const mb = new GlobalMailbox(dir);
|
|
2713
2688
|
const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
|
|
2714
2689
|
send(ws, {
|
|
@@ -2735,9 +2710,9 @@ async function handleMailboxAgents(ws, deps, payload) {
|
|
|
2735
2710
|
send(ws, { type: "mailbox.agents", payload: { agents: [], error: errMessage(err) } });
|
|
2736
2711
|
}
|
|
2737
2712
|
}
|
|
2738
|
-
async function handleMailboxClear(ws,
|
|
2713
|
+
async function handleMailboxClear(ws, deps2) {
|
|
2739
2714
|
try {
|
|
2740
|
-
const dir = resolveProjectDir(
|
|
2715
|
+
const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
|
|
2741
2716
|
const mb = new GlobalMailbox(dir);
|
|
2742
2717
|
await mb.clearAll();
|
|
2743
2718
|
send(ws, { type: "mailbox.cleared", payload: {} });
|
|
@@ -2745,9 +2720,9 @@ async function handleMailboxClear(ws, deps) {
|
|
|
2745
2720
|
send(ws, { type: "mailbox.cleared", payload: { error: errMessage(err) } });
|
|
2746
2721
|
}
|
|
2747
2722
|
}
|
|
2748
|
-
async function handleMailboxPurge(ws,
|
|
2723
|
+
async function handleMailboxPurge(ws, deps2, opts) {
|
|
2749
2724
|
try {
|
|
2750
|
-
const dir = resolveProjectDir(
|
|
2725
|
+
const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
|
|
2751
2726
|
const mb = new GlobalMailbox(dir);
|
|
2752
2727
|
const result = await mb.purgeStale(opts);
|
|
2753
2728
|
send(ws, { type: "mailbox.purged", payload: result });
|
|
@@ -2794,14 +2769,14 @@ function registerShutdownHandlers(res) {
|
|
|
2794
2769
|
|
|
2795
2770
|
// src/server/instance-registry.ts
|
|
2796
2771
|
import * as os from "os";
|
|
2797
|
-
import * as
|
|
2798
|
-
import * as
|
|
2772
|
+
import * as path3 from "path";
|
|
2773
|
+
import * as fs3 from "fs/promises";
|
|
2799
2774
|
import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
|
|
2800
2775
|
function defaultBaseDir() {
|
|
2801
|
-
return
|
|
2776
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
2802
2777
|
}
|
|
2803
2778
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
2804
|
-
return
|
|
2779
|
+
return path3.join(baseDir, "webui-instances.json");
|
|
2805
2780
|
}
|
|
2806
2781
|
function isPidAlive(pid) {
|
|
2807
2782
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2814,7 +2789,7 @@ function isPidAlive(pid) {
|
|
|
2814
2789
|
}
|
|
2815
2790
|
async function load(file) {
|
|
2816
2791
|
try {
|
|
2817
|
-
const raw = await
|
|
2792
|
+
const raw = await fs3.readFile(file, "utf8");
|
|
2818
2793
|
const parsed = JSON.parse(raw);
|
|
2819
2794
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
2820
2795
|
return parsed;
|
|
@@ -2962,15 +2937,15 @@ import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/cor
|
|
|
2962
2937
|
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
2963
2938
|
|
|
2964
2939
|
// src/server/provider-config-io.ts
|
|
2965
|
-
import * as
|
|
2966
|
-
import * as
|
|
2940
|
+
import * as fs4 from "fs/promises";
|
|
2941
|
+
import * as path4 from "path";
|
|
2967
2942
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
2968
2943
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
2969
2944
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
2970
2945
|
async function loadSavedProviders(configPath, vault) {
|
|
2971
2946
|
let raw;
|
|
2972
2947
|
try {
|
|
2973
|
-
raw = await
|
|
2948
|
+
raw = await fs4.readFile(configPath, "utf8");
|
|
2974
2949
|
} catch {
|
|
2975
2950
|
return {};
|
|
2976
2951
|
}
|
|
@@ -2987,7 +2962,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
2987
2962
|
let raw;
|
|
2988
2963
|
let fileExists = true;
|
|
2989
2964
|
try {
|
|
2990
|
-
raw = await
|
|
2965
|
+
raw = await fs4.readFile(configPath, "utf8");
|
|
2991
2966
|
} catch (err) {
|
|
2992
2967
|
if (err.code !== "ENOENT") {
|
|
2993
2968
|
throw new Error(
|
|
@@ -3137,9 +3112,9 @@ function projectSavedProviders(providers) {
|
|
|
3137
3112
|
});
|
|
3138
3113
|
}
|
|
3139
3114
|
var probeScrubber = new DefaultSecretScrubber2();
|
|
3140
|
-
function createProviderHandlers(
|
|
3141
|
-
const { globalConfigPath, vault, broadcast: broadcast2, clients } =
|
|
3142
|
-
let configWriteLock =
|
|
3115
|
+
function createProviderHandlers(deps2) {
|
|
3116
|
+
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
3117
|
+
let configWriteLock = deps2.getConfigWriteLock();
|
|
3143
3118
|
async function loadConfigProviders() {
|
|
3144
3119
|
return loadSavedProviders(globalConfigPath, vault);
|
|
3145
3120
|
}
|
|
@@ -3154,7 +3129,7 @@ function createProviderHandlers(deps) {
|
|
|
3154
3129
|
}));
|
|
3155
3130
|
});
|
|
3156
3131
|
configWriteLock = next;
|
|
3157
|
-
|
|
3132
|
+
deps2.setConfigWriteLock(next);
|
|
3158
3133
|
await next;
|
|
3159
3134
|
}
|
|
3160
3135
|
async function handleKeyUpsert(ws, providerId, label, apiKey) {
|
|
@@ -3310,11 +3285,11 @@ function createProviderHandlers(deps) {
|
|
|
3310
3285
|
}
|
|
3311
3286
|
|
|
3312
3287
|
// src/server/setup-events.ts
|
|
3313
|
-
import * as
|
|
3288
|
+
import * as fs5 from "fs/promises";
|
|
3314
3289
|
import { watch as fsWatch } from "fs";
|
|
3315
|
-
import * as
|
|
3316
|
-
function setupEvents(
|
|
3317
|
-
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } =
|
|
3290
|
+
import * as path5 from "path";
|
|
3291
|
+
function setupEvents(deps2) {
|
|
3292
|
+
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
3318
3293
|
const disposers = [];
|
|
3319
3294
|
events.on("iteration.started", (e) => {
|
|
3320
3295
|
const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
|
|
@@ -3521,16 +3496,16 @@ function setupEvents(deps) {
|
|
|
3521
3496
|
if (wpaths?.projectStatus) {
|
|
3522
3497
|
try {
|
|
3523
3498
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
3524
|
-
const dir =
|
|
3525
|
-
await
|
|
3526
|
-
await
|
|
3499
|
+
const dir = path5.dirname(statusFile);
|
|
3500
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
3501
|
+
await fs5.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
3527
3502
|
} catch (err) {
|
|
3528
3503
|
console.error("[setup-events] Failed to write status.json:", err);
|
|
3529
3504
|
}
|
|
3530
3505
|
}
|
|
3531
3506
|
});
|
|
3532
3507
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
3533
|
-
const projectsDir =
|
|
3508
|
+
const projectsDir = path5.join(wpaths.configDir, "projects");
|
|
3534
3509
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
3535
3510
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
3536
3511
|
const DEBOUNCE_MS = 150;
|
|
@@ -3593,20 +3568,20 @@ function setupEvents(deps) {
|
|
|
3593
3568
|
let watcher;
|
|
3594
3569
|
const startWatcher = async () => {
|
|
3595
3570
|
try {
|
|
3596
|
-
await
|
|
3571
|
+
await fs5.mkdir(projectsDir, { recursive: true });
|
|
3597
3572
|
watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
|
|
3598
3573
|
if (eventType === "change") {
|
|
3599
3574
|
if (filename == null) return;
|
|
3600
3575
|
if (watcherMetrics) watcherMetrics.fileChangesDetected++;
|
|
3601
|
-
const targetFile =
|
|
3576
|
+
const targetFile = path5.join(projectsDir, String(filename));
|
|
3602
3577
|
if (targetFile.endsWith("status.json")) {
|
|
3603
|
-
const projectHash2 =
|
|
3578
|
+
const projectHash2 = path5.basename(path5.dirname(targetFile));
|
|
3604
3579
|
if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash2)) {
|
|
3605
3580
|
return;
|
|
3606
3581
|
}
|
|
3607
3582
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
3608
3583
|
try {
|
|
3609
|
-
const content = await
|
|
3584
|
+
const content = await fs5.readFile(targetFile, "utf-8");
|
|
3610
3585
|
const statusData = JSON.parse(content);
|
|
3611
3586
|
if (statusData.projectHash) {
|
|
3612
3587
|
const hash = String(statusData.projectHash);
|
|
@@ -3658,7 +3633,7 @@ function setupEvents(deps) {
|
|
|
3658
3633
|
}
|
|
3659
3634
|
});
|
|
3660
3635
|
}
|
|
3661
|
-
const globalRoot = globalConfigPath ?
|
|
3636
|
+
const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
|
|
3662
3637
|
if (globalRoot) {
|
|
3663
3638
|
const broadcastSessions = async () => {
|
|
3664
3639
|
try {
|
|
@@ -3706,8 +3681,8 @@ function setupEvents(deps) {
|
|
|
3706
3681
|
let regDebounce;
|
|
3707
3682
|
try {
|
|
3708
3683
|
const regWatcher = fsWatch(globalRoot, { persistent: false }, (_event, filename) => {
|
|
3709
|
-
const
|
|
3710
|
-
if (!
|
|
3684
|
+
const name2 = filename ? String(filename) : "";
|
|
3685
|
+
if (!name2.startsWith("session-registry.json") || name2.endsWith(".lock")) return;
|
|
3711
3686
|
if (regDebounce) clearTimeout(regDebounce);
|
|
3712
3687
|
regDebounce = setTimeout(() => void broadcastSessions(), 150);
|
|
3713
3688
|
});
|
|
@@ -3731,11 +3706,11 @@ function setupEvents(deps) {
|
|
|
3731
3706
|
|
|
3732
3707
|
// src/server/custom-context-modes.ts
|
|
3733
3708
|
import { listContextWindowModes, atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
3734
|
-
import * as
|
|
3735
|
-
import * as
|
|
3709
|
+
import * as fs6 from "fs/promises";
|
|
3710
|
+
import * as path6 from "path";
|
|
3736
3711
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
3737
3712
|
function storePath(wrongstackDir) {
|
|
3738
|
-
return
|
|
3713
|
+
return path6.join(wrongstackDir, STORE_FILENAME);
|
|
3739
3714
|
}
|
|
3740
3715
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
3741
3716
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -3743,7 +3718,7 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
3743
3718
|
const load2 = async () => {
|
|
3744
3719
|
modes.clear();
|
|
3745
3720
|
try {
|
|
3746
|
-
const raw = await
|
|
3721
|
+
const raw = await fs6.readFile(storePath(wrongstackDir), "utf8");
|
|
3747
3722
|
const parsed = JSON.parse(raw);
|
|
3748
3723
|
if (Array.isArray(parsed.modes)) {
|
|
3749
3724
|
for (const m of parsed.modes) {
|
|
@@ -3923,14 +3898,14 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
3923
3898
|
}
|
|
3924
3899
|
|
|
3925
3900
|
// src/server/shell-open.ts
|
|
3926
|
-
import * as
|
|
3927
|
-
import * as
|
|
3901
|
+
import * as fs7 from "fs/promises";
|
|
3902
|
+
import * as path7 from "path";
|
|
3928
3903
|
import { spawn as spawn2 } from "child_process";
|
|
3929
3904
|
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
3930
3905
|
async function handleShellOpen(req, logger) {
|
|
3931
3906
|
try {
|
|
3932
|
-
const resolved =
|
|
3933
|
-
await
|
|
3907
|
+
const resolved = path7.resolve(req.path);
|
|
3908
|
+
await fs7.access(resolved);
|
|
3934
3909
|
if (METACHAR_REGEX.test(resolved)) {
|
|
3935
3910
|
return { success: false, message: "Path contains unsupported characters." };
|
|
3936
3911
|
}
|
|
@@ -4006,10 +3981,148 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
4006
3981
|
send(ws, { type: "git.info", payload: { branch: "", added: 0, deleted: 0, untracked: 0, ahead: 0, behind: 0 } });
|
|
4007
3982
|
}
|
|
4008
3983
|
}
|
|
3984
|
+
function makeGit(cwd) {
|
|
3985
|
+
return async (args) => {
|
|
3986
|
+
const { execFile: ef } = await import("child_process");
|
|
3987
|
+
return new Promise((resolve5) => {
|
|
3988
|
+
ef(
|
|
3989
|
+
"git",
|
|
3990
|
+
args,
|
|
3991
|
+
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
3992
|
+
(err, stdout) => resolve5(err ? "" : stdout)
|
|
3993
|
+
);
|
|
3994
|
+
});
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
async function handleGitChanges(ws, projectRoot) {
|
|
3998
|
+
const cwd = projectRoot || void 0;
|
|
3999
|
+
try {
|
|
4000
|
+
const git = makeGit(cwd);
|
|
4001
|
+
const [statusRaw, unstagedNumstat, stagedNumstat] = await Promise.all([
|
|
4002
|
+
git(["status", "--porcelain", "-z"]),
|
|
4003
|
+
git(["diff", "--numstat", "-z"]),
|
|
4004
|
+
git(["diff", "--cached", "--numstat", "-z"])
|
|
4005
|
+
]);
|
|
4006
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4007
|
+
const parseNumstat = (raw) => {
|
|
4008
|
+
const parts = raw.split("\0");
|
|
4009
|
+
for (let i = 0; i < parts.length; i++) {
|
|
4010
|
+
const entry = parts[i];
|
|
4011
|
+
if (!entry) continue;
|
|
4012
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.*)$/.exec(entry);
|
|
4013
|
+
if (!m) continue;
|
|
4014
|
+
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
4015
|
+
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
4016
|
+
let path10 = m[3] ?? "";
|
|
4017
|
+
if (path10 === "") {
|
|
4018
|
+
i += 1;
|
|
4019
|
+
path10 = parts[i + 1] ?? parts[i] ?? "";
|
|
4020
|
+
i += 1;
|
|
4021
|
+
}
|
|
4022
|
+
if (!path10) continue;
|
|
4023
|
+
const prev = counts.get(path10) ?? { added: 0, deleted: 0 };
|
|
4024
|
+
counts.set(path10, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
4027
|
+
parseNumstat(unstagedNumstat);
|
|
4028
|
+
parseNumstat(stagedNumstat);
|
|
4029
|
+
const records = statusRaw.split("\0").filter((r) => r.length > 0);
|
|
4030
|
+
const files = [];
|
|
4031
|
+
for (let i = 0; i < records.length; i++) {
|
|
4032
|
+
const rec = records[i];
|
|
4033
|
+
if (!rec || rec.length < 3) continue;
|
|
4034
|
+
const x = rec[0] ?? " ";
|
|
4035
|
+
const y = rec[1] ?? " ";
|
|
4036
|
+
const path10 = rec.slice(3);
|
|
4037
|
+
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
4038
|
+
if (isRename) i += 1;
|
|
4039
|
+
let status;
|
|
4040
|
+
if (x === "?" && y === "?") status = "?";
|
|
4041
|
+
else if (x === "U" || y === "U" || x === "A" && y === "A" || x === "D" && y === "D") status = "U";
|
|
4042
|
+
else if (x === "R" || y === "R") status = "R";
|
|
4043
|
+
else if (x === "C" || y === "C") status = "C";
|
|
4044
|
+
else if (x === "A" || y === "A") status = "A";
|
|
4045
|
+
else if (x === "D" || y === "D") status = "D";
|
|
4046
|
+
else status = "M";
|
|
4047
|
+
const staged = x !== " " && x !== "?";
|
|
4048
|
+
let added = counts.get(path10)?.added ?? 0;
|
|
4049
|
+
let deleted = counts.get(path10)?.deleted ?? 0;
|
|
4050
|
+
if (status === "?") {
|
|
4051
|
+
added = await countUntrackedLines(cwd, path10);
|
|
4052
|
+
deleted = 0;
|
|
4053
|
+
}
|
|
4054
|
+
files.push({ path: path10, status, added, deleted, staged });
|
|
4055
|
+
}
|
|
4056
|
+
send(ws, { type: "git.changes", payload: { files } });
|
|
4057
|
+
} catch (err) {
|
|
4058
|
+
send(ws, {
|
|
4059
|
+
type: "git.changes",
|
|
4060
|
+
payload: { files: [], error: err instanceof Error ? err.message : String(err) }
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
async function countUntrackedLines(cwd, relPath) {
|
|
4065
|
+
try {
|
|
4066
|
+
const { readFile: readFile8 } = await import("fs/promises");
|
|
4067
|
+
const { join: join8 } = await import("path");
|
|
4068
|
+
const abs = cwd ? join8(cwd, relPath) : relPath;
|
|
4069
|
+
const buf = await readFile8(abs);
|
|
4070
|
+
if (buf.includes(0)) return 0;
|
|
4071
|
+
if (buf.length === 0) return 0;
|
|
4072
|
+
let lines = 0;
|
|
4073
|
+
for (let i = 0; i < buf.length; i++) if (buf[i] === 10) lines++;
|
|
4074
|
+
if (buf[buf.length - 1] !== 10) lines++;
|
|
4075
|
+
return lines;
|
|
4076
|
+
} catch {
|
|
4077
|
+
return 0;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
4081
|
+
async function handleGitDiff(ws, projectRoot, path10) {
|
|
4082
|
+
const cwd = projectRoot || void 0;
|
|
4083
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path10, ...extra } });
|
|
4084
|
+
if (!path10 || path10.includes("\0") || path10.includes("..")) {
|
|
4085
|
+
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
try {
|
|
4089
|
+
const git = makeGit(cwd);
|
|
4090
|
+
const { readFile: readFile8 } = await import("fs/promises");
|
|
4091
|
+
const { join: join8 } = await import("path");
|
|
4092
|
+
const oldText = await git(["show", `HEAD:${path10}`]);
|
|
4093
|
+
let newText = "";
|
|
4094
|
+
try {
|
|
4095
|
+
const abs = cwd ? join8(cwd, path10) : path10;
|
|
4096
|
+
const buf = await readFile8(abs);
|
|
4097
|
+
if (buf.includes(0)) {
|
|
4098
|
+
reply({ oldText: "", newText: "", binary: true });
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
if (buf.length > MAX_DIFF_BYTES) {
|
|
4102
|
+
reply({ oldText: "", newText: "", tooLarge: true });
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
newText = buf.toString("utf8");
|
|
4106
|
+
} catch {
|
|
4107
|
+
newText = "";
|
|
4108
|
+
}
|
|
4109
|
+
if ((oldText.length || 0) > MAX_DIFF_BYTES) {
|
|
4110
|
+
reply({ oldText: "", newText: "", tooLarge: true });
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
if (oldText.includes("\0")) {
|
|
4114
|
+
reply({ oldText: "", newText: "", binary: true });
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
reply({ oldText, newText });
|
|
4118
|
+
} catch (err) {
|
|
4119
|
+
reply({ oldText: "", newText: "", error: err instanceof Error ? err.message : String(err) });
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4009
4122
|
|
|
4010
4123
|
// src/server/skills-handlers.ts
|
|
4011
|
-
import { promises as
|
|
4012
|
-
import
|
|
4124
|
+
import { promises as fs8 } from "fs";
|
|
4125
|
+
import path8 from "path";
|
|
4013
4126
|
import JSZip from "jszip";
|
|
4014
4127
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
4015
4128
|
|
|
@@ -4100,6 +4213,21 @@ async function startWebUI(opts = {}) {
|
|
|
4100
4213
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
4101
4214
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
4102
4215
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
4216
|
+
const mcpRegistry = new MCPRegistry({
|
|
4217
|
+
toolRegistry,
|
|
4218
|
+
events,
|
|
4219
|
+
log: logger,
|
|
4220
|
+
// Lazy-connect (per-server `lazy`) manifest cache + default idle auto-sleep.
|
|
4221
|
+
cacheDir: wpaths.cacheDir
|
|
4222
|
+
});
|
|
4223
|
+
if (config.features.mcp && config.mcpServers) {
|
|
4224
|
+
for (const [name2, cfg] of Object.entries(config.mcpServers)) {
|
|
4225
|
+
if (cfg.enabled === false) continue;
|
|
4226
|
+
void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
|
|
4227
|
+
logger.warn(`MCP server "${name2}" failed to start at boot`, err);
|
|
4228
|
+
});
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4103
4231
|
let sessionStore = opts.services?.session ?? new DefaultSessionStore2({ dir: wpaths.projectSessions });
|
|
4104
4232
|
if (!opts.services?.session) {
|
|
4105
4233
|
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
@@ -4127,7 +4255,7 @@ async function startWebUI(opts = {}) {
|
|
|
4127
4255
|
sessionId: session.id,
|
|
4128
4256
|
projectSlug: wpaths.projectSlug,
|
|
4129
4257
|
projectRoot,
|
|
4130
|
-
projectName:
|
|
4258
|
+
projectName: path9.basename(projectRoot),
|
|
4131
4259
|
workingDir,
|
|
4132
4260
|
clientType: "webui",
|
|
4133
4261
|
pid: process.pid,
|
|
@@ -4183,9 +4311,9 @@ async function startWebUI(opts = {}) {
|
|
|
4183
4311
|
} : void 0;
|
|
4184
4312
|
const skillLoader = config.features.skills ? new DefaultSkillLoader2({ paths: wpaths }) : void 0;
|
|
4185
4313
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
4186
|
-
manifestPath:
|
|
4187
|
-
projectSkillsDir:
|
|
4188
|
-
globalSkillsDir:
|
|
4314
|
+
manifestPath: path9.join(wstackGlobalRoot2(), "installed-skills.json"),
|
|
4315
|
+
projectSkillsDir: path9.join(projectRoot, ".wrongstack", "skills"),
|
|
4316
|
+
globalSkillsDir: path9.join(wstackGlobalRoot2(), "skills"),
|
|
4189
4317
|
projectHash: projectHash(projectRoot),
|
|
4190
4318
|
skillLoader
|
|
4191
4319
|
}) : void 0;
|
|
@@ -4346,7 +4474,7 @@ async function startWebUI(opts = {}) {
|
|
|
4346
4474
|
const write = async () => {
|
|
4347
4475
|
let raw;
|
|
4348
4476
|
try {
|
|
4349
|
-
raw = await
|
|
4477
|
+
raw = await fs9.readFile(globalConfigPath, "utf8");
|
|
4350
4478
|
} catch {
|
|
4351
4479
|
raw = "{}";
|
|
4352
4480
|
}
|
|
@@ -4655,7 +4783,7 @@ async function startWebUI(opts = {}) {
|
|
|
4655
4783
|
inputCost,
|
|
4656
4784
|
outputCost,
|
|
4657
4785
|
cacheReadCost,
|
|
4658
|
-
projectName:
|
|
4786
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
4659
4787
|
projectRoot,
|
|
4660
4788
|
cwd: workingDir,
|
|
4661
4789
|
mode: modeId,
|
|
@@ -4870,33 +4998,33 @@ async function startWebUI(opts = {}) {
|
|
|
4870
4998
|
});
|
|
4871
4999
|
}
|
|
4872
5000
|
async function touchProjectEntry(root, workDir) {
|
|
4873
|
-
const resolved =
|
|
5001
|
+
const resolved = path9.resolve(root);
|
|
4874
5002
|
const manifest = await loadManifest(globalConfigPath);
|
|
4875
5003
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4876
|
-
const existing = manifest.projects.find((p) =>
|
|
5004
|
+
const existing = manifest.projects.find((p) => path9.resolve(p.root) === resolved);
|
|
4877
5005
|
if (existing) {
|
|
4878
5006
|
existing.lastSeen = now;
|
|
4879
|
-
if (workDir) existing.lastWorkingDir =
|
|
5007
|
+
if (workDir) existing.lastWorkingDir = path9.resolve(workDir);
|
|
4880
5008
|
} else {
|
|
4881
5009
|
manifest.projects.push({
|
|
4882
|
-
name:
|
|
5010
|
+
name: path9.basename(resolved),
|
|
4883
5011
|
root: resolved,
|
|
4884
5012
|
slug: generateProjectSlug(resolved),
|
|
4885
5013
|
createdAt: now,
|
|
4886
5014
|
lastSeen: now,
|
|
4887
|
-
lastWorkingDir: workDir ?
|
|
5015
|
+
lastWorkingDir: workDir ? path9.resolve(workDir) : void 0
|
|
4888
5016
|
});
|
|
4889
5017
|
}
|
|
4890
5018
|
await saveManifest(manifest, globalConfigPath);
|
|
4891
5019
|
await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
|
|
4892
5020
|
}
|
|
4893
5021
|
function projectsJsonPath(globalConfigPath2) {
|
|
4894
|
-
const base =
|
|
4895
|
-
return
|
|
5022
|
+
const base = path9.dirname(globalConfigPath2);
|
|
5023
|
+
return path9.join(base, "projects.json");
|
|
4896
5024
|
}
|
|
4897
5025
|
async function loadManifest(globalConfigPath2) {
|
|
4898
5026
|
try {
|
|
4899
|
-
const raw = await
|
|
5027
|
+
const raw = await fs9.readFile(projectsJsonPath(globalConfigPath2), "utf8");
|
|
4900
5028
|
const parsed = JSON.parse(raw);
|
|
4901
5029
|
return { projects: parsed.projects ?? [] };
|
|
4902
5030
|
} catch {
|
|
@@ -4905,16 +5033,16 @@ async function startWebUI(opts = {}) {
|
|
|
4905
5033
|
}
|
|
4906
5034
|
async function saveManifest(manifest, globalConfigPath2) {
|
|
4907
5035
|
const file = projectsJsonPath(globalConfigPath2);
|
|
4908
|
-
await
|
|
4909
|
-
await
|
|
5036
|
+
await fs9.mkdir(path9.dirname(file), { recursive: true });
|
|
5037
|
+
await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
4910
5038
|
}
|
|
4911
5039
|
function generateProjectSlug(rootPath) {
|
|
4912
5040
|
return projectSlug(rootPath);
|
|
4913
5041
|
}
|
|
4914
5042
|
async function ensureProjectDataDir(slug, globalConfigPath2) {
|
|
4915
|
-
const base =
|
|
4916
|
-
const dir =
|
|
4917
|
-
await
|
|
5043
|
+
const base = path9.dirname(globalConfigPath2);
|
|
5044
|
+
const dir = path9.join(base, "projects", slug);
|
|
5045
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
4918
5046
|
return dir;
|
|
4919
5047
|
}
|
|
4920
5048
|
async function handleMessage(ws, _client, msg) {
|
|
@@ -5220,27 +5348,17 @@ async function startWebUI(opts = {}) {
|
|
|
5220
5348
|
}
|
|
5221
5349
|
case "provider.models": {
|
|
5222
5350
|
const providerId = msg.payload.providerId;
|
|
5223
|
-
const
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
inputCost: m.cost?.input,
|
|
5235
|
-
outputCost: m.cost?.output,
|
|
5236
|
-
capabilities: [
|
|
5237
|
-
...m.tool_call ? ["tools"] : [],
|
|
5238
|
-
...m.reasoning ? ["reasoning"] : []
|
|
5239
|
-
]
|
|
5240
|
-
}))
|
|
5241
|
-
}
|
|
5242
|
-
});
|
|
5243
|
-
}
|
|
5351
|
+
const saved = await providerHandlers.loadConfigProviders();
|
|
5352
|
+
const cfg = saved[providerId];
|
|
5353
|
+
const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
|
|
5354
|
+
const provider2 = await modelsRegistry.getProvider(catalogId);
|
|
5355
|
+
send(ws, {
|
|
5356
|
+
type: "provider.models",
|
|
5357
|
+
payload: {
|
|
5358
|
+
provider: providerId,
|
|
5359
|
+
models: resolveProviderModelList(cfg?.models, provider2)
|
|
5360
|
+
}
|
|
5361
|
+
});
|
|
5244
5362
|
break;
|
|
5245
5363
|
}
|
|
5246
5364
|
case "model.switch": {
|
|
@@ -5255,7 +5373,7 @@ async function startWebUI(opts = {}) {
|
|
|
5255
5373
|
updateAutoCompactionMaxContext?.(newProv);
|
|
5256
5374
|
try {
|
|
5257
5375
|
const next = configWriteLock.then(async () => {
|
|
5258
|
-
const raw = await
|
|
5376
|
+
const raw = await fs9.readFile(globalConfigPath, "utf8");
|
|
5259
5377
|
const parsed = JSON.parse(raw);
|
|
5260
5378
|
parsed.provider = newProvider;
|
|
5261
5379
|
parsed.model = newModel;
|
|
@@ -5489,27 +5607,28 @@ async function startWebUI(opts = {}) {
|
|
|
5489
5607
|
return handleMemoryRemember(ws, msg, memoryStore);
|
|
5490
5608
|
case "memory.forget":
|
|
5491
5609
|
return handleMemoryForget(ws, msg, memoryStore);
|
|
5492
|
-
// ── MCP operations — delegated to shared handlers (mcp-handlers.ts)
|
|
5610
|
+
// ── MCP operations — delegated to shared handlers (mcp-handlers.ts),
|
|
5611
|
+
// backed by the live MCPRegistry constructed above. ──
|
|
5493
5612
|
case "mcp.list":
|
|
5494
|
-
return handleMcpList(ws, msg,
|
|
5613
|
+
return handleMcpList(ws, msg, globalConfigPath, mcpRegistry);
|
|
5495
5614
|
case "mcp.add":
|
|
5496
|
-
return handleMcpAdd(ws, msg,
|
|
5615
|
+
return handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry);
|
|
5497
5616
|
case "mcp.remove":
|
|
5498
|
-
return handleMcpRemove(ws, msg,
|
|
5617
|
+
return handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry);
|
|
5499
5618
|
case "mcp.update":
|
|
5500
|
-
return handleMcpUpdate(ws, msg,
|
|
5619
|
+
return handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry);
|
|
5501
5620
|
case "mcp.wake":
|
|
5502
|
-
return handleMcpWake(ws, msg,
|
|
5621
|
+
return handleMcpWake(ws, msg, globalConfigPath, mcpRegistry);
|
|
5503
5622
|
case "mcp.sleep":
|
|
5504
|
-
return handleMcpSleep(ws, msg,
|
|
5623
|
+
return handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry);
|
|
5505
5624
|
case "mcp.discover":
|
|
5506
|
-
return handleMcpDiscover(ws, msg,
|
|
5625
|
+
return handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry);
|
|
5507
5626
|
case "mcp.enable":
|
|
5508
|
-
return handleMcpEnable(ws, msg,
|
|
5627
|
+
return handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry);
|
|
5509
5628
|
case "mcp.disable":
|
|
5510
|
-
return handleMcpDisable(ws, msg,
|
|
5629
|
+
return handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry);
|
|
5511
5630
|
case "mcp.restart":
|
|
5512
|
-
return handleMcpRestart(ws, msg,
|
|
5631
|
+
return handleMcpRestart(ws, msg, globalConfigPath, mcpRegistry);
|
|
5513
5632
|
case "skills.list": {
|
|
5514
5633
|
if (!skillLoader) {
|
|
5515
5634
|
send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
|
|
@@ -5571,33 +5690,33 @@ async function startWebUI(opts = {}) {
|
|
|
5571
5690
|
break;
|
|
5572
5691
|
}
|
|
5573
5692
|
try {
|
|
5574
|
-
const { name, source } = contentPayload;
|
|
5693
|
+
const { name: name2, source } = contentPayload;
|
|
5575
5694
|
const entries = await skillLoader.listEntries();
|
|
5576
|
-
const entry = entries.find((e) => e.name.toLowerCase() ===
|
|
5695
|
+
const entry = entries.find((e) => e.name.toLowerCase() === name2.toLowerCase());
|
|
5577
5696
|
if (!entry) {
|
|
5578
|
-
send(ws, { type: "skills.content", payload: { name, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${
|
|
5697
|
+
send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
|
|
5579
5698
|
break;
|
|
5580
5699
|
}
|
|
5581
|
-
const body = await skillLoader.readBody(
|
|
5582
|
-
const skillDir =
|
|
5700
|
+
const body = await skillLoader.readBody(name2);
|
|
5701
|
+
const skillDir = path9.dirname(entry.path);
|
|
5583
5702
|
let relatedFiles = [];
|
|
5584
5703
|
try {
|
|
5585
|
-
const files = await
|
|
5586
|
-
relatedFiles = files.filter((f) => f !==
|
|
5704
|
+
const files = await fs9.readdir(skillDir);
|
|
5705
|
+
relatedFiles = files.filter((f) => f !== path9.basename(entry.path)).map((f) => path9.join(skillDir, f));
|
|
5587
5706
|
} catch {
|
|
5588
5707
|
}
|
|
5589
5708
|
const refs = [];
|
|
5590
5709
|
for (const e of entries) {
|
|
5591
|
-
if (e.name.toLowerCase() ===
|
|
5710
|
+
if (e.name.toLowerCase() === name2.toLowerCase()) continue;
|
|
5592
5711
|
try {
|
|
5593
5712
|
const content = await skillLoader.readBody(e.name);
|
|
5594
|
-
if (content.toLowerCase().includes(
|
|
5713
|
+
if (content.toLowerCase().includes(name2.toLowerCase())) {
|
|
5595
5714
|
refs.push(e.name);
|
|
5596
5715
|
}
|
|
5597
5716
|
} catch {
|
|
5598
5717
|
}
|
|
5599
5718
|
}
|
|
5600
|
-
send(ws, { type: "skills.content", payload: { name, body, path: entry.path, source, relatedFiles, references: refs } });
|
|
5719
|
+
send(ws, { type: "skills.content", payload: { name: name2, body, path: entry.path, source, relatedFiles, references: refs } });
|
|
5601
5720
|
} catch (err) {
|
|
5602
5721
|
send(ws, { type: "skills.content", payload: { name: contentPayload.name, body: "", path: "", source: contentPayload.source, relatedFiles: [], references: [], error: errMessage(err) } });
|
|
5603
5722
|
}
|
|
@@ -5690,14 +5809,14 @@ async function startWebUI(opts = {}) {
|
|
|
5690
5809
|
break;
|
|
5691
5810
|
}
|
|
5692
5811
|
try {
|
|
5693
|
-
const targetDir = createPayload.scope === "global" ?
|
|
5812
|
+
const targetDir = createPayload.scope === "global" ? path9.join(wstackGlobalRoot2(), "skills", createPayload.name.trim()) : path9.join(projectRoot, ".wrongstack", "skills", createPayload.name.trim());
|
|
5694
5813
|
try {
|
|
5695
|
-
await
|
|
5814
|
+
await fs9.access(targetDir);
|
|
5696
5815
|
send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
|
|
5697
5816
|
break;
|
|
5698
5817
|
} catch {
|
|
5699
5818
|
}
|
|
5700
|
-
await
|
|
5819
|
+
await fs9.mkdir(targetDir, { recursive: true });
|
|
5701
5820
|
const lines = createPayload.description.trim().split("\n");
|
|
5702
5821
|
const firstLine = lines[0].trim();
|
|
5703
5822
|
const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
|
|
@@ -5745,13 +5864,13 @@ ${trigger}
|
|
|
5745
5864
|
"- `bug-hunter` \u2014 for systematic bug detection patterns",
|
|
5746
5865
|
"- `output-standards` \u2014 for standardized `<next_steps>` formatting"
|
|
5747
5866
|
].join("\n");
|
|
5748
|
-
await
|
|
5867
|
+
await fs9.writeFile(path9.join(targetDir, "SKILL.md"), skillContent, "utf-8");
|
|
5749
5868
|
send(ws, {
|
|
5750
5869
|
type: "skills.created",
|
|
5751
5870
|
payload: {
|
|
5752
5871
|
success: true,
|
|
5753
5872
|
error: null,
|
|
5754
|
-
skill: { name: createPayload.name.trim(), path:
|
|
5873
|
+
skill: { name: createPayload.name.trim(), path: path9.join(targetDir, "SKILL.md"), scope: createPayload.scope }
|
|
5755
5874
|
}
|
|
5756
5875
|
});
|
|
5757
5876
|
} catch (err) {
|
|
@@ -5784,7 +5903,7 @@ ${trigger}
|
|
|
5784
5903
|
send(ws, { type: "skills.edited", payload: { success: false, error: "Bundled skills cannot be edited" } });
|
|
5785
5904
|
break;
|
|
5786
5905
|
}
|
|
5787
|
-
await
|
|
5906
|
+
await fs9.writeFile(entry.path, editPayload.body, "utf-8");
|
|
5788
5907
|
send(ws, { type: "skills.edited", payload: { success: true, error: null } });
|
|
5789
5908
|
} catch (err) {
|
|
5790
5909
|
send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
|
|
@@ -6073,6 +6192,14 @@ ${trigger}
|
|
|
6073
6192
|
await handleGitInfo(ws, projectRoot);
|
|
6074
6193
|
break;
|
|
6075
6194
|
}
|
|
6195
|
+
case "git.changes": {
|
|
6196
|
+
await handleGitChanges(ws, projectRoot);
|
|
6197
|
+
break;
|
|
6198
|
+
}
|
|
6199
|
+
case "git.diff": {
|
|
6200
|
+
await handleGitDiff(ws, projectRoot, String(msg.payload?.path ?? ""));
|
|
6201
|
+
break;
|
|
6202
|
+
}
|
|
6076
6203
|
case "webui.shutdown": {
|
|
6077
6204
|
console.log("[WebUI] Shutdown requested from client");
|
|
6078
6205
|
process.kill(process.pid, "SIGINT");
|
|
@@ -6081,7 +6208,7 @@ ${trigger}
|
|
|
6081
6208
|
case "goal.get": {
|
|
6082
6209
|
try {
|
|
6083
6210
|
const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
|
|
6084
|
-
const raw = await
|
|
6211
|
+
const raw = await fs9.readFile(goalPath, "utf8");
|
|
6085
6212
|
const goal = JSON.parse(raw);
|
|
6086
6213
|
broadcast(clients, { type: "goal.updated", payload: goal });
|
|
6087
6214
|
} catch {
|
|
@@ -6141,7 +6268,7 @@ ${trigger}
|
|
|
6141
6268
|
try {
|
|
6142
6269
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
6143
6270
|
const rewinder = new DefaultSessionRewinder(
|
|
6144
|
-
|
|
6271
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
6145
6272
|
projectRoot
|
|
6146
6273
|
);
|
|
6147
6274
|
const checkpoints = await rewinder.listCheckpoints(session.id);
|
|
@@ -6162,7 +6289,7 @@ ${trigger}
|
|
|
6162
6289
|
try {
|
|
6163
6290
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
6164
6291
|
const rewinder = new DefaultSessionRewinder(
|
|
6165
|
-
|
|
6292
|
+
path9.join(projectRoot, ".wrongstack", "sessions"),
|
|
6166
6293
|
projectRoot
|
|
6167
6294
|
);
|
|
6168
6295
|
await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
|
|
@@ -6196,9 +6323,9 @@ ${trigger}
|
|
|
6196
6323
|
case "projects.add": {
|
|
6197
6324
|
const { root: addRoot, name: displayName } = msg.payload;
|
|
6198
6325
|
try {
|
|
6199
|
-
const resolved =
|
|
6200
|
-
await
|
|
6201
|
-
const stat2 = await
|
|
6326
|
+
const resolved = path9.resolve(addRoot);
|
|
6327
|
+
await fs9.access(resolved);
|
|
6328
|
+
const stat2 = await fs9.stat(resolved);
|
|
6202
6329
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
6203
6330
|
const manifest = await loadManifest(globalConfigPath);
|
|
6204
6331
|
const existing = manifest.projects.find((p) => p.root === resolved);
|
|
@@ -6214,26 +6341,26 @@ ${trigger}
|
|
|
6214
6341
|
});
|
|
6215
6342
|
break;
|
|
6216
6343
|
}
|
|
6217
|
-
const
|
|
6344
|
+
const name2 = displayName?.trim() || path9.basename(resolved);
|
|
6218
6345
|
const slug = generateProjectSlug(resolved);
|
|
6219
6346
|
await ensureProjectDataDir(slug, globalConfigPath);
|
|
6220
6347
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6221
|
-
manifest.projects.push({ name, root: resolved, slug, lastSeen: now, createdAt: now });
|
|
6348
|
+
manifest.projects.push({ name: name2, root: resolved, slug, lastSeen: now, createdAt: now });
|
|
6222
6349
|
await saveManifest(manifest, globalConfigPath);
|
|
6223
6350
|
send(ws, {
|
|
6224
6351
|
type: "projects.added",
|
|
6225
6352
|
payload: {
|
|
6226
|
-
name,
|
|
6353
|
+
name: name2,
|
|
6227
6354
|
root: resolved,
|
|
6228
6355
|
slug,
|
|
6229
|
-
message: `Registered project "${
|
|
6356
|
+
message: `Registered project "${name2}"`
|
|
6230
6357
|
}
|
|
6231
6358
|
});
|
|
6232
6359
|
} catch (err) {
|
|
6233
6360
|
send(ws, {
|
|
6234
6361
|
type: "projects.added",
|
|
6235
6362
|
payload: {
|
|
6236
|
-
name:
|
|
6363
|
+
name: path9.basename(addRoot),
|
|
6237
6364
|
root: addRoot,
|
|
6238
6365
|
slug: "",
|
|
6239
6366
|
message: errMessage(err)
|
|
@@ -6245,17 +6372,17 @@ ${trigger}
|
|
|
6245
6372
|
case "projects.select": {
|
|
6246
6373
|
const { root: selRoot, name: selName } = msg.payload;
|
|
6247
6374
|
try {
|
|
6248
|
-
const resolved =
|
|
6375
|
+
const resolved = path9.resolve(selRoot);
|
|
6249
6376
|
try {
|
|
6250
|
-
await
|
|
6251
|
-
const stat2 = await
|
|
6377
|
+
await fs9.access(resolved);
|
|
6378
|
+
const stat2 = await fs9.stat(resolved);
|
|
6252
6379
|
if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
6253
6380
|
} catch (err) {
|
|
6254
6381
|
send(ws, {
|
|
6255
6382
|
type: "projects.selected",
|
|
6256
6383
|
payload: {
|
|
6257
6384
|
root: selRoot,
|
|
6258
|
-
name: selName ||
|
|
6385
|
+
name: selName || path9.basename(selRoot),
|
|
6259
6386
|
message: `Cannot switch: ${errMessage(err)}`
|
|
6260
6387
|
}
|
|
6261
6388
|
});
|
|
@@ -6267,10 +6394,10 @@ ${trigger}
|
|
|
6267
6394
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
6268
6395
|
entry.lastWorkingDir = resolved;
|
|
6269
6396
|
} else {
|
|
6270
|
-
const
|
|
6397
|
+
const name2 = selName?.trim() || path9.basename(resolved);
|
|
6271
6398
|
const slug = generateProjectSlug(resolved);
|
|
6272
6399
|
manifest.projects.push({
|
|
6273
|
-
name,
|
|
6400
|
+
name: name2,
|
|
6274
6401
|
root: resolved,
|
|
6275
6402
|
slug,
|
|
6276
6403
|
lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6308,13 +6435,13 @@ ${trigger}
|
|
|
6308
6435
|
});
|
|
6309
6436
|
} catch {
|
|
6310
6437
|
}
|
|
6311
|
-
const newSessionsDir =
|
|
6312
|
-
|
|
6438
|
+
const newSessionsDir = path9.join(
|
|
6439
|
+
path9.dirname(globalConfigPath),
|
|
6313
6440
|
"projects",
|
|
6314
6441
|
switchSlug,
|
|
6315
6442
|
"sessions"
|
|
6316
6443
|
);
|
|
6317
|
-
await
|
|
6444
|
+
await fs9.mkdir(newSessionsDir, { recursive: true });
|
|
6318
6445
|
const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
|
|
6319
6446
|
const oldSessionId = session.id;
|
|
6320
6447
|
try {
|
|
@@ -6346,7 +6473,7 @@ ${trigger}
|
|
|
6346
6473
|
sessionId: session.id,
|
|
6347
6474
|
projectSlug: switchSlug,
|
|
6348
6475
|
projectRoot,
|
|
6349
|
-
projectName:
|
|
6476
|
+
projectName: path9.basename(projectRoot),
|
|
6350
6477
|
workingDir,
|
|
6351
6478
|
clientType: "webui",
|
|
6352
6479
|
pid: process.pid,
|
|
@@ -6358,8 +6485,8 @@ ${trigger}
|
|
|
6358
6485
|
type: "projects.selected",
|
|
6359
6486
|
payload: {
|
|
6360
6487
|
root: resolved,
|
|
6361
|
-
name: selName ||
|
|
6362
|
-
message: `Switched to ${selName ||
|
|
6488
|
+
name: selName || path9.basename(resolved),
|
|
6489
|
+
message: `Switched to ${selName || path9.basename(resolved)}`
|
|
6363
6490
|
}
|
|
6364
6491
|
});
|
|
6365
6492
|
broadcast(clients, {
|
|
@@ -6382,7 +6509,7 @@ ${trigger}
|
|
|
6382
6509
|
type: "projects.selected",
|
|
6383
6510
|
payload: {
|
|
6384
6511
|
root: selRoot,
|
|
6385
|
-
name: selName ||
|
|
6512
|
+
name: selName || path9.basename(selRoot),
|
|
6386
6513
|
message: errMessage(err)
|
|
6387
6514
|
}
|
|
6388
6515
|
});
|
|
@@ -6393,14 +6520,14 @@ ${trigger}
|
|
|
6393
6520
|
case "working_dir.set": {
|
|
6394
6521
|
const { path: newPath } = msg.payload;
|
|
6395
6522
|
try {
|
|
6396
|
-
const resolved =
|
|
6397
|
-
if (!resolved.startsWith(projectRoot +
|
|
6523
|
+
const resolved = path9.resolve(projectRoot, newPath);
|
|
6524
|
+
if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== projectRoot) {
|
|
6398
6525
|
sendResult2(ws, false, `Path must stay inside the project root: ${projectRoot}`);
|
|
6399
6526
|
break;
|
|
6400
6527
|
}
|
|
6401
6528
|
try {
|
|
6402
|
-
await
|
|
6403
|
-
const stat2 = await
|
|
6529
|
+
await fs9.access(resolved);
|
|
6530
|
+
const stat2 = await fs9.stat(resolved);
|
|
6404
6531
|
if (!stat2.isDirectory()) throw new Error("Not a directory");
|
|
6405
6532
|
} catch {
|
|
6406
6533
|
sendResult2(ws, false, `Directory not found or not accessible: ${resolved}`);
|
|
@@ -6431,24 +6558,24 @@ ${trigger}
|
|
|
6431
6558
|
case "mailbox.messages":
|
|
6432
6559
|
return handleMailboxMessages(
|
|
6433
6560
|
ws,
|
|
6434
|
-
{ projectRoot, globalRoot:
|
|
6561
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
6435
6562
|
msg.payload
|
|
6436
6563
|
);
|
|
6437
6564
|
case "mailbox.agents":
|
|
6438
6565
|
return handleMailboxAgents(
|
|
6439
6566
|
ws,
|
|
6440
|
-
{ projectRoot, globalRoot:
|
|
6567
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
6441
6568
|
msg.payload
|
|
6442
6569
|
);
|
|
6443
6570
|
case "mailbox.clear":
|
|
6444
6571
|
return handleMailboxClear(
|
|
6445
6572
|
ws,
|
|
6446
|
-
{ projectRoot, globalRoot:
|
|
6573
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) }
|
|
6447
6574
|
);
|
|
6448
6575
|
case "mailbox.purge":
|
|
6449
6576
|
return handleMailboxPurge(
|
|
6450
6577
|
ws,
|
|
6451
|
-
{ projectRoot, globalRoot:
|
|
6578
|
+
{ projectRoot, globalRoot: path9.dirname(globalConfigPath) },
|
|
6452
6579
|
msg.payload
|
|
6453
6580
|
);
|
|
6454
6581
|
// ── Brain — status, autonomy ceiling, direct decision support ───
|
|
@@ -6527,7 +6654,7 @@ ${trigger}
|
|
|
6527
6654
|
};
|
|
6528
6655
|
const httpServer = createHttpServer({
|
|
6529
6656
|
host: wsHost,
|
|
6530
|
-
distDir:
|
|
6657
|
+
distDir: path9.resolve(import.meta.dirname, "../../dist"),
|
|
6531
6658
|
wsPort,
|
|
6532
6659
|
globalRoot: wpaths.globalRoot,
|
|
6533
6660
|
apiToken: wsToken,
|
|
@@ -6536,7 +6663,7 @@ ${trigger}
|
|
|
6536
6663
|
void fleetBroadcast?.();
|
|
6537
6664
|
}
|
|
6538
6665
|
});
|
|
6539
|
-
const registryBaseDir =
|
|
6666
|
+
const registryBaseDir = path9.dirname(globalConfigPath);
|
|
6540
6667
|
httpServer.listen(httpPort, wsHost, () => {
|
|
6541
6668
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
6542
6669
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -6548,7 +6675,7 @@ ${trigger}
|
|
|
6548
6675
|
wsPort,
|
|
6549
6676
|
host: wsHost,
|
|
6550
6677
|
projectRoot,
|
|
6551
|
-
projectName:
|
|
6678
|
+
projectName: path9.basename(projectRoot) || projectRoot,
|
|
6552
6679
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6553
6680
|
url: `http://${wsHost}:${httpPort}`
|
|
6554
6681
|
},
|
|
@@ -6575,6 +6702,7 @@ ${trigger}
|
|
|
6575
6702
|
// reality. Crash exits are healed by the next register()/list() prune pass.
|
|
6576
6703
|
onShutdown: () => {
|
|
6577
6704
|
brainMonitor.stop();
|
|
6705
|
+
void mcpRegistry.stopAll().catch(() => void 0);
|
|
6578
6706
|
if (disposeEvents) {
|
|
6579
6707
|
disposeEvents();
|
|
6580
6708
|
disposeEvents = null;
|