@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.
@@ -181,8 +181,8 @@ import {
181
181
  createAutonomyBrain,
182
182
  createTieredBrainArbiter
183
183
  } from "@wrongstack/core";
184
- import * as fs10 from "fs/promises";
185
- import * as path10 from "path";
184
+ import * as fs9 from "fs/promises";
185
+ import * as path9 from "path";
186
186
 
187
187
  // src/server/http-server.ts
188
188
  import * as fs from "fs/promises";
@@ -224,8 +224,8 @@ function extractTokenFromCookie(cookieHeader) {
224
224
  for (const part of raw.split(";")) {
225
225
  const eq = part.indexOf("=");
226
226
  if (eq < 0) continue;
227
- const name = part.slice(0, eq).trim();
228
- if (name === "ws_token") {
227
+ const name2 = part.slice(0, eq).trim();
228
+ if (name2 === "ws_token") {
229
229
  try {
230
230
  return decodeURIComponent(part.slice(eq + 1).trim());
231
231
  } catch {
@@ -930,8 +930,8 @@ var KEEP_DOTFILES = /* @__PURE__ */ new Set([
930
930
  ".eslintrc",
931
931
  ".prettierrc"
932
932
  ]);
933
- function isHiddenEntry(name) {
934
- return name.startsWith(".") && !KEEP_DOTFILES.has(name);
933
+ function isHiddenEntry(name2) {
934
+ return name2.startsWith(".") && !KEEP_DOTFILES.has(name2);
935
935
  }
936
936
  function rankFiles(paths, query, limit) {
937
937
  const q = query.toLowerCase();
@@ -1143,245 +1143,218 @@ async function handleMemoryForget(ws, msg, memoryStore) {
1143
1143
  }
1144
1144
 
1145
1145
  // src/server/mcp-handlers.ts
1146
- import * as fs3 from "fs/promises";
1147
- import * as path3 from "path";
1148
- function isMcpServerRecord(val) {
1149
- if (typeof val !== "object" || val === null) return false;
1150
- return true;
1146
+ import { allServers } from "@wrongstack/core";
1147
+ import {
1148
+ addMcp,
1149
+ disableMcp,
1150
+ discoverMcp,
1151
+ enableMcp,
1152
+ listMcp,
1153
+ removeMcp,
1154
+ restartMcp,
1155
+ updateMcp
1156
+ } from "@wrongstack/mcp";
1157
+ function mapStatus(raw) {
1158
+ switch (raw) {
1159
+ case "connected":
1160
+ return "connected";
1161
+ case "connecting":
1162
+ case "reconnecting":
1163
+ return "connecting";
1164
+ case "failed":
1165
+ return "error";
1166
+ case "dormant":
1167
+ return "sleeping";
1168
+ default:
1169
+ return "stopped";
1170
+ }
1151
1171
  }
1152
- function projectServer(name, cfg, _status = "stopped", tools = []) {
1153
- return {
1154
- name,
1155
- transport: cfg.transport,
1156
- status: _status,
1157
- enabled: cfg.enabled ?? true,
1158
- description: cfg.description,
1159
- tools
1172
+ function toView(info) {
1173
+ const view = {
1174
+ name: info.name,
1175
+ transport: info.transport,
1176
+ // A dormant lazy server is "asleep", not stopped — preserve that even when
1177
+ // it's enabled in config.
1178
+ status: info.status === "dormant" ? "sleeping" : info.enabled === false ? "stopped" : mapStatus(info.status),
1179
+ enabled: info.enabled,
1180
+ tools: info.tools
1160
1181
  };
1182
+ if (info.description !== void 0) view.description = info.description;
1183
+ if (info.lazy !== void 0) view.lazy = info.lazy;
1184
+ return view;
1161
1185
  }
1162
- async function readConfig(configPath) {
1163
- try {
1164
- const content = await fs3.readFile(configPath, "utf-8");
1165
- return JSON.parse(content);
1166
- } catch {
1167
- return {};
1186
+ function deps(ws, globalConfigPath, registry) {
1187
+ if (!registry || !globalConfigPath) {
1188
+ send(ws, {
1189
+ type: "mcp.operation_result",
1190
+ payload: { success: false, message: "MCP registry is not available in this session." }
1191
+ });
1192
+ return null;
1168
1193
  }
1194
+ return { configPath: globalConfigPath, registry, presets: allServers() };
1169
1195
  }
1170
- async function writeConfig(configPath, cfg) {
1171
- const dir = path3.dirname(configPath);
1172
- await fs3.mkdir(dir, { recursive: true });
1173
- await fs3.writeFile(configPath, JSON.stringify(cfg, null, 2), "utf-8");
1196
+ function name(msg) {
1197
+ return msg.payload?.name ?? "";
1174
1198
  }
1175
- async function getMcpServers(config, globalConfigPath) {
1176
- const servers = [];
1177
- const configured = isMcpServerRecord(config.mcpServers) ? config.mcpServers : {};
1178
- for (const [name, cfg] of Object.entries(configured)) {
1179
- servers.push(projectServer(name, cfg));
1199
+ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
1200
+ if (!mcpRegistry || !globalConfigPath) {
1201
+ send(ws, { type: "mcp.list", payload: { servers: [] } });
1202
+ return;
1180
1203
  }
1181
- return servers;
1204
+ const servers = await listMcp({
1205
+ configPath: globalConfigPath,
1206
+ registry: mcpRegistry,
1207
+ presets: allServers()
1208
+ });
1209
+ send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
1182
1210
  }
1183
- function getRegistryStates(mcpRegistry) {
1184
- const states = /* @__PURE__ */ new Map();
1185
- if (!mcpRegistry?.list) return states;
1186
- try {
1187
- const list = mcpRegistry.list();
1188
- for (const item of list) {
1189
- states.set(item.name, { state: item.state, toolCount: item.toolCount });
1211
+ async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
1212
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1213
+ if (!d) return;
1214
+ const result = await addMcp(msg.payload, d);
1215
+ if (result.ok && result.server) {
1216
+ send(ws, { type: "mcp.server.added", payload: { server: toView(result.server) } });
1217
+ if (result.registryError) {
1218
+ send(ws, {
1219
+ type: "mcp.server.error",
1220
+ payload: { name: result.server.name, error: result.registryError }
1221
+ });
1222
+ } else if (result.server.enabled) {
1223
+ send(ws, { type: "mcp.server.connected", payload: { name: result.server.name } });
1190
1224
  }
1191
- } catch {
1192
1225
  }
1193
- return states;
1226
+ send(ws, {
1227
+ type: "mcp.operation_result",
1228
+ payload: { success: result.ok, message: result.message }
1229
+ });
1194
1230
  }
1195
- async function handleMcpList(ws, _msg, config, _globalConfigPath, mcpRegistry) {
1196
- const servers = await getMcpServers(config, _globalConfigPath);
1197
- const registryStates = getRegistryStates(mcpRegistry);
1198
- for (const server of servers) {
1199
- const registryState = registryStates.get(server.name);
1200
- if (registryState) {
1201
- server.status = registryState.state;
1202
- server.tools = Array.from({ length: registryState.toolCount }, (_, i) => `tool-${i + 1}`);
1203
- }
1231
+ async function handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry) {
1232
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1233
+ if (!d) return;
1234
+ const result = await updateMcp(msg.payload, d);
1235
+ if (result.ok && result.server) {
1236
+ send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
1204
1237
  }
1205
- send(ws, { type: "mcp.list", payload: { servers } });
1238
+ send(ws, {
1239
+ type: "mcp.operation_result",
1240
+ payload: { success: result.ok, message: result.message }
1241
+ });
1206
1242
  }
1207
- async function handleMcpAdd(ws, msg, config, globalConfigPath, mcpRegistry) {
1208
- const payload = msg.payload;
1209
- if (!payload.name) {
1210
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1211
- return;
1212
- }
1213
- try {
1214
- const diskConfig = await readConfig(globalConfigPath);
1215
- const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
1216
- if (mcpServers[payload.name]) {
1217
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" already exists` } });
1218
- return;
1219
- }
1220
- mcpServers[payload.name] = {
1221
- transport: payload.transport,
1222
- description: payload.description,
1223
- enabled: payload.enabled ?? true,
1224
- command: payload.command,
1225
- args: payload.args,
1226
- env: payload.env,
1227
- allowedTools: payload.allowedTools
1228
- };
1229
- diskConfig.mcpServers = mcpServers;
1230
- await writeConfig(globalConfigPath, diskConfig);
1231
- const newServer = projectServer(payload.name, mcpServers[payload.name]);
1232
- send(ws, { type: "mcp.server.added", payload: { server: newServer } });
1233
- if (mcpRegistry && (payload.enabled ?? true)) {
1234
- const serverConfig = mcpServers[payload.name];
1235
- try {
1236
- await mcpRegistry.start({
1237
- name: payload.name,
1238
- transport: payload.transport,
1239
- command: payload.command,
1240
- args: payload.args,
1241
- env: payload.env,
1242
- allowedTools: payload.allowedTools,
1243
- enabled: true
1244
- });
1245
- } catch (err) {
1246
- send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
1247
- }
1248
- }
1249
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" added` } });
1250
- } catch (err) {
1251
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to add server: ${err}` } });
1243
+ async function handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry) {
1244
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1245
+ if (!d) return;
1246
+ const result = await removeMcp(name(msg), d);
1247
+ if (result.ok) {
1248
+ send(ws, { type: "mcp.server.removed", payload: { name: name(msg) } });
1252
1249
  }
1250
+ send(ws, {
1251
+ type: "mcp.operation_result",
1252
+ payload: { success: result.ok, message: result.message }
1253
+ });
1253
1254
  }
1254
- async function handleMcpRemove(ws, msg, _config, globalConfigPath, mcpRegistry) {
1255
- const payload = msg.payload;
1256
- if (!payload.name) {
1257
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1258
- return;
1259
- }
1260
- try {
1261
- if (mcpRegistry) {
1262
- try {
1263
- await mcpRegistry.stop(payload.name);
1264
- } catch {
1265
- }
1266
- }
1267
- const diskConfig = await readConfig(globalConfigPath);
1268
- const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
1269
- if (!mcpServers[payload.name]) {
1270
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" not found` } });
1271
- return;
1255
+ async function handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry) {
1256
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1257
+ if (!d) return;
1258
+ const result = await enableMcp(name(msg), d);
1259
+ if (result.ok && result.server) {
1260
+ send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
1261
+ if (result.registryError) {
1262
+ send(ws, {
1263
+ type: "mcp.server.error",
1264
+ payload: { name: name(msg), error: result.registryError }
1265
+ });
1266
+ } else {
1267
+ send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
1272
1268
  }
1273
- delete mcpServers[payload.name];
1274
- diskConfig.mcpServers = mcpServers;
1275
- await writeConfig(globalConfigPath, diskConfig);
1276
- send(ws, { type: "mcp.server.removed", payload: { name: payload.name } });
1277
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" removed` } });
1278
- } catch (err) {
1279
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to remove server: ${err}` } });
1280
1269
  }
1270
+ send(ws, {
1271
+ type: "mcp.operation_result",
1272
+ payload: { success: result.ok, message: result.message }
1273
+ });
1281
1274
  }
1282
- async function handleMcpUpdate(ws, msg, _config, globalConfigPath) {
1283
- const payload = msg.payload;
1284
- if (!payload.name) {
1285
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1286
- return;
1287
- }
1288
- try {
1289
- const diskConfig = await readConfig(globalConfigPath);
1290
- const mcpServers = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : {};
1291
- if (!mcpServers[payload.name]) {
1292
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Server "${payload.name}" not found` } });
1293
- return;
1275
+ async function handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry) {
1276
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1277
+ if (!d) return;
1278
+ const result = await disableMcp(name(msg), d);
1279
+ if (result.ok) {
1280
+ send(ws, { type: "mcp.server.sleeping", payload: { name: name(msg) } });
1281
+ if (result.server) {
1282
+ send(ws, { type: "mcp.server.updated", payload: { server: toView(result.server) } });
1294
1283
  }
1295
- const existing = mcpServers[payload.name];
1296
- mcpServers[payload.name] = {
1297
- transport: payload.transport ?? existing.transport,
1298
- description: payload.description ?? existing.description,
1299
- enabled: payload.enabled ?? existing.enabled,
1300
- command: payload.command ?? existing.command,
1301
- args: payload.args ?? existing.args,
1302
- env: payload.env ?? existing.env,
1303
- allowedTools: payload.allowedTools ?? existing.allowedTools
1304
- };
1305
- diskConfig.mcpServers = mcpServers;
1306
- await writeConfig(globalConfigPath, diskConfig);
1307
- const updatedServer = projectServer(payload.name, mcpServers[payload.name]);
1308
- send(ws, { type: "mcp.server.updated", payload: { server: updatedServer } });
1309
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" updated` } });
1310
- } catch (err) {
1311
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to update server: ${err}` } });
1312
- }
1313
- }
1314
- async function handleMcpWake(ws, msg, _config, _globalConfigPath, mcpRegistry) {
1315
- const payload = msg.payload;
1316
- if (!payload.name) {
1317
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1318
- return;
1319
- }
1320
- if (!mcpRegistry) {
1321
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "MCP registry not available" } });
1322
- return;
1323
- }
1324
- try {
1325
- send(ws, { type: "mcp.server.waking", payload: { name: payload.name } });
1326
- await mcpRegistry.restart(payload.name);
1327
- send(ws, { type: "mcp.server.connected", payload: { name: payload.name } });
1328
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" restarted` } });
1329
- } catch (err) {
1330
- send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
1331
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to restart "${payload.name}": ${err}` } });
1332
1284
  }
1285
+ send(ws, {
1286
+ type: "mcp.operation_result",
1287
+ payload: { success: result.ok, message: result.message }
1288
+ });
1333
1289
  }
1334
- async function handleMcpSleep(ws, msg, _config, _globalConfigPath, mcpRegistry) {
1335
- const payload = msg.payload;
1336
- if (!payload.name) {
1337
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1338
- return;
1339
- }
1340
- if (!mcpRegistry) {
1341
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "MCP registry not available" } });
1342
- return;
1343
- }
1290
+ async function handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry) {
1291
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1292
+ if (!d) return;
1344
1293
  try {
1345
- await mcpRegistry.stop(payload.name);
1346
- send(ws, { type: "mcp.server.sleeping", payload: { name: payload.name } });
1347
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" stopped` } });
1294
+ await d.registry.stop(name(msg));
1295
+ send(ws, { type: "mcp.server.sleeping", payload: { name: name(msg) } });
1296
+ send(ws, {
1297
+ type: "mcp.operation_result",
1298
+ payload: { success: true, message: `Server "${name(msg)}" stopped` }
1299
+ });
1348
1300
  } catch (err) {
1349
- send(ws, { type: "mcp.server.error", payload: { name: payload.name, error: String(err) } });
1350
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: `Failed to stop "${payload.name}": ${err}` } });
1351
- }
1352
- }
1353
- async function handleMcpDiscover(ws, msg, _config, _globalConfigPath, _mcpRegistry) {
1354
- const payload = msg.payload;
1355
- if (!payload.name) {
1356
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1357
- return;
1301
+ const error = err instanceof Error ? err.message : String(err);
1302
+ send(ws, { type: "mcp.server.error", payload: { name: name(msg), error } });
1303
+ send(ws, {
1304
+ type: "mcp.operation_result",
1305
+ payload: { success: false, message: `Failed to stop "${name(msg)}": ${error}` }
1306
+ });
1358
1307
  }
1359
- send(ws, { type: "mcp.server.discovered", payload: { name: payload.name, tools: [] } });
1360
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Server "${payload.name}" tools were discovered on connect` } });
1361
1308
  }
1362
- async function handleMcpEnable(ws, msg, _config, _globalConfigPath) {
1363
- const payload = msg.payload;
1364
- if (!payload.name) {
1365
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1366
- return;
1309
+ async function handleMcpWake(ws, msg, globalConfigPath, mcpRegistry) {
1310
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1311
+ if (!d) return;
1312
+ send(ws, { type: "mcp.server.waking", payload: { name: name(msg) } });
1313
+ const result = await restartMcp(name(msg), d);
1314
+ if (result.ok && !result.registryError) {
1315
+ send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
1316
+ } else if (result.registryError) {
1317
+ send(ws, {
1318
+ type: "mcp.server.error",
1319
+ payload: { name: name(msg), error: result.registryError }
1320
+ });
1367
1321
  }
1368
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Enable command sent for "${payload.name}"` } });
1322
+ send(ws, {
1323
+ type: "mcp.operation_result",
1324
+ payload: { success: result.ok, message: result.message }
1325
+ });
1369
1326
  }
1370
- async function handleMcpDisable(ws, msg, _config, _globalConfigPath) {
1371
- const payload = msg.payload;
1372
- if (!payload.name) {
1373
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1374
- return;
1327
+ async function handleMcpRestart(ws, msg, globalConfigPath, mcpRegistry) {
1328
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1329
+ if (!d) return;
1330
+ const result = await restartMcp(name(msg), d);
1331
+ if (result.ok && !result.registryError) {
1332
+ send(ws, { type: "mcp.server.connected", payload: { name: name(msg) } });
1333
+ } else if (result.registryError) {
1334
+ send(ws, {
1335
+ type: "mcp.server.error",
1336
+ payload: { name: name(msg), error: result.registryError }
1337
+ });
1375
1338
  }
1376
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Disable command sent for "${payload.name}"` } });
1339
+ send(ws, {
1340
+ type: "mcp.operation_result",
1341
+ payload: { success: result.ok, message: result.message }
1342
+ });
1377
1343
  }
1378
- async function handleMcpRestart(ws, msg, _config, _globalConfigPath) {
1379
- const payload = msg.payload;
1380
- if (!payload.name) {
1381
- send(ws, { type: "mcp.operation_result", payload: { success: false, message: "Server name is required" } });
1382
- return;
1344
+ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
1345
+ const d = deps(ws, globalConfigPath, mcpRegistry);
1346
+ if (!d) return;
1347
+ const result = await discoverMcp(name(msg), d);
1348
+ if (result.ok) {
1349
+ send(ws, {
1350
+ type: "mcp.server.discovered",
1351
+ payload: { name: name(msg), tools: result.tools ?? [] }
1352
+ });
1383
1353
  }
1384
- send(ws, { type: "mcp.operation_result", payload: { success: true, message: `Restart command sent for "${payload.name}"` } });
1354
+ send(ws, {
1355
+ type: "mcp.operation_result",
1356
+ payload: { success: result.ok, message: result.message }
1357
+ });
1385
1358
  }
1386
1359
 
1387
1360
  // src/server/index.ts
@@ -1417,12 +1390,14 @@ import {
1417
1390
  repairToolUseAdjacency,
1418
1391
  resolveContextWindowPolicy,
1419
1392
  enhanceUserPrompt,
1420
- recentTextTurns
1393
+ recentTextTurns,
1394
+ resolveProviderModelList
1421
1395
  } from "@wrongstack/core";
1422
1396
  import { ToolExecutor } from "@wrongstack/core/execution";
1423
1397
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
1424
1398
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
1425
1399
  import { builtinToolsPack, forgetTool, rememberTool, searchMemoryTool, relatedMemoryTool } from "@wrongstack/tools";
1400
+ import { MCPRegistry } from "@wrongstack/mcp";
1426
1401
  import { WebSocketServer } from "ws";
1427
1402
 
1428
1403
  // ../runtime/src/container.ts
@@ -2669,9 +2644,9 @@ var WorktreeWebSocketHandler = class {
2669
2644
 
2670
2645
  // src/server/mailbox-handlers.ts
2671
2646
  import { GlobalMailbox, resolveProjectDir } from "@wrongstack/core";
2672
- async function handleMailboxMessages(ws, deps, payload) {
2647
+ async function handleMailboxMessages(ws, deps2, payload) {
2673
2648
  try {
2674
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
2649
+ const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
2675
2650
  const mb = new GlobalMailbox(dir);
2676
2651
  const messages = await mb.query({
2677
2652
  limit: payload?.limit ?? 30,
@@ -2705,9 +2680,9 @@ async function handleMailboxMessages(ws, deps, payload) {
2705
2680
  send(ws, { type: "mailbox.messages", payload: { messages: [], error: errMessage(err) } });
2706
2681
  }
2707
2682
  }
2708
- async function handleMailboxAgents(ws, deps, payload) {
2683
+ async function handleMailboxAgents(ws, deps2, payload) {
2709
2684
  try {
2710
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
2685
+ const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
2711
2686
  const mb = new GlobalMailbox(dir);
2712
2687
  const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
2713
2688
  send(ws, {
@@ -2734,9 +2709,9 @@ async function handleMailboxAgents(ws, deps, payload) {
2734
2709
  send(ws, { type: "mailbox.agents", payload: { agents: [], error: errMessage(err) } });
2735
2710
  }
2736
2711
  }
2737
- async function handleMailboxClear(ws, deps) {
2712
+ async function handleMailboxClear(ws, deps2) {
2738
2713
  try {
2739
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
2714
+ const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
2740
2715
  const mb = new GlobalMailbox(dir);
2741
2716
  await mb.clearAll();
2742
2717
  send(ws, { type: "mailbox.cleared", payload: {} });
@@ -2744,9 +2719,9 @@ async function handleMailboxClear(ws, deps) {
2744
2719
  send(ws, { type: "mailbox.cleared", payload: { error: errMessage(err) } });
2745
2720
  }
2746
2721
  }
2747
- async function handleMailboxPurge(ws, deps, opts) {
2722
+ async function handleMailboxPurge(ws, deps2, opts) {
2748
2723
  try {
2749
- const dir = resolveProjectDir(deps.projectRoot, deps.globalRoot);
2724
+ const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
2750
2725
  const mb = new GlobalMailbox(dir);
2751
2726
  const result = await mb.purgeStale(opts);
2752
2727
  send(ws, { type: "mailbox.purged", payload: result });
@@ -2793,14 +2768,14 @@ function registerShutdownHandlers(res) {
2793
2768
 
2794
2769
  // src/server/instance-registry.ts
2795
2770
  import * as os from "os";
2796
- import * as path4 from "path";
2797
- import * as fs4 from "fs/promises";
2771
+ import * as path3 from "path";
2772
+ import * as fs3 from "fs/promises";
2798
2773
  import { atomicWrite as atomicWrite2 } from "@wrongstack/core";
2799
2774
  function defaultBaseDir() {
2800
- return path4.join(os.homedir(), ".wrongstack");
2775
+ return path3.join(os.homedir(), ".wrongstack");
2801
2776
  }
2802
2777
  function registryPath(baseDir = defaultBaseDir()) {
2803
- return path4.join(baseDir, "webui-instances.json");
2778
+ return path3.join(baseDir, "webui-instances.json");
2804
2779
  }
2805
2780
  function isPidAlive(pid) {
2806
2781
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2813,7 +2788,7 @@ function isPidAlive(pid) {
2813
2788
  }
2814
2789
  async function load(file) {
2815
2790
  try {
2816
- const raw = await fs4.readFile(file, "utf8");
2791
+ const raw = await fs3.readFile(file, "utf8");
2817
2792
  const parsed = JSON.parse(raw);
2818
2793
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2819
2794
  return parsed;
@@ -2961,15 +2936,15 @@ import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/cor
2961
2936
  import { probeLocalLlm } from "@wrongstack/runtime/probe";
2962
2937
 
2963
2938
  // src/server/provider-config-io.ts
2964
- import * as fs5 from "fs/promises";
2965
- import * as path5 from "path";
2939
+ import * as fs4 from "fs/promises";
2940
+ import * as path4 from "path";
2966
2941
  import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2967
2942
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
2968
2943
  import { DefaultSecretVault } from "@wrongstack/core";
2969
2944
  async function loadSavedProviders(configPath, vault) {
2970
2945
  let raw;
2971
2946
  try {
2972
- raw = await fs5.readFile(configPath, "utf8");
2947
+ raw = await fs4.readFile(configPath, "utf8");
2973
2948
  } catch {
2974
2949
  return {};
2975
2950
  }
@@ -2986,7 +2961,7 @@ async function saveProviders(configPath, vault, providers) {
2986
2961
  let raw;
2987
2962
  let fileExists = true;
2988
2963
  try {
2989
- raw = await fs5.readFile(configPath, "utf8");
2964
+ raw = await fs4.readFile(configPath, "utf8");
2990
2965
  } catch (err) {
2991
2966
  if (err.code !== "ENOENT") {
2992
2967
  throw new Error(
@@ -3014,7 +2989,7 @@ async function saveProviders(configPath, vault, providers) {
3014
2989
  await atomicWrite3(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3015
2990
  }
3016
2991
  function createProviderConfigIO(configPath) {
3017
- const keyFile = path5.join(path5.dirname(configPath), ".key");
2992
+ const keyFile = path4.join(path4.dirname(configPath), ".key");
3018
2993
  const vault = new DefaultSecretVault({ keyFile });
3019
2994
  return {
3020
2995
  load: () => loadSavedProviders(configPath, vault),
@@ -3144,9 +3119,9 @@ function projectSavedProviders(providers) {
3144
3119
  });
3145
3120
  }
3146
3121
  var probeScrubber = new DefaultSecretScrubber2();
3147
- function createProviderHandlers(deps) {
3148
- const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps;
3149
- let configWriteLock = deps.getConfigWriteLock();
3122
+ function createProviderHandlers(deps2) {
3123
+ const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
3124
+ let configWriteLock = deps2.getConfigWriteLock();
3150
3125
  async function loadConfigProviders() {
3151
3126
  return loadSavedProviders(globalConfigPath, vault);
3152
3127
  }
@@ -3161,7 +3136,7 @@ function createProviderHandlers(deps) {
3161
3136
  }));
3162
3137
  });
3163
3138
  configWriteLock = next;
3164
- deps.setConfigWriteLock(next);
3139
+ deps2.setConfigWriteLock(next);
3165
3140
  await next;
3166
3141
  }
3167
3142
  async function handleKeyUpsert(ws, providerId, label, apiKey) {
@@ -3317,11 +3292,11 @@ function createProviderHandlers(deps) {
3317
3292
  }
3318
3293
 
3319
3294
  // src/server/setup-events.ts
3320
- import * as fs6 from "fs/promises";
3295
+ import * as fs5 from "fs/promises";
3321
3296
  import { watch as fsWatch } from "fs";
3322
- import * as path6 from "path";
3323
- function setupEvents(deps) {
3324
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps;
3297
+ import * as path5 from "path";
3298
+ function setupEvents(deps2) {
3299
+ const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
3325
3300
  const disposers = [];
3326
3301
  events.on("iteration.started", (e) => {
3327
3302
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
@@ -3528,16 +3503,16 @@ function setupEvents(deps) {
3528
3503
  if (wpaths?.projectStatus) {
3529
3504
  try {
3530
3505
  const statusFile = wpaths.projectStatus(e.projectHash);
3531
- const dir = path6.dirname(statusFile);
3532
- await fs6.mkdir(dir, { recursive: true });
3533
- await fs6.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
3506
+ const dir = path5.dirname(statusFile);
3507
+ await fs5.mkdir(dir, { recursive: true });
3508
+ await fs5.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
3534
3509
  } catch (err) {
3535
3510
  console.error("[setup-events] Failed to write status.json:", err);
3536
3511
  }
3537
3512
  }
3538
3513
  });
3539
3514
  if (wpaths?.projectStatus && wpaths.configDir) {
3540
- const projectsDir = path6.join(wpaths.configDir, "projects");
3515
+ const projectsDir = path5.join(wpaths.configDir, "projects");
3541
3516
  const knownProjectHashes = /* @__PURE__ */ new Set();
3542
3517
  const debounceTimers = /* @__PURE__ */ new Map();
3543
3518
  const DEBOUNCE_MS = 150;
@@ -3600,20 +3575,20 @@ function setupEvents(deps) {
3600
3575
  let watcher;
3601
3576
  const startWatcher = async () => {
3602
3577
  try {
3603
- await fs6.mkdir(projectsDir, { recursive: true });
3578
+ await fs5.mkdir(projectsDir, { recursive: true });
3604
3579
  watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
3605
3580
  if (eventType === "change") {
3606
3581
  if (filename == null) return;
3607
3582
  if (watcherMetrics) watcherMetrics.fileChangesDetected++;
3608
- const targetFile = path6.join(projectsDir, String(filename));
3583
+ const targetFile = path5.join(projectsDir, String(filename));
3609
3584
  if (targetFile.endsWith("status.json")) {
3610
- const projectHash2 = path6.basename(path6.dirname(targetFile));
3585
+ const projectHash2 = path5.basename(path5.dirname(targetFile));
3611
3586
  if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash2)) {
3612
3587
  return;
3613
3588
  }
3614
3589
  if (watcherMetrics) watcherMetrics.filesProcessed++;
3615
3590
  try {
3616
- const content = await fs6.readFile(targetFile, "utf-8");
3591
+ const content = await fs5.readFile(targetFile, "utf-8");
3617
3592
  const statusData = JSON.parse(content);
3618
3593
  if (statusData.projectHash) {
3619
3594
  const hash = String(statusData.projectHash);
@@ -3665,7 +3640,7 @@ function setupEvents(deps) {
3665
3640
  }
3666
3641
  });
3667
3642
  }
3668
- const globalRoot = globalConfigPath ? path6.dirname(globalConfigPath) : void 0;
3643
+ const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
3669
3644
  if (globalRoot) {
3670
3645
  const broadcastSessions = async () => {
3671
3646
  try {
@@ -3713,8 +3688,8 @@ function setupEvents(deps) {
3713
3688
  let regDebounce;
3714
3689
  try {
3715
3690
  const regWatcher = fsWatch(globalRoot, { persistent: false }, (_event, filename) => {
3716
- const name = filename ? String(filename) : "";
3717
- if (!name.startsWith("session-registry.json") || name.endsWith(".lock")) return;
3691
+ const name2 = filename ? String(filename) : "";
3692
+ if (!name2.startsWith("session-registry.json") || name2.endsWith(".lock")) return;
3718
3693
  if (regDebounce) clearTimeout(regDebounce);
3719
3694
  regDebounce = setTimeout(() => void broadcastSessions(), 150);
3720
3695
  });
@@ -3738,11 +3713,11 @@ function setupEvents(deps) {
3738
3713
 
3739
3714
  // src/server/custom-context-modes.ts
3740
3715
  import { listContextWindowModes, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3741
- import * as fs7 from "fs/promises";
3742
- import * as path7 from "path";
3716
+ import * as fs6 from "fs/promises";
3717
+ import * as path6 from "path";
3743
3718
  var STORE_FILENAME = "custom-context-modes.json";
3744
3719
  function storePath(wrongstackDir) {
3745
- return path7.join(wrongstackDir, STORE_FILENAME);
3720
+ return path6.join(wrongstackDir, STORE_FILENAME);
3746
3721
  }
3747
3722
  var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
3748
3723
  function createCustomModeStore(wrongstackDir) {
@@ -3750,7 +3725,7 @@ function createCustomModeStore(wrongstackDir) {
3750
3725
  const load2 = async () => {
3751
3726
  modes.clear();
3752
3727
  try {
3753
- const raw = await fs7.readFile(storePath(wrongstackDir), "utf8");
3728
+ const raw = await fs6.readFile(storePath(wrongstackDir), "utf8");
3754
3729
  const parsed = JSON.parse(raw);
3755
3730
  if (Array.isArray(parsed.modes)) {
3756
3731
  for (const m of parsed.modes) {
@@ -3930,14 +3905,14 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
3930
3905
  }
3931
3906
 
3932
3907
  // src/server/shell-open.ts
3933
- import * as fs8 from "fs/promises";
3934
- import * as path8 from "path";
3908
+ import * as fs7 from "fs/promises";
3909
+ import * as path7 from "path";
3935
3910
  import { spawn as spawn2 } from "child_process";
3936
3911
  var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
3937
3912
  async function handleShellOpen(req, logger) {
3938
3913
  try {
3939
- const resolved = path8.resolve(req.path);
3940
- await fs8.access(resolved);
3914
+ const resolved = path7.resolve(req.path);
3915
+ await fs7.access(resolved);
3941
3916
  if (METACHAR_REGEX.test(resolved)) {
3942
3917
  return { success: false, message: "Path contains unsupported characters." };
3943
3918
  }
@@ -4013,10 +3988,148 @@ async function handleGitInfo(ws, projectRoot) {
4013
3988
  send(ws, { type: "git.info", payload: { branch: "", added: 0, deleted: 0, untracked: 0, ahead: 0, behind: 0 } });
4014
3989
  }
4015
3990
  }
3991
+ function makeGit(cwd) {
3992
+ return async (args) => {
3993
+ const { execFile: ef } = await import("child_process");
3994
+ return new Promise((resolve5) => {
3995
+ ef(
3996
+ "git",
3997
+ args,
3998
+ { cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
3999
+ (err, stdout) => resolve5(err ? "" : stdout)
4000
+ );
4001
+ });
4002
+ };
4003
+ }
4004
+ async function handleGitChanges(ws, projectRoot) {
4005
+ const cwd = projectRoot || void 0;
4006
+ try {
4007
+ const git = makeGit(cwd);
4008
+ const [statusRaw, unstagedNumstat, stagedNumstat] = await Promise.all([
4009
+ git(["status", "--porcelain", "-z"]),
4010
+ git(["diff", "--numstat", "-z"]),
4011
+ git(["diff", "--cached", "--numstat", "-z"])
4012
+ ]);
4013
+ const counts = /* @__PURE__ */ new Map();
4014
+ const parseNumstat = (raw) => {
4015
+ const parts = raw.split("\0");
4016
+ for (let i = 0; i < parts.length; i++) {
4017
+ const entry = parts[i];
4018
+ if (!entry) continue;
4019
+ const m = /^(\d+|-)\t(\d+|-)\t(.*)$/.exec(entry);
4020
+ if (!m) continue;
4021
+ const added = m[1] === "-" ? 0 : Number(m[1]);
4022
+ const deleted = m[2] === "-" ? 0 : Number(m[2]);
4023
+ let path10 = m[3] ?? "";
4024
+ if (path10 === "") {
4025
+ i += 1;
4026
+ path10 = parts[i + 1] ?? parts[i] ?? "";
4027
+ i += 1;
4028
+ }
4029
+ if (!path10) continue;
4030
+ const prev = counts.get(path10) ?? { added: 0, deleted: 0 };
4031
+ counts.set(path10, { added: prev.added + added, deleted: prev.deleted + deleted });
4032
+ }
4033
+ };
4034
+ parseNumstat(unstagedNumstat);
4035
+ parseNumstat(stagedNumstat);
4036
+ const records = statusRaw.split("\0").filter((r) => r.length > 0);
4037
+ const files = [];
4038
+ for (let i = 0; i < records.length; i++) {
4039
+ const rec = records[i];
4040
+ if (!rec || rec.length < 3) continue;
4041
+ const x = rec[0] ?? " ";
4042
+ const y = rec[1] ?? " ";
4043
+ const path10 = rec.slice(3);
4044
+ const isRename = x === "R" || x === "C" || y === "R" || y === "C";
4045
+ if (isRename) i += 1;
4046
+ let status;
4047
+ if (x === "?" && y === "?") status = "?";
4048
+ else if (x === "U" || y === "U" || x === "A" && y === "A" || x === "D" && y === "D") status = "U";
4049
+ else if (x === "R" || y === "R") status = "R";
4050
+ else if (x === "C" || y === "C") status = "C";
4051
+ else if (x === "A" || y === "A") status = "A";
4052
+ else if (x === "D" || y === "D") status = "D";
4053
+ else status = "M";
4054
+ const staged = x !== " " && x !== "?";
4055
+ let added = counts.get(path10)?.added ?? 0;
4056
+ let deleted = counts.get(path10)?.deleted ?? 0;
4057
+ if (status === "?") {
4058
+ added = await countUntrackedLines(cwd, path10);
4059
+ deleted = 0;
4060
+ }
4061
+ files.push({ path: path10, status, added, deleted, staged });
4062
+ }
4063
+ send(ws, { type: "git.changes", payload: { files } });
4064
+ } catch (err) {
4065
+ send(ws, {
4066
+ type: "git.changes",
4067
+ payload: { files: [], error: err instanceof Error ? err.message : String(err) }
4068
+ });
4069
+ }
4070
+ }
4071
+ async function countUntrackedLines(cwd, relPath) {
4072
+ try {
4073
+ const { readFile: readFile8 } = await import("fs/promises");
4074
+ const { join: join8 } = await import("path");
4075
+ const abs = cwd ? join8(cwd, relPath) : relPath;
4076
+ const buf = await readFile8(abs);
4077
+ if (buf.includes(0)) return 0;
4078
+ if (buf.length === 0) return 0;
4079
+ let lines = 0;
4080
+ for (let i = 0; i < buf.length; i++) if (buf[i] === 10) lines++;
4081
+ if (buf[buf.length - 1] !== 10) lines++;
4082
+ return lines;
4083
+ } catch {
4084
+ return 0;
4085
+ }
4086
+ }
4087
+ var MAX_DIFF_BYTES = 2 * 1024 * 1024;
4088
+ async function handleGitDiff(ws, projectRoot, path10) {
4089
+ const cwd = projectRoot || void 0;
4090
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path10, ...extra } });
4091
+ if (!path10 || path10.includes("\0") || path10.includes("..")) {
4092
+ reply({ oldText: "", newText: "", error: "invalid path" });
4093
+ return;
4094
+ }
4095
+ try {
4096
+ const git = makeGit(cwd);
4097
+ const { readFile: readFile8 } = await import("fs/promises");
4098
+ const { join: join8 } = await import("path");
4099
+ const oldText = await git(["show", `HEAD:${path10}`]);
4100
+ let newText = "";
4101
+ try {
4102
+ const abs = cwd ? join8(cwd, path10) : path10;
4103
+ const buf = await readFile8(abs);
4104
+ if (buf.includes(0)) {
4105
+ reply({ oldText: "", newText: "", binary: true });
4106
+ return;
4107
+ }
4108
+ if (buf.length > MAX_DIFF_BYTES) {
4109
+ reply({ oldText: "", newText: "", tooLarge: true });
4110
+ return;
4111
+ }
4112
+ newText = buf.toString("utf8");
4113
+ } catch {
4114
+ newText = "";
4115
+ }
4116
+ if ((oldText.length || 0) > MAX_DIFF_BYTES) {
4117
+ reply({ oldText: "", newText: "", tooLarge: true });
4118
+ return;
4119
+ }
4120
+ if (oldText.includes("\0")) {
4121
+ reply({ oldText: "", newText: "", binary: true });
4122
+ return;
4123
+ }
4124
+ reply({ oldText, newText });
4125
+ } catch (err) {
4126
+ reply({ oldText: "", newText: "", error: err instanceof Error ? err.message : String(err) });
4127
+ }
4128
+ }
4016
4129
 
4017
4130
  // src/server/skills-handlers.ts
4018
- import { promises as fs9 } from "fs";
4019
- import path9 from "path";
4131
+ import { promises as fs8 } from "fs";
4132
+ import path8 from "path";
4020
4133
  import JSZip from "jszip";
4021
4134
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
4022
4135
  async function handleSkillsContent(ws, ctx, msg) {
@@ -4030,26 +4143,26 @@ async function handleSkillsContent(ws, ctx, msg) {
4030
4143
  return;
4031
4144
  }
4032
4145
  try {
4033
- const { name, source } = contentPayload;
4146
+ const { name: name2, source } = contentPayload;
4034
4147
  const entries = await ctx.skillLoader.listEntries();
4035
- const entry = entries.find((e) => e.name.toLowerCase() === name.toLowerCase());
4148
+ const entry = entries.find((e) => e.name.toLowerCase() === name2.toLowerCase());
4036
4149
  if (!entry) {
4037
- send(ws, { type: "skills.content", payload: { name, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name}" not found` } });
4150
+ send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
4038
4151
  return;
4039
4152
  }
