@wrongstack/webui-server 0.284.1 → 0.286.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.
Files changed (176) hide show
  1. package/dist/index.d.ts +11 -2300
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +1807 -539
  4. package/dist/index.js.map +7 -1
  5. package/dist/server/autophase-routes.d.ts +10 -0
  6. package/dist/server/autophase-routes.d.ts.map +1 -0
  7. package/dist/server/autophase-ws-handler.d.ts +87 -0
  8. package/dist/server/autophase-ws-handler.d.ts.map +1 -0
  9. package/dist/server/backend-services.d.ts +103 -0
  10. package/dist/server/backend-services.d.ts.map +1 -0
  11. package/dist/server/boot.d.ts +19 -0
  12. package/dist/server/boot.d.ts.map +1 -0
  13. package/dist/server/brain-routes.d.ts +11 -0
  14. package/dist/server/brain-routes.d.ts.map +1 -0
  15. package/dist/server/codebase-indexing.d.ts +15 -0
  16. package/dist/server/codebase-indexing.d.ts.map +1 -0
  17. package/dist/server/collaboration-ws-handler.d.ts +167 -0
  18. package/dist/server/collaboration-ws-handler.d.ts.map +1 -0
  19. package/dist/server/completion-handlers.d.ts +39 -0
  20. package/dist/server/completion-handlers.d.ts.map +1 -0
  21. package/dist/server/connection-handler.d.ts +72 -0
  22. package/dist/server/connection-handler.d.ts.map +1 -0
  23. package/dist/server/context-meta.d.ts +28 -0
  24. package/dist/server/context-meta.d.ts.map +1 -0
  25. package/dist/server/custom-context-modes.d.ts +43 -0
  26. package/dist/server/custom-context-modes.d.ts.map +1 -0
  27. package/dist/server/design-handlers.d.ts +40 -0
  28. package/dist/server/design-handlers.d.ts.map +1 -0
  29. package/dist/server/discover-mailbox-bridge.d.ts +26 -0
  30. package/dist/server/discover-mailbox-bridge.d.ts.map +1 -0
  31. package/dist/server/entry.d.ts +2 -2
  32. package/dist/server/entry.d.ts.map +1 -0
  33. package/dist/server/entry.js +1595 -562
  34. package/dist/server/entry.js.map +7 -1
  35. package/dist/server/eternal-iteration-broadcast.d.ts +11 -0
  36. package/dist/server/eternal-iteration-broadcast.d.ts.map +1 -0
  37. package/dist/server/file-handlers.d.ts +45 -0
  38. package/dist/server/file-handlers.d.ts.map +1 -0
  39. package/dist/server/file-picker.d.ts +26 -0
  40. package/dist/server/file-picker.d.ts.map +1 -0
  41. package/dist/server/git-handlers.d.ts +55 -0
  42. package/dist/server/git-handlers.d.ts.map +1 -0
  43. package/dist/server/goal-handlers.d.ts +19 -0
  44. package/dist/server/goal-handlers.d.ts.map +1 -0
  45. package/dist/server/handlers/index.d.ts +12 -0
  46. package/dist/server/handlers/index.d.ts.map +1 -0
  47. package/dist/server/handlers/worklist-handlers.d.ts +49 -0
  48. package/dist/server/handlers/worklist-handlers.d.ts.map +1 -0
  49. package/dist/server/handlers.d.ts +1 -51
  50. package/dist/server/handlers.js +17 -8
  51. package/dist/server/handlers.js.map +7 -1
  52. package/dist/server/http-server/analytics-handler.d.ts +35 -0
  53. package/dist/server/http-server/analytics-handler.d.ts.map +1 -0
  54. package/dist/server/http-server/api-handlers.d.ts +44 -0
  55. package/dist/server/http-server/api-handlers.d.ts.map +1 -0
  56. package/dist/server/http-server.d.ts +108 -0
  57. package/dist/server/http-server.d.ts.map +1 -0
  58. package/dist/server/index.d.ts +93 -0
  59. package/dist/server/index.d.ts.map +1 -0
  60. package/dist/server/instance-registry.d.ts +63 -0
  61. package/dist/server/instance-registry.d.ts.map +1 -0
  62. package/dist/server/kanban-routes.d.ts +7 -0
  63. package/dist/server/kanban-routes.d.ts.map +1 -0
  64. package/dist/server/lifecycle.d.ts +51 -0
  65. package/dist/server/lifecycle.d.ts.map +1 -0
  66. package/dist/server/mailbox-handlers.d.ts +63 -0
  67. package/dist/server/mailbox-handlers.d.ts.map +1 -0
  68. package/dist/server/mailbox-routes.d.ts +11 -0
  69. package/dist/server/mailbox-routes.d.ts.map +1 -0
  70. package/dist/server/mcp-handlers.d.ts +55 -0
  71. package/dist/server/mcp-handlers.d.ts.map +1 -0
  72. package/dist/server/mcp-routes.d.ts +41 -0
  73. package/dist/server/mcp-routes.d.ts.map +1 -0
  74. package/dist/server/memory-handlers.d.ts +28 -0
  75. package/dist/server/memory-handlers.d.ts.map +1 -0
  76. package/dist/server/message-dispatcher.d.ts +41 -0
  77. package/dist/server/message-dispatcher.d.ts.map +1 -0
  78. package/dist/server/mode-handlers.d.ts +50 -0
  79. package/dist/server/mode-handlers.d.ts.map +1 -0
  80. package/dist/server/mode-routes.d.ts +8 -0
  81. package/dist/server/mode-routes.d.ts.map +1 -0
  82. package/dist/server/model-auto-discovery.d.ts +9 -0
  83. package/dist/server/model-auto-discovery.d.ts.map +1 -0
  84. package/dist/server/model-catalog.d.ts +16 -0
  85. package/dist/server/model-catalog.d.ts.map +1 -0
  86. package/dist/server/open-browser.d.ts +16 -0
  87. package/dist/server/open-browser.d.ts.map +1 -0
  88. package/dist/server/path-containment.d.ts +3 -0
  89. package/dist/server/path-containment.d.ts.map +1 -0
  90. package/dist/server/pending-confirms.d.ts +14 -0
  91. package/dist/server/pending-confirms.d.ts.map +1 -0
  92. package/dist/server/port-utils.d.ts +28 -0
  93. package/dist/server/port-utils.d.ts.map +1 -0
  94. package/dist/server/pre-context-services.d.ts +60 -0
  95. package/dist/server/pre-context-services.d.ts.map +1 -0
  96. package/dist/server/pref-helpers.d.ts +43 -0
  97. package/dist/server/pref-helpers.d.ts.map +1 -0
  98. package/dist/server/prefs-routes.d.ts +27 -0
  99. package/dist/server/prefs-routes.d.ts.map +1 -0
  100. package/dist/server/process-handlers.d.ts +20 -0
  101. package/dist/server/process-handlers.d.ts.map +1 -0
  102. package/dist/server/project-handlers.d.ts +53 -0
  103. package/dist/server/project-handlers.d.ts.map +1 -0
  104. package/dist/server/project-routes.d.ts +10 -0
  105. package/dist/server/project-routes.d.ts.map +1 -0
  106. package/dist/server/projects-manifest.d.ts +18 -0
  107. package/dist/server/projects-manifest.d.ts.map +1 -0
  108. package/dist/server/prompts-handlers.d.ts +38 -0
  109. package/dist/server/prompts-handlers.d.ts.map +1 -0
  110. package/dist/server/provider-config-io.d.ts +15 -0
  111. package/dist/server/provider-config-io.d.ts.map +1 -0
  112. package/dist/server/provider-config-standalone.d.ts +13 -0
  113. package/dist/server/provider-config-standalone.d.ts.map +1 -0
  114. package/dist/server/provider-handlers.d.ts +74 -0
  115. package/dist/server/provider-handlers.d.ts.map +1 -0
  116. package/dist/server/provider-keys.d.ts +53 -0
  117. package/dist/server/provider-keys.d.ts.map +1 -0
  118. package/dist/server/provider-routes.d.ts +13 -0
  119. package/dist/server/provider-routes.d.ts.map +1 -0
  120. package/dist/server/provider-store.d.ts +26 -0
  121. package/dist/server/provider-store.d.ts.map +1 -0
  122. package/dist/server/routes.d.ts +187 -0
  123. package/dist/server/routes.d.ts.map +1 -0
  124. package/dist/server/sdd-board-routes.d.ts +11 -0
  125. package/dist/server/sdd-board-routes.d.ts.map +1 -0
  126. package/dist/server/sdd-board-ws-handler.d.ts +57 -0
  127. package/dist/server/sdd-board-ws-handler.d.ts.map +1 -0
  128. package/dist/server/sdd-wizard-routes.d.ts +16 -0
  129. package/dist/server/sdd-wizard-routes.d.ts.map +1 -0
  130. package/dist/server/sdd-wizard-wiring.d.ts +26 -0
  131. package/dist/server/sdd-wizard-wiring.d.ts.map +1 -0
  132. package/dist/server/sdd-wizard-ws-handler.d.ts +66 -0
  133. package/dist/server/sdd-wizard-ws-handler.d.ts.map +1 -0
  134. package/dist/server/server-runtime.d.ts +118 -0
  135. package/dist/server/server-runtime.d.ts.map +1 -0
  136. package/dist/server/session-handlers.d.ts +57 -0
  137. package/dist/server/session-handlers.d.ts.map +1 -0
  138. package/dist/server/session-routes.d.ts +23 -0
  139. package/dist/server/session-routes.d.ts.map +1 -0
  140. package/dist/server/setup-events.d.ts +67 -0
  141. package/dist/server/setup-events.d.ts.map +1 -0
  142. package/dist/server/setup-screen.d.ts +44 -0
  143. package/dist/server/setup-screen.d.ts.map +1 -0
  144. package/dist/server/shell-git-routes.d.ts +10 -0
  145. package/dist/server/shell-git-routes.d.ts.map +1 -0
  146. package/dist/server/shell-open.d.ts +12 -0
  147. package/dist/server/shell-open.d.ts.map +1 -0
  148. package/dist/server/skills-handlers.d.ts +77 -0
  149. package/dist/server/skills-handlers.d.ts.map +1 -0
  150. package/dist/server/specs-routes.d.ts +11 -0
  151. package/dist/server/specs-routes.d.ts.map +1 -0
  152. package/dist/server/specs-ws-handler.d.ts +34 -0
  153. package/dist/server/specs-ws-handler.d.ts.map +1 -0
  154. package/dist/server/standalone-session-identity.d.ts +35 -0
  155. package/dist/server/standalone-session-identity.d.ts.map +1 -0
  156. package/dist/server/start-webui.d.ts +29 -0
  157. package/dist/server/start-webui.d.ts.map +1 -0
  158. package/dist/server/terminal-ws-handler.d.ts +70 -0
  159. package/dist/server/terminal-ws-handler.d.ts.map +1 -0
  160. package/dist/server/token-estimator.d.ts +66 -0
  161. package/dist/server/token-estimator.d.ts.map +1 -0
  162. package/dist/server/types.d.ts +143 -0
  163. package/dist/server/types.d.ts.map +1 -0
  164. package/dist/server/usage-cost.d.ts +39 -0
  165. package/dist/server/usage-cost.d.ts.map +1 -0
  166. package/dist/server/worktree-ws-handler.d.ts +67 -0
  167. package/dist/server/worktree-ws-handler.d.ts.map +1 -0
  168. package/dist/server/ws-auth.d.ts +83 -0
  169. package/dist/server/ws-auth.d.ts.map +1 -0
  170. package/dist/server/ws-payload-validation.d.ts +126 -0
  171. package/dist/server/ws-payload-validation.d.ts.map +1 -0
  172. package/dist/server/ws-utils.d.ts +38 -0
  173. package/dist/server/ws-utils.d.ts.map +1 -0
  174. package/dist/server/zip.d.ts +9 -0
  175. package/dist/server/zip.d.ts.map +1 -0
  176. package/package.json +11 -12
