@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.
@@ -182,8 +182,8 @@ import {
182
182
  createAutonomyBrain,
183
183
  createTieredBrainArbiter
184
184
  } from "@wrongstack/core";
185
- import * as fs10 from "fs/promises";
186
- import * as path10 from "path";
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 name = part.slice(0, eq).trim();
229
- if (name === "ws_token") {
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(name) {
935
- return name.startsWith(".") && !KEEP_DOTFILES.has(name);
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 * as fs3 from "fs/promises";
1148
- import * as path3 from "path";
1149
- function isMcpServerRecord(val) {
1150
- if (typeof val !== "object" || val === null) return false;
1151
- return true;
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 projectServer(name, cfg, _status = "stopped", tools = []) {
1154
- return {
1155
- name,
1156
- transport: cfg.transport,
1157
- status: _status,
1158
- enabled: cfg.enabled ?? true,
1159
- description: cfg.description,
1160
- tools
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
- async function readConfig(configPath) {
1164
- try {
1165
- const content = await fs3.readFile(configPath, "utf-8");
1166
- return JSON.parse(content);
1167
- } catch {
1168
- return {};
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
- async function writeConfig(configPath, cfg) {
1172
- const dir = path3.dirname(configPath);
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 getMcpServers(config, globalConfigPath) {
1177
- const servers = [];
1178
- const configured = isMcpServerRecord(config.mcpServers) ? config.mcpServers : {};
1179
- for (const [name, cfg] of Object.entries(configured)) {
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
- return servers;
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 getRegistryStates(mcpRegistry) {
1185
- const states = /* @__PURE__ */ new Map();
1186
- if (!mcpRegistry?.list) return states;
1187
- try {
1188
- const list = mcpRegistry.list();
1189
- for (const item of list) {
1190
- states.set(item.name, { state: item.state, toolCount: item.toolCount });
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
- return states;
1227
+ send(ws, {
1228
+ type: "mcp.operation_result",
1229
+ payload: { success: result.ok, message: result.message }
1230
+ });
1195
1231
  }
1196
- async function handleMcpList(ws, _msg, config, _globalConfigPath, mcpRegistry) {
1197
- const servers = await getMcpServers(config, _globalConfigPath);
1198
- const registryStates = getRegistryStates(mcpRegistry);
1199
- for (const server of servers) {
1200
- const registryState = registryStates.get(server.name);
1201
- if (registryState) {
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, { type: "mcp.list", payload: { servers } });
1239
+ send(ws, {
1240
+ type: "mcp.operation_result",
1241
+ payload: { success: result.ok, message: result.message }
1242
+ });
1207
1243
  }
1208
- async function handleMcpAdd(ws, msg, config, globalConfigPath, mcpRegistry) {
1209
- const payload = msg.payload;
1210
- if (!payload.name) {
1211
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1212
- return;
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 handleMcpRemove(ws, msg, _config, globalConfigPath, mcpRegistry) {
1256
- const payload = msg.payload;
1257
- if (!payload.name) {
1258
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1259
- return;
1260
- }
1261
- try {
1262
- if (mcpRegistry) {
1263
- try {
1264
- await mcpRegistry.stop(payload.name);
1265
- } catch {
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 handleMcpUpdate(ws, msg, _config, globalConfigPath) {
1284
- const payload = msg.payload;
1285
- if (!payload.name) {
1286
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1287
- return;
1288
- }
1289
- try {
1290
- const diskConfig = await readConfig(globalConfigPath);
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, _config, _globalConfigPath, mcpRegistry) {
1336
- const payload = msg.payload;
1337
- if (!payload.name) {
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 mcpRegistry.stop(payload.name);
1347
- send(ws, { type: "mcp.server.sleeping", payload: { name: payload.name } });
1348
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" stopped` } });
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
- send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
1351
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to stop "${payload.name}": ${err}` } });
1352
- }
1353
- }
1354
- async function handleMcpDiscover(ws, msg, _config, _globalConfigPath, _mcpRegistry) {
1355
- const payload = msg.payload;
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 handleMcpEnable(ws, msg, _config, _globalConfigPath) {
1364
- const payload = msg.payload;
1365
- if (!payload.name) {
1366
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1367
- return;
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, { type: "mcp.operation_result", payload: { success: true, message: `Enable command sent for "${payload.name}"` } });
1323
+ send(ws, {
1324
+ type: "mcp.operation_result",
1325
+ payload: { success: result.ok, message: result.message }
1326
+ });
1370
1327
  }
1371
- async function handleMcpDisable(ws, msg, _config, _globalConfigPath) {
1372
- const payload = msg.payload;
1373
- if (!payload.name) {
1374
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1375
- return;
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, { type: "mcp.operation_result", payload: { success: true, message: `Disable command sent for "${payload.name}"` } });
1340
+ send(ws, {
1341
+ type: "mcp.operation_result",
1342
+ payload: { success: result.ok, message: result.message }
1343
+ });
1378
1344
  }
1379
- async function handleMcpRestart(ws, msg, _config, _globalConfigPath) {
1380
- const payload = msg.payload;
1381
- if (!payload.name) {
1382
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1383
- return;
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, { type: "mcp.operation_result", payload: { success: true, message: `Restart command sent for "${payload.name}"` } });
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, deps, payload) {
2648
+ async function handleMailboxMessages(ws, deps2, payload) {
2674
2649
  try {
2675
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
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, deps, payload) {
2684
+ async function handleMailboxAgents(ws, deps2, payload) {
2710
2685
  try {
2711
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
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, deps) {
2713
+ async function handleMailboxClear(ws, deps2) {
2739
2714
  try {
2740
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
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, deps, opts) {
2723
+ async function handleMailboxPurge(ws, deps2, opts) {
2749
2724
  try {
2750
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
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 path4 from "path";
2798
- import * as fs4 from "fs/promises";
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 path4.join(os.homedir(), ".wrongstack");
2776
+ return path3.join(os.homedir(), ".wrongstack");
2802
2777
  }
2803
2778
  function registryPath(baseDir = defaultBaseDir()) {
2804
- return path4.join(baseDir, "webui-instances.json");
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 fs4.readFile(file, "utf8");
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 fs5 from "fs/promises";
2966
- import * as path5 from "path";
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 fs5.readFile(configPath, "utf8");
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 fs5.readFile(configPath, "utf8");
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(deps) {
3141
- const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps;
3142
- let configWriteLock = deps.getConfigWriteLock();
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
- deps.setConfigWriteLock(next);
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 fs6 from "fs/promises";
3288
+ import * as fs5 from "fs/promises";
3314
3289
  import { watch as fsWatch } from "fs";
3315
- import * as path6 from "path";
3316
- function setupEvents(deps) {
3317
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps;
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 = path6.dirname(statusFile);
3525
- await fs6.mkdir(dir, { recursive: true });
3526
- await fs6.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
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 = path6.join(wpaths.configDir, "projects");
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 fs6.mkdir(projectsDir, { recursive: true });
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 = path6.join(projectsDir, String(filename));
3576
+ const targetFile = path5.join(projectsDir, String(filename));
3602
3577
  if (targetFile.endsWith("status.json")) {
3603
- const projectHash2 = path6.basename(path6.dirname(targetFile));
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 fs6.readFile(targetFile, "utf-8");
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 ? path6.dirname(globalConfigPath) : void 0;
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 name = filename ? String(filename) : "";
3710
- if (!name.startsWith("session-registry.json") || name.endsWith(".lock")) return;
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 fs7 from "fs/promises";
3735
- import * as path7 from "path";
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 path7.join(wrongstackDir, STORE_FILENAME);
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 fs7.readFile(storePath(wrongstackDir), "utf8");
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 fs8 from "fs/promises";
3927
- import * as path8 from "path";
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 = path8.resolve(req.path);
3933
- await fs8.access(resolved);
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 fs9 } from "fs";
4012
- import path9 from "path";
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: path10.basename(projectRoot),
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: path10.join(wstackGlobalRoot2(), "installed-skills.json"),
4187
- projectSkillsDir: path10.join(projectRoot, ".wrongstack", "skills"),
4188
- globalSkillsDir: path10.join(wstackGlobalRoot2(), "skills"),
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 fs10.readFile(globalConfigPath, "utf8");
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: path10.basename(projectRoot) || projectRoot,
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 = path10.resolve(root);
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) => path10.resolve(p.root) === resolved);
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 = path10.resolve(workDir);
5007
+ if (workDir) existing.lastWorkingDir = path9.resolve(workDir);
4880
5008
  } else {
4881
5009
  manifest.projects.push({
4882
- name: path10.basename(resolved),
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 ? path10.resolve(workDir) : void 0
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 = path10.dirname(globalConfigPath2);
4895
- return path10.join(base, "projects.json");
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 fs10.readFile(projectsJsonPath(globalConfigPath2), "utf8");
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 fs10.mkdir(path10.dirname(file), { recursive: true });
4909
- await fs10.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
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 = path10.dirname(globalConfigPath2);
4916
- const dir = path10.join(base, "projects", slug);
4917
- await fs10.mkdir(dir, { recursive: true });
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 provider2 = await modelsRegistry.getProvider(providerId);
5224
- if (provider2) {
5225
- send(ws, {
5226
- type: "provider.models",
5227
- payload: {
5228
- provider: providerId,
5229
- models: provider2.models.map((m) => ({
5230
- id: m.id,
5231
- name: m.name,
5232
- releaseDate: m.release_date,
5233
- contextWindow: m.limit?.context,
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 fs10.readFile(globalConfigPath, "utf8");
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, config, globalConfigPath, void 0);
5613
+ return handleMcpList(ws, msg, globalConfigPath, mcpRegistry);
5495
5614
  case "mcp.add":
5496
- return handleMcpAdd(ws, msg, config, globalConfigPath, void 0);
5615
+ return handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry);
5497
5616
  case "mcp.remove":
5498
- return handleMcpRemove(ws, msg, config, globalConfigPath, void 0);
5617
+ return handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry);
5499
5618
  case "mcp.update":
5500
- return handleMcpUpdate(ws, msg, config, globalConfigPath);
5619
+ return handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry);
5501
5620
  case "mcp.wake":
5502
- return handleMcpWake(ws, msg, config, globalConfigPath, void 0);
5621
+ return handleMcpWake(ws, msg, globalConfigPath, mcpRegistry);
5503
5622
  case "mcp.sleep":
5504
- return handleMcpSleep(ws, msg, config, globalConfigPath, void 0);
5623
+ return handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry);
5505
5624
  case "mcp.discover":
5506
- return handleMcpDiscover(ws, msg, config, globalConfigPath);
5625
+ return handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry);
5507
5626
  case "mcp.enable":
5508
- return handleMcpEnable(ws, msg, config, globalConfigPath);
5627
+ return handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry);
5509
5628
  case "mcp.disable":
5510
- return handleMcpDisable(ws, msg, config, globalConfigPath);
5629
+ return handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry);
5511
5630
  case "mcp.restart":
5512
- return handleMcpRestart(ws, msg, config, globalConfigPath);
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() === 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 "${name}" not found` } });
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(name);
5582
- const skillDir = path10.dirname(entry.path);
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 fs10.readdir(skillDir);
5586
- relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, 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() === name.toLowerCase()) continue;
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(name.toLowerCase())) {
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" ? path10.join(wstackGlobalRoot2(), "skills", createPayload.name.trim()) : path10.join(projectRoot, ".wrongstack", "skills", createPayload.name.trim());
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 fs10.access(targetDir);
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 fs10.mkdir(targetDir, { recursive: true });
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 fs10.writeFile(path10.join(targetDir, "SKILL.md"), skillContent, "utf-8");
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: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
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 fs10.writeFile(entry.path, editPayload.body, "utf-8");
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 fs10.readFile(goalPath, "utf8");
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
- path10.join(projectRoot, ".wrongstack", "sessions"),
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
- path10.join(projectRoot, ".wrongstack", "sessions"),
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 = path10.resolve(addRoot);
6200
- await fs10.access(resolved);
6201
- const stat2 = await fs10.stat(resolved);
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 name = displayName?.trim() || path10.basename(resolved);
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 "${name}"`
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: path10.basename(addRoot),
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 = path10.resolve(selRoot);
6375
+ const resolved = path9.resolve(selRoot);
6249
6376
  try {
6250
- await fs10.access(resolved);
6251
- const stat2 = await fs10.stat(resolved);
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 || path10.basename(selRoot),
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 name = selName?.trim() || path10.basename(resolved);
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 = path10.join(
6312
- path10.dirname(globalConfigPath),
6438
+ const newSessionsDir = path9.join(
6439
+ path9.dirname(globalConfigPath),
6313
6440
  "projects",
6314
6441
  switchSlug,
6315
6442
  "sessions"
6316
6443
  );
6317
- await fs10.mkdir(newSessionsDir, { recursive: true });
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: path10.basename(projectRoot),
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 || path10.basename(resolved),
6362
- message: `Switched to ${selName || path10.basename(resolved)}`
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 || path10.basename(selRoot),
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 = path10.resolve(projectRoot, newPath);
6397
- if (!resolved.startsWith(projectRoot + path10.sep) && resolved !== 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 fs10.access(resolved);
6403
- const stat2 = await fs10.stat(resolved);
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: path10.dirname(globalConfigPath) },
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: path10.dirname(globalConfigPath) },
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: path10.dirname(globalConfigPath) }
6573
+ { projectRoot, globalRoot: path9.dirname(globalConfigPath) }
6447
6574
  );
6448
6575
  case "mailbox.purge":
6449
6576
  return handleMailboxPurge(
6450
6577
  ws,
6451
- { projectRoot, globalRoot: path10.dirname(globalConfigPath) },
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: path10.resolve(import.meta.dirname, "../../dist"),
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 = path10.dirname(globalConfigPath);
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: path10.basename(projectRoot) || projectRoot,
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;