4040
- const body = await fs9.readFile(entry.path, "utf8");
4041
- const skillDir = path9.dirname(entry.path);
4153
+ const body = await fs8.readFile(entry.path, "utf8");
4154
+ const skillDir = path8.dirname(entry.path);
4042
4155
  let relatedFiles = [];
4043
4156
  try {
4044
- const files = await fs9.readdir(skillDir);
4045
- relatedFiles = files.filter((f) => f !== path9.basename(entry.path)).map((f) => path9.join(skillDir, f));
4157
+ const files = await fs8.readdir(skillDir);
4158
+ relatedFiles = files.filter((f) => f !== path8.basename(entry.path)).map((f) => path8.join(skillDir, f));
4046
4159
  } catch {
4047
4160
  }
4048
- const nameLower = name.toLowerCase();
4161
+ const nameLower = name2.toLowerCase();
4049
4162
  const refResults = await Promise.all(
4050
4163
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
4051
4164
  try {
4052
- const content = await fs9.readFile(e.path, "utf8");
4165
+ const content = await fs8.readFile(e.path, "utf8");
4053
4166
  return [e.name, content.toLowerCase().includes(nameLower)];
4054
4167
  } catch {
4055
4168
  return [e.name, false];
@@ -4057,7 +4170,7 @@ async function handleSkillsContent(ws, ctx, msg) {
4057
4170
  })
4058
4171
  );