@@ -1,35 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { DefaultSecretScrubber, ToolValidationError, expectDefined, createDefaultPipelines, resolveSessionLoggingConfig, createSessionEventBridge, watchProviderConfig, atomicWrite, bootConfig as bootConfig$1, DefaultModelsRegistry, TOKENS, ProviderRegistry, ToolRegistry, DefaultMemoryStore, EventBus, makeMailboxTool, makeMailSendTool, makeMailInboxTool, makeFleetStatusTool, applyToolDescriptionModes, applyToolResultRenderModes, DefaultSessionStore, getSessionRegistry, DEFAULT_SESSION_PRUNE_DAYS, DefaultSessionReader, AnnotationsStore, FleetNotifier, AgentStatusTracker, DefaultModeStore, DefaultSkillLoader, SkillInstaller, DefaultPromptLoader, PromptUsageStore, DefaultSystemPromptBuilder, GlobalMailbox, Context, resolveContextWindowPolicy, CollaborationBus, collabInjectMiddleware, collabPauseMiddleware, installDesignStudioMiddleware, createStrategyCompactor, AutoCompactionMiddleware, estimateRequestTokensCalibrated, ToolExecutor, DEFAULT_TOOLS_CONFIG, Agent, ObservableBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, resolveProjectDir, wstackGlobalRoot, createAutonomyBrain, PhaseStore, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, AutoPhasePlanner, assignNickname, computeTaskProgress, DEFAULT_CONTEXT_WINDOW_MODE_ID, repairToolUseAdjacency, resolveWstackPaths, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, resolveProviderModelList, listContextWindowModes, projectSlug, allServers, ConfigError, deserializeTaskGraph, serializeTaskGraph, loadActiveKit, getDesignKitLoader, applyTokenOverrides, runDesignVerify, isDesignStack, materializeTokens, recordOverrides, setDesignOverrides, getDesignState, setActiveKit, recordKitChoice } from '@wrongstack/core';
3
- import { spawnSync, spawn } from 'child_process';
4
- import { toErrorMessage, atomicWrite as atomicWrite$1, resolveWstackPaths as resolveWstackPaths$1, wstackGlobalRoot as wstackGlobalRoot$1 } from '@wrongstack/core/utils';
5
- import * as path5 from 'path';
6
- import path5__default, { resolve, join, sep, dirname } from 'path';
7
- import { searchCodebaseIndex } from '@wrongstack/tools/codebase-index/index';
8
- import { randomUUID, randomBytes, timingSafeEqual } from 'crypto';
9
- import { WebSocket, WebSocketServer } from 'ws';
10
- import * as fs4 from 'fs/promises';
11
- import * as http from 'http';
12
- import { Buffer } from 'buffer';
13
- import * as os from 'os';
14
- import { MCPRegistry, discoverMcp, restartMcp, disableMcp, enableMcp, removeMcp, updateMcp, addMcp, listMcp } from '@wrongstack/mcp';
15
- import * as net from 'net';
16
- import { decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core/security';
17
- import { SpecStore, TaskGraphStore, SddBoardStore, applySddLifecycle, SddRunRegistry, SddInterviewDriver, cleanupStaleSddWorktrees, makeCommandVerifier, SddSupervisor, makeLlmSubtaskGenerator, startSddRun } from '@wrongstack/sdd';
18
- import * as fs12 from 'fs';
19
- import { watch, existsSync, promises } from 'fs';
20
- import JSZip from 'jszip';
21
- import { createRequire } from 'module';
22
- import { fileURLToPath } from 'url';
23
- import { makeProviderFromConfig, buildProviderFactoriesFromRegistry, discoverOpenAICompatibleModels, capabilitiesFor, COMPATIBLE_PRESETS } from '@wrongstack/providers';
24
- import { DefaultTokenCounter } from '@wrongstack/core/infrastructure';
25
- import { createDefaultContainer, makeLightSubagentFactory } from '@wrongstack/runtime';
26
- import { ensureSessionShell, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, configureExecPolicy, configureDangerBypass, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost } from '@wrongstack/tools';
27
- import { readLiveLock } from '@wrongstack/core/coordination';
28
- import { removeColumn, addColumn, getTask, removeTask, assignTask, updateGoalMetricOnTask, addGoalMetricToTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, copyTaskToBoard, transferTaskToBoard, moveTask, updateTask, mergeTasks, splitTask, addTask, syncBoardFromTaskGraph, exportBoardToTaskGraph, getKanbanOrchestrationSnapshot, listReadyTasks, createBoard, generateBoardFromDescription, parseLinesIntoTasks, getBoard, removeBoard, duplicateBoard, updateBoard, getKanbanQueueHealth, listBoards } from '@wrongstack/kanban';
29
- import { beginOAuthLogin } from '@wrongstack/providers/oauth';
30
- import { probeLocalLlm } from '@wrongstack/runtime/probe';
31
-
32
2
  // src/server/entry.ts
3
+ import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core";
4
+
5
+ // src/server/autophase-ws-handler.ts
6
+ import { spawnSync } from "node:child_process";
7
+ import { toErrorMessage } from "@wrongstack/core/utils";
8
+ import {
9
+ assignNickname,
10
+ AutoPhasePlanner,
11
+ PhaseGraphBuilder,
12
+ PhaseOrchestrator,
13
+ PhaseStore,
14
+ WorktreeManager
15
+ } from "@wrongstack/core";
33
16
  function deriveTitle(goal) {
34
17
  const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
35
18
  if (!firstLine) return "AutoPhase";
@@ -520,6 +503,14 @@ Type: ${task.type}`;
520
503
  }
521
504
  }
522
505
  };
506
+
507
+ // src/server/completion-handlers.ts
508
+ import * as path from "node:path";
509
+ import { searchCodebaseIndex } from "@wrongstack/tools/codebase-index/index";
510
+
511
+ // src/server/ws-utils.ts
512
+ import { randomBytes } from "node:crypto";
513
+ import { WebSocket } from "ws";
523
514
  function send(ws, msg) {
524
515
  if (ws.readyState === WebSocket.OPEN) {
525
516
  ws.send(JSON.stringify(msg));
@@ -651,8 +642,8 @@ async function handleCompletionRequest(ws, msg, opts) {
651
642
  return;
652
643
  }
653
644
  const payload = parsed.payload;
654
- const projectRoot = path5.resolve(opts.projectRoot);
655
- const resolved = path5.resolve(projectRoot, payload.filePath);
645
+ const projectRoot = path.resolve(opts.projectRoot);
646
+ const resolved = path.resolve(projectRoot, payload.filePath);
656
647
  if (!isInside(projectRoot, resolved)) {
657
648
  send(ws, {
658
649
  type: "completion.result",
@@ -1051,7 +1042,7 @@ function buildSearchQuery(linePrefix, filePath) {
1051
1042
  if (memberMatch?.[1]) return memberMatch[1];
1052
1043
  const token = linePrefix.match(/([A-Za-z_$][\w$]*)$/)?.[1];
1053
1044
  if (token && token.length >= 2) return token;
1054
- return path5.basename(filePath, path5.extname(filePath));
1045
+ return path.basename(filePath, path.extname(filePath));
1055
1046
  }
1056
1047
  function currentLinePrefix(prefix) {
1057
1048
  const idx = Math.max(prefix.lastIndexOf("\n"), prefix.lastIndexOf("\r"));
@@ -1081,11 +1072,16 @@ function head(value, max) {
1081
1072
  return value.length <= max ? value : value.slice(0, max);
1082
1073
  }
1083
1074
  function isInside(root, target) {
1084
- return target === root || target.startsWith(root + path5.sep);
1075
+ return target === root || target.startsWith(root + path.sep);
1085
1076
  }
1077
+
1078
+ // src/server/custom-context-modes.ts
1079
+ import { listContextWindowModes, atomicWrite } from "@wrongstack/core";
1080
+ import * as fs from "node:fs/promises";
1081
+ import * as path2 from "node:path";
1086
1082
  var STORE_FILENAME = "custom-context-modes.json";
1087
1083
  function storePath(wrongstackDir) {
1088
- return path5.join(wrongstackDir, STORE_FILENAME);
1084
+ return path2.join(wrongstackDir, STORE_FILENAME);
1089
1085
  }
1090
1086
  var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
1091
1087
  function createCustomModeStore(wrongstackDir) {
@@ -1093,7 +1089,7 @@ function createCustomModeStore(wrongstackDir) {
1093
1089
  const load2 = async () => {
1094
1090
  modes.clear();
1095
1091
  try {
1096
- const raw = await fs4.readFile(storePath(wrongstackDir), "utf8");
1092
+ const raw = await fs.readFile(storePath(wrongstackDir), "utf8");
1097
1093
  const parsed = JSON.parse(raw);
1098
1094
  if (Array.isArray(parsed.modes)) {
1099
1095
  for (const m of parsed.modes) {
@@ -1198,6 +1194,23 @@ function createCustomModeStore(wrongstackDir) {
1198
1194
  };
1199
1195
  return { modes, load: load2, save: save2, create, update, remove, list };
1200
1196
  }
1197
+
1198
+ // src/server/design-handlers.ts
1199
+ import * as fs2 from "node:fs/promises";
1200
+ import * as path3 from "node:path";
1201
+ import {
1202
+ applyTokenOverrides,
1203
+ getDesignKitLoader,
1204
+ getDesignState,
1205
+ isDesignStack,
1206
+ loadActiveKit,
1207
+ materializeTokens,
1208
+ recordKitChoice,
1209
+ recordOverrides,
1210
+ runDesignVerify,
1211
+ setActiveKit,
1212
+ setDesignOverrides
1213
+ } from "@wrongstack/core";
1201
1214
  function readOverrides(value) {
1202
1215
  const out = {};
1203
1216
  if (value && typeof value === "object") {
@@ -1345,9 +1358,9 @@ async function handleDesignMaterialize(ws, ctx, msg) {
1345
1358
  kitId: active.kit,
1346
1359
  outPath: typeof payload.out === "string" ? payload.out : void 0
1347
1360
  });
1348
- const abs = path5.join(ctx.projectRoot, result.path);
1349
- await fs4.mkdir(path5.dirname(abs), { recursive: true });
1350
- await fs4.writeFile(abs, result.content);
1361
+ const abs = path3.join(ctx.projectRoot, result.path);
1362
+ await fs2.mkdir(path3.dirname(abs), { recursive: true });
1363
+ await fs2.writeFile(abs, result.content);
1351
1364
  send(ws, {
1352
1365
  type: "design.materialize",
1353
1366
  payload: { ok: true, path: result.path, format: result.format, stack }
@@ -1406,6 +1419,11 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
1406
1419
  };
1407
1420
  }
1408
1421
 
1422
+ // src/server/file-handlers.ts
1423
+ import * as fs4 from "node:fs/promises";
1424
+ import * as path5 from "node:path";
1425
+ import { atomicWrite as atomicWrite2, ToolValidationError as ToolValidationError2 } from "@wrongstack/core";
1426
+
1409
1427
  // src/server/file-picker.ts
1410
1428
  var SKIP_DIRS = /* @__PURE__ */ new Set([
1411
1429
  ".git",
@@ -1453,15 +1471,20 @@ function rankFiles(paths, query, limit) {
1453
1471
  scored.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
1454
1472
  return scored.slice(0, limit).map((s) => s.path);
1455
1473
  }
1474
+
1475
+ // src/server/path-containment.ts
1476
+ import * as fs3 from "node:fs/promises";
1477
+ import * as path4 from "node:path";
1478
+ import { ToolValidationError } from "@wrongstack/core";
1456
1479
  function isPathInside(root, target) {
1457
- const relative4 = path5.relative(root, target);
1458
- return relative4 === "" || !relative4.startsWith("..") && !path5.isAbsolute(relative4);
1480
+ const relative4 = path4.relative(root, target);
1481
+ return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
1459
1482
  }
1460
1483
  async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1461
- const resolved = path5.resolve(projectRoot, inputPath);
1484
+ const resolved = path4.resolve(projectRoot, inputPath);
1462
1485
  let stat2;
1463
1486
  try {
1464
- stat2 = await fs4.stat(resolved);
1487
+ stat2 = await fs3.stat(resolved);
1465
1488
  } catch {
1466
1489
  throw new ToolValidationError({
1467
1490
  message: `Directory not found or not accessible: ${resolved}`,
@@ -1475,8 +1498,8 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1475
1498
  });
1476
1499
  }
1477
1500
  const [realProjectRoot, realResolved] = await Promise.all([
1478
- fs4.realpath(projectRoot),
1479
- fs4.realpath(resolved)
1501
+ fs3.realpath(projectRoot),
1502
+ fs3.realpath(resolved)
1480
1503
  ]);
1481
1504
  if (!isPathInside(realProjectRoot, realResolved)) {
1482
1505
  throw new ToolValidationError({
@@ -1491,14 +1514,14 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1491
1514
  async function resolveFileInsideProject(projectRoot, filePath) {
1492
1515
  const resolved = path5.resolve(projectRoot, filePath);
1493
1516
  if (!isPathInside(projectRoot, resolved)) {
1494
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1517
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1495
1518
  }
1496
1519
  const { parent, base } = splitParentAndBase(resolved);
1497
1520
  const realProjectRoot = await fs4.realpath(projectRoot);
1498
1521
  const realParent = await realpathAllowMissing(parent);
1499
1522
  const realFull = path5.join(realParent, base);
1500
1523
  if (!isPathInside(realProjectRoot, realFull)) {
1501
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1524
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1502
1525
  }
1503
1526
  return realFull;
1504
1527
  }
@@ -1518,7 +1541,7 @@ async function realpathAllowMissing(p) {
1518
1541
  while (true) {
1519
1542
  const parent = path5.dirname(cursor);
1520
1543
  if (parent === cursor) {
1521
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1544
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1522
1545
  }
1523
1546
  segments.unshift(path5.basename(cursor));
1524
1547
  try {
@@ -1651,7 +1674,7 @@ async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
1651
1674
  return;
1652
1675
  }
1653
1676
  try {
1654
- await atomicWrite(realResolved, content);
1677
+ await atomicWrite2(realResolved, content);
1655
1678
  send(ws, { type: "files.written", payload: { filePath, success: true } });
1656
1679
  if (opts.onWritten) {
1657
1680
  void Promise.resolve(opts.onWritten(realResolved)).catch(() => void 0);
@@ -1715,10 +1738,13 @@ async function handleFilesList(ws, msg, projectRoot) {
1715
1738
  payload: { files: rankFiles(results, payload.query ?? "", limit) }
1716
1739
  });
1717
1740
  }
1741
+
1742
+ // src/server/git-handlers.ts
1743
+ import nodePath from "node:path";
1718
1744
  async function handleGitInfo(ws, projectRoot) {
1719
1745
  const cwd = projectRoot || void 0;
1720
1746
  try {
1721
- const { execFile: ef } = await import('child_process');
1747
+ const { execFile: ef } = await import("node:child_process");
1722
1748
  const git = (args) => new Promise((resolve10) => {
1723
1749
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1724
1750
  resolve10(err ? "" : stdout.trim());
@@ -1746,7 +1772,7 @@ async function handleGitInfo(ws, projectRoot) {
1746
1772
  }
1747
1773
  function makeGit(cwd) {
1748
1774
  return async (args) => {
1749
- const { execFile: ef } = await import('child_process');
1775
+ const { execFile: ef } = await import("node:child_process");
1750
1776
  return new Promise((resolve10) => {
1751
1777
  ef(
1752
1778
  "git",
@@ -1776,15 +1802,15 @@ async function handleGitChanges(ws, projectRoot) {
1776
1802
  if (!m) continue;
1777
1803
  const added = m[1] === "-" ? 0 : Number(m[1]);
1778
1804
  const deleted = m[2] === "-" ? 0 : Number(m[2]);
1779
- let path23 = m[3] ?? "";
1780
- if (path23 === "") {
1805
+ let path22 = m[3] ?? "";
1806
+ if (path22 === "") {
1781
1807
  i += 1;
1782
- path23 = parts[i + 1] ?? parts[i] ?? "";
1808
+ path22 = parts[i + 1] ?? parts[i] ?? "";
1783
1809
  i += 1;
1784
1810
  }
1785
- if (!path23) continue;
1786
- const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
1787
- counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
1811
+ if (!path22) continue;
1812
+ const prev = counts.get(path22) ?? { added: 0, deleted: 0 };
1813
+ counts.set(path22, { added: prev.added + added, deleted: prev.deleted + deleted });
1788
1814
  }
1789
1815
  };
1790
1816
  parseNumstat(unstagedNumstat);
@@ -1796,7 +1822,7 @@ async function handleGitChanges(ws, projectRoot) {
1796
1822
  if (!rec || rec.length < 3) continue;
1797
1823
  const x = rec[0] ?? " ";
1798
1824
  const y = rec[1] ?? " ";
1799
- const path23 = rec.slice(3);
1825
+ const path22 = rec.slice(3);
1800
1826
  const isRename = x === "R" || x === "C" || y === "R" || y === "C";
1801
1827
  if (isRename) i += 1;
1802
1828
  let status;
@@ -1808,13 +1834,13 @@ async function handleGitChanges(ws, projectRoot) {
1808
1834
  else if (x === "D" || y === "D") status = "D";
1809
1835
  else status = "M";
1810
1836
  const staged = x !== " " && x !== "?";
1811
- let added = counts.get(path23)?.added ?? 0;
1812
- let deleted = counts.get(path23)?.deleted ?? 0;
1837
+ let added = counts.get(path22)?.added ?? 0;
1838
+ let deleted = counts.get(path22)?.deleted ?? 0;
1813
1839
  if (status === "?") {
1814
1840
  added = 0;
1815
1841
  deleted = 0;
1816
1842
  }
1817
- files.push({ path: path23, status, added, deleted, staged });
1843
+ files.push({ path: path22, status, added, deleted, staged });
1818
1844
  }
1819
1845
  send(ws, { type: "git.changes", payload: { files } });
1820
1846
  } catch (err) {
@@ -1825,21 +1851,21 @@ async function handleGitChanges(ws, projectRoot) {
1825
1851
  }
1826
1852
  }
1827
1853
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1828
- async function handleGitDiff(ws, projectRoot, path23) {
1854
+ async function handleGitDiff(ws, projectRoot, path22) {
1829
1855
  const cwd = projectRoot || void 0;
1830
- const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1831
- if (!path23 || path23.includes("\0") || path23.includes("..") || path5__default.isAbsolute(path23)) {
1856
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path22, ...extra } });
1857
+ if (!path22 || path22.includes("\0") || path22.includes("..") || nodePath.isAbsolute(path22)) {
1832
1858
  reply({ oldText: "", newText: "", error: "invalid path" });
1833
1859
  return;
1834
1860
  }
1835
1861
  try {
1836
1862
  const git = makeGit(cwd);
1837
- const { readFile: readFile10 } = await import('fs/promises');
1838
- const { join: join15 } = await import('path');
1839
- const oldText = await git(["show", `HEAD:${path23}`]);
1863
+ const { readFile: readFile10 } = await import("node:fs/promises");
1864
+ const { join: join14 } = await import("node:path");
1865
+ const oldText = await git(["show", `HEAD:${path22}`]);
1840
1866
  let newText = "";
1841
1867
  try {
1842
- const abs = cwd ? join15(cwd, path23) : path23;
1868
+ const abs = cwd ? join14(cwd, path22) : path22;
1843
1869
  const buf = await readFile10(abs);
1844
1870
  if (buf.includes(0)) {
1845
1871
  reply({ oldText: "", newText: "", binary: true });
@@ -1867,6 +1893,11 @@ async function handleGitDiff(ws, projectRoot, path23) {
1867
1893
  }
1868
1894
  }
1869
1895
 
1896
+ // src/server/http-server.ts
1897
+ import * as fs5 from "node:fs/promises";
1898
+ import * as http from "node:http";
1899
+ import * as path6 from "node:path";
1900
+
1870
1901
  // src/server/http-server/api-handlers.ts
1871
1902
  async function handleApiSessions(res, globalRoot) {
1872
1903
  if (!globalRoot) {
@@ -1875,7 +1906,7 @@ async function handleApiSessions(res, globalRoot) {
1875
1906
  return;
1876
1907
  }
1877
1908
  try {
1878
- const { SessionRegistry } = await import('@wrongstack/core');
1909
+ const { SessionRegistry } = await import("@wrongstack/core");
1879
1910
  const registry = new SessionRegistry(globalRoot);
1880
1911
  const sessions = await registry.list();
1881
1912
  const result = sessions.map((s) => ({
@@ -1913,7 +1944,7 @@ async function handleApiSessionAgents(res, globalRoot, sessionId) {
1913
1944
  return;
1914
1945
  }
1915
1946
  try {
1916
- const { SessionRegistry } = await import('@wrongstack/core');
1947
+ const { SessionRegistry } = await import("@wrongstack/core");
1917
1948
  const registry = new SessionRegistry(globalRoot);
1918
1949
  const entry = await registry.get(sessionId);
1919
1950
  if (!entry) {
@@ -2053,7 +2084,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2053
2084
  return;
2054
2085
  }
2055
2086
  try {
2056
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import('@wrongstack/core');
2087
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2057
2088
  const registry = new SessionRegistry(globalRoot);
2058
2089
  const entry = await registry.get(sessionId);
2059
2090
  if (!entry) {
@@ -2135,7 +2166,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2135
2166
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2136
2167
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2137
2168
  try {
2138
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2169
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2139
2170
  const registry = new SessionRegistry(globalRoot);
2140
2171
  const entry = await registry.get(sessionId);
2141
2172
  if (!entry) {
@@ -2161,7 +2192,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2161
2192
  return;
2162
2193
  }
2163
2194
  try {
2164
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2195
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2165
2196
  const registry = new SessionRegistry(globalRoot);
2166
2197
  const entry = await registry.get(sessionId);
2167
2198
  if (!entry) {
@@ -2219,7 +2250,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2219
2250
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2220
2251
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2221
2252
  try {
2222
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2253
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2223
2254
  const registry = new SessionRegistry(globalRoot);
2224
2255
  const entry = await registry.get(sessionId);
2225
2256
  if (!entry) {
@@ -2267,7 +2298,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2267
2298
  }
2268
2299
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2269
2300
  try {
2270
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2301
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2271
2302
  const registry = new SessionRegistry(globalRoot);
2272
2303
  const all = await registry.list();
2273
2304
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2415,6 +2446,10 @@ async function handleApiAnalyticsSummary(res) {
2415
2446
  })
2416
2447
  );
2417
2448
  }
2449
+
2450
+ // src/server/ws-auth.ts
2451
+ import { Buffer as Buffer2 } from "node:buffer";
2452
+ import { timingSafeEqual } from "node:crypto";
2418
2453
  function isLoopbackHostname(hostname) {
2419
2454
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
2420
2455
  }
@@ -2443,8 +2478,8 @@ function allowedHostname(hostname, allowedHostnames) {
2443
2478
  }
2444
2479
  function tokenMatches(provided, expected) {
2445
2480
  if (!provided) return false;
2446
- const a = Buffer.from(provided);
2447
- const b = Buffer.from(expected);
2481
+ const a = Buffer2.from(provided);
2482
+ const b = Buffer2.from(expected);
2448
2483
  if (a.length !== b.length) return false;
2449
2484
  return timingSafeEqual(a, b);
2450
2485
  }
@@ -2600,9 +2635,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2600
2635
  return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
2601
2636
  }
2602
2637
  function isInsideDist(candidate, distDir) {
2603
- const root = path5.resolve(distDir);
2604
- const resolved = path5.resolve(candidate);
2605
- return resolved === root || resolved.startsWith(root + path5.sep);
2638
+ const root = path6.resolve(distDir);
2639
+ const resolved = path6.resolve(candidate);
2640
+ return resolved === root || resolved.startsWith(root + path6.sep);
2606
2641
  }
2607
2642
  function decodeSessionId(segment) {
2608
2643
  try {
@@ -2613,7 +2648,7 @@ function decodeSessionId(segment) {
2613
2648
  }
2614
2649
  function createHttpServer(opts) {
2615
2650
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2616
- const distDir = path5.resolve(opts.distDir);
2651
+ const distDir = path6.resolve(opts.distDir);
2617
2652
  const wsPort = opts.wsPort;
2618
2653
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2619
2654
  return http.createServer(async (req, res) => {
@@ -2790,21 +2825,21 @@ function createHttpServer(opts) {
2790
2825
  }
2791
2826
  let filePath;
2792
2827
  if (url.pathname === "/" || url.pathname === "") {
2793
- filePath = path5.join(distDir, "index.html");
2828
+ filePath = path6.join(distDir, "index.html");
2794
2829
  } else if (url.pathname.startsWith("/assets/")) {
2795
- filePath = path5.join(distDir, url.pathname);
2830
+ filePath = path6.join(distDir, url.pathname);
2796
2831
  } else if (url.pathname.startsWith("/")) {
2797
- filePath = path5.join(distDir, url.pathname);
2832
+ filePath = path6.join(distDir, url.pathname);
2798
2833
  } else {
2799
- filePath = path5.join(distDir, "index.html");
2834
+ filePath = path6.join(distDir, "index.html");
2800
2835
  }
2801
- const resolvedPath = path5.resolve(filePath);
2836
+ const resolvedPath = path6.resolve(filePath);
2802
2837
  if (!isInsideDist(resolvedPath, distDir)) {
2803
2838
  res.writeHead(403, { "Content-Type": "text/plain" });
2804
2839
  res.end("Forbidden");
2805
2840
  return;
2806
2841
  }
2807
- const ext = path5.extname(resolvedPath);
2842
+ const ext = path6.extname(resolvedPath);
2808
2843
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2809
2844
  res.setHeader("Content-Type", contentType);
2810
2845
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2816,18 +2851,18 @@ function createHttpServer(opts) {
2816
2851
  "Content-Security-Policy",
2817
2852
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2818
2853
  );
2819
- const html = await fs4.readFile(resolvedPath, "utf8");
2854
+ const html = await fs5.readFile(resolvedPath, "utf8");
2820
2855
  res.writeHead(200);
2821
2856
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2822
2857
  return;
2823
2858
  }
2824
- const fileContent = await fs4.readFile(resolvedPath);
2859
+ const fileContent = await fs5.readFile(resolvedPath);
2825
2860
  res.writeHead(200);
2826
2861
  res.end(fileContent);
2827
2862
  } catch (err) {
2828
2863
  if (err.code === "ENOENT") {
2829
2864
  try {
2830
- const html = await fs4.readFile(path5.join(distDir, "index.html"), "utf8");
2865
+ const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
2831
2866
  res.writeHead(200, {
2832
2867
  "Content-Type": "text/html",
2833
2868
  "X-Content-Type-Options": "nosniff",
@@ -2851,11 +2886,17 @@ function createHttpServer(opts) {
2851
2886
  }
2852
2887
  });
2853
2888
  }
2889
+
2890
+ // src/server/instance-registry.ts
2891
+ import * as os from "node:os";
2892
+ import * as path7 from "node:path";
2893
+ import * as fs6 from "node:fs/promises";
2894
+ import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2854
2895
  function defaultBaseDir() {
2855
- return path5.join(os.homedir(), ".wrongstack");
2896
+ return path7.join(os.homedir(), ".wrongstack");
2856
2897
  }
2857
2898
  function registryPath(baseDir = defaultBaseDir()) {
2858
- return path5.join(baseDir, "webui-instances.json");
2899
+ return path7.join(baseDir, "webui-instances.json");
2859
2900
  }
2860
2901
  function isPidAlive(pid) {
2861
2902
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2868,7 +2909,7 @@ function isPidAlive(pid) {
2868
2909
  }
2869
2910
  async function load(file) {
2870
2911
  try {
2871
- const raw = await fs4.readFile(file, "utf8");
2912
+ const raw = await fs6.readFile(file, "utf8");
2872
2913
  const parsed = JSON.parse(raw);
2873
2914
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2874
2915
  return parsed;
@@ -2878,7 +2919,7 @@ async function load(file) {
2878
2919
  return { version: 1, instances: [] };
2879
2920
  }
2880
2921
  async function save(file, instances) {
2881
- await atomicWrite(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
2922
+ await atomicWrite3(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
2882
2923
  `, {
2883
2924
  mode: 384
2884
2925
  });
@@ -2923,6 +2964,19 @@ function formatInstances(instances) {
2923
2964
  }
2924
2965
  return lines.join("\n");
2925
2966
  }
2967
+
2968
+ // src/server/mcp-handlers.ts
2969
+ import { allServers } from "@wrongstack/core";
2970
+ import {
2971
+ addMcp,
2972
+ disableMcp,
2973
+ discoverMcp,
2974
+ enableMcp,
2975
+ listMcp,
2976
+ removeMcp,
2977
+ restartMcp,
2978
+ updateMcp
2979
+ } from "@wrongstack/mcp";
2926
2980
  function mapStatus(raw) {
2927
2981
  switch (raw) {
2928
2982
  case "connected":
@@ -2938,7 +2992,7 @@ function mapStatus(raw) {
2938
2992
  return "stopped";
2939
2993
  }
2940
2994
  }
2941
- function toView(info) {
2995
+ function toView(info, health) {
2942
2996
  const view = {
2943
2997
  name: info.name,
2944
2998
  transport: info.transport,
@@ -2950,6 +3004,7 @@ function toView(info) {
2950
3004
  };
2951
3005
  if (info.description !== void 0) view.description = info.description;
2952
3006
  if (info.lazy !== void 0) view.lazy = info.lazy;
3007
+ if (health !== void 0) view.health = health;
2953
3008
  return view;
2954
3009
  }
2955
3010
  function deps(ws, globalConfigPath, registry) {
@@ -2975,7 +3030,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
2975
3030
  registry: mcpRegistry,
2976
3031
  presets: allServers()
2977
3032
  });
2978
- send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
3033
+ const health = new Map(
3034
+ (typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
3035
+ );
3036
+ send(ws, {
3037
+ type: "mcp.list",
3038
+ payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
3039
+ });
2979
3040
  }
2980
3041
  async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
2981
3042
  const d = deps(ws, globalConfigPath, mcpRegistry);
@@ -3125,6 +3186,99 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
3125
3186
  payload: { success: result.ok, message: result.message }
3126
3187
  });
3127
3188
  }
3189
+ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3190
+ if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
3191
+ const payload = payloadRecord(msg);
3192
+ let serverName = "";
3193
+ try {
3194
+ serverName = requiredPayloadString(payload, "name");
3195
+ const refresh = payload["refresh"] === true;
3196
+ const [resources, resourceTemplates] = await Promise.all([
3197
+ mcpRegistry.listResources(serverName, { refresh }),
3198
+ mcpRegistry.listResourceTemplates(serverName, { refresh })
3199
+ ]);
3200
+ send(ws, {
3201
+ type: "mcp.resources",
3202
+ payload: { name: serverName, resources, resourceTemplates }
3203
+ });
3204
+ } catch (err) {
3205
+ sendContentError(ws, "resources", serverName, errorMessage(err));
3206
+ }
3207
+ }
3208
+ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3209
+ if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
3210
+ const payload = payloadRecord(msg);
3211
+ let serverName = "";
3212
+ try {
3213
+ serverName = requiredPayloadString(payload, "name");
3214
+ const prompts = await mcpRegistry.listPrompts(serverName, {
3215
+ refresh: payload["refresh"] === true
3216
+ });
3217
+ send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3218
+ } catch (err) {
3219
+ sendContentError(ws, "prompts", serverName, errorMessage(err));
3220
+ }
3221
+ }
3222
+ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3223
+ if (!mcpRegistry)
3224
+ return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
3225
+ const payload = payloadRecord(msg);
3226
+ let serverName = "";
3227
+ try {
3228
+ serverName = requiredPayloadString(payload, "name");
3229
+ const insertion = await mcpRegistry.selectResourceForInsertion(
3230
+ serverName,
3231
+ requiredPayloadString(payload, "uri")
3232
+ );
3233
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3234
+ } catch (err) {
3235
+ sendContentError(ws, "resource.read", serverName, errorMessage(err));
3236
+ }
3237
+ }
3238
+ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3239
+ if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
3240
+ const payload = payloadRecord(msg);
3241
+ let serverName = "";
3242
+ try {
3243
+ serverName = requiredPayloadString(payload, "name");
3244
+ const insertion = await mcpRegistry.selectPromptForInsertion(
3245
+ serverName,
3246
+ requiredPayloadString(payload, "prompt"),
3247
+ promptArguments(payload["arguments"])
3248
+ );
3249
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3250
+ } catch (err) {
3251
+ sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3252
+ }
3253
+ }
3254
+ function payloadRecord(msg) {
3255
+ return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
3256
+ }
3257
+ function requiredPayloadString(payload, field) {
3258
+ const value = payload[field];
3259
+ if (typeof value !== "string" || value.length === 0) {
3260
+ throw new Error(`MCP payload field "${field}" must be a non-empty string`);
3261
+ }
3262
+ return value;
3263
+ }
3264
+ function promptArguments(value) {
3265
+ if (value === void 0) return void 0;
3266
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3267
+ throw new Error("MCP prompt arguments must be an object");
3268
+ }
3269
+ const args = {};
3270
+ for (const [key, item] of Object.entries(value)) {
3271
+ if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
3272
+ args[key] = item;
3273
+ }
3274
+ return args;
3275
+ }
3276
+ function sendContentError(ws, action, name2, error) {
3277
+ send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3278
+ }
3279
+ function errorMessage(err) {
3280
+ return err instanceof Error ? err.message : String(err);
3281
+ }
3128
3282
 
3129
3283
  // src/server/memory-handlers.ts
3130
3284
  async function handleMemoryList(ws, memoryStore) {
@@ -3160,6 +3314,9 @@ async function handleMemoryForget(ws, msg, memoryStore) {
3160
3314
  sendResult(ws, false, errMessage(err));
3161
3315
  }
3162
3316
  }