4059
4172
  const refs = refResults.filter(([, hasRef]) => hasRef).map(([n]) => n);
4060
- send(ws, { type: "skills.content", payload: { name, body, path: entry.path, source, relatedFiles, references: refs } });
4173
+ send(ws, { type: "skills.content", payload: { name: name2, body, path: entry.path, source, relatedFiles, references: refs } });
4061
4174
  } catch (err) {
4062
4175
  send(ws, { type: "skills.content", payload: { name: contentPayload.name, body: "", path: "", source: contentPayload.source, relatedFiles: [], references: [], error: errMessage(err) } });
4063
4176
  }
@@ -4146,14 +4259,14 @@ async function handleSkillsCreate(ws, ctx, msg) {
4146
4259
  return;
4147
4260
  }
4148
4261
  try {
4149
- const targetDir = createPayload.scope === "global" ? path9.join(wstackGlobalRoot(), "skills", createPayload.name.trim()) : path9.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
4262
+ const targetDir = createPayload.scope === "global" ? path8.join(wstackGlobalRoot(), "skills", createPayload.name.trim()) : path8.join(ctx.projectRoot, ".wrongstack", "skills", createPayload.name.trim());
4150
4263
  try {
4151
- await fs9.access(targetDir);
4264
+ await fs8.access(targetDir);
4152
4265
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
4153
4266
  return;
4154
4267
  } catch {
4155
4268
  }
4156
- await fs9.mkdir(targetDir, { recursive: true });
4269
+ await fs8.mkdir(targetDir, { recursive: true });
4157
4270
  const lines = createPayload.description.trim().split("\n");
4158
4271
  const firstLine = lines[0].trim();
4159
4272
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -4201,13 +4314,13 @@ ${trigger}
4201
4314
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
4202
4315
  "- `output-standards` \u2014 for standardized `<next_steps>` formatting"
4203
4316
  ].join("\n");
4204
- await fs9.writeFile(path9.join(targetDir, "SKILL.md"), skillContent, "utf-8");
4317
+ await fs8.writeFile(path8.join(targetDir, "SKILL.md"), skillContent, "utf-8");
4205
4318
  send(ws, {
4206
4319
  type: "skills.created",
4207
4320
  payload: {
4208
4321
  success: true,
4209
4322
  error: null,
4210
- skill: { name: createPayload.name.trim(), path: path9.join(targetDir, "SKILL.md"), scope: createPayload.scope }
4323
+ skill: { name: createPayload.name.trim(), path: path8.join(targetDir, "SKILL.md"), scope: createPayload.scope }
4211
4324
  }
4212
4325
  });
4213
4326
  } catch (err) {
@@ -4239,7 +4352,7 @@ async function handleSkillsEdit(ws, ctx, msg) {
4239
4352
  send(ws, { type: "skills.edited", payload: { success: false, error: "Bundled skills cannot be edited" } });
4240
4353
  return;
4241
4354
  }
4242
- await fs9.writeFile(entry.path, editPayload.body, "utf-8");
4355
+ await fs8.writeFile(entry.path, editPayload.body, "utf-8");
4243
4356
  send(ws, { type: "skills.edited", payload: { success: true, error: null } });
4244
4357
  } catch (err) {
4245
4358
  send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
@@ -4356,6 +4469,21 @@ async function startWebUI(opts = {}) {
4356
4469
  toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
4357
4470
  toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
4358
4471
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
4472
+ const mcpRegistry = new MCPRegistry({
4473
+ toolRegistry,
4474
+ events,
4475
+ log: logger,
4476
+ // Lazy-connect (per-server `lazy`) manifest cache + default idle auto-sleep.
4477
+ cacheDir: wpaths.cacheDir
4478
+ });
4479
+ if (config.features.mcp && config.mcpServers) {
4480
+ for (const [name2, cfg] of Object.entries(config.mcpServers)) {
4481
+ if (cfg.enabled === false) continue;
4482
+ void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
4483
+ logger.warn(`MCP server "${name2}" failed to start at boot`, err);
4484
+ });
4485
+ }
4486
+ }
4359
4487
  let sessionStore = opts.services?.session ?? new DefaultSessionStore2({ dir: wpaths.projectSessions });