3317
+
3318
+ // src/server/open-browser.ts
3319
+ import { spawn } from "node:child_process";
3163
3320
  function browserOpenCommand(url, platform = process.platform) {
3164
3321
  if (platform === "win32") {
3165
3322
  return { command: "cmd", args: ["/c", "start", "", url] };
@@ -3178,7 +3335,7 @@ function openBrowser(url, platform = process.platform) {
3178
3335
  child.unref();
3179
3336
  if (child.pid) {
3180
3337
  try {
3181
- import('@wrongstack/tools').then(({ getProcessRegistry }) => {
3338
+ import("@wrongstack/tools").then(({ getProcessRegistry }) => {
3182
3339
  const pid = child.pid;
3183
3340
  if (pid === void 0) return;
3184
3341
  getProcessRegistry().register({
@@ -3200,6 +3357,10 @@ function openBrowser(url, platform = process.platform) {
3200
3357
  } catch {
3201
3358
  }
3202
3359
  }
3360
+
3361
+ // src/server/port-utils.ts
3362
+ import * as net from "node:net";
3363
+ import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3203
3364
  function isPortFree(host, port) {
3204
3365
  return new Promise((resolve10) => {
3205
3366
  const srv = net.createServer();
@@ -3225,7 +3386,7 @@ async function findFreePort(host, startPort, opts = {}) {
3225
3386
  }
3226
3387
  port++;
3227
3388
  }
3228
- throw new ToolValidationError({
3389
+ throw new ToolValidationError3({
3229
3390
  message: `No free port found near ${startPort} on ${host} after ${maxTries} attempts.`,
3230
3391
  field: "port"
3231
3392
  });
@@ -3435,10 +3596,15 @@ async function handlePromptsRecent(ws, ctx) {
3435
3596
  send(ws, { type: "prompts.recent", payload: { slugs: [], error: errMessage(err) } });
3436
3597
  }
3437
3598
  }
3599
+
3600
+ // src/server/provider-config-io.ts
3601
+ import * as fs7 from "node:fs/promises";
3602
+ import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3603
+ import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3438
3604
  async function loadSavedProviders(configPath, vault) {
3439
3605
  let raw;
3440
3606
  try {
3441
- raw = await fs4.readFile(configPath, "utf8");
3607
+ raw = await fs7.readFile(configPath, "utf8");
3442
3608
  } catch {
3443
3609
  return {};
3444
3610
  }
@@ -3455,7 +3621,7 @@ async function saveProviders(configPath, vault, providers) {
3455
3621
  let raw;
3456
3622
  let fileExists = true;
3457
3623
  try {
3458
- raw = await fs4.readFile(configPath, "utf8");
3624
+ raw = await fs7.readFile(configPath, "utf8");
3459
3625
  } catch (err) {
3460
3626
  if (err.code !== "ENOENT") {
3461
3627
  throw new ConfigError({
@@ -3484,8 +3650,11 @@ async function saveProviders(configPath, vault, providers) {
3484
3650
  }
3485
3651
  parsed.providers = providers;
3486
3652
  const encrypted = encryptConfigSecrets(parsed, vault);
3487
- await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3653
+ await atomicWrite4(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3488
3654
  }
3655
+
3656
+ // src/server/provider-keys.ts
3657
+ import { expectDefined } from "@wrongstack/core";
3489
3658
  function normalizeKeys(cfg) {
3490
3659
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3491
3660
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3579,6 +3748,12 @@ function removeProvider(providers, providerId) {
3579
3748
  delete providers[providerId];
3580
3749
  return { ok: true, message: `Provider "${providerId}" removed` };
3581
3750
  }
3751
+
3752
+ // src/server/sdd-board-ws-handler.ts
3753
+ import {
3754
+ applySddLifecycle,
3755
+ SddBoardStore
3756
+ } from "@wrongstack/sdd";
3582
3757
  var CONTROL_TYPES = /* @__PURE__ */ new Set([
3583
3758
  "pause",
3584
3759
  "resume",
@@ -3723,6 +3898,26 @@ var SddBoardWebSocketHandler = class {
3723
3898
  if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
3724
3899
  }
3725
3900
  };
3901
+
3902
+ // src/server/sdd-wizard-wiring.ts
3903
+ import * as path8 from "node:path";
3904
+ import { spawnSync as spawnSync2 } from "node:child_process";
3905
+ import {
3906
+ WorktreeManager as WorktreeManager2,
3907
+ ToolValidationError as ToolValidationError4
3908
+ } from "@wrongstack/core";
3909
+ import {
3910
+ cleanupStaleSddWorktrees,
3911
+ makeCommandVerifier,
3912
+ makeLlmSubtaskGenerator,
3913
+ SddBoardStore as SddBoardStore2,
3914
+ SddInterviewDriver,
3915
+ SddRunRegistry,
3916
+ SddSupervisor,
3917
+ SpecStore,
3918
+ startSddRun,
3919
+ TaskGraphStore
3920
+ } from "@wrongstack/sdd";
3726
3921
  var PLANNING_ONLY_GUARD = "SYSTEM: You are running a PLANNING-ONLY specification interview. Do NOT write, create, or edit any files, and do NOT run shell/terminal commands or use any code-editing tools \u2014 they are disabled here and any attempt will fail and waste the turn. Respond with TEXT ONLY: ask your question, or emit the requested spec / plan / task JSON. All code is written later, automatically, once the plan is approved and the multi-agent run starts.\n\n---\n\n";
3727
3922
  function buildSddWizardDeps(opts) {
3728
3923
  const registry = new SddRunRegistry();
@@ -3746,14 +3941,14 @@ function buildSddWizardDeps(opts) {
3746
3941
  makeDriver: () => new SddInterviewDriver({
3747
3942
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
3748
3943
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
3749
- sessionPath: path5.join(opts.paths.projectDir, "sdd-wizard-session.json")
3944
+ sessionPath: path8.join(opts.paths.projectDir, "sdd-wizard-session.json")
3750
3945
  }),
3751
3946
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
3752
3947
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
3753
3948
  const graph = driver.getGraph();
3754
3949
  const tracker = driver.getTracker();
3755
3950
  if (!graph || !tracker) {
3756
- throw new ToolValidationError({
3951
+ throw new ToolValidationError4({
3757
3952
  message: "No task graph to run \u2014 finish the interview first.",
3758
3953
  field: "taskGraph"
3759
3954
  });
@@ -3761,7 +3956,7 @@ function buildSddWizardDeps(opts) {
3761
3956
  const worktreesEnabled = useWorktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
3762
3957
  let worktrees;
3763
3958
  if (worktreesEnabled) {
3764
- const inGit = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
3959
+ const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
3765
3960
  cwd: opts.projectRoot,
3766
3961
  encoding: "utf8",
3767
3962
  windowsHide: true
@@ -3771,14 +3966,14 @@ function buildSddWizardDeps(opts) {
3771
3966
  projectRoot: opts.projectRoot,
3772
3967
  boardsDir: opts.paths.projectSddBoards
3773
3968
  }).catch(() => void 0);
3774
- worktrees = new WorktreeManager({
3969
+ worktrees = new WorktreeManager2({
3775
3970
  projectRoot: opts.projectRoot,
3776
3971
  events: opts.events,
3777
3972
  sessionId: () => opts.agent.ctx.session?.id
3778
3973
  });
3779
3974
  }
3780
3975
  }
3781
- const boardStore = new SddBoardStore({ baseDir: opts.paths.projectSddBoards });
3976
+ const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
3782
3977
  const verifyTask = makeCommandVerifier();
3783
3978
  const superviseFailure = opts.brain ? new SddSupervisor({
3784
3979
  brain: opts.brain,
@@ -3958,20 +4153,25 @@ var SddWizardWebSocketHandler = class {
3958
4153
  if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
3959
4154
  }
3960
4155
  };
4156
+
4157
+ // src/server/shell-open.ts
4158
+ import * as fs8 from "node:fs/promises";
4159
+ import * as path9 from "node:path";
4160
+ import { spawn as spawn2 } from "node:child_process";
3961
4161
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
3962
4162
  function shellQuote(s) {
3963
4163
  return s.replaceAll("'", `'"'"'`);
3964
4164
  }
3965
4165
  async function handleShellOpen(req, logger) {
3966
4166
  try {
3967
- const resolved = path5.resolve(req.path);
3968
- await fs4.access(resolved);
4167
+ const resolved = path9.resolve(req.path);
4168
+ await fs8.access(resolved);
3969
4169
  if (METACHAR_REGEX.test(resolved)) {
3970
4170
  return { success: false, message: "Path contains unsupported characters." };
3971
4171
  }
3972
4172
  const platform = process.platform;
3973
4173
  const launch = (cmd, args, onError) => {
3974
- const child = spawn(cmd, args, {
4174
+ const child = spawn2(cmd, args, {
3975
4175
  detached: true,
3976
4176
  stdio: "ignore",
3977
4177
  windowsHide: true
@@ -4015,6 +4215,12 @@ async function handleShellOpen(req, logger) {
4015
4215
  }
4016
4216
  }
4017
4217
 
4218
+ // src/server/skills-handlers.ts
4219
+ import { promises as fs9 } from "node:fs";
4220
+ import path10 from "node:path";
4221
+ import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4222
+ import { wstackGlobalRoot } from "@wrongstack/core/utils";
4223
+
4018
4224
  // src/server/ws-payload-validation.ts
4019
4225
  function isRecord(value) {
4020
4226
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4148,6 +4354,16 @@ function validateBrainAskPayload(payload) {
4148
4354
  }
4149
4355
  return { ok: true, value: { question: question.trim() } };
4150
4356
  }
4357
+ function validateBrainConfigSetPayload(payload) {
4358
+ if (!isRecord(payload)) {
4359
+ return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
4360
+ }
4361
+ const patch = payload["patch"];
4362
+ if (!isRecord(patch)) {
4363
+ return { ok: false, message: "brain.config.set payload.patch must be an object" };
4364
+ }
4365
+ return { ok: true, value: { patch } };
4366
+ }
4151
4367
  function validateAutonomySwitchPayload(payload) {
4152
4368
  if (!isRecord(payload)) {
4153
4369
  return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
@@ -4209,7 +4425,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4209
4425
  "hqEnabled",
4210
4426
  "hqRawContent",
4211
4427
  "fallbackAuto",
4212
- "favoriteModelsOnly"
4428
+ "favoriteModelsOnly",
4429
+ "breakerEnabled",
4430
+ "debugStream"
4213
4431
  ]);
4214
4432
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4215
4433
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
@@ -4220,9 +4438,17 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4220
4438
  "maxIterations",
4221
4439
  "maxConcurrent",
4222
4440
  "enhanceDelayMs",
4223
- "tgLongToolMs"
4441
+ "tgLongToolMs",
4442
+ "breakerAutoKillResetMs"
4443
+ ]);
4444
+ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4445
+ "hqUrl",
4446
+ "hqToken",
4447
+ "uiLocale",
4448
+ "thinkingWord",
4449
+ "refinerProvider",
4450
+ "refinerModel"
4224
4451
  ]);
4225
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4226
4452
  var ENUM_PREF_KEYS = {
4227
4453
  autonomy: AUTONOMY_VALUES,
4228
4454
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4233,36 +4459,39 @@ var ENUM_PREF_KEYS = {
4233
4459
  auditLevel: AUDIT_LEVEL_VALUES,
4234
4460
  reasoningMode: REASONING_MODE_VALUES,
4235
4461
  reasoningEffort: REASONING_EFFORT_VALUES,
4236
- cacheTtl: CACHE_TTL_VALUES
4462
+ cacheTtl: CACHE_TTL_VALUES,
4463
+ statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4464
+ animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4465
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
4237
4466
  };
4238
- function validateModelRuntimeValue(modelRuntime, path23) {
4467
+ function validateModelRuntimeValue(modelRuntime, path22) {
4239
4468
  const reasoning = modelRuntime["reasoning"];
4240
4469
  if (reasoning !== void 0) {
4241
- if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
4470
+ if (!isRecord(reasoning)) return `${path22}.reasoning must be an object when provided`;
4242
4471
  const mode = reasoning["mode"];
4243
4472
  const effort = reasoning["effort"];
4244
4473
  const preserve = reasoning["preserve"];
4245
4474
  if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
4246
- return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4475
+ return `${path22}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4247
4476
  }
4248
4477
  if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
4249
- return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4478
+ return `${path22}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4250
4479
  }
4251
4480
  if (preserve !== void 0 && typeof preserve !== "boolean") {
4252
- return `${path23}.reasoning.preserve must be a boolean when provided`;
4481
+ return `${path22}.reasoning.preserve must be a boolean when provided`;
4253
4482
  }
4254
4483
  }
4255
4484
  const cache = modelRuntime["cache"];
4256
4485
  if (cache !== void 0) {
4257
- if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
4486
+ if (!isRecord(cache)) return `${path22}.cache must be an object when provided`;
4258
4487
  const ttl = cache["ttl"];
4259
4488
  if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
4260
- return `${path23}.cache.ttl must be one of: 5m, 1h`;
4489
+ return `${path22}.cache.ttl must be one of: 5m, 1h`;
4261
4490
  }
4262
4491
  }
4263
4492
  const parameters = modelRuntime["parameters"];
4264
4493
  if (parameters !== void 0 && !isRecord(parameters)) {
4265
- return `${path23}.parameters must be an object when provided`;
4494
+ return `${path22}.parameters must be an object when provided`;
4266
4495
  }
4267
4496
  return null;
4268
4497
  }
@@ -4542,8 +4771,8 @@ function validateShellOpenPayload(payload) {
4542
4771
  if (!isRecord(payload)) {
4543
4772
  return { ok: false, message: "shell.open payload must be an object with string path" };
4544
4773
  }
4545
- const path23 = payload["path"];
4546
- if (typeof path23 !== "string" || path23.trim().length === 0) {
4774
+ const path22 = payload["path"];
4775
+ if (typeof path22 !== "string" || path22.trim().length === 0) {
4547
4776
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4548
4777
  }
4549
4778
  const target = payload["target"];
@@ -4556,7 +4785,7 @@ function validateShellOpenPayload(payload) {
4556
4785
  return {
4557
4786
  ok: true,
4558
4787
  value: {
4559
- path: path23,
4788
+ path: path22,
4560
4789
  ...target !== void 0 ? { target } : {}
4561
4790
  }
4562
4791
  };
@@ -4565,14 +4794,89 @@ function validateGitDiffPayload(payload) {
4565
4794
  if (!isRecord(payload)) {
4566
4795
  return { ok: false, message: "git.diff payload must be an object" };
4567
4796
  }
4568
- const path23 = payload["path"];
4569
- if (path23 === void 0 || path23 === null) {
4797
+ const path22 = payload["path"];
4798
+ if (path22 === void 0 || path22 === null) {
4570
4799
  return { ok: true, value: { path: "" } };
4571
4800
  }
4572
- if (typeof path23 !== "string") {
4801
+ if (typeof path22 !== "string") {
4573
4802
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
4574
4803
  }
4575
- return { ok: true, value: { path: path23 } };
4804
+ return { ok: true, value: { path: path22 } };
4805
+ }
4806
+
4807
+ // src/server/zip.ts
4808
+ import { deflateRawSync, inflateRawSync } from "node:zlib";
4809
+ function createZipBuffer(entries) {
4810
+ const localParts = [];
4811
+ const centralParts = [];
4812
+ let offset = 0;
4813
+ const { date, time } = dosDateTime(/* @__PURE__ */ new Date());
4814
+ for (const entry of entries) {
4815
+ const name2 = Buffer.from(normalizeZipPath(entry.name), "utf8");
4816
+ const data = typeof entry.data === "string" ? Buffer.from(entry.data, "utf8") : Buffer.from(entry.data);
4817
+ const compressed = deflateRawSync(data);
4818
+ const crc = crc32(data);
4819
+ const local = Buffer.alloc(30);
4820
+ local.writeUInt32LE(67324752, 0);
4821
+ local.writeUInt16LE(20, 4);
4822
+ local.writeUInt16LE(2048, 6);
4823
+ local.writeUInt16LE(8, 8);
4824
+ local.writeUInt16LE(time, 10);
4825
+ local.writeUInt16LE(date, 12);
4826
+ local.writeUInt32LE(crc, 14);
4827
+ local.writeUInt32LE(compressed.length, 18);
4828
+ local.writeUInt32LE(data.length, 22);
4829
+ local.writeUInt16LE(name2.length, 26);
4830
+ localParts.push(local, name2, compressed);
4831
+ const central = Buffer.alloc(46);
4832
+ central.writeUInt32LE(33639248, 0);
4833
+ central.writeUInt16LE(798, 4);
4834
+ central.writeUInt16LE(20, 6);
4835
+ central.writeUInt16LE(2048, 8);
4836
+ central.writeUInt16LE(8, 10);
4837
+ central.writeUInt16LE(time, 12);
4838
+ central.writeUInt16LE(date, 14);
4839
+ central.writeUInt32LE(crc, 16);
4840
+ central.writeUInt32LE(compressed.length, 20);
4841
+ central.writeUInt32LE(data.length, 24);
4842
+ central.writeUInt16LE(name2.length, 28);
4843
+ central.writeUInt32LE(offset, 42);
4844
+ centralParts.push(central, name2);
4845
+ offset += local.length + name2.length + compressed.length;
4846
+ }
4847
+ const centralDirectory = Buffer.concat(centralParts);
4848
+ const end = Buffer.alloc(22);
4849
+ end.writeUInt32LE(101010256, 0);
4850
+ end.writeUInt16LE(entries.length, 8);
4851
+ end.writeUInt16LE(entries.length, 10);
4852
+ end.writeUInt32LE(centralDirectory.length, 12);
4853
+ end.writeUInt32LE(offset, 16);
4854
+ return Buffer.concat([...localParts, centralDirectory, end]);
4855
+ }
4856
+ function normalizeZipPath(value) {
4857
+ const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
4858
+ if (!isSafeZipPath(normalized)) throw new Error(`Unsafe ZIP entry path: ${value}`);
4859
+ return normalized;
4860
+ }
4861
+ function isSafeZipPath(value) {
4862
+ return value.length > 0 && !value.includes("\0") && !value.startsWith("/") && !/^[a-z]:/i.test(value) && !value.split("/").includes("..");
4863
+ }
4864
+ function dosDateTime(value) {
4865
+ const year = Math.max(1980, Math.min(2107, value.getFullYear()));
4866
+ return {
4867
+ date: year - 1980 << 9 | value.getMonth() + 1 << 5 | value.getDate(),
4868
+ time: value.getHours() << 11 | value.getMinutes() << 5 | Math.floor(value.getSeconds() / 2)
4869
+ };
4870
+ }
4871
+ var CRC_TABLE = Array.from({ length: 256 }, (_, index) => {
4872
+ let crc = index;
4873
+ for (let bit = 0; bit < 8; bit++) crc = (crc & 1) !== 0 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
4874
+ return crc >>> 0;
4875
+ });
4876
+ function crc32(data) {
4877
+ let crc = 4294967295;
4878
+ for (const byte of data) crc = crc >>> 8 ^ (CRC_TABLE[(crc ^ byte) & 255] ?? 0);
4879
+ return (crc ^ 4294967295) >>> 0;
4576
4880
  }
4577
4881
 
4578
4882
  // src/server/skills-handlers.ts
@@ -4643,19 +4947,19 @@ async function handleSkillsContent(ws, ctx, msg) {
4643
4947
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
4644
4948
  return;
4645
4949
  }
4646
- const body = await promises.readFile(entry.path, "utf8");
4647
- const skillDir = path5__default.dirname(entry.path);
4950
+ const body = await fs9.readFile(entry.path, "utf8");
4951
+ const skillDir = path10.dirname(entry.path);
4648
4952
  let relatedFiles = [];
4649
4953
  try {
4650
- const files = await promises.readdir(skillDir);
4651
- relatedFiles = files.filter((f) => f !== path5__default.basename(entry.path)).map((f) => path5__default.join(skillDir, f));
4954
+ const files = await fs9.readdir(skillDir);
4955
+ relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, f));
4652
4956
  } catch {
4653
4957
  }
4654
4958
  const nameLower = name2.toLowerCase();
4655
4959
  const refResults = await Promise.all(
4656
4960
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
4657
4961
  try {
4658
- const content = await promises.readFile(e.path, "utf8");
4962
+ const content = await fs9.readFile(e.path, "utf8");
4659
4963
  return [e.name, content.toLowerCase().includes(nameLower)];
4660
4964
  } catch {
4661
4965
  return [e.name, false];
@@ -4745,20 +5049,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
4745
5049
  }
4746
5050
  const createPayload = parsed.value;
4747
5051
  try {
4748
- const targetDir = createPayload.scope === "global" ? path5__default.join(
4749
- ctx.globalSkillsDir ?? path5__default.join(wstackGlobalRoot$1(), "skills"),
5052
+ const targetDir = createPayload.scope === "global" ? path10.join(
5053
+ ctx.globalSkillsDir ?? path10.join(wstackGlobalRoot(), "skills"),
4750
5054
  createPayload.name.trim()
4751
- ) : path5__default.join(
4752
- ctx.projectSkillsDir ?? path5__default.join(ctx.projectRoot, ".wrongstack", "skills"),
5055
+ ) : path10.join(
5056
+ ctx.projectSkillsDir ?? path10.join(ctx.projectRoot, ".wrongstack", "skills"),
4753
5057
  createPayload.name.trim()
4754
5058
  );
4755
5059
  try {
4756
- await promises.access(targetDir);
5060
+ await fs9.access(targetDir);
4757
5061
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
4758
5062
  return;
4759
5063
  } catch {
4760
5064
  }
4761
- await promises.mkdir(targetDir, { recursive: true });
5065
+ await fs9.mkdir(targetDir, { recursive: true });
4762
5066
  const lines = createPayload.description.trim().split("\n");
4763
5067
  const firstLine = (lines[0] ?? "").trim();
4764
5068
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -4806,13 +5110,13 @@ ${trigger}
4806
5110
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
4807
5111
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
4808
5112
  ].join("\n");
4809
- await atomicWrite(path5__default.join(targetDir, "SKILL.md"), skillContent);
5113
+ await atomicWrite5(path10.join(targetDir, "SKILL.md"), skillContent);
4810
5114
  send(ws, {
4811
5115
  type: "skills.created",
4812
5116
  payload: {
4813
5117
  success: true,
4814
5118
  error: null,
4815
- skill: { name: createPayload.name.trim(), path: path5__default.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5119
+ skill: { name: createPayload.name.trim(), path: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
4816
5120
  }
4817
5121
  });
4818
5122
  } catch (err) {
@@ -4842,7 +5146,7 @@ async function handleSkillsEdit(ws, ctx, msg) {
4842
5146
  send(ws, { type: "skills.edited", payload: { success: false, error: `${label} skills cannot be edited` } });
4843
5147
  return;
4844
5148
  }
4845
- await atomicWrite(entry.path, editPayload.body);
5149
+ await atomicWrite5(entry.path, editPayload.body);
4846
5150
  send(ws, { type: "skills.edited", payload: { success: true, error: null } });
4847
5151
  } catch (err) {
4848
5152
  send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
@@ -4855,29 +5159,35 @@ async function handleSkillsExport(ws, ctx) {
4855
5159
  }
4856
5160
  try {
4857
5161
  const entries = await ctx.skillLoader.listEntries();
4858
- const zip = new JSZip();
5162
+ const zipEntries = [];
4859
5163
  for (const entry of entries) {
4860
5164
  try {
4861
5165
  const body = await ctx.skillLoader.readBody(entry.name);
4862
5166
  const safeName = entry.name.replace(/\//g, "_");
4863
- zip.file(`${safeName}/SKILL.md`, body);
5167
+ zipEntries.push({ name: `${safeName}/SKILL.md`, data: body });
4864
5168
  } catch {
4865
5169
  }
4866
5170
  }
4867
- const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
5171
+ const zipBuffer = createZipBuffer(zipEntries);
4868
5172
  const zipBase64 = zipBuffer.toString("base64");
4869
5173
  send(ws, { type: "skills.exported", payload: { zipBase64, skillCount: entries.length, error: void 0 } });
4870
5174
  } catch (err) {
4871
5175
  send(ws, { type: "skills.exported", payload: { zipBase64: "", skillCount: 0, error: errMessage(err) } });
4872
5176
  }
4873
5177
  }
5178
+
5179
+ // src/server/specs-ws-handler.ts
5180
+ import {
5181
+ computeTaskProgress
5182
+ } from "@wrongstack/core";
5183
+ import { SpecStore as SpecStore2, TaskGraphStore as TaskGraphStore2 } from "@wrongstack/sdd";
4874
5184
  var SpecsWebSocketHandler = class {
4875
5185
  specStore;
4876
5186
  graphStore;
4877
5187
  clients = /* @__PURE__ */ new Set();
4878
5188
  constructor(specsDir, taskGraphsDir) {
4879
- this.specStore = new SpecStore({ baseDir: specsDir });
4880
- this.graphStore = new TaskGraphStore({ baseDir: taskGraphsDir });
5189
+ this.specStore = new SpecStore2({ baseDir: specsDir });
5190
+ this.graphStore = new TaskGraphStore2({ baseDir: taskGraphsDir });
4881
5191
  }
4882
5192
  addClient(ws) {
4883
5193
  const client = { ws, id: crypto.randomUUID() };
@@ -5081,6 +5391,13 @@ function estimateContextBreakdown(input) {
5081
5391
  messages: { total: msgTokens, count: input.messages.length, breakdown: messageBreakdown }
5082
5392
  };
5083
5393
  }
5394
+
5395
+ // src/server/worktree-ws-handler.ts
5396
+ import { join as join7, resolve as resolve6, sep as sep3 } from "node:path";
5397
+ import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5398
+ import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5399
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
5400
+ var MAX_ACTIVITY = 6;
5084
5401
  var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
5085
5402
  var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
5086
5403
  var WorktreeWebSocketHandler = class {
@@ -5137,13 +5454,13 @@ var WorktreeWebSocketHandler = class {
5137
5454
  // ── orphan management ─────────────────────────────────────────────────────
5138
5455
  /** Absolute managed-worktrees root for this project. */
5139
5456
  worktreesRoot() {
5140
- return resolve(join(this.management.projectRoot, ".wrongstack", "worktrees"));
5457
+ return resolve6(join7(this.management.projectRoot, ".wrongstack", "worktrees"));
5141
5458
  }
5142
5459
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5143
5460
  underRoot(dir) {
5144
- const abs = resolve(dir);
5461
+ const abs = resolve6(dir);
5145
5462
  const root = this.worktreesRoot();
5146
- return abs !== root && abs.startsWith(root + sep);
5463
+ return abs !== root && abs.startsWith(root + sep3);
5147
5464
  }
5148
5465
  /** Branches of worktrees a live in-session run currently owns. */
5149
5466
  liveActiveBranches() {
@@ -5164,7 +5481,7 @@ var WorktreeWebSocketHandler = class {
5164
5481
  return;
5165
5482
  }
5166
5483
  try {
5167
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5484
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5168
5485
  const { worktrees, branches } = await wt.listManaged();
5169
5486
  const live = this.liveActiveBranches();
5170
5487
  const orphans = [];
@@ -5188,7 +5505,7 @@ var WorktreeWebSocketHandler = class {
5188
5505
  }
5189
5506
  });
5190
5507
  } catch (err) {
5191
- this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage(err)}`);
5508
+ this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage2(err)}`);
5192
5509
  this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
5193
5510
  }
5194
5511
  }
@@ -5212,7 +5529,7 @@ var WorktreeWebSocketHandler = class {
5212
5529
  });
5213
5530
  return;
5214
5531
  }
5215
- const res = await cleanupStaleSddWorktrees({
5532
+ const res = await cleanupStaleSddWorktrees2({
5216
5533
  projectRoot: this.management.projectRoot,
5217
5534
  boardsDir: this.management.boardsDir
5218
5535
  });
@@ -5251,7 +5568,7 @@ var WorktreeWebSocketHandler = class {
5251
5568
  }
5252
5569
  let removed = false;
5253
5570
  if (dir) {
5254
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5571
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5255
5572
  ({ removed } = await wt.removeOne(dir, branch));
5256
5573
  }
5257
5574
  for (const [id, h] of [...this.handles]) {
@@ -5275,7 +5592,7 @@ var WorktreeWebSocketHandler = class {
5275
5592
  this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" } });
5276
5593
  return;
5277
5594
  }
5278
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5595
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5279
5596
  const res = await wt.mergeBranch(branch);
5280
5597
  this.broadcast({
5281
5598
  type: "worktree.merge_result",
@@ -5290,8 +5607,8 @@ var WorktreeWebSocketHandler = class {
5290
5607
  return;
5291
5608
  }
5292
5609
  const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
5293
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5294
- const summary = await wt.diffSummary(resolve(dir), base);
5610
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5611
+ const summary = await wt.diffSummary(resolve6(dir), base);
5295
5612
  this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
5296
5613
  }
5297
5614
  // ── internals ───────────────────────────────────────────────────────────
@@ -5363,7 +5680,7 @@ var WorktreeWebSocketHandler = class {
5363
5680
  activity(id, kind, text) {
5364
5681
  const cur = this.handles.get(id);
5365
5682
  if (cur) {
5366
- const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-6);
5683
+ const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-MAX_ACTIVITY);
5367
5684
  this.handles.set(id, { ...cur, recentActivity });
5368
5685
  }
5369
5686
  this.broadcast({ type: "worktree.event", payload: { kind, handleId: id, text, at: Date.now() } });
@@ -5395,7 +5712,7 @@ var WorktreeWebSocketHandler = class {
5395
5712
  try {
5396
5713
  if (ws.readyState === 1) ws.send(data);
5397
5714
  } catch (err) {
5398
- this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage(err)}`);
5715
+ this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage2(err)}`);
5399
5716
  }
5400
5717
  }
5401
5718
  }
@@ -5407,6 +5724,12 @@ var WorktreeWebSocketHandler = class {
5407
5724
  }
5408
5725
  };
5409
5726
 
5727
+ // src/server/server-runtime.ts
5728
+ import * as path12 from "node:path";
5729
+ import { createRequire } from "node:module";
5730
+ import { fileURLToPath } from "node:url";
5731
+ import { WebSocketServer } from "ws";
5732
+
5410
5733
  // src/server/lifecycle.ts
5411
5734
  function createShutdown(res) {
5412
5735
  const log = res.log ?? ((m) => console.log(m));
@@ -5442,9 +5765,14 @@ function registerShutdownHandlers(res) {
5442
5765
  process.off("SIGTERM", shutdown);
5443
5766
  };
5444
5767
  }
5768
+
5769
+ // src/server/setup-events.ts
5770
+ import * as fs10 from "node:fs/promises";
5771
+ import { watch as fsWatch } from "node:fs";
5772
+ import * as path11 from "node:path";
5445
5773
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
5446
5774
  const raw = String(filename);
5447
- const relative4 = path5.isAbsolute(raw) ? path5.relative(projectsDir, raw) : raw;
5775
+ const relative4 = path11.isAbsolute(raw) ? path11.relative(projectsDir, raw) : raw;
5448
5776
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
5449
5777
  if (parts.length < 2) return null;
5450
5778
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -5457,6 +5785,10 @@ function shouldLogWatcherStats() {
5457
5785
  function setupEvents(deps2) {
5458
5786
  const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5459
5787
  const disposers = [];
5788
+ let disposed = false;
5789
+ const on = (event, listener) => {
5790
+ disposers.push(events.on(event, listener));
5791
+ };
5460
5792
  const currentSessionId = () => context.session?.id ?? "";
5461
5793
  const sessionPayload2 = (payload) => {
5462
5794
  const provided = payload["sessionId"];
@@ -5472,20 +5804,20 @@ function setupEvents(deps2) {
5472
5804
  sessionBridge?.append(event).catch(() => {
5473
5805
  });
5474
5806
  };
5475
- events.on("iteration.started", (e) => {
5807
+ on("iteration.started", (e) => {
5476
5808
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5477
5809
  broadcast2(clients, {
5478
5810
  type: "iteration.started",
5479
5811
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5480
5812
  });
5481
5813
  });
5482
- events.on("iteration.completed", (e) => {
5814
+ on("iteration.completed", (e) => {
5483
5815
  broadcast2(clients, {
5484
5816
  type: "iteration.completed",
5485
5817
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5486
5818
  });
5487
5819
  });
5488
- events.on("iteration.limit_reached", (e) => {
5820
+ on("iteration.limit_reached", (e) => {
5489
5821
  broadcast2(clients, {
5490
5822
  type: "iteration.limit_reached",
5491
5823
  payload: sessionPayload2({
@@ -5495,19 +5827,19 @@ function setupEvents(deps2) {
5495
5827
  })
5496
5828
  });
5497
5829
  });
5498
- events.on("provider.text_delta", (e) => {
5830
+ on("provider.text_delta", (e) => {
5499
5831
  broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5500
5832
  });
5501
- events.on("provider.thinking_delta", (e) => {
5833
+ on("provider.thinking_delta", (e) => {
5502
5834
  broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5503
5835
  });
5504
- events.on("provider.stream_error", (e) => {
5836
+ on("provider.stream_error", (e) => {
5505
5837
  broadcast2(clients, {
5506
5838
  type: "provider.stream_error",
5507
5839
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5508
5840
  });
5509
5841
  });
5510
- events.on("tool.started", (e) => {
5842
+ on("tool.started", (e) => {
5511
5843
  broadcast2(clients, {
5512
5844
  type: "tool.started",
5513
5845
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
@@ -5520,7 +5852,7 @@ function setupEvents(deps2) {
5520
5852
  input: e.input
5521
5853
  });
5522
5854
  });
5523
- events.on("tool.progress", (e) => {
5855
+ on("tool.progress", (e) => {
5524
5856
  broadcast2(clients, {
5525
5857
  type: "tool.progress",
5526
5858
  // Nested `event` shape — the client handler reads `payload.event?.text`
@@ -5541,7 +5873,7 @@ function setupEvents(deps2) {
5541
5873
  }
5542
5874
  });
5543
5875
  });
5544
- events.on("tool.executed", (e) => {
5876
+ on("tool.executed", (e) => {
5545
5877
  broadcast2(clients, {
5546
5878
  type: "tool.executed",
5547
5879
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
@@ -5581,7 +5913,7 @@ function setupEvents(deps2) {
5581
5913
  try {
5582
5914
  const taskPath = context.meta["task.path"];
5583
5915
  if (typeof taskPath === "string" && taskPath) {
5584
- const { loadTasks } = await import('@wrongstack/core');
5916
+ const { loadTasks } = await import("@wrongstack/core");
5585
5917
  const file = await loadTasks(taskPath);
5586
5918
  broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5587
5919
  }
@@ -5590,7 +5922,7 @@ function setupEvents(deps2) {
5590
5922
  try {
5591
5923
  const planPath = context.meta["plan.path"];
5592
5924
  if (typeof planPath === "string" && planPath) {
5593
- const { loadPlan } = await import('@wrongstack/core');
5925
+ const { loadPlan } = await import("@wrongstack/core");
5594
5926
  const plan = await loadPlan(planPath);
5595
5927
  broadcast2(clients, { type: "plan.updated", payload: sessionPayload2({ sessionId: e.sessionId, plan: plan ?? { version: 1, sessionId: e.sessionId ?? context.session?.id ?? "", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }) });
5596
5928
  }
@@ -5599,7 +5931,7 @@ function setupEvents(deps2) {
5599
5931
  })();
5600
5932
  }
5601
5933
  });
5602
- events.on("tool.loop_detected", (e) => {
5934
+ on("tool.loop_detected", (e) => {
5603
5935
  broadcast2(clients, {
5604
5936
  type: "tool.loop_detected",
5605
5937
  payload: sessionPayload2({
@@ -5611,19 +5943,19 @@ function setupEvents(deps2) {
5611
5943
  })
5612
5944
  });
5613
5945
  });
5614
- events.on("trust.persisted", (e) => {
5946
+ on("trust.persisted", (e) => {
5615
5947
  broadcast2(clients, {
5616
5948
  type: "trust.persisted",
5617
5949
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5618
5950
  });
5619
5951
  });
5620
- events.on("delegate.started", (e) => {
5952
+ on("delegate.started", (e) => {
5621
5953
  broadcast2(clients, {
5622
5954
  type: "delegate.started",
5623
5955
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5624
5956
  });
5625
5957
  });
5626
- events.on("delegate.completed", (e) => {
5958
+ on("delegate.completed", (e) => {
5627
5959
  broadcast2(clients, {
5628
5960
  type: "delegate.completed",
5629
5961
  payload: sessionPayload2({
@@ -5641,7 +5973,7 @@ function setupEvents(deps2) {
5641
5973
  })
5642
5974
  });
5643
5975
  });
5644
- events.on("provider.response", (e) => {
5976
+ on("provider.response", (e) => {
5645
5977
  broadcast2(clients, {
5646
5978
  type: "provider.response",
5647
5979
  payload: sessionPayload2({
@@ -5653,7 +5985,7 @@ function setupEvents(deps2) {
5653
5985
  })
5654
5986
  });
5655
5987
  });
5656
- events.on("ctx.pct", (e) => {
5988
+ on("ctx.pct", (e) => {
5657
5989
  broadcast2(clients, {
5658
5990
  type: "ctx.pct",
5659
5991
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
@@ -5670,28 +6002,28 @@ function setupEvents(deps2) {
5670
6002
  })
5671
6003
  });
5672
6004
  });
5673
- events.on("ctx.max_context", (e) => {
6005
+ on("ctx.max_context", (e) => {
5674
6006
  broadcast2(clients, {
5675
6007
  type: "ctx.max_context",
5676
6008
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5677
6009
  });
5678
6010
  });
5679
- events.on("token.threshold", (e) => {
6011
+ on("token.threshold", (e) => {
5680
6012
  broadcast2(clients, {
5681
6013
  type: "token.threshold",
5682
6014
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5683
6015
  });
5684
6016
  });
5685
- events.on("token.cost_estimate_unavailable", (e) => {
6017
+ on("token.cost_estimate_unavailable", (e) => {
5686
6018
  broadcast2(clients, {
5687
6019
  type: "token.cost_estimate_unavailable",
5688
6020
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5689
6021
  });
5690
6022
  });
5691
- events.on("context.repaired", (e) => {
6023
+ on("context.repaired", (e) => {
5692
6024
  broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5693
6025
  });
5694
- events.on("tool.confirm_needed", (e) => {
6026
+ on("tool.confirm_needed", (e) => {
5695
6027
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5696
6028
  const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
5697
6029
  pendingConfirms.set(id, {
@@ -5702,7 +6034,7 @@ function setupEvents(deps2) {
5702
6034
  });
5703
6035
  broadcast2(clients, { type: "tool.confirm_needed", payload });
5704
6036
  });
5705
- events.on("error", (e) => {
6037
+ on("error", (e) => {
5706
6038
  broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5707
6039
  appendForCurrentSession(e.sessionId, {
5708
6040
  type: "error",
@@ -5711,13 +6043,13 @@ function setupEvents(deps2) {
5711
6043
  phase: e.phase
5712
6044
  });
5713
6045
  });
5714
- events.on("session.damaged", (e) => {
6046
+ on("session.damaged", (e) => {
5715
6047
  broadcast2(clients, {
5716
6048
  type: "session.damaged",
5717
6049
  payload: { sessionId: e.sessionId, detail: e.detail }
5718
6050
  });
5719
6051
  });
5720
- events.on("session.rewound", (e) => {
6052
+ on("session.rewound", (e) => {
5721
6053
  broadcast2(clients, {
5722
6054
  type: "session.rewound",
5723
6055
  payload: sessionPayload2({
@@ -5728,7 +6060,7 @@ function setupEvents(deps2) {
5728
6060
  })
5729
6061
  });
5730
6062
  });
5731
- events.on("checkpoint.written", (e) => {
6063
+ on("checkpoint.written", (e) => {
5732
6064
  broadcast2(clients, {
5733
6065
  type: "checkpoint.written",
5734
6066
  payload: sessionPayload2({
@@ -5740,19 +6072,19 @@ function setupEvents(deps2) {
5740
6072
  })
5741
6073
  });
5742
6074
  });
5743
- events.on("in_flight.started", (e) => {
6075
+ on("in_flight.started", (e) => {
5744
6076
  broadcast2(clients, {
5745
6077
  type: "in_flight.started",
5746
6078
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
5747
6079
  });
5748
6080
  });
5749
- events.on("in_flight.ended", (e) => {
6081
+ on("in_flight.ended", (e) => {
5750
6082
  broadcast2(clients, {
5751
6083
  type: "in_flight.ended",
5752
6084
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
5753
6085
  });
5754
6086
  });
5755
- events.on("provider.retry", (e) => {
6087
+ on("provider.retry", (e) => {
5756
6088
  broadcast2(clients, {
5757
6089
  type: "provider.retry",
5758
6090
  payload: sessionPayload2({
@@ -5774,7 +6106,7 @@ function setupEvents(deps2) {
5774
6106
  description: e.description
5775
6107
  });
5776
6108
  });
5777
- events.on("provider.error", (e) => {
6109
+ on("provider.error", (e) => {
5778
6110
  broadcast2(clients, {
5779
6111
  type: "provider.error",
5780
6112
  payload: sessionPayload2({
@@ -5794,7 +6126,7 @@ function setupEvents(deps2) {
5794
6126
  retryable: e.retryable
5795
6127
  });
5796
6128
  });
5797
- events.on("provider.fallback", (e) => {
6129
+ on("provider.fallback", (e) => {
5798
6130
  broadcast2(clients, {
5799
6131
  type: "provider.fallback",
5800
6132
  payload: sessionPayload2({
@@ -5806,7 +6138,7 @@ function setupEvents(deps2) {
5806
6138
  })
5807
6139
  });
5808
6140
  });
5809
- events.on("compaction.fired", (e) => {
6141
+ on("compaction.fired", (e) => {
5810
6142
  broadcast2(clients, {
5811
6143
  type: "context.compacted",
5812
6144
  payload: sessionPayload2({
@@ -5818,7 +6150,7 @@ function setupEvents(deps2) {
5818
6150
  })
5819
6151
  });
5820
6152
  });
5821
- events.on("compaction.failed", (e) => {
6153
+ on("compaction.failed", (e) => {
5822
6154
  broadcast2(clients, {
5823
6155
  type: "compaction.failed",
5824
6156
  payload: sessionPayload2({
@@ -5833,25 +6165,25 @@ function setupEvents(deps2) {
5833
6165
  })
5834
6166
  });
5835
6167
  });
5836
- events.on("mcp.server.connected", (e) => {
6168
+ on("mcp.server.connected", (e) => {
5837
6169
  broadcast2(clients, {
5838
6170
  type: "mcp.server.connected",
5839
6171
  payload: { name: e.name, toolCount: e.toolCount }
5840
6172
  });
5841
6173
  });
5842
- events.on("mcp.server.reconnected", (e) => {
6174
+ on("mcp.server.reconnected", (e) => {
5843
6175
  broadcast2(clients, {
5844
6176
  type: "mcp.server.reconnected",
5845
6177
  payload: { name: e.name, toolCount: e.toolCount }
5846
6178
  });
5847
6179
  });
5848
- events.on("mcp.server.disconnected", (e) => {
6180
+ on("mcp.server.disconnected", (e) => {
5849
6181
  broadcast2(clients, {
5850
6182
  type: "mcp.server.disconnected",
5851
6183
  payload: { name: e.name, reason: e.reason }
5852
6184
  });
5853
6185
  });
5854
- events.on("coordinator.stats", (e) => {
6186
+ on("coordinator.stats", (e) => {
5855
6187
  broadcast2(clients, {
5856
6188
  type: "coordinator.stats",
5857
6189
  payload: sessionPayload2({
@@ -5879,15 +6211,16 @@ function setupEvents(deps2) {
5879
6211
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
5880
6212
  });
5881
6213
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5882
- events.on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
5883
- events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
5884
- events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
5885
- events.on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { sessionId: e.sessionId, subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool, partialText: e.partialText }));
5886
- events.on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
5887
- events.on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
5888
- events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
5889
- events.on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
5890
- events.on("agent.timeline.message", (e) => {
6214
+ on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
6215
+ on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6216
+ on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6217
+ on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { sessionId: e.sessionId, subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool, partialText: e.partialText }));
6218
+ on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6219
+ on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6220
+ on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6221
+ on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
6222
+ on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
6223
+ on("agent.timeline.message", (e) => {
5891
6224
  broadcast2(clients, {
5892
6225
  type: "agent.timeline.message",
5893
6226
  payload: sessionPayload2({
@@ -5903,7 +6236,7 @@ function setupEvents(deps2) {
5903
6236
  })
5904
6237
  });
5905
6238
  });
5906
- events.on("agent.status_changed", (e) => {
6239
+ on("agent.status_changed", (e) => {
5907
6240
  broadcast2(clients, {
5908
6241
  type: "agent.status_changed",
5909
6242
  payload: sessionPayload2({
@@ -5918,7 +6251,7 @@ function setupEvents(deps2) {
5918
6251
  });
5919
6252
  });
5920
6253
  let leaderSpawned = false;
5921
- events.on("iteration.started", (e) => {
6254
+ on("iteration.started", (e) => {
5922
6255
  if (!leaderSpawned) {
5923
6256
  leaderSpawned = true;
5924
6257
  const provider = context.provider?.id ?? "unknown";
@@ -5932,7 +6265,7 @@ function setupEvents(deps2) {
5932
6265
  });
5933
6266
  }
5934
6267
  });
5935
- events.on("tool.executed", (e) => {
6268
+ on("tool.executed", (e) => {
5936
6269
  forwardSubagent("tool_executed", {
5937
6270
  sessionId: e.sessionId,
5938
6271
  subagentId: "leader",
@@ -5941,7 +6274,7 @@ function setupEvents(deps2) {
5941
6274
  ok: e.ok
5942
6275
  });
5943
6276
  });
5944
- events.on("provider.response", (e) => {
6277
+ on("provider.response", (e) => {
5945
6278
  if (e.usage?.input != null) {
5946
6279
  const maxCtx = context.provider.capabilities.maxContext;
5947
6280
  const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
@@ -5958,7 +6291,7 @@ function setupEvents(deps2) {
5958
6291
  });
5959
6292
  }
5960
6293
  });
5961
- events.on("iteration.completed", (e) => {
6294
+ on("iteration.completed", (e) => {
5962
6295
  if (!leaderSpawned) {
5963
6296
  leaderSpawned = true;
5964
6297
  const provider = context.provider?.id ?? "unknown";
@@ -5984,14 +6317,20 @@ function setupEvents(deps2) {
5984
6317
  payload: sessionPayload2({ event: eventName, ...payload })
5985
6318
  });
5986
6319
  });
5987
- events.on("client.status", async (e) => {
6320
+ events.onPattern("memory.*", (eventName, payload) => {
6321
+ broadcast2(clients, {
6322
+ type: "memory.event",
6323
+ payload: sessionPayload2({ event: eventName, ...payload })
6324
+ });
6325
+ });
6326
+ on("client.status", async (e) => {
5988
6327
  broadcast2(clients, { type: "client.status_update", payload: e });
5989
6328
  if (wpaths?.projectStatus) {
5990
6329
  try {
5991
6330
  const statusFile = wpaths.projectStatus(e.projectHash);
5992
- const dir = path5.dirname(statusFile);
5993
- await fs4.mkdir(dir, { recursive: true });
5994
- await fs4.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6331
+ const dir = path11.dirname(statusFile);
6332
+ await fs10.mkdir(dir, { recursive: true });
6333
+ await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
5995
6334
  } catch (err) {
5996
6335
  console.error(
5997
6336
  JSON.stringify({
@@ -6005,7 +6344,7 @@ function setupEvents(deps2) {
6005
6344
  }
6006
6345
  });
6007
6346
  if (wpaths?.projectStatus && wpaths.configDir) {
6008
- const projectsDir = path5.join(wpaths.configDir, "projects");
6347
+ const projectsDir = path11.join(wpaths.configDir, "projects");
6009
6348
  const knownProjectHashes = /* @__PURE__ */ new Set();
6010
6349
  const debounceTimers = /* @__PURE__ */ new Map();
6011
6350
  const DEBOUNCE_MS = 150;
@@ -6069,8 +6408,9 @@ function setupEvents(deps2) {
6069
6408
  let watcher;
6070
6409
  const startWatcher = async () => {
6071
6410
  try {
6072
- await fs4.mkdir(projectsDir, { recursive: true });
6073
- watcher = watch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6411
+ await fs10.mkdir(projectsDir, { recursive: true });
6412
+ if (disposed) return;
6413
+ watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6074
6414
  if (eventType !== "change" && eventType !== "rename") return;
6075
6415
  if (filename == null) return;
6076
6416
  const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
@@ -6079,8 +6419,8 @@ function setupEvents(deps2) {
6079
6419
  if (!knownProjectHashes.has(projectHash)) return;
6080
6420
  if (watcherMetrics) watcherMetrics.filesProcessed++;
6081
6421
  try {
6082
- const targetFile = path5.join(projectsDir, projectHash, "status.json");
6083
- const content = await fs4.readFile(targetFile, "utf-8");
6422
+ const targetFile = path11.join(projectsDir, projectHash, "status.json");
6423
+ const content = await fs10.readFile(targetFile, "utf-8");
6084
6424
  const statusData = JSON.parse(content);
6085
6425
  scheduleBroadcast(projectHash, statusData);
6086
6426
  } catch {
@@ -6100,7 +6440,7 @@ function setupEvents(deps2) {
6100
6440
  );
6101
6441
  }
6102
6442
  };
6103
- events.on("client.status", (e) => {
6443
+ on("client.status", (e) => {
6104
6444
  if (e.projectHash) {
6105
6445
  const hash = String(e.projectHash);
6106
6446
  if (!knownProjectHashes.has(hash)) {
@@ -6132,15 +6472,17 @@ function setupEvents(deps2) {
6132
6472
  }
6133
6473
  });
6134
6474
  }
6135
- const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
6475
+ const globalRoot = globalConfigPath ? path11.dirname(globalConfigPath) : void 0;
6136
6476
  if (globalRoot) {
6137
6477
  const broadcastSessions = async () => {
6138
6478
  try {
6139
- const { SessionRegistry } = await import('@wrongstack/core');
6479
+ const { SessionRegistry } = await import("@wrongstack/core");
6140
6480
  const registry = new SessionRegistry(globalRoot);
6141
6481
  const sessions = await registry.list();
6142
6482
  const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6143
- const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6483
+ const live = sessions.filter(
6484
+ (s) => s.status === "active" || s.status === "idle"
6485
+ ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6144
6486
  sessionId: s.sessionId,
6145
6487
  projectName: s.projectName,
6146
6488
  projectSlug: s.projectSlug,
@@ -6179,7 +6521,7 @@ function setupEvents(deps2) {
6179
6521
  disposers.push(() => clearInterval(statusInterval));
6180
6522
  let regDebounce;
6181
6523
  try {
6182
- const regWatcher = watch(globalRoot, { persistent: false }, (_event, filename) => {
6524
+ const regWatcher = fsWatch(globalRoot, { persistent: false }, (_event, filename) => {
6183
6525
  const name2 = filename ? String(filename) : "";
6184
6526
  if (!name2.startsWith("session-registry.json") || name2.endsWith(".lock")) return;
6185
6527
  if (regDebounce) clearTimeout(regDebounce);
@@ -6194,6 +6536,8 @@ function setupEvents(deps2) {
6194
6536
  void broadcastSessions();
6195
6537
  }
6196
6538
  return () => {
6539
+ if (disposed) return;
6540
+ disposed = true;
6197
6541
  for (const dispose of disposers) {
6198
6542
  try {
6199
6543
  dispose();
@@ -6215,6 +6559,9 @@ function getCostRates(model) {
6215
6559
  function computeUsageCost(usage, rates) {
6216
6560
  return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
6217
6561
  }
6562
+
6563
+ // src/server/model-catalog.ts
6564
+ import { capabilitiesFor } from "@wrongstack/providers";
6218
6565
  var SIBLING_CATALOG = {
6219
6566
  "anthropic-oauth": "anthropic",
6220
6567
  "openai-codex": "openai",
@@ -6321,6 +6668,9 @@ function readConfiguredBool(providerConfig, key) {
6321
6668
  function positiveNumber(value) {
6322
6669
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
6323
6670
  }
6671
+
6672
+ // src/server/server-runtime.ts
6673
+ import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6324
6674
  async function resolvePorts(opts) {
6325
6675
  const requestedWsPort = opts.wsPort ?? 3457;
6326
6676
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
@@ -6381,7 +6731,7 @@ function createSessionStartPayload(g) {
6381
6731
  inputCost,
6382
6732
  outputCost,
6383
6733
  cacheReadCost,
6384
- projectName: path5.basename(projectRoot) || projectRoot,
6734
+ projectName: path12.basename(projectRoot) || projectRoot,
6385
6735
  projectRoot,
6386
6736
  cwd: g.getWorkingDir(),
6387
6737
  mode: g.getModeId(),
@@ -6414,18 +6764,31 @@ function createWsServers(ports, accessToken) {
6414
6764
  allowedHostnames: publicHostnames,
6415
6765
  allowBrowserUrlToken: Boolean(ports.publicWsUrl)
6416
6766
  });
6417
- const WS_MAX_PAYLOAD = 8 * 1024 * 1024;
6767
+ const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
6418
6768
  const wssPrimary = new WebSocketServer({
6419
6769
  port: ports.wsPort,
6420
6770
  host: ports.wsHost,
6421
6771
  verifyClient: verifyClient2,
6422
- maxPayload: WS_MAX_PAYLOAD
6772
+ maxPayload: WS_MAX_PAYLOAD,
6773
+ // Send a ping every 15s to keep idle connections alive. Without this,
6774
+ // network equipment (routers, proxies, NAT gateways) may drop idle TCP
6775
+ // connections, causing the browser to see a close event and show the
6776
+ // "reconnecting" banner. The browser responds with a pong automatically
6777
+ // (the WebSocket API handles this at the transport level).
6778
+ pingInterval: 15e3,
6779
+ // Wait 5s for a pong before considering the connection dead. A client
6780
+ // that hasn't responded to 3 consecutive pings in a row (45s total
6781
+ // quiet) is almost certainly unreachable — close the socket so the
6782
+ // client can reconnect cleanly rather than hanging indefinitely.
6783
+ pingTimeout: 5e3
6423
6784
  });
6424
6785
  const wssSecondary = ports.wsHost === "127.0.0.1" ? new WebSocketServer({
6425
6786
  port: ports.wsPort,
6426
6787
  host: "::1",
6427
6788
  verifyClient: verifyClient2,
6428
- maxPayload: WS_MAX_PAYLOAD
6789
+ maxPayload: WS_MAX_PAYLOAD,
6790
+ pingInterval: 15e3,
6791
+ pingTimeout: 5e3
6429
6792
  }) : null;
6430
6793
  const clients = /* @__PURE__ */ new Map();
6431
6794
  console.log(
@@ -6447,7 +6810,7 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
6447
6810
  };
6448
6811
  wssPrimary.on("listening", () => arm(`${wsHost}:${wsPort}`));
6449
6812
  wssPrimary.on("error", (err) => {
6450
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6813
+ console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage3(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6451
6814
  });
6452
6815
  if (wssSecondary) {
6453
6816
  wssSecondary.on("listening", () => arm(`::1:${wsPort}`));
@@ -6466,13 +6829,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
6466
6829
  };
6467
6830
  }
6468
6831
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
6469
- if (explicitDistDir) return path5.resolve(explicitDistDir);
6832
+ if (explicitDistDir) return path12.resolve(explicitDistDir);
6470
6833
  try {
6471
6834
  const requireFromHere2 = createRequire(fromUrl);
6472
6835
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui/server");
6473
- return path5.resolve(path5.dirname(serverEntry), "..");
6836
+ return path12.resolve(path12.dirname(serverEntry), "..");
6474
6837
  } catch {
6475
- return path5.resolve(path5.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6838
+ return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6476
6839
  }
6477
6840
  }
6478
6841
  function startHttpServer(opts) {
@@ -6487,13 +6850,13 @@ function startHttpServer(opts) {
6487
6850
  watcherMetrics: opts.watcherMetrics,
6488
6851
  onFleetPing: opts.onFleetPing
6489
6852
  });
6490
- const registryBaseDir = path5.dirname(opts.globalConfigPath);
6853
+ const registryBaseDir = path12.dirname(opts.globalConfigPath);
6491
6854
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
6492
6855
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
6493
6856
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
6494
6857
  if (opts.openBrowser) openBrowser(openUrl);
6495
6858
  void registerInstance(
6496
- { pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path5.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
6859
+ { pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
6497
6860
  registryBaseDir
6498
6861
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
6499
6862
  });
@@ -6507,6 +6870,65 @@ function registerShutdown(deps2) {
6507
6870
  onShutdown: deps2.onShutdown
6508
6871
  });
6509
6872
  }
6873
+
6874
+ // src/server/pre-context-services.ts
6875
+ import * as path15 from "node:path";
6876
+ import { createRequire as createRequire2 } from "node:module";
6877
+ import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
6878
+ import {
6879
+ AnnotationsStore,
6880
+ DEFAULT_SESSION_PRUNE_DAYS,
6881
+ DefaultModelsRegistry,
6882
+ DefaultModeStore,
6883
+ DefaultPromptLoader,
6884
+ DefaultSessionReader,
6885
+ DefaultSessionStore,
6886
+ DefaultSkillLoader,
6887
+ DefaultSystemPromptBuilder,
6888
+ EventBus,
6889
+ GlobalMailbox,
6890
+ PromptUsageStore,
6891
+ ProviderRegistry,
6892
+ SkillInstaller,
6893
+ TOKENS,
6894
+ ToolRegistry,
6895
+ Context,
6896
+ applyToolDescriptionModes,
6897
+ applyToolResultRenderModes,
6898
+ configureChildEnvGitIdentity,
6899
+ getSessionRegistry as getSessionRegistry2,
6900
+ resolveContextWindowPolicy,
6901
+ makeMailboxTool,
6902
+ makeMailInboxTool,
6903
+ makeFleetStatusTool,
6904
+ makeMailSendTool
6905
+ } from "@wrongstack/core";
6906
+ import {
6907
+ createSuperMemoryTools
6908
+ } from "@wrongstack/super-memory";
6909
+ import {
6910
+ MCPAuthorizationManager,
6911
+ MCPRegistry,
6912
+ MCPVaultTokenStore,
6913
+ createVaultBackedMcpAuthorizationProviderFactory
6914
+ } from "@wrongstack/mcp";
6915
+ import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
6916
+ import { createDefaultContainer } from "@wrongstack/runtime";
6917
+ import {
6918
+ builtinToolsPack,
6919
+ configureDangerBypass,
6920
+ configureExecPolicy,
6921
+ forgetTool,
6922
+ relatedMemoryTool,
6923
+ rememberTool,
6924
+ searchMemoryTool
6925
+ } from "@wrongstack/tools";
6926
+ import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6927
+
6928
+ // src/server/setup-screen.ts
6929
+ import { expectDefined as expectDefined2 } from "@wrongstack/core";
6930
+ import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
6931
+ import { makeProviderFromConfig } from "@wrongstack/providers";
6510
6932
  var UNCONFIGURED_CAPABILITIES = {
6511
6933
  tools: false,
6512
6934
  parallelTools: false,
@@ -6546,7 +6968,7 @@ function logCreateFailure(event, err) {
6546
6968
  JSON.stringify({
6547
6969
  level: "error",
6548
6970
  event,
6549
- message: toErrorMessage(err),
6971
+ message: toErrorMessage4(err),
6550
6972
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6551
6973
  })
6552
6974
  );
@@ -6571,7 +6993,7 @@ function resolveSetupProvider(opts) {
6571
6993
  const savedProviders = config.providers ?? {};
6572
6994
  const firstKey = Object.keys(savedProviders)[0];
6573
6995
  if (firstKey) {
6574
- const firstProvider = expectDefined(savedProviders[firstKey]);
6996
+ const firstProvider = expectDefined2(savedProviders[firstKey]);
6575
6997
  try {
6576
6998
  const provider2 = makeProviderFromConfig(firstKey, {
6577
6999
  ...firstProvider,
@@ -6639,6 +7061,20 @@ function seedContextMeta(config, context) {
6639
7061
  meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
6640
7062
  meta["cacheTtl"] = mr.cache?.ttl ?? "default";
6641
7063
  }
7064
+ meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
7065
+ meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
7066
+ meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7067
+ meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7068
+ meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
7069
+ meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7070
+ meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7071
+ {
7072
+ const featuresAllow = config.features?.allowOutsideProjectRoot;
7073
+ const toolsRestrict = config.tools?.restrictToProjectRoot;
7074
+ const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
7075
+ meta["fsAccess"] = allow ? "unrestricted" : "project";
7076
+ }
7077
+ meta["debugStream"] = config.debugStream === true;
6642
7078
  const hqConfig = config.hq;
6643
7079
  meta["hqEnabled"] = hqConfig?.enabled === true;
6644
7080
  meta["hqUrl"] = hqConfig?.url ?? "";
@@ -6651,6 +7087,11 @@ function seedContextMeta(config, context) {
6651
7087
  const tgMs = tgExt?.["longToolThresholdMs"];
6652
7088
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
6653
7089
  }
7090
+
7091
+ // src/server/model-auto-discovery.ts
7092
+ import * as fs11 from "node:fs/promises";
7093
+ import * as path13 from "node:path";
7094
+ import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
6654
7095
  function isOverlayRegistry(value) {
6655
7096
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
6656
7097
  }
@@ -6675,7 +7116,7 @@ function eligibleProviders(config) {
6675
7116
  }
6676
7117
  async function readCache(file) {
6677
7118
  try {
6678
- return JSON.parse(await fs4.readFile(file, "utf8"));
7119
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6679
7120
  } catch {
6680
7121
  return {};
6681
7122
  }
@@ -6685,7 +7126,7 @@ async function discoverAndMergeWebuiProviders(opts) {
6685
7126
  if (!isOverlayRegistry(registry)) return;
6686
7127
  const targets = eligibleProviders(opts.config);
6687
7128
  if (targets.length === 0) return;
6688
- const cacheFile = path5.join(opts.cacheDir, "discovered-models-cache.json");
7129
+ const cacheFile = path13.join(opts.cacheDir, "discovered-models-cache.json");
6689
7130
  const cache = await readCache(cacheFile);
6690
7131
  let cacheDirty = false;
6691
7132
  await Promise.all(
@@ -6722,18 +7163,196 @@ async function discoverAndMergeWebuiProviders(opts) {
6722
7163
  );
6723
7164
  if (cacheDirty) {
6724
7165
  try {
6725
- await fs4.mkdir(path5.dirname(cacheFile), { recursive: true });
6726
- await fs4.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7166
+ await fs11.mkdir(path13.dirname(cacheFile), { recursive: true });
7167
+ await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
6727
7168
  } catch {
6728
7169
  opts.logger?.debug?.("provider auto-discovery cache write failed");
6729
7170
  }
6730
7171
  }
6731
7172
  }
6732
7173
 
7174
+ // src/server/standalone-session-identity.ts
7175
+ import * as path14 from "node:path";
7176
+ import {
7177
+ AgentStatusTracker,
7178
+ FleetNotifier,
7179
+ getSessionRegistry,
7180
+ RecoveryLock
7181
+ } from "@wrongstack/core";
7182
+ import { WebSocket as WebSocket2 } from "ws";
7183
+ async function createStandaloneSessionIdentityLifecycle(opts) {
7184
+ const { paths, events, logger } = opts;
7185
+ const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
7186
+ const fleetNotifier = new FleetNotifier({
7187
+ baseDir: paths.globalRoot,
7188
+ projectRoot: paths.projectRoot,
7189
+ selfPid: process.pid
7190
+ });
7191
+ let activeSessionId = opts.initialSessionId;
7192
+ let stopped = false;
7193
+ let transition = Promise.resolve();
7194
+ const statusTracker = new AgentStatusTracker({
7195
+ events,
7196
+ registry,
7197
+ sessionId: () => activeSessionId,
7198
+ onUpdate: () => fleetNotifier.notify()
7199
+ });
7200
+ const register = async (sessionId) => {
7201
+ try {
7202
+ await registry.register({
7203
+ sessionId,
7204
+ projectSlug: paths.projectSlug,
7205
+ projectRoot: paths.projectRoot,
7206
+ projectName: path14.basename(paths.projectRoot),
7207
+ workingDir: opts.workingDir,
7208
+ clientType: "webui",
7209
+ pid: process.pid,
7210
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7211
+ agents: statusTracker.getAgents()
7212
+ });
7213
+ fleetNotifier.notify();
7214
+ } catch (err) {
7215
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
7216
+ }
7217
+ };
7218
+ await register(activeSessionId);
7219
+ statusTracker.start();
7220
+ const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
7221
+ let ownsRecoveryLock = false;
7222
+ if (recoveryLock) {
7223
+ try {
7224
+ await recoveryLock.write(activeSessionId);
7225
+ ownsRecoveryLock = true;
7226
+ } catch {
7227
+ }
7228
+ }
7229
+ let stopHqBridges = () => {
7230
+ };
7231
+ let closeHqPublisher = () => {
7232
+ };
7233
+ let restartHqBridges = (_sessionId) => {
7234
+ };
7235
+ if (opts.enableHqTelemetry !== false) {
7236
+ try {
7237
+ const core = await import("@wrongstack/core");
7238
+ const publisher = core.createHqPublisherFromEnv({
7239
+ clientKind: "webui",
7240
+ projectRoot: paths.projectRoot,
7241
+ projectName: path14.basename(paths.projectRoot),
7242
+ appConfig: opts.config,
7243
+ socketFactory: (url) => new WebSocket2(url)
7244
+ });
7245
+ if (publisher) {
7246
+ publisher.connect();
7247
+ closeHqPublisher = () => publisher.close();
7248
+ restartHqBridges = (sessionId) => {
7249
+ stopHqBridges();
7250
+ const stops = [];
7251
+ const add = (start) => {
7252
+ try {
7253
+ stops.push(start());
7254
+ } catch {
7255
+ }
7256
+ };
7257
+ add(
7258
+ () => core.startSessionTelemetryBridge({
7259
+ publisher,
7260
+ events,
7261
+ sessionId,
7262
+ projectRoot: paths.projectRoot,
7263
+ projectName: path14.basename(paths.projectRoot),
7264
+ globalRoot: paths.globalRoot,
7265
+ initialAgents: statusTracker.getAgents(),
7266
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
7267
+ })
7268
+ );
7269
+ add(
7270
+ () => core.startFleetTelemetryBridge({
7271
+ events,
7272
+ publisher,
7273
+ runId: sessionId,
7274
+ sessionId
7275
+ })
7276
+ );
7277
+ add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
7278
+ add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
7279
+ add(
7280
+ () => core.startToolTelemetryBridge({
7281
+ events,
7282
+ publisher,
7283
+ projectRoot: paths.projectRoot,
7284
+ sessionId
7285
+ })
7286
+ );
7287
+ add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
7288
+ stopHqBridges = () => {
7289
+ for (const stop2 of stops.splice(0)) {
7290
+ try {
7291
+ stop2();
7292
+ } catch {
7293
+ }
7294
+ }
7295
+ };
7296
+ };
7297
+ restartHqBridges(activeSessionId);
7298
+ }
7299
+ } catch (err) {
7300
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
7301
+ }
7302
+ }
7303
+ const repointRecovery = async (sessionId) => {
7304
+ if (!recoveryLock) return;
7305
+ try {
7306
+ if (ownsRecoveryLock) await recoveryLock.clear();
7307
+ await recoveryLock.write(sessionId);
7308
+ ownsRecoveryLock = true;
7309
+ } catch {
7310
+ ownsRecoveryLock = false;
7311
+ }
7312
+ };
7313
+ const activate = async (sessionId) => {
7314
+ if (stopped || sessionId === activeSessionId) return;
7315
+ transition = transition.then(async () => {
7316
+ if (stopped || sessionId === activeSessionId) return;
7317
+ activeSessionId = sessionId;
7318
+ await register(sessionId);
7319
+ await repointRecovery(sessionId);
7320
+ try {
7321
+ restartHqBridges(sessionId);
7322
+ } catch (err) {
7323
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
7324
+ }
7325
+ });
7326
+ await transition;
7327
+ };
7328
+ const stop = async () => {
7329
+ if (stopped) return;
7330
+ stopped = true;
7331
+ await transition.catch(() => void 0);
7332
+ statusTracker.stop();
7333
+ stopHqBridges();
7334
+ closeHqPublisher();
7335
+ fleetNotifier.dispose();
7336
+ try {
7337
+ await registry.markClosing();
7338
+ await registry.unregister();
7339
+ } catch {
7340
+ }
7341
+ if (recoveryLock && ownsRecoveryLock) {
7342
+ await recoveryLock.clear().catch(() => void 0);
7343
+ ownsRecoveryLock = false;
7344
+ }
7345
+ };
7346
+ return { statusTracker, activate, stop };
7347
+ }
7348
+ function errorMessage2(err) {
7349
+ return err instanceof Error ? err.message : String(err);
7350
+ }
7351
+
6733
7352
  // src/server/pre-context-services.ts
6734
7353
  var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
6735
7354
  async function createPreContextServices(input) {
6736
- const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
7355
+ const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
6737
7356
  const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
6738
7357
  cacheFile: wpaths.modelsCache,
6739
7358
  ttlSeconds: 0,
@@ -6745,20 +7364,22 @@ async function createPreContextServices(input) {
6745
7364
  await modelsRegistry.refresh();
6746
7365
  logger.info("models.dev catalog refreshed");
6747
7366
  } catch (err) {
6748
- logger.warn(`models.dev refresh failed (${toErrorMessage(err)}); using cached catalog`);
7367
+ logger.warn(`models.dev refresh failed (${toErrorMessage5(err)}); using cached catalog`);
6749
7368
  }
6750
7369
  }
6751
7370
  try {
6752
7371
  await discoverAndMergeWebuiProviders({
6753
7372
  config,
6754
7373
  registry: modelsRegistry,
6755
- cacheDir: path5.dirname(wpaths.modelsCache),
7374
+ cacheDir: path15.dirname(wpaths.modelsCache),
6756
7375
  logger
6757
7376
  });
6758
7377
  } catch (err) {
6759
- logger.debug(`provider auto-discovery skipped: ${toErrorMessage(err)}`);
7378
+ logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
6760
7379
  }
6761
- const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
7380
+ const events = opts.services?.events ?? new EventBus();
7381
+ events.setLogger(logger);
7382
+ const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry, events });
6762
7383
  const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
6763
7384
  const providerRegistry = new ProviderRegistry();
6764
7385
  try {
@@ -6766,22 +7387,23 @@ async function createPreContextServices(input) {
6766
7387
  for (const f of factories) providerRegistry.register(f);
6767
7388
  console.log("[WebUI] Provider registry loaded:", providerRegistry.list().length, "providers");
6768
7389
  } catch (err) {
6769
- console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message: toErrorMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
7390
+ console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message: toErrorMessage5(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6770
7391
  }
6771
7392
  const toolRegistry = opts.services?.toolRegistry ?? (() => {
6772
7393
  const r = new ToolRegistry();
6773
7394
  r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
6774
7395
  return r;
6775
7396
  })();
6776
- const memoryStore = new DefaultMemoryStore({ paths: wpaths });
7397
+ const memoryStore = container.resolve(TOKENS.MemoryStore);
6777
7398
  if (config.features.memory) {
6778
7399
  toolRegistry.register(rememberTool(memoryStore));
6779
7400
  toolRegistry.register(forgetTool(memoryStore));
6780
7401
  toolRegistry.register(searchMemoryTool(memoryStore));
6781
7402
  toolRegistry.register(relatedMemoryTool(memoryStore));
7403
+ if (isSuperMemoryService(memoryStore)) {
7404
+ for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
7405
+ }
6782
7406
  }
6783
- const events = opts.services?.events ?? new EventBus();
6784
- events.setLogger(logger);
6785
7407
  toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
6786
7408
  toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
6787
7409
  toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
@@ -6790,11 +7412,25 @@ async function createPreContextServices(input) {
6790
7412
  applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
6791
7413
  configureExecPolicy(config.tools?.exec ?? {});
6792
7414
  configureDangerBypass(config.tools?.exec?.danger ?? {});
7415
+ configureChildEnvGitIdentity(config.git?.identity ?? null);
6793
7416
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
6794
- const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
7417
+ const mcpTokenStore = new MCPVaultTokenStore(
7418
+ path15.join(wpaths.projectDir, "mcp-auth.json"),
7419
+ vault
7420
+ );
7421
+ const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
7422
+ const mcpRegistry = new MCPRegistry({
7423
+ toolRegistry,
7424
+ events,
7425
+ log: logger,
7426
+ cacheDir: wpaths.cacheDir,
7427
+ authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
7428
+ store: mcpTokenStore
7429
+ }),
7430
+ authorizationManager: mcpAuthorizationManager
7431
+ });
6795
7432
  if (config.features.mcp && config.mcpServers) {
6796
7433
  for (const [name2, cfg] of Object.entries(config.mcpServers)) {
6797
- if (cfg.enabled === false) continue;
6798
7434
  void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
6799
7435
  logger.warn(`MCP server "${name2}" failed to start at boot`, err);
6800
7436
  });
@@ -6809,7 +7445,7 @@ async function createPreContextServices(input) {
6809
7445
  // surface via the SessionRegistry.
6810
7446
  isSessionInUse: async (sessionId) => {
6811
7447
  try {
6812
- const registry = getSessionRegistry(wpaths.globalRoot);
7448
+ const registry = getSessionRegistry2(wpaths.globalRoot);
6813
7449
  const live = await registry.listByProject(wpaths.projectSlug);
6814
7450
  const hit = live.find((e) => e.sessionId === sessionId);
6815
7451
  if (hit) {
@@ -6834,120 +7470,22 @@ async function createPreContextServices(input) {
6834
7470
  await input.touchProject(projectRoot, workingDir);
6835
7471
  } catch {
6836
7472
  }
6837
- let statusTracker;
6838
- try {
6839
- const registry = getSessionRegistry(wpaths.globalRoot);
6840
- await registry.register({
6841
- sessionId: session.id,
6842
- projectSlug: wpaths.projectSlug,
7473
+ const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
7474
+ config,
7475
+ events,
7476
+ logger,
7477
+ paths: {
7478
+ globalRoot: wpaths.globalRoot,
6843
7479
  projectRoot,
6844
- projectName: path5.basename(projectRoot),
6845
- workingDir,
6846
- clientType: "webui",
6847
- pid: process.pid,
6848
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
6849
- });
6850
- const fleetNotifier = new FleetNotifier({ baseDir: wpaths.globalRoot, projectRoot, selfPid: process.pid });
6851
- statusTracker = new AgentStatusTracker({
6852
- events,
6853
- registry,
6854
- sessionId: () => session.id,
6855
- onUpdate: () => fleetNotifier.notify()
6856
- });
6857
- statusTracker.start();
6858
- let stopHqSessionBridge;
6859
- let hqTelemetryPublisher;
6860
- const stopHqAuxBridges = [];
6861
- try {
6862
- const {
6863
- createHqPublisherFromEnv,
6864
- startSessionTelemetryBridge,
6865
- startFleetTelemetryBridge,
6866
- startBrainTelemetryBridge,
6867
- startWorktreeTelemetryBridge,
6868
- startToolTelemetryBridge,
6869
- startCostTelemetryBridge
6870
- } = await import('@wrongstack/core');
6871
- const hqTelemetry = createHqPublisherFromEnv({
6872
- clientKind: "webui",
6873
- projectRoot,
6874
- projectName: path5.basename(projectRoot),
6875
- appConfig: config,
6876
- socketFactory: (url) => new WebSocket(url)
6877
- });
6878
- if (hqTelemetry) {
6879
- hqTelemetry.connect();
6880
- hqTelemetryPublisher = hqTelemetry;
6881
- stopHqSessionBridge = startSessionTelemetryBridge({
6882
- publisher: hqTelemetry,
6883
- events,
6884
- sessionId: session.id,
6885
- projectRoot,
6886
- projectName: path5.basename(projectRoot),
6887
- globalRoot: wpaths.globalRoot,
6888
- initialAgents: statusTracker?.getAgents(),
6889
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
6890
- });
6891
- try {
6892
- stopHqAuxBridges.push(
6893
- startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
6894
- );
6895
- } catch {
6896
- }
6897
- try {
6898
- stopHqAuxBridges.push(
6899
- startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6900
- );
6901
- } catch {
6902
- }
6903
- try {
6904
- stopHqAuxBridges.push(
6905
- startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6906
- );
6907
- } catch {
6908
- }
6909
- try {
6910
- stopHqAuxBridges.push(
6911
- startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
6912
- );
6913
- } catch {
6914
- }
6915
- try {
6916
- stopHqAuxBridges.push(
6917
- startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6918
- );
6919
- } catch {
6920
- }
6921
- }
6922
- } catch {
6923
- }
6924
- const stopTracking = async () => {
6925
- try {
6926
- fleetNotifier.dispose();
6927
- await registry.markClosing();
6928
- statusTracker?.stop();
6929
- stopHqSessionBridge?.();
6930
- for (const stop of stopHqAuxBridges) {
6931
- try {
6932
- stop();
6933
- } catch {
6934
- }
6935
- }
6936
- hqTelemetryPublisher?.close();
6937
- } catch {
6938
- }
6939
- };
6940
- process.once("beforeExit", () => {
6941
- void stopTracking();
6942
- });
6943
- process.once("SIGINT", () => {
6944
- void stopTracking();
6945
- });
6946
- process.once("SIGTERM", () => {
6947
- void stopTracking();
6948
- });
6949
- } catch {
6950
- }
7480
+ projectSlug: wpaths.projectSlug,
7481
+ projectSessions: wpaths.projectSessions
7482
+ },
7483
+ workingDir,
7484
+ initialSessionId: session.id,
7485
+ sessionStore,
7486
+ manageRecoveryLock: !opts.services?.session
7487
+ });
7488
+ const statusTracker = sessionIdentity.statusTracker;
6951
7489
  let context;
6952
7490
  const tokenCounter = new DefaultTokenCounter({
6953
7491
  registry: modelsRegistry,
@@ -6972,7 +7510,7 @@ async function createPreContextServices(input) {
6972
7510
  const modelCapabilitiesRef = { current: modelCapabilities };
6973
7511
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
6974
7512
  const skillInstaller = config.features.skills ? new SkillInstaller({
6975
- manifestPath: path5.join(wpaths.globalRoot, "installed-skills.json"),
7513
+ manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
6976
7514
  projectSkillsDir: wpaths.inProjectSkills,
6977
7515
  globalSkillsDir: wpaths.globalSkills,
6978
7516
  projectHash: wpaths.projectHash,
@@ -6981,8 +7519,8 @@ async function createPreContextServices(input) {
6981
7519
  const promptsEnabled = config.features.prompts !== false;
6982
7520
  const bundledPromptsDir = promptsEnabled ? (() => {
6983
7521
  try {
6984
- const req = createRequire(import.meta.url);
6985
- return path5.join(path5.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7522
+ const req = createRequire2(import.meta.url);
7523
+ return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
6986
7524
  } catch {
6987
7525
  return void 0;
6988
7526
  }
@@ -7034,6 +7572,14 @@ async function createPreContextServices(input) {
7034
7572
  const initialContextPolicy = resolveContextWindowPolicy(config.context);
7035
7573
  context.meta["contextWindowMode"] = initialContextPolicy.id;
7036
7574
  context.meta["contextWindowPolicy"] = initialContextPolicy;
7575
+ context.state.setMeta(
7576
+ "plan.path",
7577
+ sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
7578
+ );
7579
+ context.state.setMeta(
7580
+ "task.path",
7581
+ sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
7582
+ );
7037
7583
  seedContextMeta(config, context);
7038
7584
  return {
7039
7585
  modelsRegistry,
@@ -7050,6 +7596,7 @@ async function createPreContextServices(input) {
7050
7596
  session,
7051
7597
  sessionStartedAt,
7052
7598
  statusTracker,
7599
+ sessionIdentity,
7053
7600
  tokenCounter,
7054
7601
  modeStore,
7055
7602
  modeId,
@@ -7063,8 +7610,33 @@ async function createPreContextServices(input) {
7063
7610
  needsSetup
7064
7611
  };
7065
7612
  }
7613
+ function isSuperMemoryService(memoryStore) {
7614
+ const value = memoryStore;
7615
+ return typeof value["retrieveForPath"] === "function" && typeof value["searchSuper"] === "function" && typeof value["graphFor"] === "function" && typeof value["verify"] === "function" && typeof value["hygiene"] === "function" && typeof value["listCandidates"] === "function";
7616
+ }
7617
+
7618
+ // src/server/start-webui.ts
7619
+ import * as path21 from "node:path";
7620
+ import {
7621
+ createDefaultPipelines,
7622
+ createSessionEventBridge,
7623
+ DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
7624
+ expectDefined as expectDefined3,
7625
+ resolveSessionLoggingConfig,
7626
+ watchProviderConfig
7627
+ } from "@wrongstack/core";
7628
+ import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
7629
+ import { makeProviderFromConfig as makeProviderFromConfig3 } from "@wrongstack/providers";
7630
+ import {
7631
+ ensureSessionShell
7632
+ } from "@wrongstack/tools";
7633
+
7634
+ // src/server/boot.ts
7635
+ import {
7636
+ bootConfig as coreBootConfig
7637
+ } from "@wrongstack/core";
7066
7638
  async function bootConfig() {
7067
- const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await bootConfig$1({
7639
+ const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await coreBootConfig({
7068
7640
  appLabel: "WebUI"
7069
7641
  });
7070
7642
  return { config, vault, globalConfigPath, projectRoot, wpaths, logger };
@@ -7072,14 +7644,46 @@ async function bootConfig() {
7072
7644
  function patchConfig(config, updates) {
7073
7645
  return Object.freeze({ ...config, ...updates });
7074
7646
  }
7647
+
7648
+ // src/server/backend-services.ts
7649
+ import { join as join12 } from "node:path";
7650
+ import {
7651
+ Agent,
7652
+ AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
7653
+ BrainMonitor,
7654
+ CollaborationBus as CollaborationBusCtor,
7655
+ DEFAULT_TOOLS_CONFIG,
7656
+ createStrategyCompactor,
7657
+ collabInjectMiddleware,
7658
+ collabPauseMiddleware,
7659
+ estimateRequestTokensCalibrated,
7660
+ installDesignStudioMiddleware,
7661
+ ObservableBrainArbiter as ObservableBrainArbiterCtor,
7662
+ resolveContextWindowPolicy as resolveContextWindowPolicy2,
7663
+ BrainDecisionLedger,
7664
+ createBrainRuntime,
7665
+ resolveBrainConfigDefaults,
7666
+ EscalationRoutingBrainArbiter,
7667
+ GlobalMailbox as GlobalMailbox2,
7668
+ mailboxSessionTag,
7669
+ SessionMemoryConsolidator,
7670
+ TOKENS as TOKENS2,
7671
+ ToolExecutor
7672
+ } from "@wrongstack/core";
7673
+
7674
+ // src/server/collaboration-ws-handler.ts
7675
+ import { randomUUID } from "node:crypto";
7676
+ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7677
+ var REPLAY_LIMIT = 50;
7075
7678
  var PAUSE_TIMEOUT_MS = 6e4;
7076
7679
  var CollaborationWebSocketHandler = class {
7077
- constructor(events, logger, reader, annotations, bus) {
7680
+ constructor(events, logger, reader, annotations, bus, options = {}) {
7078
7681
  this.events = events;
7079
7682
  this.logger = logger;
7080
7683
  this.reader = reader;
7081
7684
  this.annotations = annotations;
7082
7685
  this.bus = bus;
7686
+ this.options = options;
7083
7687
  this.subscribe();
7084
7688
  this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
7085
7689
  }
@@ -7088,6 +7692,7 @@ var CollaborationWebSocketHandler = class {
7088
7692
  reader;
7089
7693
  annotations;
7090
7694
  bus;
7695
+ options;
7091
7696
  clients = /* @__PURE__ */ new Set();
7092
7697
  /** sessionId → participants currently watching it. */
7093
7698
  bySession = /* @__PURE__ */ new Map();
@@ -7115,11 +7720,16 @@ var CollaborationWebSocketHandler = class {
7115
7720
  handleMessage(ws, msg) {
7116
7721
  if (msg.type === "collab.join") {
7117
7722
  const payload = msg.payload;
7118
- if (!payload?.sessionId) {
7723
+ if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
7119
7724
  this.send(ws, this.errorMessage("collab.join requires sessionId"));
7120
7725
  return true;
7121
7726
  }
7122
- this.join(ws, payload.sessionId, payload.role ?? "observer");
7727
+ const role = payload.role ?? "observer";
7728
+ if (role !== "observer" && role !== "annotator" && role !== "controller") {
7729
+ this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
7730
+ return true;
7731
+ }
7732
+ this.join(ws, payload.sessionId, role);
7123
7733
  return true;
7124
7734
  }
7125
7735
  if (msg.type === "collab.leave") {
@@ -7154,6 +7764,20 @@ var CollaborationWebSocketHandler = class {
7154
7764
  }
7155
7765
  // ── Join / leave flow ──────────────────────────────────────────────────
7156
7766
  join(ws, sessionId, role) {
7767
+ const activeSessionId = this.options.getActiveSessionId?.();
7768
+ if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
7769
+ this.send(
7770
+ ws,
7771
+ this.errorMessage(
7772
+ `collab.join sessionId mismatch (active: ${activeSessionId})`
7773
+ )
7774
+ );
7775
+ return;
7776
+ }
7777
+ if (this.findParticipant(ws)) {
7778
+ this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
7779
+ return;
7780
+ }
7157
7781
  if (role === "controller" && !this.bus) {
7158
7782
  this.send(
7159
7783
  ws,
@@ -7172,6 +7796,13 @@ var CollaborationWebSocketHandler = class {
7172
7796
  );
7173
7797
  return;
7174
7798
  }
7799
+ if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
7800
+ this.send(
7801
+ ws,
7802
+ this.errorMessage(`role '${role}' requires explicit server authorization`)
7803
+ );
7804
+ return;
7805
+ }
7175
7806
  const participant = {
7176
7807
  participantId: randomUUID(),
7177
7808
  ws,
@@ -7199,7 +7830,7 @@ var CollaborationWebSocketHandler = class {
7199
7830
  if (this.reader) {
7200
7831
  this.replayHistory(ws, sessionId).catch((err) => {
7201
7832
  this.logger.debug?.(
7202
- `collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`
7833
+ `collab: replay failed for ${sessionId}: ${toErrorMessage6(err)}`
7203
7834
  );
7204
7835
  });
7205
7836
  }
@@ -7317,7 +7948,7 @@ var CollaborationWebSocketHandler = class {
7317
7948
  this.send(
7318
7949
  ws,
7319
7950
  this.errorMessage(
7320
- `annotation rejected: ${toErrorMessage(err)}`
7951
+ `annotation rejected: ${toErrorMessage6(err)}`
7321
7952
  )
7322
7953
  );
7323
7954
  }
@@ -7384,7 +8015,7 @@ var CollaborationWebSocketHandler = class {
7384
8015
  this.send(
7385
8016
  ws,
7386
8017
  this.errorMessage(
7387
- `resolve failed: ${toErrorMessage(err)}`
8018
+ `resolve failed: ${toErrorMessage6(err)}`
7388
8019
  )
7389
8020
  );
7390
8021
  }
@@ -7425,17 +8056,13 @@ var CollaborationWebSocketHandler = class {
7425
8056
  type: "collab.event",
7426
8057
  payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
7427
8058
  };
7428
- const data = JSON.stringify(msg);
7429
- for (const bucket of this.bySession.values()) {
7430
- for (const p of bucket) {
7431
- try {
7432
- if (p.ws.readyState === 1) p.ws.send(data);
7433
- } catch (err) {
7434
- this.logger.debug?.(
7435
- `collab broadcast failed: ${toErrorMessage(err)}`
7436
- );
7437
- }
7438
- }
8059
+ const activeSessionId = this.options.getActiveSessionId?.();
8060
+ if (activeSessionId !== void 0) {
8061
+ this.broadcast(activeSessionId, msg);
8062
+ return;
8063
+ }
8064
+ for (const sessionId of this.bySession.keys()) {
8065
+ this.broadcast(sessionId, msg);
7439
8066
  }
7440
8067
  }
7441
8068
  /**
@@ -7459,11 +8086,11 @@ var CollaborationWebSocketHandler = class {
7459
8086
  }
7460
8087
  } catch (err) {
7461
8088
  this.logger.debug?.(
7462
- `collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`
8089
+ `collab: session reader rejected ${sessionId}: ${toErrorMessage6(err)}`
7463
8090
  );
7464
8091
  return;
7465
8092
  }
7466
- const tail2 = all.slice(-50);
8093
+ const tail2 = all.slice(-REPLAY_LIMIT);
7467
8094
  if (tail2.length === 0) return;
7468
8095
  for (const raw of tail2) {
7469
8096
  const ev = raw;
@@ -7540,7 +8167,7 @@ var CollaborationWebSocketHandler = class {
7540
8167
  if (p.ws.readyState === 1) p.ws.send(data);
7541
8168
  } catch (err) {
7542
8169
  this.logger.debug?.(
7543
- `collab broadcast failed: ${toErrorMessage(err)}`
8170
+ `collab broadcast failed: ${toErrorMessage6(err)}`
7544
8171
  );
7545
8172
  }
7546
8173
  }
@@ -7759,7 +8386,8 @@ var CollaborationWebSocketHandler = class {
7759
8386
  * Bus callback: a queued injection was spliced into a real tool call. Re-emit
7760
8387
  * `collab.injection.granted` with phase `'consumed'` and the now-known tool
7761
8388
  * name. The injection carries no sessionId, so resolve it from the author's
7762
- * current session; if they've already left, fall back to every live session.
8389
+ * current participant. If the author has left, fail closed rather than leak
8390
+ * the payload to unrelated sessions.
7763
8391
  */
7764
8392
  broadcastInjectionConsumed(info) {
7765
8393
  let sessionId = null;
@@ -7788,10 +8416,23 @@ var CollaborationWebSocketHandler = class {
7788
8416
  if (sessionId) {
7789
8417
  this.broadcast(sessionId, message(sessionId));
7790
8418
  } else {
7791
- for (const sid of this.bySession.keys()) this.broadcast(sid, message(sid));
8419
+ this.logger.debug?.(
8420
+ `collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
8421
+ );
7792
8422
  }
7793
8423
  }
7794
8424
  };
8425
+
8426
+ // src/server/codebase-indexing.ts
8427
+ import * as fs12 from "node:fs";
8428
+ import * as path16 from "node:path";
8429
+ import {
8430
+ cancelPendingReindexes,
8431
+ enqueueReindex,
8432
+ isIndexableFile,
8433
+ runStartupIndex,
8434
+ shutdownCodebaseIndexHost
8435
+ } from "@wrongstack/tools";
7795
8436
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
7796
8437
  "node_modules",
7797
8438
  ".git",
@@ -7837,7 +8478,7 @@ function setupWebUICodebaseIndexing(deps2) {
7837
8478
  if (!filename) return;
7838
8479
  const rel = filename.toString();
7839
8480
  if (isIgnored(rel)) return;
7840
- const abs = path5.resolve(deps2.projectRoot, rel);
8481
+ const abs = path16.resolve(deps2.projectRoot, rel);
7841
8482
  enqueueFile(abs);
7842
8483
  });
7843
8484
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -7850,7 +8491,7 @@ function setupWebUICodebaseIndexing(deps2) {
7850
8491
  }
7851
8492
  function enqueueFile(filePath) {
7852
8493
  if (!idx.onEdit && !idx.watchExternal) return;
7853
- const abs = path5.isAbsolute(filePath) ? path5.normalize(filePath) : path5.resolve(deps2.projectRoot, filePath);
8494
+ const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
7854
8495
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
7855
8496
  enqueueReindex({
7856
8497
  projectRoot: deps2.projectRoot,
@@ -7887,15 +8528,23 @@ function isIgnored(rel) {
7887
8528
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
7888
8529
  }
7889
8530
  function isInside2(root, target) {
7890
- const normalizedRoot = path5.resolve(root);
7891
- const normalizedTarget = path5.resolve(target);
7892
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path5.sep);
8531
+ const normalizedRoot = path16.resolve(root);
8532
+ const normalizedTarget = path16.resolve(target);
8533
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
7893
8534
  }
8535
+
8536
+ // src/server/discover-mailbox-bridge.ts
8537
+ import { spawn as spawn3 } from "node:child_process";
8538
+ import { createRequire as createRequire3 } from "node:module";
8539
+ import { existsSync } from "node:fs";
8540
+ import { dirname as dirname7, join as join11 } from "node:path";
8541
+ import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8542
+ import { readLiveLock } from "@wrongstack/core/coordination";
7894
8543
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
7895
8544
  async function discoverMailboxBridgeForWebui(params) {
7896
8545
  const mode = params.config?.features?.mailboxBridge ?? "auto";
7897
8546
  if (mode === "off") return;
7898
- const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot());
8547
+ const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot2());
7899
8548
  let result = await readLiveLock(projectDir);
7900
8549
  let spawnedPid = null;
7901
8550
  if (result.kind !== "live") {
@@ -7961,7 +8610,7 @@ async function waitForLiveMailboxBridge(projectDir, timeoutMs) {
7961
8610
  function spawnMailboxBridge(projectRoot, logger) {
7962
8611
  const invocation = mailboxServeInvocation(projectRoot);
7963
8612
  try {
7964
- const child = spawn(invocation.command, invocation.args, {
8613
+ const child = spawn3(invocation.command, invocation.args, {
7965
8614
  cwd: projectRoot,
7966
8615
  detached: process.platform !== "win32" && !invocation.useShell,
7967
8616
  env: process.env,
@@ -7992,7 +8641,7 @@ function mailboxServeInvocation(projectRoot) {
7992
8641
  return { command: process.execPath, args: [explicitCliEntry, "mailbox", "serve"] };
7993
8642
  }
7994
8643
  try {
7995
- const require2 = createRequire(import.meta.url);
8644
+ const require2 = createRequire3(import.meta.url);
7996
8645
  const cliEntry = require2.resolve("@wrongstack/cli");
7997
8646
  return { command: process.execPath, args: [cliEntry, "mailbox", "serve"] };
7998
8647
  } catch {
@@ -8016,9 +8665,9 @@ function mailboxServeInvocation(projectRoot) {
8016
8665
  function findWorkspaceCliEntry(projectRoot) {
8017
8666
  let dir = projectRoot;
8018
8667
  for (let i = 0; i < 6; i++) {
8019
- const candidate = join(dir, "packages", "cli", "dist", "index.js");
8668
+ const candidate = join11(dir, "packages", "cli", "dist", "index.js");
8020
8669
  if (existsSync(candidate)) return candidate;
8021
- const parent = dirname(dir);
8670
+ const parent = dirname7(dir);
8022
8671
  if (parent === dir) return null;
8023
8672
  dir = parent;
8024
8673
  }
@@ -8027,10 +8676,15 @@ function findWorkspaceCliEntry(projectRoot) {
8027
8676
  function sleep(ms) {
8028
8677
  return new Promise((resolve10) => setTimeout(resolve10, ms));
8029
8678
  }
8679
+
8680
+ // src/server/terminal-ws-handler.ts
8681
+ import { spawn as spawnChild } from "node:child_process";
8682
+ import { createRequire as createRequire4 } from "node:module";
8683
+ import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
8030
8684
  var MAX_SESSIONS_PER_CLIENT = 8;
8031
8685
  var DEFAULT_COLS = 80;
8032
8686
  var DEFAULT_ROWS = 24;
8033
- var requireFromHere = createRequire(import.meta.url);
8687
+ var requireFromHere = createRequire4(import.meta.url);
8034
8688
  var cachedNodePty;
8035
8689
  var TerminalWebSocketHandler = class {
8036
8690
  constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
@@ -8108,8 +8762,8 @@ var TerminalWebSocketHandler = class {
8108
8762
  ...windowsPtyOptions()
8109
8763
  });
8110
8764
  } catch (err) {
8111
- const msg = `Integrated terminal failed to start: ${toErrorMessage(err)}`;
8112
- this.logger.warn?.(`terminal spawn failed: ${toErrorMessage(err)}`);
8765
+ const msg = `Integrated terminal failed to start: ${toErrorMessage7(err)}`;
8766
+ this.logger.warn?.(`terminal spawn failed: ${toErrorMessage7(err)}`);
8113
8767
  this.send(ws, { type: "terminal.output", payload: { id: payload.id, data: `${msg}\r
8114
8768
  ` } });
8115
8769
  this.send(ws, { type: "terminal.exit", payload: { id: payload.id, exitCode: -1 } });
@@ -8162,7 +8816,7 @@ var TerminalWebSocketHandler = class {
8162
8816
  try {
8163
8817
  pty.kill();
8164
8818
  } catch (err) {
8165
- this.logger.warn?.(`${reason} failed: ${toErrorMessage(err)}`);
8819
+ this.logger.warn?.(`${reason} failed: ${toErrorMessage7(err)}`);
8166
8820
  }
8167
8821
  }
8168
8822
  killWindowsProcessTree(pty, reason) {
@@ -8174,7 +8828,7 @@ var TerminalWebSocketHandler = class {
8174
8828
  } catch {
8175
8829
  }
8176
8830
  } catch (err) {
8177
- this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage(err)}`);
8831
+ this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage7(err)}`);
8178
8832
  }
8179
8833
  }
8180
8834
  send(ws, msg) {
@@ -8205,7 +8859,7 @@ function windowsPtyOptions() {
8205
8859
  return { useConptyDll: true };
8206
8860
  }
8207
8861
  function defaultKillProcessTree(pid) {
8208
- const child = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
8862
+ const child = spawnChild("taskkill", ["/PID", String(pid), "/T", "/F"], {
8209
8863
  stdio: "ignore",
8210
8864
  windowsHide: true
8211
8865
  });
@@ -8218,6 +8872,14 @@ function clampDim(value, fallback) {
8218
8872
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
8219
8873
  return Math.max(1, Math.min(1e3, Math.floor(value)));
8220
8874
  }
8875
+
8876
+ // src/server/backend-services.ts
8877
+ import { makeLightSubagentFactory } from "@wrongstack/runtime";
8878
+ import {
8879
+ createSuperMemoryToolCallMiddleware,
8880
+ createSuperMemoryTurnMiddleware
8881
+ } from "@wrongstack/super-memory";
8882
+ import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
8221
8883
  async function createAgentServices(input) {
8222
8884
  const {
8223
8885
  config,
@@ -8232,15 +8894,37 @@ async function createAgentServices(input) {
8232
8894
  providerRegistry,
8233
8895
  modelsRegistry,
8234
8896
  events,
8897
+ memoryStore,
8235
8898
  modelCapabilitiesRef
8236
8899
  } = input;
8237
- const collabBus = new CollaborationBus();
8900
+ const collabBus = new CollaborationBusCtor();
8238
8901
  const pipelines = input.pipelines;
8239
8902
  const collabInject = collabInjectMiddleware(collabBus, { logger });
8240
8903
  pipelines.toolCall.prepend(collabInject);
8241
8904
  const collabPause = collabPauseMiddleware(collabBus, { logger });
8242
8905
  pipelines.toolCall.prepend(collabPause);
8243
8906
  installDesignStudioMiddleware({ pipelines, ctx: context });
8907
+ if (config.features.memory !== false && config.superMemory?.enabled !== false && isSuperMemoryRetriever(memoryStore)) {
8908
+ if (config.superMemory?.inject?.toolResults !== false) {
8909
+ pipelines.toolCall.use(createSuperMemoryToolCallMiddleware({
8910
+ memory: memoryStore,
8911
+ maxHintsPerTool: config.superMemory?.inject?.maxHintsPerTool,
8912
+ maxCharsPerTool: config.superMemory?.inject?.maxCharsPerTool,
8913
+ minScore: config.superMemory?.inject?.minScore,
8914
+ repeatCooldownMs: config.superMemory?.inject?.repeatCooldownMs,
8915
+ verifyOnMutation: config.superMemory?.hygiene?.autoOnFileChange,
8916
+ triggers: config.superMemory?.inject?.triggers
8917
+ }));
8918
+ }
8919
+ if (config.superMemory?.inject?.turnContext !== false) {
8920
+ pipelines.request.use(createSuperMemoryTurnMiddleware({
8921
+ memory: memoryStore,
8922
+ maxMemories: config.superMemory?.inject?.maxTurnMemories,
8923
+ maxChars: config.superMemory?.inject?.maxCharsPerTurn,
8924
+ minScore: config.superMemory?.inject?.minScore
8925
+ }));
8926
+ }
8927
+ }
8244
8928
  const codebaseIndexing = setupWebUICodebaseIndexing({
8245
8929
  config,
8246
8930
  context,
@@ -8254,7 +8938,7 @@ async function createAgentServices(input) {
8254
8938
  summarizerModel: config.context?.summarizerModel,
8255
8939
  llmSelector: config.context?.llmSelector
8256
8940
  });
8257
- const initialContextPolicy = resolveContextWindowPolicy(config.context);
8941
+ const initialContextPolicy = resolveContextWindowPolicy2(config.context);
8258
8942
  let autoCompactor;
8259
8943
  if (config.context?.autoCompact !== false) {
8260
8944
  let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
@@ -8271,7 +8955,7 @@ async function createAgentServices(input) {
8271
8955
  }
8272
8956
  }
8273
8957
  if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
8274
- autoCompactor = new AutoCompactionMiddleware(
8958
+ autoCompactor = new AutoCompactionMiddlewareCtor(
8275
8959
  compactor,
8276
8960
  effectiveMaxContext,
8277
8961
  (ctx) => estimateRequestTokensCalibrated(
@@ -8299,7 +8983,7 @@ async function createAgentServices(input) {
8299
8983
  const updateAutoCompactionMaxContext = async (newProvider, providerId = newProvider.id, providerCfg) => {
8300
8984
  await modelsRegistry.refresh().catch((err) => {
8301
8985
  logger.warn(
8302
- `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage(err)}; using cached catalog`
8986
+ `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
8303
8987
  );
8304
8988
  });
8305
8989
  const currentConfig = input.config;
@@ -8336,9 +9020,9 @@ async function createAgentServices(input) {
8336
9020
  maxContext: newMaxContext
8337
9021
  });
8338
9022
  };
8339
- const secretScrubber = container.resolve(TOKENS.SecretScrubber);
8340
- const renderer = container.has(TOKENS.Renderer) ? container.resolve(TOKENS.Renderer) : void 0;
8341
- const permissionPolicy = container.resolve(TOKENS.PermissionPolicy);
9023
+ const secretScrubber = container.resolve(TOKENS2.SecretScrubber);
9024
+ const renderer = container.has(TOKENS2.Renderer) ? container.resolve(TOKENS2.Renderer) : void 0;
9025
+ const permissionPolicy = container.resolve(TOKENS2.PermissionPolicy);
8342
9026
  const toolExecutor = new ToolExecutor(toolRegistry, {
8343
9027
  permissionPolicy,
8344
9028
  secretScrubber,
@@ -8349,7 +9033,7 @@ async function createAgentServices(input) {
8349
9033
  perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
8350
9034
  tracer: void 0
8351
9035
  });
8352
- const webuiLogger = container.resolve(TOKENS.Logger);
9036
+ const webuiLogger = container.resolve(TOKENS2.Logger);
8353
9037
  void discoverMailboxBridgeForWebui({
8354
9038
  projectRoot,
8355
9039
  config,
@@ -8375,25 +9059,66 @@ async function createAgentServices(input) {
8375
9059
  confirmAwaiter: void 0,
8376
9060
  toolExecutor
8377
9061
  });
9062
+ if (config.features.memory && config.features.memoryConsolidation !== false) {
9063
+ agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
9064
+ }
8378
9065
  console.log("[WebUI] Agent initialized");
8379
- const brainSettings = { maxAutoRisk: "medium" };
8380
- const autonomousBrain = {
8381
- decide: (request) => createAutonomyBrain({
8382
- provider,
8383
- model: context.model,
8384
- maxAutoRisk: "all"
8385
- // the tiered ceiling gates risk — keep inner permissive
8386
- }).decide(request)
9066
+ const brainCfg = resolveBrainConfigDefaults(config.brain, {
9067
+ fallbackModels: config.fallbackModels
9068
+ });
9069
+ const brainLedgerPath = join12(wpaths.projectDir, "brain-ledger.jsonl");
9070
+ let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9071
+ let brainLedger;
9072
+ const startBrainLedger = () => {
9073
+ if (brainLedger) return;
9074
+ brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
9075
+ void brainLedger.start();
8387
9076
  };
8388
- const brain = new ObservableBrainArbiter(
8389
- createTieredBrainArbiter({
8390
- policy: new DefaultBrainArbiter(),
8391
- autonomous: autonomousBrain,
8392
- getMaxAutoRisk: () => brainSettings.maxAutoRisk
8393
- }),
9077
+ if (brainLedgerEnabled) startBrainLedger();
9078
+ const brainRuntime = createBrainRuntime({
9079
+ initialConfig: brainCfg,
9080
+ defaultProviderId: config.provider,
9081
+ sessionProvider: () => provider,
9082
+ sessionModel: () => context.model,
9083
+ resolveProvider: (providerId) => {
9084
+ const savedCfg = config.providers?.[providerId] ?? {};
9085
+ return providerRegistry.create({
9086
+ ...savedCfg,
9087
+ apiKey: savedCfg.apiKey ?? config.apiKey,
9088
+ baseUrl: savedCfg.baseUrl ?? config.baseUrl,
9089
+ type: providerId
9090
+ });
9091
+ },
9092
+ ledger: {
9093
+ getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
9094
+ isEnabled: () => brainLedgerEnabled,
9095
+ setEnabled: (on) => {
9096
+ brainLedgerEnabled = on;
9097
+ if (on) {
9098
+ startBrainLedger();
9099
+ } else {
9100
+ void brainLedger?.stop();
9101
+ brainLedger = void 0;
9102
+ }
9103
+ },
9104
+ failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
9105
+ getDecisionDigest: (request) => brainLedger?.digestFor(request)
9106
+ },
9107
+ persist: input.persistBrainConfig
9108
+ });
9109
+ const brainSettings = {
9110
+ get maxAutoRisk() {
9111
+ return brainRuntime.getMaxAutoRisk();
9112
+ },
9113
+ set maxAutoRisk(level) {
9114
+ void brainRuntime.apply({ maxAutoRisk: level }).persisted;
9115
+ }
9116
+ };
9117
+ const brain = new ObservableBrainArbiterCtor(
9118
+ new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
8394
9119
  events
8395
9120
  );
8396
- container.bind(TOKENS.BrainArbiter, () => brain);
9121
+ container.bind(TOKENS2.BrainArbiter, () => brain);
8397
9122
  const brainLog = [];
8398
9123
  const pushBrainLog = (entry) => {
8399
9124
  brainLog.push(entry);
@@ -8426,10 +9151,16 @@ async function createAgentServices(input) {
8426
9151
  outcome: e.decision.type === "deny" ? e.decision.reason : ""
8427
9152
  })
8428
9153
  );
8429
- const brainMailbox = new GlobalMailbox(wpaths.projectDir, events);
9154
+ const brainMailbox = new GlobalMailbox2(wpaths.projectDir, events);
8430
9155
  const brainMonitor = new BrainMonitor({
8431
9156
  events,
8432
9157
  brain,
9158
+ toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
9159
+ errorStormCount: brainCfg.monitor?.errorStormCount,
9160
+ stallMs: brainCfg.monitor?.stallMs,
9161
+ fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
9162
+ fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
9163
+ cooldownMs: brainCfg.monitor?.cooldownMs,
8433
9164
  sessionId: () => context.session?.id,
8434
9165
  intervene: async ({ subject, body }) => {
8435
9166
  const tag = mailboxSessionTag(input.sessionGetter().id);
@@ -8494,7 +9225,10 @@ async function createAgentServices(input) {
8494
9225
  logger,
8495
9226
  input.sessionReader,
8496
9227
  input.annotationsStore,
8497
- collabBus
9228
+ collabBus,
9229
+ {
9230
+ getActiveSessionId: () => context.session.id
9231
+ }
8498
9232
  );
8499
9233
  return {
8500
9234
  collabBus,
@@ -8506,8 +9240,13 @@ async function createAgentServices(input) {
8506
9240
  pipelines,
8507
9241
  brain,
8508
9242
  brainSettings,
9243
+ brainRuntime,
8509
9244
  brainLog,
8510
9245
  brainMonitor,
9246
+ // Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
9247
+ get brainLedger() {
9248
+ return brainLedger;
9249
+ },
8511
9250
  codebaseIndexing,
8512
9251
  autoPhaseHandler,
8513
9252
  specsHandler,
@@ -8519,14 +9258,14 @@ async function createAgentServices(input) {
8519
9258
  updateAutoCompactionMaxContext
8520
9259
  };
8521
9260
  }
9261
+ function isSuperMemoryRetriever(memoryStore) {
9262
+ const value = memoryStore;
9263
+ return typeof value["retrieveForPath"] === "function" && typeof value["searchSuper"] === "function";
9264
+ }
8522
9265
 
8523
9266
  // src/server/pending-confirms.ts
8524
- function isDestructivePendingConfirm(confirm) {
8525
- return confirm.riskTier === "destructive" || confirm.decisionSource === "yolo_destructive";
8526
- }
8527
9267
  function resolveYoloEligiblePendingConfirms(pendingConfirms) {
8528
9268
  for (const [id, confirm] of pendingConfirms) {
8529
- if (isDestructivePendingConfirm(confirm)) continue;
8530
9269
  pendingConfirms.delete(id);
8531
9270
  confirm.resolve("yes");
8532
9271
  }
@@ -8682,6 +9421,20 @@ function createConnectionHandler(opts) {
8682
9421
  };
8683
9422
  }
8684
9423
 
9424
+ // src/server/message-dispatcher.ts
9425
+ import path17 from "node:path";
9426
+ import {
9427
+ buildUserContentBlocks,
9428
+ IncomingImageError,
9429
+ parseIncomingImages
9430
+ } from "@wrongstack/core/utils";
9431
+ import {
9432
+ createToolVisionAdapters,
9433
+ ImageInputUnsupportedError,
9434
+ routeImagesForModel,
9435
+ VisionUrlBlockedError
9436
+ } from "@wrongstack/runtime/vision";
9437
+
8685
9438
  // src/server/autophase-routes.ts
8686
9439
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
8687
9440
  if (!msg.type.startsWith("autophase.")) return false;
@@ -8701,14 +9454,23 @@ async function handleBrainRoute(ws, msg, handlers) {
8701
9454
  case "brain.ask":
8702
9455
  await handlers.ask(ws, msg);
8703
9456
  return true;
9457
+ case "brain.config.get":
9458
+ await handlers.configGet(ws, msg);
9459
+ return true;
9460
+ case "brain.config.set":
9461
+ await handlers.configSet(ws, msg);
9462
+ return true;
8704
9463
  default:
8705
9464
  return false;
8706
9465
  }
8707
9466
  }
9467
+
9468
+ // src/server/goal-handlers.ts
9469
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
8708
9470
  async function handleGoalGet(projectRoot, broadcast2) {
8709
9471
  try {
8710
- const goalPath = resolveWstackPaths$1({ projectRoot }).projectGoal;
8711
- const { readFile: readFile10 } = await import('fs/promises');
9472
+ const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
9473
+ const { readFile: readFile10 } = await import("node:fs/promises");
8712
9474
  const raw = await readFile10(goalPath, "utf8");
8713
9475
  const goal = JSON.parse(raw);
8714
9476
  broadcast2({ type: "goal.updated", payload: goal });
@@ -8762,7 +9524,7 @@ async function handleTasksGet(ctx, ws) {
8762
9524
  const taskPath = ctx.context.meta["task.path"];
8763
9525
  if (typeof taskPath === "string" && taskPath) {
8764
9526
  try {
8765
- const { loadTasks } = await import('@wrongstack/core');
9527
+ const { loadTasks } = await import("@wrongstack/core");
8766
9528
  const file = await loadTasks(taskPath);
8767
9529
  ctx.send(ws, { type: "tasks.updated", payload: sessionPayload(ctx, { tasks: file?.tasks ?? [] }) });
8768
9530
  } catch {
@@ -8782,7 +9544,7 @@ async function handleTaskUpdate(ctx, ws, payload) {
8782
9544
  return;
8783
9545
  }
8784
9546
  try {
8785
- const { loadTasks, saveTasks } = await import('@wrongstack/core');
9547
+ const { loadTasks, saveTasks } = await import("@wrongstack/core");
8786
9548
  const file = await loadTasks(taskPath);
8787
9549
  if (!file) {
8788
9550
  sendResult2(ws, ctx, false, "No task file found.");
@@ -8811,7 +9573,7 @@ async function handlePlanGet(ctx, ws) {
8811
9573
  const sessionId = ctx.context.session?.id ?? "";
8812
9574
  if (typeof planPath === "string" && planPath) {
8813
9575
  try {
8814
- const { loadPlan } = await import('@wrongstack/core');
9576
+ const { loadPlan } = await import("@wrongstack/core");
8815
9577
  const plan = await loadPlan(planPath);
8816
9578
  ctx.send(ws, {
8817
9579
  type: "plan.updated",
@@ -8852,7 +9614,7 @@ async function handlePlanTemplateUse(ctx, ws, template) {
8852
9614
  return;
8853
9615
  }
8854
9616
  try {
8855
- const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import('@wrongstack/core');
9617
+ const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import("@wrongstack/core");
8856
9618
  const tpl = getPlanTemplate(template);
8857
9619
  if (!tpl) {
8858
9620
  sendResult2(ws, ctx, false, `Unknown template "${template}".`);
@@ -8877,7 +9639,7 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
8877
9639
  return;
8878
9640
  }
8879
9641
  try {
8880
- const { mutatePlan, setPlanItemStatus } = await import('@wrongstack/core');
9642
+ const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
8881
9643
  let changed = false;
8882
9644
  const plan = await mutatePlan(planPath, sessionId, async (p) => {
8883
9645
  const before = p.updatedAt;
@@ -8944,6 +9706,45 @@ async function handleWorklistMessage(ctx, ws, msg) {
8944
9706
  return;
8945
9707
  }
8946
9708
  }
9709
+
9710
+ // src/server/kanban-routes.ts
9711
+ import {
9712
+ deserializeTaskGraph,
9713
+ serializeTaskGraph
9714
+ } from "@wrongstack/core";
9715
+ import {
9716
+ addColumn,
9717
+ addGoalMetricToTask,
9718
+ addTask,
9719
+ assignTask,
9720
+ claimReadyTask,
9721
+ copyTaskToBoard,
9722
+ createBoard,
9723
+ duplicateBoard,
9724
+ exportBoardToTaskGraph,
9725
+ generateBoardFromDescription,
9726
+ getBoard,
9727
+ getKanbanOrchestrationSnapshot,
9728
+ getKanbanQueueHealth,
9729
+ getTask,
9730
+ getTaskChain,
9731
+ listReadyTasks,
9732
+ listBoards,
9733
+ mergeTasks,
9734
+ moveTask,
9735
+ parseLinesIntoTasks,
9736
+ removeBoard,
9737
+ removeColumn,
9738
+ removeTask,
9739
+ releaseTaskClaim,
9740
+ setTaskChain,
9741
+ splitTask,
9742
+ syncBoardFromTaskGraph,
9743
+ transferTaskToBoard,
9744
+ updateBoard,
9745
+ updateGoalMetricOnTask,
9746
+ updateTask
9747
+ } from "@wrongstack/kanban";
8947
9748
  function ok(ws, type, data) {
8948
9749
  send(ws, { type, payload: { success: true, data: data ?? null } });
8949
9750
  }
@@ -9389,6 +10190,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
9389
10190
  "Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
9390
10191
  );
9391
10192
  return true;
10193
+ case "kanban.capabilities":
10194
+ ok(ws, type, { dispatchSupported: false });
10195
+ return true;
9392
10196
  case "kanban.task.remove": {
9393
10197
  const boardId = payload?.boardId;
9394
10198
  const taskId = payload?.taskId;
@@ -9468,6 +10272,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
9468
10272
  case "mailbox.purge":
9469
10273
  await handlers.purge(ws, msg);
9470
10274
  return true;
10275
+ case "mailbox.compact":
10276
+ await handlers.compact(ws, msg);
10277
+ return true;
9471
10278
  default:
9472
10279
  return false;
9473
10280
  }
@@ -9506,6 +10313,18 @@ async function handleMcpRoute(ws, msg, handlers) {
9506
10313
  case "mcp.discover":
9507
10314
  await handlers.discover(ws, msg);
9508
10315
  return true;
10316
+ case "mcp.resources":
10317
+ await handlers.resources(ws, msg);
10318
+ return true;
10319
+ case "mcp.prompts":
10320
+ await handlers.prompts(ws, msg);
10321
+ return true;
10322
+ case "mcp.resource.read":
10323
+ await handlers.resourceRead(ws, msg);
10324
+ return true;
10325
+ case "mcp.prompt.get":
10326
+ await handlers.promptGet(ws, msg);
10327
+ return true;
9509
10328
  default:
9510
10329
  return false;
9511
10330
  }
@@ -9544,7 +10363,7 @@ async function handlePrefsRoute(ws, msg, handlers) {
9544
10363
  // src/server/process-handlers.ts
9545
10364
  async function handleProcessList(ws) {
9546
10365
  try {
9547
- const { getProcessRegistry } = await import('@wrongstack/tools');
10366
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9548
10367
  const procs = getProcessRegistry().list();
9549
10368
  send(ws, {
9550
10369
  type: "process.list",
@@ -9571,7 +10390,7 @@ async function handleProcessKill(ws, payload) {
9571
10390
  }
9572
10391
  const { pid } = parsed.value;
9573
10392
  try {
9574
- const { getProcessRegistry } = await import('@wrongstack/tools');
10393
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9575
10394
  const proc = getProcessRegistry().get(pid);
9576
10395
  if (proc?.protected) {
9577
10396
  sendResult(ws, false, `Cannot kill protected process (PID ${pid})`);
@@ -9585,7 +10404,7 @@ async function handleProcessKill(ws, payload) {
9585
10404
  }
9586
10405
  async function handleProcessKillAll(ws) {
9587
10406
  try {
9588
- const { getProcessRegistry } = await import('@wrongstack/tools');
10407
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9589
10408
  getProcessRegistry().killAll();
9590
10409
  sendResult(ws, true, "All processes killed");
9591
10410
  } catch (err) {
@@ -9895,7 +10714,7 @@ function createMessageDispatcher(opts) {
9895
10714
  skillLoader: deps2.skillLoader,
9896
10715
  skillInstaller: deps2.skillInstaller,
9897
10716
  projectRoot,
9898
- projectSkillsDir: path5__default.join(projectRoot, ".wrongstack", "skills"),
10717
+ projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
9899
10718
  globalSkillsDir: deps2.wpaths.globalSkills
9900
10719
  };
9901
10720
  }
@@ -9968,7 +10787,8 @@ function createMessageDispatcher(opts) {
9968
10787
  }
9969
10788
  case "user_message": {
9970
10789
  if (!ensureCurrentSession(ws, msg, "user_message")) return;
9971
- const content = msg.payload.content;
10790
+ const userPayload = msg.payload;
10791
+ const content = userPayload.content;
9972
10792
  if (runLock.get()) {
9973
10793
  send(ws, {
9974
10794
  type: "error",
@@ -9982,8 +10802,24 @@ function createMessageDispatcher(opts) {
9982
10802
  const thisRun = new AbortController();
9983
10803
  runLock.set(thisRun);
9984
10804
  try {
10805
+ let input = content;
10806
+ const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
10807
+ if (imageBlocks.length > 0) {
10808
+ const routed = await routeImagesForModel(
10809
+ buildUserContentBlocks(content, imageBlocks),
10810
+ {
10811
+ supportsVision: deps2.agent.ctx.provider.capabilities.vision,
10812
+ adapters: () => createToolVisionAdapters(deps2.agent.tools),
10813
+ ctx: deps2.agent.ctx,
10814
+ signal: thisRun.signal,
10815
+ providerId: deps2.agent.ctx.provider.id,
10816
+ model: deps2.agent.ctx.model
10817
+ }
10818
+ );
10819
+ input = routed.blocks;
10820
+ }
9985
10821
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
9986
- const result = await deps2.agent.run(content, {
10822
+ const result = await deps2.agent.run(input, {
9987
10823
  signal: thisRun.signal,
9988
10824
  maxIterations: maxIt
9989
10825
  });
@@ -10001,13 +10837,24 @@ function createMessageDispatcher(opts) {
10001
10837
  })
10002
10838
  });
10003
10839
  } catch (err) {
10004
- send(ws, {
10005
- type: "error",
10006
- payload: sessionPayload2({
10007
- phase: "agent.run",
10008
- message: errMessage(err)
10009
- })
10010
- });
10840
+ if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
10841
+ send(ws, {
10842
+ type: "error",
10843
+ payload: sessionPayload2({
10844
+ phase: "user_message",
10845
+ ...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
10846
+ message: err.message
10847
+ })
10848
+ });
10849
+ } else {
10850
+ send(ws, {
10851
+ type: "error",
10852
+ payload: sessionPayload2({
10853
+ phase: "agent.run",
10854
+ message: errMessage(err)
10855
+ })
10856
+ });
10857
+ }
10011
10858
  } finally {
10012
10859
  if (runLock.get() === thisRun) {
10013
10860
  runLock.set(null);
@@ -10055,7 +10902,7 @@ function createMessageDispatcher(opts) {
10055
10902
  break;
10056
10903
  }
10057
10904
  case "tool.disable": {
10058
- const name2 = (msg.payload ?? {}).name;
10905
+ const name2 = msg.payload?.name;
10059
10906
  if (!name2) {
10060
10907
  send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
10061
10908
  break;
@@ -10070,7 +10917,7 @@ function createMessageDispatcher(opts) {
10070
10917
  break;
10071
10918
  }
10072
10919
  case "tool.enable": {
10073
- const name2 = (msg.payload ?? {}).name;
10920
+ const name2 = msg.payload?.name;
10074
10921
  if (!name2) {
10075
10922
  send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
10076
10923
  break;
@@ -10114,6 +10961,14 @@ function createMessageDispatcher(opts) {
10114
10961
  throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
10115
10962
  case "mcp.discover":
10116
10963
  throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
10964
+ case "mcp.resources":
10965
+ throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
10966
+ case "mcp.prompts":
10967
+ throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
10968
+ case "mcp.resource.read":
10969
+ throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
10970
+ case "mcp.prompt.get":
10971
+ throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
10117
10972
  // Skills — full request→response cycle lives in skills-handlers.ts.
10118
10973
  case "skills.list":
10119
10974
  await handleSkillsList(ws, makeSkillsContext());
@@ -10350,6 +11205,7 @@ function createMessageDispatcher(opts) {
10350
11205
  break;
10351
11206
  }
10352
11207
  case "prefs.update": {
11208
+ void ws;
10353
11209
  throw new Error("handlePrefsRoute did not claim prefs.update \u2014 check chain order");
10354
11210
  }
10355
11211
  case "prefs.get": {
@@ -10363,6 +11219,11 @@ function createMessageDispatcher(opts) {
10363
11219
  }
10364
11220
  };
10365
11221
  }
11222
+
11223
+ // src/server/pref-helpers.ts
11224
+ import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
11225
+ import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11226
+ import * as fs13 from "node:fs/promises";
10366
11227
  var PREF_KEYS = [
10367
11228
  "autonomy",
10368
11229
  "autonomyDelayMs",
@@ -10408,7 +11269,19 @@ var PREF_KEYS = [
10408
11269
  "favoriteModels",
10409
11270
  "favoriteModelsOnly",
10410
11271
  "modelMatrix",
10411
- "fallbackAuto"
11272
+ "fallbackAuto",
11273
+ // Refiner + TUI visual prefs (parity with the CLI's embedded server —
11274
+ // these were browser-editable there but rejected as unknown keys here).
11275
+ "refinerProvider",
11276
+ "refinerModel",
11277
+ "thinkingWord",
11278
+ "statuslineMode",
11279
+ "animationStyle",
11280
+ // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11281
+ "breakerEnabled",
11282
+ "breakerAutoKillResetMs",
11283
+ "fsAccess",
11284
+ "debugStream"
10412
11285
  ];
10413
11286
  function prefSnapshot(contextMeta) {
10414
11287
  const snapshot = {};
@@ -10422,7 +11295,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
10422
11295
  const write = async () => {
10423
11296
  let raw;
10424
11297
  try {
10425
- raw = await fs4.readFile(globalConfigPath, "utf8");
11298
+ raw = await fs13.readFile(globalConfigPath, "utf8");
10426
11299
  } catch {
10427
11300
  raw = "{}";
10428
11301
  }
@@ -10433,10 +11306,10 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
10433
11306
  logger.warn(`${errorLabel}: refusing to overwrite corrupt config at ${globalConfigPath}`);
10434
11307
  return;
10435
11308
  }
10436
- const decrypted = decryptConfigSecrets(parsed, vault);
11309
+ const decrypted = decryptConfigSecrets2(parsed, vault);
10437
11310
  mutate(decrypted);
10438
- const encrypted = encryptConfigSecrets(decrypted, vault);
10439
- await atomicWrite$1(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
11311
+ const encrypted = encryptConfigSecrets2(decrypted, vault);
11312
+ await atomicWrite6(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
10440
11313
  };
10441
11314
  const next = holder.lock.then(write);
10442
11315
  holder.lock = next.then(
@@ -10479,6 +11352,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
10479
11352
  setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
10480
11353
  if (typeof payload["enhanceLanguage"] === "string")
10481
11354
  setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
11355
+ if (typeof payload["refinerProvider"] === "string")
11356
+ setAutonomy("refinerProvider", payload["refinerProvider"]);
11357
+ if (typeof payload["refinerModel"] === "string")
11358
+ setAutonomy("refinerModel", payload["refinerModel"]);
11359
+ if (typeof payload["thinkingWord"] === "string")
11360
+ setAutonomy("thinkingWord", payload["thinkingWord"]);
11361
+ if (typeof payload["statuslineMode"] === "string")
11362
+ setAutonomy("statuslineMode", payload["statuslineMode"]);
11363
+ if (typeof payload["animationStyle"] === "string")
11364
+ setAutonomy("animationStyle", payload["animationStyle"]);
10482
11365
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
10483
11366
  if (typeof payload["nextPrediction"] === "boolean")
10484
11367
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -10596,15 +11479,38 @@ async function persistPrefsToConfig(deps2, holder, payload) {
10596
11479
  }
10597
11480
  decrypted.modelRuntime = mr;
10598
11481
  }
11482
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
11483
+ const cb = decrypted.circuitBreaker ?? {};
11484
+ if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
11485
+ if (typeof payload["breakerAutoKillResetMs"] === "number")
11486
+ cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
11487
+ decrypted.circuitBreaker = cb;
11488
+ }
11489
+ if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
11490
+ const restrict = payload["fsAccess"] === "project";
11491
+ const toolsCfg = decrypted.tools ?? {};
11492
+ toolsCfg.restrictToProjectRoot = restrict;
11493
+ decrypted.tools = toolsCfg;
11494
+ const featsCfg = decrypted.features ?? {};
11495
+ featsCfg.allowOutsideProjectRoot = !restrict;
11496
+ decrypted.features = featsCfg;
11497
+ }
11498
+ if (typeof payload["debugStream"] === "boolean")
11499
+ decrypted.debugStream = payload["debugStream"];
10599
11500
  }, "prefs");
10600
11501
  }
11502
+
11503
+ // src/server/projects-manifest.ts
11504
+ import * as fs14 from "node:fs/promises";
11505
+ import * as path18 from "node:path";
11506
+ import { projectSlug } from "@wrongstack/core";
10601
11507
  function projectsJsonPath(globalConfigPath) {
10602
- const base = path5.dirname(globalConfigPath);
10603
- return path5.join(base, "projects.json");
11508
+ const base = path18.dirname(globalConfigPath);
11509
+ return path18.join(base, "projects.json");
10604
11510
  }
10605
11511
  async function loadManifest(globalConfigPath) {
10606
11512
  try {
10607
- const raw = await fs4.readFile(projectsJsonPath(globalConfigPath), "utf8");
11513
+ const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
10608
11514
  const parsed = JSON.parse(raw);
10609
11515
  return { projects: parsed.projects ?? [] };
10610
11516
  } catch {
@@ -10613,18 +11519,26 @@ async function loadManifest(globalConfigPath) {
10613
11519
  }
10614
11520
  async function saveManifest(manifest, globalConfigPath) {
10615
11521
  const file = projectsJsonPath(globalConfigPath);
10616
- await fs4.mkdir(path5.dirname(file), { recursive: true });
10617
- await fs4.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11522
+ await fs14.mkdir(path18.dirname(file), { recursive: true });
11523
+ await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
10618
11524
  }
10619
11525
  function generateProjectSlug(rootPath) {
10620
11526
  return projectSlug(rootPath);
10621
11527
  }
10622
11528
  async function ensureProjectDataDir(slug, globalConfigPath) {
10623
- const base = path5.dirname(globalConfigPath);
10624
- const dir = path5.join(base, "projects", slug);
10625
- await fs4.mkdir(dir, { recursive: true });
11529
+ const base = path18.dirname(globalConfigPath);
11530
+ const dir = path18.join(base, "projects", slug);
11531
+ await fs14.mkdir(dir, { recursive: true });
10626
11532
  return dir;
10627
11533
  }
11534
+
11535
+ // src/server/provider-handlers.ts
11536
+ import { DefaultSecretScrubber } from "@wrongstack/core";
11537
+ import {
11538
+ beginOAuthLogin
11539
+ } from "@wrongstack/providers/oauth";
11540
+ import { probeLocalLlm } from "@wrongstack/runtime/probe";
11541
+ import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
10628
11542
  function projectSavedProviders(providers) {
10629
11543
  return Object.entries(providers).map(([id, cfg]) => {
10630
11544
  const keys = normalizeKeys(cfg);
@@ -10655,7 +11569,7 @@ function createProviderHandlers(deps2) {
10655
11569
  }
10656
11570
  async function saveConfigProviders(providers) {
10657
11571
  const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers)).catch((err) => {
10658
- const msg = toErrorMessage(err);
11572
+ const msg = toErrorMessage9(err);
10659
11573
  console.error(JSON.stringify({
10660
11574
  level: "error",
10661
11575
  event: "webui.provider_save_failed",
@@ -10927,10 +11841,23 @@ function createProviderHandlers(deps2) {
10927
11841
  loadConfigProviders
10928
11842
  };
10929
11843
  }
11844
+
11845
+ // src/server/routes.ts
11846
+ import path20 from "node:path";
11847
+ import {
11848
+ enhanceUserPrompt,
11849
+ gatedEnhancerReasoning,
11850
+ recentTextTurns,
11851
+ resolveProviderModelList
11852
+ } from "@wrongstack/core";
11853
+ import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
11854
+
11855
+ // src/server/mailbox-handlers.ts
11856
+ import { GlobalMailbox as GlobalMailbox3, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core";
10930
11857
  async function handleMailboxMessages(ws, deps2, payload) {
10931
11858
  try {
10932
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
10933
- const mb = new GlobalMailbox(dir);
11859
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11860
+ const mb = new GlobalMailbox3(dir);
10934
11861
  const limit = payload?.limit ?? 30;
10935
11862
  const unreadForAgent = payload?.unreadOnly === true && payload.agentId !== void 0;
10936
11863
  const messages = await mb.query({
@@ -10970,8 +11897,8 @@ async function handleMailboxMessages(ws, deps2, payload) {
10970
11897
  }
10971
11898
  async function handleMailboxAgents(ws, deps2, payload) {
10972
11899
  try {
10973
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
10974
- const mb = new GlobalMailbox(dir);
11900
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11901
+ const mb = new GlobalMailbox3(dir);
10975
11902
  const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
10976
11903
  send(ws, {
10977
11904
  type: "mailbox.agents",
@@ -10999,8 +11926,8 @@ async function handleMailboxAgents(ws, deps2, payload) {
10999
11926
  }
11000
11927
  async function handleMailboxClear(ws, deps2) {
11001
11928
  try {
11002
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
11003
- const mb = new GlobalMailbox(dir);
11929
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11930
+ const mb = new GlobalMailbox3(dir);
11004
11931
  await mb.clearAll();
11005
11932
  send(ws, { type: "mailbox.cleared", payload: {} });
11006
11933
  } catch (err) {
@@ -11009,14 +11936,31 @@ async function handleMailboxClear(ws, deps2) {
11009
11936
  }
11010
11937
  async function handleMailboxPurge(ws, deps2, opts) {
11011
11938
  try {
11012
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
11013
- const mb = new GlobalMailbox(dir);
11939
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11940
+ const mb = new GlobalMailbox3(dir);
11014
11941
  const result = await mb.purgeStale(opts);
11015
11942
  send(ws, { type: "mailbox.purged", payload: result });
11016
11943
  } catch (err) {
11017
11944
  send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
11018
11945
  }
11019
11946
  }
11947
+ async function handleMailboxCompact(ws, deps2, opts) {
11948
+ try {
11949
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11950
+ const mb = new GlobalMailbox3(dir);
11951
+ const result = await mb.autoCompact(opts);
11952
+ send(ws, { type: "mailbox.compacted", payload: result });
11953
+ } catch (err) {
11954
+ send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
11955
+ }
11956
+ }
11957
+
11958
+ // src/server/mode-handlers.ts
11959
+ import {
11960
+ DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
11961
+ resolveWstackPaths as resolveWstackPaths2,
11962
+ ToolValidationError as ToolValidationError5
11963
+ } from "@wrongstack/core";
11020
11964
  function createModeHandlers(ctx) {
11021
11965
  return {
11022
11966
  listModes: async (ws) => {
@@ -11059,14 +12003,14 @@ function createModeHandlers(ctx) {
11059
12003
  } else {
11060
12004
  const found = await ctx.modeStore.getMode(id);
11061
12005
  if (!found) {
11062
- throw new ToolValidationError({ message: `Unknown mode "${id}"`, field: "modeId" });
12006
+ throw new ToolValidationError5({ message: `Unknown mode "${id}"`, field: "modeId" });
11063
12007
  }
11064
12008
  await ctx.modeStore.setActiveMode(id);
11065
12009
  }
11066
12010
  ctx.setModeId(id);
11067
12011
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
11068
- const paths = resolveWstackPaths({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
11069
- const freshBuilder = new DefaultSystemPromptBuilder({
12012
+ const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12013
+ const freshBuilder = new DefaultSystemPromptBuilder2({
11070
12014
  memoryStore: ctx.memoryStore,
11071
12015
  skillLoader: ctx.skillLoader,
11072
12016
  modeStore: ctx.modeStore,
@@ -11096,6 +12040,9 @@ function createModeHandlers(ctx) {
11096
12040
  }
11097
12041
  };
11098
12042
  }
12043
+
12044
+ // src/server/project-handlers.ts
12045
+ import * as path19 from "node:path";
11099
12046
  function createProjectHandlers(ctx) {
11100
12047
  return {
11101
12048
  listProjects: async (ws) => {
@@ -11121,7 +12068,7 @@ function createProjectHandlers(ctx) {
11121
12068
  selectProject: async (ws, msg) => {
11122
12069
  const payload = msg.payload;
11123
12070
  const root = typeof payload?.root === "string" ? payload.root : "";
11124
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path5.basename(root) : "";
12071
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
11125
12072
  send(ws, {
11126
12073
  type: "projects.selected",
11127
12074
  payload: {
@@ -11154,6 +12101,14 @@ function createProjectHandlers(ctx) {
11154
12101
  }
11155
12102
  };
11156
12103
  }
12104
+
12105
+ // src/server/session-handlers.ts
12106
+ import {
12107
+ DEFAULT_CONTEXT_WINDOW_MODE_ID,
12108
+ repairToolUseAdjacency,
12109
+ resolveContextWindowPolicy as resolveContextWindowPolicy3
12110
+ } from "@wrongstack/core";
12111
+ import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
11157
12112
  function createSessionHandlers(ctx) {
11158
12113
  const currentSessionId = () => ctx.getSession().id;
11159
12114
  const sessionPayload2 = (payload) => {
@@ -11179,33 +12134,46 @@ function createSessionHandlers(ctx) {
11179
12134
  });
11180
12135
  return false;
11181
12136
  };
12137
+ const finalizeSession = async (writer) => {
12138
+ await writer.append({
12139
+ type: "session_end",
12140
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12141
+ usage: ctx.tokenCounter.total()
12142
+ }).catch(() => void 0);
12143
+ await writer.close().catch(() => void 0);
12144
+ };
12145
+ const activateSession = async (next, messages, usage) => {
12146
+ const current = ctx.getSession();
12147
+ if (current !== next) await finalizeSession(current);
12148
+ ctx.setSession(next);
12149
+ ctx.context.session = next;
12150
+ ctx.context.state.replaceMessages(messages);
12151
+ ctx.context.state.replaceTodos([]);
12152
+ ctx.context.readFiles.clear();
12153
+ ctx.context.fileMtimes.clear();
12154
+ ctx.context.state.setMeta(
12155
+ "plan.path",
12156
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
12157
+ );
12158
+ ctx.context.state.setMeta(
12159
+ "task.path",
12160
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
12161
+ );
12162
+ ctx.tokenCounter.reset();
12163
+ if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
12164
+ ctx.setSessionStartedAt(Date.now());
12165
+ await ctx.onSessionSwapped(next.id);
12166
+ };
11182
12167
  return {
11183
12168
  newSession: async (ws, msg) => {
11184
12169
  if (!ensureCurrentSession(ws, msg, "session.new")) return;
11185
- const session = ctx.getSession();
11186
- try {
11187
- await session.append({
11188
- type: "session_end",
11189
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11190
- usage: ctx.tokenCounter.total()
11191
- });
11192
- await session.close();
11193
- } catch {
11194
- }
11195
12170
  const next = await ctx.getSessionStore().create({
11196
12171
  id: "",
11197
12172
  title: "",
11198
12173
  model: ctx.config.model,
11199
12174
  provider: ctx.config.provider
11200
12175
  });
11201
- ctx.setSession(next);
11202
- ctx.context.session = next;
11203
- ctx.context.state.replaceMessages([]);
11204
- ctx.context.state.replaceTodos([]);
11205
- ctx.context.readFiles.clear();
11206
- ctx.context.fileMtimes.clear();
11207
- ctx.tokenCounter.reset();
11208
- ctx.setSessionStartedAt(Date.now());
12176
+ await activateSession(next, []);
11209
12177
  broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
11210
12178
  },
11211
12179
  clearContext: async (ws, msg) => {
@@ -11307,7 +12275,7 @@ function createSessionHandlers(ctx) {
11307
12275
  return;
11308
12276
  }
11309
12277
  const { id } = parsed.value;
11310
- let policy = resolveContextWindowPolicy({}, id);
12278
+ let policy = resolveContextWindowPolicy3({}, id);
11311
12279
  if (policy.id !== id) {
11312
12280
  const customModes = ctx.customModeStore.list().filter((m) => m.custom === true);
11313
12281
  const custom = customModes.find((m) => m.id === id);
@@ -11379,7 +12347,7 @@ function createSessionHandlers(ctx) {
11379
12347
  const { id } = parsed.value;
11380
12348
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
11381
12349
  ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
11382
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
12350
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
11383
12351
  }
11384
12352
  const result = ctx.customModeStore.remove(id);
11385
12353
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -11461,23 +12429,7 @@ function createSessionHandlers(ctx) {
11461
12429
  return;
11462
12430
  }
11463
12431
  const resumed = await ctx.getSessionStore().resume(id);
11464
- try {
11465
- await current.append({
11466
- type: "session_end",
11467
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11468
- usage: ctx.tokenCounter.total()
11469
- });
11470
- await current.close();
11471
- } catch {
11472
- }
11473
- ctx.setSession(resumed.writer);
11474
- ctx.context.session = resumed.writer;
11475
- ctx.context.state.replaceMessages(resumed.data.messages);
11476
- ctx.context.readFiles.clear();
11477
- ctx.context.fileMtimes.clear();
11478
- ctx.tokenCounter.reset();
11479
- ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
11480
- ctx.setSessionStartedAt(Date.now());
12432
+ await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
11481
12433
  broadcast(ctx.clients, {
11482
12434
  type: "session.start",
11483
12435
  payload: {
@@ -11499,12 +12451,9 @@ function createSessionHandlers(ctx) {
11499
12451
  listCheckpoints: async (ws, msg) => {
11500
12452
  if (!ensureCurrentSession(ws, msg, "session.checkpoints")) return;
11501
12453
  try {
11502
- const { DefaultSessionRewinder } = await import('@wrongstack/core');
12454
+ const { DefaultSessionRewinder } = await import("@wrongstack/core");
11503
12455
  const projectRoot = ctx.getProjectRoot();
11504
- const rewinder = new DefaultSessionRewinder(
11505
- path5.join(projectRoot, ".wrongstack", "sessions"),
11506
- projectRoot
11507
- );
12456
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
11508
12457
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
11509
12458
  send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
11510
12459
  } catch {
@@ -11515,12 +12464,9 @@ function createSessionHandlers(ctx) {
11515
12464
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
11516
12465
  const { checkpointIndex } = msg.payload;
11517
12466
  try {
11518
- const { DefaultSessionRewinder } = await import('@wrongstack/core');
12467
+ const { DefaultSessionRewinder } = await import("@wrongstack/core");
11519
12468
  const projectRoot = ctx.getProjectRoot();
11520
- const rewinder = new DefaultSessionRewinder(
11521
- path5.join(projectRoot, ".wrongstack", "sessions"),
11522
- projectRoot
11523
- );
12469
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
11524
12470
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
11525
12471
  await ctx.context.session.truncateToCheckpoint(checkpointIndex);
11526
12472
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
@@ -11603,11 +12549,13 @@ function buildRoutes(state, deps2, cb) {
11603
12549
  const saved = await providerHandlers.loadConfigProviders();
11604
12550
  const cfg = saved[providerId];
11605
12551
  const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
12552
+ const siblingId = SIBLING_CATALOG[providerId];
12553
+ const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
11606
12554
  const models = await enrichProviderModelDescriptors(
11607
12555
  deps2.modelsRegistry,
11608
12556
  providerId,
11609
12557
  cfg,
11610
- resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId)
12558
+ resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId, siblingCatalog)
11611
12559
  );
11612
12560
  send(ws, {
11613
12561
  type: "provider.models",
@@ -11633,7 +12581,7 @@ function buildRoutes(state, deps2, cb) {
11633
12581
  const providerCfg = newCfg.providers?.[newProvider] ?? {
11634
12582
  type: newProvider
11635
12583
  };
11636
- const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig(newProvider, providerCfg);
12584
+ const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
11637
12585
  deps2.context.provider = newProv;
11638
12586
  await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
11639
12587
  await cb.updateGlobalConfig((config) => {
@@ -11734,8 +12682,10 @@ function buildRoutes(state, deps2, cb) {
11734
12682
  getProjectRoot: state.getProjectRoot,
11735
12683
  getSession: state.getSession,
11736
12684
  getSessionStore: state.getSessionStore,
12685
+ sessionsDir: deps2.wpaths.projectSessions,
11737
12686
  setSession: state.setSession,
11738
12687
  setSessionStartedAt: state.setSessionStartedAt,
12688
+ onSessionSwapped: cb.onSessionSwapped,
11739
12689
  sessionStartPayload: cb.sessionStartPayload
11740
12690
  });
11741
12691
  const projectRoutes = createProjectHandlers({
@@ -11830,6 +12780,17 @@ function buildRoutes(state, deps2, cb) {
11830
12780
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
11831
12781
  }
11832
12782
  }
12783
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
12784
+ const { getProcessRegistry } = await import("@wrongstack/tools");
12785
+ getProcessRegistry().setBreakerConfig({
12786
+ ...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
12787
+ ...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
12788
+ });
12789
+ }
12790
+ if (typeof payload["debugStream"] === "boolean") {
12791
+ const { setDebugStreamEnabled } = await import("@wrongstack/providers");
12792
+ setDebugStreamEnabled(payload["debugStream"]);
12793
+ }
11833
12794
  if (typeof payload["logLevel"] === "string") {
11834
12795
  const valid = ["debug", "info", "warn", "error"];
11835
12796
  if (valid.includes(payload["logLevel"])) {
@@ -11876,7 +12837,7 @@ function buildRoutes(state, deps2, cb) {
11876
12837
  }
11877
12838
  return handleMailboxMessages(
11878
12839
  ws,
11879
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12840
+ { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
11880
12841
  parsed.value
11881
12842
  );
11882
12843
  },
@@ -11888,13 +12849,13 @@ function buildRoutes(state, deps2, cb) {
11888
12849
  }
11889
12850
  return handleMailboxAgents(
11890
12851
  ws,
11891
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12852
+ { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
11892
12853
  parsed.value
11893
12854
  );
11894
12855
  },
11895
12856
  clear: (ws) => handleMailboxClear(ws, {
11896
12857
  projectRoot: state.getProjectRoot(),
11897
- globalRoot: path5__default.dirname(deps2.globalConfigPath)
12858
+ globalRoot: path20.dirname(deps2.globalConfigPath)
11898
12859
  }),
11899
12860
  purge: (ws, msg) => {
11900
12861
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -11904,9 +12865,16 @@ function buildRoutes(state, deps2, cb) {
11904
12865
  }
11905
12866
  return handleMailboxPurge(
11906
12867
  ws,
11907
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12868
+ { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
11908
12869
  parsed.value
11909
12870
  );
12871
+ },
12872
+ compact: (ws, msg) => {
12873
+ return handleMailboxCompact(
12874
+ ws,
12875
+ { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
12876
+ msg.payload ?? {}
12877
+ );
11910
12878
  }
11911
12879
  };
11912
12880
  const mcpRoutes = {
@@ -11919,14 +12887,29 @@ function buildRoutes(state, deps2, cb) {
11919
12887
  sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11920
12888
  wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11921
12889
  restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11922
- discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12890
+ discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12891
+ resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12892
+ prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12893
+ resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12894
+ promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12895
+ };
12896
+ const sendBrainStatus = (ws) => {
12897
+ const snapshot = deps2.brainRuntime.getSnapshot();
12898
+ send(ws, {
12899
+ type: "brain.status",
12900
+ payload: {
12901
+ maxAutoRisk: deps2.brainSettings.maxAutoRisk,
12902
+ log: deps2.brainLog,
12903
+ mode: snapshot.mode,
12904
+ poolLabels: snapshot.poolLabels,
12905
+ councilLabels: snapshot.councilLabels,
12906
+ ledgerPath: snapshot.ledger.path
12907
+ }
12908
+ });
11923
12909
  };
11924
12910
  const brainRoutes = {
11925
12911
  status: (ws) => {
11926
- send(ws, {
11927
- type: "brain.status",
11928
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
11929
- });
12912
+ sendBrainStatus(ws);
11930
12913
  },
11931
12914
  risk: (ws, msg) => {
11932
12915
  const parsed = validateBrainRiskPayload(msg.payload);
@@ -11936,11 +12919,43 @@ function buildRoutes(state, deps2, cb) {
11936
12919
  }
11937
12920
  const { level } = parsed.value;
11938
12921
  deps2.brainSettings.maxAutoRisk = level;
12922
+ sendBrainStatus(ws);
12923
+ },
12924
+ configGet: (ws) => {
11939
12925
  send(ws, {
11940
- type: "brain.status",
11941
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
12926
+ type: "brain.config",
12927
+ payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
11942
12928
  });
11943
12929
  },
12930
+ configSet: async (ws, msg) => {
12931
+ const parsed = validateBrainConfigSetPayload(msg.payload);
12932
+ if (!parsed.ok) {
12933
+ sendResult(ws, false, parsed.message);
12934
+ return;
12935
+ }
12936
+ try {
12937
+ const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
12938
+ const result = await persisted;
12939
+ send(ws, {
12940
+ type: "brain.config",
12941
+ payload: {
12942
+ config: deps2.brainRuntime.getSnapshot(),
12943
+ persisted: result.ok,
12944
+ ...result.ok ? {} : { error: result.error ?? "Persist failed." }
12945
+ }
12946
+ });
12947
+ sendBrainStatus(ws);
12948
+ } catch (err) {
12949
+ send(ws, {
12950
+ type: "brain.config",
12951
+ payload: {
12952
+ config: deps2.brainRuntime.getSnapshot(),
12953
+ persisted: false,
12954
+ error: `Invalid Brain setting: ${errMessage(err)}`
12955
+ }
12956
+ });
12957
+ }
12958
+ },
11944
12959
  ask: async (ws, msg) => {
11945
12960
  const parsed = validateBrainAskPayload(msg.payload);
11946
12961
  if (!parsed.ok) {
@@ -12012,7 +13027,7 @@ async function startWebUI(opts = {}) {
12012
13027
  const updateGlobalConfig2 = async (mutate, errorLabel) => updateGlobalConfig(prefHelperDeps, configWriteLock, mutate, errorLabel);
12013
13028
  console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
12014
13029
  if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
12015
- const firstKey = expectDefined(Object.keys(config.providers)[0]);
13030
+ const firstKey = expectDefined3(Object.keys(config.providers)[0]);
12016
13031
  config = patchConfig(config, { provider: firstKey });
12017
13032
  console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
12018
13033
  }
@@ -12048,7 +13063,8 @@ async function startWebUI(opts = {}) {
12048
13063
  promptsCtx,
12049
13064
  modelCapabilitiesRef,
12050
13065
  provider,
12051
- context
13066
+ context,
13067
+ sessionIdentity
12052
13068
  } = preContext;
12053
13069
  let sessionStore = preContext.sessionStore;
12054
13070
  let session = preContext.session;
@@ -12081,7 +13097,12 @@ async function startWebUI(opts = {}) {
12081
13097
  modelCapabilitiesRef,
12082
13098
  sessionGetter: () => session,
12083
13099
  sessionReader,
12084
- annotationsStore
13100
+ annotationsStore,
13101
+ // Brain settings persist to the GLOBAL config only (config.brain is on
13102
+ // the in-project deny list), serialized behind the shared write lock.
13103
+ persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
13104
+ decrypted["brain"] = brainConfig;
13105
+ }, "brain.config")
12085
13106
  });
12086
13107
  const {
12087
13108
  compactor,
@@ -12091,6 +13112,7 @@ async function startWebUI(opts = {}) {
12091
13112
  pipelines,
12092
13113
  brain,
12093
13114
  brainSettings,
13115
+ brainRuntime,
12094
13116
  brainLog,
12095
13117
  brainMonitor,
12096
13118
  codebaseIndexing,
@@ -12112,7 +13134,7 @@ async function startWebUI(opts = {}) {
12112
13134
  getProjectRoot: () => projectRoot,
12113
13135
  getWorkingDir: () => workingDir,
12114
13136
  getModeId: () => modeId,
12115
- getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID),
13137
+ getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2),
12116
13138
  getNeedsSetup: () => needsSetup,
12117
13139
  modelsRegistry
12118
13140
  });
@@ -12155,21 +13177,21 @@ async function startWebUI(opts = {}) {
12155
13177
  wpaths
12156
13178
  }, watcherMetricsRef);
12157
13179
  async function touchProjectEntry(root, workDir) {
12158
- const resolved = path5.resolve(root);
13180
+ const resolved = path21.resolve(root);
12159
13181
  const manifest = await loadManifest(globalConfigPath);
12160
13182
  const now = (/* @__PURE__ */ new Date()).toISOString();
12161
- const existing = manifest.projects.find((p) => path5.resolve(p.root) === resolved);
13183
+ const existing = manifest.projects.find((p) => path21.resolve(p.root) === resolved);
12162
13184
  if (existing) {
12163
13185
  existing.lastSeen = now;
12164
- if (workDir) existing.lastWorkingDir = path5.resolve(workDir);
13186
+ if (workDir) existing.lastWorkingDir = path21.resolve(workDir);
12165
13187
  } else {
12166
13188
  manifest.projects.push({
12167
- name: path5.basename(resolved),
13189
+ name: path21.basename(resolved),
12168
13190
  root: resolved,
12169
13191
  slug: generateProjectSlug(resolved),
12170
13192
  createdAt: now,
12171
13193
  lastSeen: now,
12172
- lastWorkingDir: workDir ? path5.resolve(workDir) : void 0
13194
+ lastWorkingDir: workDir ? path21.resolve(workDir) : void 0
12173
13195
  });
12174
13196
  }
12175
13197
  await saveManifest(manifest, globalConfigPath);
@@ -12261,10 +13283,12 @@ async function startWebUI(opts = {}) {
12261
13283
  terminalHandler,
12262
13284
  brain,
12263
13285
  brainSettings,
13286
+ brainRuntime,
12264
13287
  brainLog
12265
13288
  };
12266
13289
  const cb = {
12267
13290
  sessionStartPayload,
13291
+ onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
12268
13292
  updateAutoCompactionMaxContext,
12269
13293
  updateGlobalConfig: updateGlobalConfig2,
12270
13294
  persistPrefsToConfig: persistPrefsToConfig2,
@@ -12314,13 +13338,13 @@ async function startWebUI(opts = {}) {
12314
13338
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
12315
13339
  ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
12316
13340
  };
12317
- const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig(activeId, { ...providerCfg, type: activeId });
13341
+ const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...providerCfg, type: activeId });
12318
13342
  deps2.context.provider = newProv;
12319
13343
  void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
12320
13344
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
12321
13345
  } catch (err) {
12322
13346
  console.warn(
12323
- `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage(err)}`
13347
+ `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage10(err)}`
12324
13348
  );
12325
13349
  }
12326
13350
  },
@@ -12387,17 +13411,27 @@ async function startWebUI(opts = {}) {
12387
13411
  },
12388
13412
  clients: () => clients.keys(),
12389
13413
  servers: [httpServer, wssPrimary, ...wssSecondary ? [wssSecondary] : []],
12390
- onShutdown: () => {
13414
+ onShutdown: async () => {
12391
13415
  credentialWatcherClose?.();
12392
13416
  brainMonitor.stop();
12393
- void mcpRegistry.stopAll().catch(() => void 0);
13417
+ await agentServices.brainLedger?.stop().catch(() => {
13418
+ });
13419
+ await mcpRegistry.stopAll().catch(() => void 0);
13420
+ await sessionIdentity.stop();
12394
13421
  eventArming.getDispose()?.();
12395
13422
  if (eternalSubscription) {
12396
13423
  eternalSubscription.dispose();
12397
13424
  eternalSubscription = null;
12398
13425
  }
12399
13426
  codebaseIndexing.dispose();
12400
- return unregisterInstance(process.pid, path5.dirname(globalConfigPath));
13427
+ if (config.superMemory?.enabled !== false && config.superMemory?.hygiene?.autoAfterSession !== false) {
13428
+ const candidate = memoryStore;
13429
+ await candidate.hygiene?.({
13430
+ retentionDays: config.superMemory?.hygiene?.retentionDays,
13431
+ archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
13432
+ }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
13433
+ }
13434
+ await unregisterInstance(process.pid, path21.dirname(globalConfigPath));
12401
13435
  }
12402
13436
  });
12403
13437
  }
@@ -12412,7 +13446,7 @@ function readArg(names) {
12412
13446
  if (current === name2) {
12413
13447
  const next = argv[i + 1];
12414
13448
  if (!next || next.startsWith("-")) {
12415
- throw new ToolValidationError({ message: `${name2} requires a value`, field: name2 });
13449
+ throw new ToolValidationError6({ message: `${name2} requires a value`, field: name2 });
12416
13450
  }
12417
13451
  return next;
12418
13452
  }
@@ -12425,7 +13459,7 @@ function parsePort(value, fallback, label) {
12425
13459
  if (value === void 0) return fallback;
12426
13460
  const parsed = Number.parseInt(value, 10);
12427
13461
  if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
12428
- throw new ToolValidationError({
13462
+ throw new ToolValidationError6({
12429
13463
  message: `${label} must be a port between 1 and 65535`,
12430
13464
  field: "port"
12431
13465
  });
@@ -12517,4 +13551,3 @@ if (argv.includes("--help") || argv.includes("-h")) {
12517
13551
  });
12518
13552
  }
12519
13553
  //# sourceMappingURL=entry.js.map
12520
- //# sourceMappingURL=entry.js.map