4360
4488
  if (!opts.services?.session) {
4361
4489
  sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
@@ -4383,7 +4511,7 @@ async function startWebUI(opts = {}) {
4383
4511
  sessionId: session.id,
4384
4512
  projectSlug: wpaths.projectSlug,
4385
4513
  projectRoot,
4386
- projectName: path10.basename(projectRoot),
4514
+ projectName: path9.basename(projectRoot),
4387
4515
  workingDir,
4388
4516
  clientType: "webui",
4389
4517
  pid: process.pid,
@@ -4439,9 +4567,9 @@ async function startWebUI(opts = {}) {
4439
4567
  } : void 0;
4440
4568
  const skillLoader = config.features.skills ? new DefaultSkillLoader2({ paths: wpaths }) : void 0;
4441
4569
  const skillInstaller = config.features.skills ? new SkillInstaller({
4442
- manifestPath: path10.join(wstackGlobalRoot2(), "installed-skills.json"),
4443
- projectSkillsDir: path10.join(projectRoot, ".wrongstack", "skills"),
4444
- globalSkillsDir: path10.join(wstackGlobalRoot2(), "skills"),
4570
+ manifestPath: path9.join(wstackGlobalRoot2(), "installed-skills.json"),
4571
+ projectSkillsDir: path9.join(projectRoot, ".wrongstack", "skills"),
4572
+ globalSkillsDir: path9.join(wstackGlobalRoot2(), "skills"),
4445
4573
  projectHash: projectHash(projectRoot),
4446
4574
  skillLoader
4447
4575
  }) : void 0;
@@ -4602,7 +4730,7 @@ async function startWebUI(opts = {}) {
4602
4730
  const write = async () => {
4603
4731
  let raw;
4604
4732
  try {
4605
- raw = await fs10.readFile(globalConfigPath, "utf8");
4733
+ raw = await fs9.readFile(globalConfigPath, "utf8");
4606
4734
  } catch {
4607
4735
  raw = "{}";
4608
4736
  }
@@ -4911,7 +5039,7 @@ async function startWebUI(opts = {}) {
4911
5039
  inputCost,
4912
5040
  outputCost,
4913
5041
  cacheReadCost,
4914
- projectName: path10.basename(projectRoot) || projectRoot,
5042
+ projectName: path9.basename(projectRoot) || projectRoot,
4915
5043
  projectRoot,
4916
5044
  cwd: workingDir,
4917
5045
  mode: modeId,
@@ -5126,33 +5254,33 @@ async function startWebUI(opts = {}) {
5126
5254
  });
5127
5255
  }
5128
5256
  async function touchProjectEntry(root, workDir) {
5129
- const resolved = path10.resolve(root);
5257
+ const resolved = path9.resolve(root);
5130
5258
  const manifest = await loadManifest(globalConfigPath);
5131
5259
  const now = (/* @__PURE__ */ new Date()).toISOString();
5132
- const existing = manifest.projects.find((p) => path10.resolve(p.root) === resolved);
5260
+ const existing = manifest.projects.find((p) => path9.resolve(p.root) === resolved);
5133
5261
  if (existing) {
5134
5262
  existing.lastSeen = now;
5135
- if (workDir) existing.lastWorkingDir = path10.resolve(workDir);
5263
+ if (workDir) existing.lastWorkingDir = path9.resolve(workDir);
5136
5264
  } else {
5137
5265
  manifest.projects.push({
5138
- name: path10.basename(resolved),
5266
+ name: path9.basename(resolved),
5139
5267
  root: resolved,
5140
5268
  slug: generateProjectSlug(resolved),
5141
5269
  createdAt: now,
5142
5270
  lastSeen: now,
5143
- lastWorkingDir: workDir ? path10.resolve(workDir) : void 0
5271
+ lastWorkingDir: workDir ? path9.resolve(workDir) : void 0
5144
5272
  });
5145
5273
  }
5146
5274
  await saveManifest(manifest, globalConfigPath);
5147
5275
  await ensureProjectDataDir(generateProjectSlug(resolved), globalConfigPath);
5148
5276
  }
5149
5277
  function projectsJsonPath(globalConfigPath2) {
5150
- const base = path10.dirname(globalConfigPath2);
5151
- return path10.join(base, "projects.json");
5278
+ const base = path9.dirname(globalConfigPath2);
5279
+ return path9.join(base, "projects.json");
5152
5280
  }
5153
5281
  async function loadManifest(globalConfigPath2) {
5154
5282
  try {
5155
- const raw = await fs10.readFile(projectsJsonPath(globalConfigPath2), "utf8");
5283
+ const raw = await fs9.readFile(projectsJsonPath(globalConfigPath2), "utf8");
5156
5284
  const parsed = JSON.parse(raw);
5157
5285
  return { projects: parsed.projects ?? [] };
5158
5286
  } catch {
@@ -5161,16 +5289,16 @@ async function startWebUI(opts = {}) {
5161
5289
  }
5162
5290
  async function saveManifest(manifest, globalConfigPath2) {
5163
5291
  const file = projectsJsonPath(globalConfigPath2);
5164
- await fs10.mkdir(path10.dirname(file), { recursive: true });
5165
- await fs10.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
5292
+ await fs9.mkdir(path9.dirname(file), { recursive: true });
5293
+ await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
5166
5294
  }
5167
5295
  function generateProjectSlug(rootPath) {
5168
5296
  return projectSlug(rootPath);
5169
5297
  }
5170
5298
  async function ensureProjectDataDir(slug, globalConfigPath2) {
5171
- const base = path10.dirname(globalConfigPath2);
5172
- const dir = path10.join(base, "projects", slug);
5173
- await fs10.mkdir(dir, { recursive: true });
5299
+ const base = path9.dirname(globalConfigPath2);
5300
+ const dir = path9.join(base, "projects", slug);
5301
+ await fs9.mkdir(dir, { recursive: true });
5174
5302
  return dir;
5175
5303
  }
5176
5304
  async function handleMessage(ws, _client, msg) {
@@ -5476,27 +5604,17 @@ async function startWebUI(opts = {}) {
5476
5604
  }
5477
5605
  case "provider.models": {
5478
5606
  const providerId = msg.payload.providerId;
5479
- const provider2 = await modelsRegistry.getProvider(providerId);
5480
- if (provider2) {
5481
- send(ws, {
5482
- type: "provider.models",
5483
- payload: {
5484
- provider: providerId,
5485
- models: provider2.models.map((m) => ({
5486
- id: m.id,
5487
- name: m.name,
5488
- releaseDate: m.release_date,
5489
- contextWindow: m.limit?.context,
5490
- inputCost: m.cost?.input,
5491
- outputCost: m.cost?.output,
5492
- capabilities: [
5493
- ...m.tool_call ? ["tools"] : [],
5494
- ...m.reasoning ? ["reasoning"] : []
5495
- ]
5496
- }))
5497
- }
5498
- });
5499
- }
5607
+ const saved = await providerHandlers.loadConfigProviders();
5608
+ const cfg = saved[providerId];
5609
+ const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
5610
+ const provider2 = await modelsRegistry.getProvider(catalogId);
5611
+ send(ws, {
5612
+ type: "provider.models",
5613
+ payload: {
5614
+ provider: providerId,
5615
+ models: resolveProviderModelList(cfg?.models, provider2)
5616
+ }
5617
+ });
5500
5618
  break;
5501
5619
  }
5502
5620
  case "model.switch": {
@@ -5511,7 +5629,7 @@ async function startWebUI(opts = {}) {
5511
5629
  updateAutoCompactionMaxContext?.(newProv);
5512
5630
  try {
5513
5631
  const next = configWriteLock.then(async () => {
5514
- const raw = await fs10.readFile(globalConfigPath, "utf8");
5632
+ const raw = await fs9.readFile(globalConfigPath, "utf8");
5515
5633
  const parsed = JSON.parse(raw);
5516
5634
  parsed.provider = newProvider;
5517
5635
  parsed.model = newModel;
@@ -5745,27 +5863,28 @@ async function startWebUI(opts = {}) {
5745
5863
  return handleMemoryRemember(ws, msg, memoryStore);
5746
5864
  case "memory.forget":
5747
5865
  return handleMemoryForget(ws, msg, memoryStore);
5748
- // ── MCP operations — delegated to shared handlers (mcp-handlers.ts) ──
5866
+ // ── MCP operations — delegated to shared handlers (mcp-handlers.ts),
5867
+ // backed by the live MCPRegistry constructed above. ──
5749
5868
  case "mcp.list":
5750
- return handleMcpList(ws, msg, config, globalConfigPath, void 0);
5869
+ return handleMcpList(ws, msg, globalConfigPath, mcpRegistry);
5751
5870
  case "mcp.add":
5752
- return handleMcpAdd(ws, msg, config, globalConfigPath, void 0);
5871
+ return handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry);
5753
5872
  case "mcp.remove":
5754
- return handleMcpRemove(ws, msg, config, globalConfigPath, void 0);
5873
+ return handleMcpRemove(ws, msg, globalConfigPath, mcpRegistry);
5755
5874
  case "mcp.update":
5756
- return handleMcpUpdate(ws, msg, config, globalConfigPath);
5875
+ return handleMcpUpdate(ws, msg, globalConfigPath, mcpRegistry);
5757
5876
  case "mcp.wake":
5758
- return handleMcpWake(ws, msg, config, globalConfigPath, void 0);
5877
+ return handleMcpWake(ws, msg, globalConfigPath, mcpRegistry);
5759
5878
  case "mcp.sleep":
5760
- return handleMcpSleep(ws, msg, config, globalConfigPath, void 0);
5879
+ return handleMcpSleep(ws, msg, globalConfigPath, mcpRegistry);
5761
5880
  case "mcp.discover":
5762
- return handleMcpDiscover(ws, msg, config, globalConfigPath);
5881
+ return handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry);
5763
5882
  case "mcp.enable":
5764
- return handleMcpEnable(ws, msg, config, globalConfigPath);
5883
+ return handleMcpEnable(ws, msg, globalConfigPath, mcpRegistry);
5765
5884
  case "mcp.disable":
5766
- return handleMcpDisable(ws, msg, config, globalConfigPath);
5885
+ return handleMcpDisable(ws, msg, globalConfigPath, mcpRegistry);
5767
5886
  case "mcp.restart":
5768
- return handleMcpRestart(ws, msg, config, globalConfigPath);
5887
+ return handleMcpRestart(ws, msg, globalConfigPath, mcpRegistry);
5769
5888
  case "skills.list": {
5770
5889
  if (!skillLoader) {
5771
5890
  send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
@@ -5827,33 +5946,33 @@ async function startWebUI(opts = {}) {
5827
5946
  break;
5828
5947
  }
5829
5948
  try {
5830
- const { name, source } = contentPayload;
5949
+ const { name: name2, source } = contentPayload;
5831
5950
  const entries = await skillLoader.listEntries();
5832
- const entry = entries.find((e) => e.name.toLowerCase() === name.toLowerCase());
5951
+ const entry = entries.find((e) => e.name.toLowerCase() === name2.toLowerCase());
5833
5952
  if (!entry) {
5834
- send(ws, { type: "skills.content", payload: { name, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name}" not found` } });
5953
+ send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
5835
5954
  break;
5836
5955
  }
5837
- const body = await skillLoader.readBody(name);
5838
- const skillDir = path10.dirname(entry.path);
5956
+ const body = await skillLoader.readBody(name2);
5957
+ const skillDir = path9.dirname(entry.path);
5839
5958
  let relatedFiles = [];
5840
5959
  try {
5841
- const files = await fs10.readdir(skillDir);
5842
- relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, f));
5960
+ const files = await fs9.readdir(skillDir);
5961
+ relatedFiles = files.filter((f) => f !== path9.basename(entry.path)).map((f) => path9.join(skillDir, f));
5843
5962
  } catch {
5844
5963
  }
5845
5964
  const refs = [];
5846
5965
  for (const e of entries) {
5847
- if (e.name.toLowerCase() === name.toLowerCase()) continue;
5966
+ if (e.name.toLowerCase() === name2.toLowerCase()) continue;
5848
5967
  try {
5849
5968
  const content = await skillLoader.readBody(e.name);
5850
- if (content.toLowerCase().includes(name.toLowerCase())) {
5969
+ if (content.toLowerCase().includes(name2.toLowerCase())) {
5851
5970
  refs.push(e.name);
5852
5971
  }
5853
5972
  } catch {
5854
5973
  }
5855
5974
  }
5856
- send(ws, { type: "skills.content", payload: { name, body, path: entry.path, source, relatedFiles, references: refs } });
5975
+ send(ws, { type: "skills.content", payload: { name: name2, body, path: entry.path, source, relatedFiles, references: refs } });
5857
5976
  } catch (err) {
5858
5977
  send(ws, { type: "skills.content", payload: { name: contentPayload.name, body: "", path: "", source: contentPayload.source, relatedFiles: [], references: [], error: errMessage(err) } });
5859
5978
  }
@@ -5946,14 +6065,14 @@ async function startWebUI(opts = {}) {
5946
6065
  break;
5947
6066
  }
5948
6067
  try {
5949
- const targetDir = createPayload.scope === "global" ? path10.join(wstackGlobalRoot2(), "skills", createPayload.name.trim()) : path10.join(projectRoot, ".wrongstack", "skills", createPayload.name.trim());
6068
+ const targetDir = createPayload.scope === "global" ? path9.join(wstackGlobalRoot2(), "skills", createPayload.name.trim()) : path9.join(projectRoot, ".wrongstack", "skills", createPayload.name.trim());
5950
6069
  try {
5951
- await fs10.access(targetDir);
6070
+ await fs9.access(targetDir);
5952
6071
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
5953
6072
  break;
5954
6073
  } catch {
5955
6074
  }
5956
- await fs10.mkdir(targetDir, { recursive: true });
6075
+ await fs9.mkdir(targetDir, { recursive: true });
5957
6076
  const lines = createPayload.description.trim().split("\n");
5958
6077
  const firstLine = lines[0].trim();
5959
6078
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -6001,13 +6120,13 @@ ${trigger}
6001
6120
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
6002
6121
  "- `output-standards` \u2014 for standardized `<next_steps>` formatting"
6003
6122
  ].join("\n");
6004
- await fs10.writeFile(path10.join(targetDir, "SKILL.md"), skillContent, "utf-8");
6123
+ await fs9.writeFile(path9.join(targetDir, "SKILL.md"), skillContent, "utf-8");
6005
6124
  send(ws, {
6006
6125
  type: "skills.created",
6007
6126
  payload: {
6008
6127
  success: true,
6009
6128
  error: null,
6010
- skill: { name: createPayload.name.trim(), path: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
6129
+ skill: { name: createPayload.name.trim(), path: path9.join(targetDir, "SKILL.md"), scope: createPayload.scope }
6011
6130
  }
6012
6131
  });
6013
6132
  } catch (err) {
@@ -6040,7 +6159,7 @@ ${trigger}
6040
6159
  send(ws, { type: "skills.edited", payload: { success: false, error: "Bundled skills cannot be edited" } });
6041
6160
  break;
6042
6161
  }
6043
- await fs10.writeFile(entry.path, editPayload.body, "utf-8");
6162
+ await fs9.writeFile(entry.path, editPayload.body, "utf-8");
6044
6163
  send(ws, { type: "skills.edited", payload: { success: true, error: null } });
6045
6164
  } catch (err) {
6046
6165
  send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
@@ -6329,6 +6448,14 @@ ${trigger}
6329
6448
  await handleGitInfo(ws, projectRoot);
6330
6449
  break;
6331
6450
  }
6451
+ case "git.changes": {
6452
+ await handleGitChanges(ws, projectRoot);
6453
+ break;
6454
+ }
6455
+ case "git.diff": {
6456
+ await handleGitDiff(ws, projectRoot, String(msg.payload?.path ?? ""));
6457
+ break;
6458
+ }
6332
6459
  case "webui.shutdown": {
6333
6460
  console.log("[WebUI] Shutdown requested from client");
6334
6461
  process.kill(process.pid, "SIGINT");
@@ -6337,7 +6464,7 @@ ${trigger}
6337
6464
  case "goal.get": {
6338
6465
  try {
6339
6466
  const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
6340
- const raw = await fs10.readFile(goalPath, "utf8");
6467
+ const raw = await fs9.readFile(goalPath, "utf8");
6341
6468
  const goal = JSON.parse(raw);
6342
6469
  broadcast(clients, { type: "goal.updated", payload: goal });
6343
6470
  } catch {
@@ -6397,7 +6524,7 @@ ${trigger}
6397
6524
  try {
6398
6525
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
6399
6526
  const rewinder = new DefaultSessionRewinder(
6400
- path10.join(projectRoot, ".wrongstack", "sessions"),
6527
+ path9.join(projectRoot, ".wrongstack", "sessions"),
6401
6528
  projectRoot
6402
6529
  );
6403
6530
  const checkpoints = await rewinder.listCheckpoints(session.id);
@@ -6418,7 +6545,7 @@ ${trigger}
6418
6545
  try {
6419
6546
  const { DefaultSessionRewinder } = await import("@wrongstack/core");
6420
6547
  const rewinder = new DefaultSessionRewinder(
6421
- path10.join(projectRoot, ".wrongstack", "sessions"),
6548
+ path9.join(projectRoot, ".wrongstack", "sessions"),
6422
6549
  projectRoot
6423
6550
  );
6424
6551
  await rewinder.rewindToCheckpoint(session.id, checkpointIndex);
@@ -6452,9 +6579,9 @@ ${trigger}
6452
6579
  case "projects.add": {
6453
6580
  const { root: addRoot, name: displayName } = msg.payload;
6454
6581
  try {
6455
- const resolved = path10.resolve(addRoot);
6456
- await fs10.access(resolved);
6457
- const stat2 = await fs10.stat(resolved);
6582
+ const resolved = path9.resolve(addRoot);
6583
+ await fs9.access(resolved);
6584
+ const stat2 = await fs9.stat(resolved);
6458
6585
  if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
6459
6586
  const manifest = await loadManifest(globalConfigPath);
6460
6587
  const existing = manifest.projects.find((p) => p.root === resolved);
@@ -6470,26 +6597,26 @@ ${trigger}
6470
6597
  });
6471
6598
  break;
6472
6599
  }
6473
- const name = displayName?.trim() || path10.basename(resolved);
6600
+ const name2 = displayName?.trim() || path9.basename(resolved);
6474
6601
  const slug = generateProjectSlug(resolved);
6475
6602
  await ensureProjectDataDir(slug, globalConfigPath);
6476
6603
  const now = (/* @__PURE__ */ new Date()).toISOString();
6477
- manifest.projects.push({ name, root: resolved, slug, lastSeen: now, createdAt: now });
6604
+ manifest.projects.push({ name: name2, root: resolved, slug, lastSeen: now, createdAt: now });
6478
6605
  await saveManifest(manifest, globalConfigPath);
6479
6606
  send(ws, {
6480
6607
  type: "projects.added",
6481
6608
  payload: {
6482
- name,
6609
+ name: name2,
6483
6610
  root: resolved,
6484
6611
  slug,
6485
- message: `Registered project "${name}"`
6612
+ message: `Registered project "${name2}"`
6486
6613
  }
6487
6614
  });
6488
6615
  } catch (err) {
6489
6616
  send(ws, {
6490
6617
  type: "projects.added",
6491
6618
  payload: {
6492
- name: path10.basename(addRoot),
6619
+ name: path9.basename(addRoot),
6493
6620
  root: addRoot,
6494
6621
  slug: "",
6495
6622
  message: errMessage(err)
@@ -6501,17 +6628,17 @@ ${trigger}
6501
6628
  case "projects.select": {
6502
6629
  const { root: selRoot, name: selName } = msg.payload;
6503
6630
  try {
6504
- const resolved = path10.resolve(selRoot);
6631
+ const resolved = path9.resolve(selRoot);
6505
6632
  try {
6506
- await fs10.access(resolved);
6507
- const stat2 = await fs10.stat(resolved);
6633
+ await fs9.access(resolved);
6634
+ const stat2 = await fs9.stat(resolved);
6508
6635
  if (!stat2.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
6509
6636
  } catch (err) {
6510
6637
  send(ws, {
6511
6638
  type: "projects.selected",
6512
6639
  payload: {
6513
6640
  root: selRoot,
6514
- name: selName || path10.basename(selRoot),
6641
+ name: selName || path9.basename(selRoot),
6515
6642
  message: `Cannot switch: ${errMessage(err)}`
6516
6643
  }
6517
6644
  });
@@ -6523,10 +6650,10 @@ ${trigger}
6523
6650
  entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
6524
6651
  entry.lastWorkingDir = resolved;
6525
6652
  } else {
6526
- const name = selName?.trim() || path10.basename(resolved);
6653
+ const name2 = selName?.trim() || path9.basename(resolved);
6527
6654
  const slug = generateProjectSlug(resolved);
6528
6655
  manifest.projects.push({
6529
- name,
6656
+ name: name2,
6530
6657
  root: resolved,
6531
6658
  slug,
6532
6659
  lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6564,13 +6691,13 @@ ${trigger}
6564
6691
  });
6565
6692
  } catch {
6566
6693
  }
6567
- const newSessionsDir = path10.join(
6568
- path10.dirname(globalConfigPath),
6694
+ const newSessionsDir = path9.join(
6695
+ path9.dirname(globalConfigPath),
6569
6696
  "projects",
6570
6697
  switchSlug,
6571
6698
  "sessions"
6572
6699
  );
6573
- await fs10.mkdir(newSessionsDir, { recursive: true });
6700
+ await fs9.mkdir(newSessionsDir, { recursive: true });
6574
6701
  const newSessionStore = new DefaultSessionStore2({ dir: newSessionsDir });
6575
6702
  const oldSessionId = session.id;
6576
6703
  try {
@@ -6602,7 +6729,7 @@ ${trigger}
6602
6729
  sessionId: session.id,
6603
6730
  projectSlug: switchSlug,
6604
6731
  projectRoot,
6605
- projectName: path10.basename(projectRoot),
6732
+ projectName: path9.basename(projectRoot),
6606
6733
  workingDir,
6607
6734
  clientType: "webui",
6608
6735
  pid: process.pid,
@@ -6614,8 +6741,8 @@ ${trigger}
6614
6741
  type: "projects.selected",
6615
6742
  payload: {
6616
6743
  root: resolved,
6617
- name: selName || path10.basename(resolved),
6618
- message: `Switched to ${selName || path10.basename(resolved)}`
6744
+ name: selName || path9.basename(resolved),
6745
+ message: `Switched to ${selName || path9.basename(resolved)}`
6619
6746
  }
6620
6747
  });
6621
6748
  broadcast(clients, {
@@ -6638,7 +6765,7 @@ ${trigger}
6638
6765
  type: "projects.selected",
6639
6766
  payload: {
6640
6767
  root: selRoot,
6641
- name: selName || path10.basename(selRoot),
6768
+ name: selName || path9.basename(selRoot),
6642
6769
  message: errMessage(err)
6643
6770
  }
6644
6771
  });
@@ -6649,14 +6776,14 @@ ${trigger}
6649
6776
  case "working_dir.set": {
6650
6777
  const { path: newPath } = msg.payload;
6651
6778
  try {
6652
- const resolved = path10.resolve(projectRoot, newPath);
6653
- if (!resolved.startsWith(projectRoot + path10.sep) && resolved !== projectRoot) {
6779
+ const resolved = path9.resolve(projectRoot, newPath);
6780
+ if (!resolved.startsWith(projectRoot + path9.sep) && resolved !== projectRoot) {
6654
6781
  sendResult2(ws, false, `Path must stay inside the project root: ${projectRoot}`);
6655
6782
  break;
6656
6783
  }
6657
6784
  try {
6658
- await fs10.access(resolved);
6659
- const stat2 = await fs10.stat(resolved);
6785
+ await fs9.access(resolved);
6786
+ const stat2 = await fs9.stat(resolved);
6660
6787
  if (!stat2.isDirectory()) throw new Error("Not a directory");
6661
6788
  } catch {
6662
6789
  sendResult2(ws, false, `Directory not found or not accessible: ${resolved}`);
@@ -6687,24 +6814,24 @@ ${trigger}
6687
6814
  case "mailbox.messages":
6688
6815
  return handleMailboxMessages(
6689
6816
  ws,
6690
- { projectRoot, globalRoot: path10.dirname(globalConfigPath) },
6817
+ { projectRoot, globalRoot: path9.dirname(globalConfigPath) },
6691
6818
  msg.payload
6692
6819
  );
6693
6820
  case "mailbox.agents":
6694
6821
  return handleMailboxAgents(
6695
6822
  ws,
6696
- { projectRoot, globalRoot: path10.dirname(globalConfigPath) },
6823
+ { projectRoot, globalRoot: path9.dirname(globalConfigPath) },
6697
6824
  msg.payload
6698
6825
  );
6699
6826
  case "mailbox.clear":
6700
6827
  return handleMailboxClear(
6701
6828
  ws,
6702
- { projectRoot, globalRoot: path10.dirname(globalConfigPath) }
6829
+ { projectRoot, globalRoot: path9.dirname(globalConfigPath) }
6703
6830
  );
6704
6831
  case "mailbox.purge":
6705
6832
  return handleMailboxPurge(
6706
6833
  ws,
6707
- { projectRoot, globalRoot: path10.dirname(globalConfigPath) },
6834
+ { projectRoot, globalRoot: path9.dirname(globalConfigPath) },
6708
6835
  msg.payload
6709
6836
  );
6710
6837
  // ── Brain — status, autonomy ceiling, direct decision support ───
@@ -6783,7 +6910,7 @@ ${trigger}
6783
6910
  };
6784
6911
  const httpServer = createHttpServer({
6785
6912
  host: wsHost,
6786
- distDir: path10.resolve(import.meta.dirname, "../../dist"),
6913
+ distDir: path9.resolve(import.meta.dirname, "../../dist"),
6787
6914
  wsPort,
6788
6915
  globalRoot: wpaths.globalRoot,
6789
6916
  apiToken: wsToken,
@@ -6792,7 +6919,7 @@ ${trigger}
6792
6919
  void fleetBroadcast?.();
6793
6920
  }
6794
6921
  });
6795
- const registryBaseDir = path10.dirname(globalConfigPath);
6922
+ const registryBaseDir = path9.dirname(globalConfigPath);
6796
6923
  httpServer.listen(httpPort, wsHost, () => {
6797
6924
  const openUrl = `http://${wsHost}:${httpPort}`;
6798
6925
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
@@ -6804,7 +6931,7 @@ ${trigger}
6804
6931
  wsPort,
6805
6932
  host: wsHost,
6806
6933
  projectRoot,
6807
- projectName: path10.basename(projectRoot) || projectRoot,
6934
+ projectName: path9.basename(projectRoot) || projectRoot,
6808
6935
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6809
6936
  url: `http://${wsHost}:${httpPort}`
6810
6937
  },
@@ -6831,6 +6958,7 @@ ${trigger}
6831
6958
  // reality. Crash exits are healed by the next register()/list() prune pass.
6832
6959
  onShutdown: () => {
6833
6960
  brainMonitor.stop();
6961
+ void mcpRegistry.stopAll().catch(() => void 0);
6834
6962
  if (disposeEvents) {
6835
6963
  disposeEvents();
6836
6964
  disposeEvents = null;
@@ -6865,6 +6993,8 @@ export {
6865
6993
  handleFilesRead,
6866
6994
  handleFilesTree,
6867
6995
  handleFilesWrite,
6996
+ handleGitChanges,
6997
+ handleGitDiff,
6868
6998
  handleGitInfo,
6869
6999
  handleMcpAdd,
6870
7000
  handleMcpDisable,