@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
package/dist/index.js CHANGED
@@ -1,34 +1,14 @@
1
- import { spawn, spawnSync } from 'child_process';
2
- import { toErrorMessage, wstackGlobalRoot, resolveWstackPaths, atomicWrite as atomicWrite$1 } from '@wrongstack/core/utils';
3
- import { DefaultSecretScrubber, PhaseStore, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, AutoPhasePlanner, assignNickname, getDesignState, getDesignKitLoader, isDesignStack, loadActiveKit, setActiveKit, recordKitChoice, applyTokenOverrides, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, ToolValidationError, atomicWrite, allServers, ConfigError, DefaultSecretVault, expectDefined, computeTaskProgress, bootConfig as bootConfig$1, resolveProjectDir, GlobalMailbox, createDefaultPipelines, resolveSessionLoggingConfig, createSessionEventBridge, watchProviderConfig, listContextWindowModes, 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, Context, resolveContextWindowPolicy, CollaborationBus, collabInjectMiddleware, collabPauseMiddleware, installDesignStudioMiddleware, createStrategyCompactor, AutoCompactionMiddleware, estimateRequestTokensCalibrated, ToolExecutor, DEFAULT_TOOLS_CONFIG, Agent, ObservableBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, wstackGlobalRoot as wstackGlobalRoot$1, createAutonomyBrain, DEFAULT_CONTEXT_WINDOW_MODE_ID, repairToolUseAdjacency, resolveWstackPaths as resolveWstackPaths$1, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, resolveProviderModelList, projectSlug, deserializeTaskGraph, serializeTaskGraph } from '@wrongstack/core';
4
- import * as path5 from 'path';
5
- import path5__default, { resolve, join, sep, dirname } from 'path';
6
- import { searchCodebaseIndex } from '@wrongstack/tools/codebase-index/index';
7
- import { randomBytes, timingSafeEqual, randomUUID } from 'crypto';
8
- import { WebSocket, WebSocketServer } from 'ws';
9
- import * as fs4 from 'fs/promises';
10
- import * as http from 'http';
11
- import { Buffer } from 'buffer';
12
- import * as os from 'os';
13
- import { listMcp, addMcp, updateMcp, removeMcp, enableMcp, disableMcp, restartMcp, discoverMcp, MCPRegistry } from '@wrongstack/mcp';
14
- import * as net from 'net';
15
- import { decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core/security';
16
- import { SddBoardStore, applySddLifecycle, SddRunRegistry, SddInterviewDriver, cleanupStaleSddWorktrees, makeCommandVerifier, SddSupervisor, makeLlmSubtaskGenerator, startSddRun, TaskGraphStore, SpecStore } from '@wrongstack/sdd';
17
- import * as fs12 from 'fs';
18
- import { promises, watch, existsSync } from 'fs';
19
- import JSZip from 'jszip';
20
- import { createRequire } from 'module';
21
- import { fileURLToPath } from 'url';
22
- import { capabilitiesFor, makeProviderFromConfig, buildProviderFactoriesFromRegistry, discoverOpenAICompatibleModels, COMPATIBLE_PRESETS } from '@wrongstack/providers';
23
- import { DefaultTokenCounter } from '@wrongstack/core/infrastructure';
24
- import { createDefaultContainer, makeLightSubagentFactory } from '@wrongstack/runtime';
25
- import { runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, ensureSessionShell, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, configureExecPolicy, configureDangerBypass } from '@wrongstack/tools';
26
- import { readLiveLock } from '@wrongstack/core/coordination';
27
- 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';
28
- import { beginOAuthLogin } from '@wrongstack/providers/oauth';
29
- import { probeLocalLlm } from '@wrongstack/runtime/probe';
30
-
31
1
  // src/server/autophase-ws-handler.ts
2
+ import { spawnSync } from "node:child_process";
3
+ import { toErrorMessage } from "@wrongstack/core/utils";
4
+ import {
5
+ assignNickname,
6
+ AutoPhasePlanner,
7
+ PhaseGraphBuilder,
8
+ PhaseOrchestrator,
9
+ PhaseStore,
10
+ WorktreeManager
11
+ } from "@wrongstack/core";
32
12
  function deriveTitle(goal) {
33
13
  const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
34
14
  if (!firstLine) return "AutoPhase";
@@ -519,6 +499,14 @@ Type: ${task.type}`;
519
499
  }
520
500
  }
521
501
  };
502
+
503
+ // src/server/completion-handlers.ts
504
+ import * as path from "node:path";
505
+ import { searchCodebaseIndex } from "@wrongstack/tools/codebase-index/index";
506
+
507
+ // src/server/ws-utils.ts
508
+ import { randomBytes } from "node:crypto";
509
+ import { WebSocket } from "ws";
522
510
  function send(ws, msg) {
523
511
  if (ws.readyState === WebSocket.OPEN) {
524
512
  ws.send(JSON.stringify(msg));
@@ -650,8 +638,8 @@ async function handleCompletionRequest(ws, msg, opts) {
650
638
  return;
651
639
  }
652
640
  const payload = parsed.payload;
653
- const projectRoot = path5.resolve(opts.projectRoot);
654
- const resolved = path5.resolve(projectRoot, payload.filePath);
641
+ const projectRoot = path.resolve(opts.projectRoot);
642
+ const resolved = path.resolve(projectRoot, payload.filePath);
655
643
  if (!isInside(projectRoot, resolved)) {
656
644
  send(ws, {
657
645
  type: "completion.result",
@@ -1050,7 +1038,7 @@ function buildSearchQuery(linePrefix, filePath) {
1050
1038
  if (memberMatch?.[1]) return memberMatch[1];
1051
1039
  const token = linePrefix.match(/([A-Za-z_$][\w$]*)$/)?.[1];
1052
1040
  if (token && token.length >= 2) return token;
1053
- return path5.basename(filePath, path5.extname(filePath));
1041
+ return path.basename(filePath, path.extname(filePath));
1054
1042
  }
1055
1043
  function currentLinePrefix(prefix) {
1056
1044
  const idx = Math.max(prefix.lastIndexOf("\n"), prefix.lastIndexOf("\r"));
@@ -1080,11 +1068,16 @@ function head(value, max) {
1080
1068
  return value.length <= max ? value : value.slice(0, max);
1081
1069
  }
1082
1070
  function isInside(root, target) {
1083
- return target === root || target.startsWith(root + path5.sep);
1071
+ return target === root || target.startsWith(root + path.sep);
1084
1072
  }
1073
+
1074
+ // src/server/custom-context-modes.ts
1075
+ import { listContextWindowModes, atomicWrite } from "@wrongstack/core";
1076
+ import * as fs from "node:fs/promises";
1077
+ import * as path2 from "node:path";
1085
1078
  var STORE_FILENAME = "custom-context-modes.json";
1086
1079
  function storePath(wrongstackDir) {
1087
- return path5.join(wrongstackDir, STORE_FILENAME);
1080
+ return path2.join(wrongstackDir, STORE_FILENAME);
1088
1081
  }
1089
1082
  var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
1090
1083
  function createCustomModeStore(wrongstackDir) {
@@ -1092,7 +1085,7 @@ function createCustomModeStore(wrongstackDir) {
1092
1085
  const load2 = async () => {
1093
1086
  modes.clear();
1094
1087
  try {
1095
- const raw = await fs4.readFile(storePath(wrongstackDir), "utf8");
1088
+ const raw = await fs.readFile(storePath(wrongstackDir), "utf8");
1096
1089
  const parsed = JSON.parse(raw);
1097
1090
  if (Array.isArray(parsed.modes)) {
1098
1091
  for (const m of parsed.modes) {
@@ -1197,6 +1190,23 @@ function createCustomModeStore(wrongstackDir) {
1197
1190
  };
1198
1191
  return { modes, load: load2, save: save2, create, update, remove, list };
1199
1192
  }
1193
+
1194
+ // src/server/design-handlers.ts
1195
+ import * as fs2 from "node:fs/promises";
1196
+ import * as path3 from "node:path";
1197
+ import {
1198
+ applyTokenOverrides,
1199
+ getDesignKitLoader,
1200
+ getDesignState,
1201
+ isDesignStack,
1202
+ loadActiveKit,
1203
+ materializeTokens,
1204
+ recordKitChoice,
1205
+ recordOverrides,
1206
+ runDesignVerify,
1207
+ setActiveKit,
1208
+ setDesignOverrides
1209
+ } from "@wrongstack/core";
1200
1210
  function readOverrides(value) {
1201
1211
  const out = {};
1202
1212
  if (value && typeof value === "object") {
@@ -1344,9 +1354,9 @@ async function handleDesignMaterialize(ws, ctx, msg) {
1344
1354
  kitId: active.kit,
1345
1355
  outPath: typeof payload.out === "string" ? payload.out : void 0
1346
1356
  });
1347
- const abs = path5.join(ctx.projectRoot, result.path);
1348
- await fs4.mkdir(path5.dirname(abs), { recursive: true });
1349
- await fs4.writeFile(abs, result.content);
1357
+ const abs = path3.join(ctx.projectRoot, result.path);
1358
+ await fs2.mkdir(path3.dirname(abs), { recursive: true });
1359
+ await fs2.writeFile(abs, result.content);
1350
1360
  send(ws, {
1351
1361
  type: "design.materialize",
1352
1362
  payload: { ok: true, path: result.path, format: result.format, stack }
@@ -1405,6 +1415,11 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
1405
1415
  };
1406
1416
  }
1407
1417
 
1418
+ // src/server/file-handlers.ts
1419
+ import * as fs4 from "node:fs/promises";
1420
+ import * as path5 from "node:path";
1421
+ import { atomicWrite as atomicWrite2, ToolValidationError as ToolValidationError2 } from "@wrongstack/core";
1422
+
1408
1423
  // src/server/file-picker.ts
1409
1424
  var SKIP_DIRS = /* @__PURE__ */ new Set([
1410
1425
  ".git",
@@ -1452,15 +1467,20 @@ function rankFiles(paths, query, limit) {
1452
1467
  scored.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
1453
1468
  return scored.slice(0, limit).map((s) => s.path);
1454
1469
  }
1470
+
1471
+ // src/server/path-containment.ts
1472
+ import * as fs3 from "node:fs/promises";
1473
+ import * as path4 from "node:path";
1474
+ import { ToolValidationError } from "@wrongstack/core";
1455
1475
  function isPathInside(root, target) {
1456
- const relative4 = path5.relative(root, target);
1457
- return relative4 === "" || !relative4.startsWith("..") && !path5.isAbsolute(relative4);
1476
+ const relative4 = path4.relative(root, target);
1477
+ return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
1458
1478
  }
1459
1479
  async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1460
- const resolved = path5.resolve(projectRoot, inputPath);
1480
+ const resolved = path4.resolve(projectRoot, inputPath);
1461
1481
  let stat2;
1462
1482
  try {
1463
- stat2 = await fs4.stat(resolved);
1483
+ stat2 = await fs3.stat(resolved);
1464
1484
  } catch {
1465
1485
  throw new ToolValidationError({
1466
1486
  message: `Directory not found or not accessible: ${resolved}`,
@@ -1474,8 +1494,8 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1474
1494
  });
1475
1495
  }
1476
1496
  const [realProjectRoot, realResolved] = await Promise.all([
1477
- fs4.realpath(projectRoot),
1478
- fs4.realpath(resolved)
1497
+ fs3.realpath(projectRoot),
1498
+ fs3.realpath(resolved)
1479
1499
  ]);
1480
1500
  if (!isPathInside(realProjectRoot, realResolved)) {
1481
1501
  throw new ToolValidationError({
@@ -1490,14 +1510,14 @@ async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
1490
1510
  async function resolveFileInsideProject(projectRoot, filePath) {
1491
1511
  const resolved = path5.resolve(projectRoot, filePath);
1492
1512
  if (!isPathInside(projectRoot, resolved)) {
1493
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1513
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1494
1514
  }
1495
1515
  const { parent, base } = splitParentAndBase(resolved);
1496
1516
  const realProjectRoot = await fs4.realpath(projectRoot);
1497
1517
  const realParent = await realpathAllowMissing(parent);
1498
1518
  const realFull = path5.join(realParent, base);
1499
1519
  if (!isPathInside(realProjectRoot, realFull)) {
1500
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1520
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1501
1521
  }
1502
1522
  return realFull;
1503
1523
  }
@@ -1517,7 +1537,7 @@ async function realpathAllowMissing(p) {
1517
1537
  while (true) {
1518
1538
  const parent = path5.dirname(cursor);
1519
1539
  if (parent === cursor) {
1520
- throw new ToolValidationError({ message: "Path outside project root", field: "path" });
1540
+ throw new ToolValidationError2({ message: "Path outside project root", field: "path" });
1521
1541
  }
1522
1542
  segments.unshift(path5.basename(cursor));
1523
1543
  try {
@@ -1650,7 +1670,7 @@ async function handleFilesWrite(ws, msg, projectRoot, opts = {}) {
1650
1670
  return;
1651
1671
  }
1652
1672
  try {
1653
- await atomicWrite(realResolved, content);
1673
+ await atomicWrite2(realResolved, content);
1654
1674
  send(ws, { type: "files.written", payload: { filePath, success: true } });
1655
1675
  if (opts.onWritten) {
1656
1676
  void Promise.resolve(opts.onWritten(realResolved)).catch(() => void 0);
@@ -1714,10 +1734,13 @@ async function handleFilesList(ws, msg, projectRoot) {
1714
1734
  payload: { files: rankFiles(results, payload.query ?? "", limit) }
1715
1735
  });
1716
1736
  }
1737
+
1738
+ // src/server/git-handlers.ts
1739
+ import nodePath from "node:path";
1717
1740
  async function handleGitInfo(ws, projectRoot) {
1718
1741
  const cwd = projectRoot || void 0;
1719
1742
  try {
1720
- const { execFile: ef } = await import('child_process');
1743
+ const { execFile: ef } = await import("node:child_process");
1721
1744
  const git = (args) => new Promise((resolve10) => {
1722
1745
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1723
1746
  resolve10(err ? "" : stdout.trim());
@@ -1745,7 +1768,7 @@ async function handleGitInfo(ws, projectRoot) {
1745
1768
  }
1746
1769
  function makeGit(cwd) {
1747
1770
  return async (args) => {
1748
- const { execFile: ef } = await import('child_process');
1771
+ const { execFile: ef } = await import("node:child_process");
1749
1772
  return new Promise((resolve10) => {
1750
1773
  ef(
1751
1774
  "git",
@@ -1827,14 +1850,14 @@ var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1827
1850
  async function handleGitDiff(ws, projectRoot, path23) {
1828
1851
  const cwd = projectRoot || void 0;
1829
1852
  const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1830
- if (!path23 || path23.includes("\0") || path23.includes("..") || path5__default.isAbsolute(path23)) {
1853
+ if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
1831
1854
  reply({ oldText: "", newText: "", error: "invalid path" });
1832
1855
  return;
1833
1856
  }
1834
1857
  try {
1835
1858
  const git = makeGit(cwd);
1836
- const { readFile: readFile11 } = await import('fs/promises');
1837
- const { join: join15 } = await import('path');
1859
+ const { readFile: readFile11 } = await import("node:fs/promises");
1860
+ const { join: join15 } = await import("node:path");
1838
1861
  const oldText = await git(["show", `HEAD:${path23}`]);
1839
1862
  let newText = "";
1840
1863
  try {
@@ -1866,6 +1889,11 @@ async function handleGitDiff(ws, projectRoot, path23) {
1866
1889
  }
1867
1890
  }
1868
1891
 
1892
+ // src/server/http-server.ts
1893
+ import * as fs5 from "node:fs/promises";
1894
+ import * as http from "node:http";
1895
+ import * as path6 from "node:path";
1896
+
1869
1897
  // src/server/http-server/api-handlers.ts
1870
1898
  async function handleApiSessions(res, globalRoot) {
1871
1899
  if (!globalRoot) {
@@ -1874,7 +1902,7 @@ async function handleApiSessions(res, globalRoot) {
1874
1902
  return;
1875
1903
  }
1876
1904
  try {
1877
- const { SessionRegistry } = await import('@wrongstack/core');
1905
+ const { SessionRegistry } = await import("@wrongstack/core");
1878
1906
  const registry = new SessionRegistry(globalRoot);
1879
1907
  const sessions = await registry.list();
1880
1908
  const result = sessions.map((s) => ({
@@ -1912,7 +1940,7 @@ async function handleApiSessionAgents(res, globalRoot, sessionId) {
1912
1940
  return;
1913
1941
  }
1914
1942
  try {
1915
- const { SessionRegistry } = await import('@wrongstack/core');
1943
+ const { SessionRegistry } = await import("@wrongstack/core");
1916
1944
  const registry = new SessionRegistry(globalRoot);
1917
1945
  const entry = await registry.get(sessionId);
1918
1946
  if (!entry) {
@@ -2052,7 +2080,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2052
2080
  return;
2053
2081
  }
2054
2082
  try {
2055
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import('@wrongstack/core');
2083
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2056
2084
  const registry = new SessionRegistry(globalRoot);
2057
2085
  const entry = await registry.get(sessionId);
2058
2086
  if (!entry) {
@@ -2134,7 +2162,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2134
2162
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2135
2163
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2136
2164
  try {
2137
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2165
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2138
2166
  const registry = new SessionRegistry(globalRoot);
2139
2167
  const entry = await registry.get(sessionId);
2140
2168
  if (!entry) {
@@ -2160,7 +2188,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2160
2188
  return;
2161
2189
  }
2162
2190
  try {
2163
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2191
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2164
2192
  const registry = new SessionRegistry(globalRoot);
2165
2193
  const entry = await registry.get(sessionId);
2166
2194
  if (!entry) {
@@ -2218,7 +2246,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2218
2246
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2219
2247
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2220
2248
  try {
2221
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2249
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2222
2250
  const registry = new SessionRegistry(globalRoot);
2223
2251
  const entry = await registry.get(sessionId);
2224
2252
  if (!entry) {
@@ -2266,7 +2294,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2266
2294
  }
2267
2295
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2268
2296
  try {
2269
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import('@wrongstack/core');
2297
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2270
2298
  const registry = new SessionRegistry(globalRoot);
2271
2299
  const all = await registry.list();
2272
2300
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2422,6 +2450,10 @@ function clearAnalyticsBuffer() {
2422
2450
  function getAnalyticsBuffer() {
2423
2451
  return [...EVENT_BUFFER];
2424
2452
  }
2453
+
2454
+ // src/server/ws-auth.ts
2455
+ import { Buffer as Buffer2 } from "node:buffer";
2456
+ import { timingSafeEqual } from "node:crypto";
2425
2457
  function isLoopbackHostname(hostname) {
2426
2458
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
2427
2459
  }
@@ -2450,8 +2482,8 @@ function allowedHostname(hostname, allowedHostnames) {
2450
2482
  }
2451
2483
  function tokenMatches(provided, expected) {
2452
2484
  if (!provided) return false;
2453
- const a = Buffer.from(provided);
2454
- const b = Buffer.from(expected);
2485
+ const a = Buffer2.from(provided);
2486
+ const b = Buffer2.from(expected);
2455
2487
  if (a.length !== b.length) return false;
2456
2488
  return timingSafeEqual(a, b);
2457
2489
  }
@@ -2607,9 +2639,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2607
2639
  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'`;
2608
2640
  }
2609
2641
  function isInsideDist(candidate, distDir) {
2610
- const root = path5.resolve(distDir);
2611
- const resolved = path5.resolve(candidate);
2612
- return resolved === root || resolved.startsWith(root + path5.sep);
2642
+ const root = path6.resolve(distDir);
2643
+ const resolved = path6.resolve(candidate);
2644
+ return resolved === root || resolved.startsWith(root + path6.sep);
2613
2645
  }
2614
2646
  function decodeSessionId(segment) {
2615
2647
  try {
@@ -2620,7 +2652,7 @@ function decodeSessionId(segment) {
2620
2652
  }
2621
2653
  function createHttpServer(opts) {
2622
2654
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2623
- const distDir = path5.resolve(opts.distDir);
2655
+ const distDir = path6.resolve(opts.distDir);
2624
2656
  const wsPort = opts.wsPort;
2625
2657
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2626
2658
  return http.createServer(async (req, res) => {
@@ -2797,21 +2829,21 @@ function createHttpServer(opts) {
2797
2829
  }
2798
2830
  let filePath;
2799
2831
  if (url.pathname === "/" || url.pathname === "") {
2800
- filePath = path5.join(distDir, "index.html");
2832
+ filePath = path6.join(distDir, "index.html");
2801
2833
  } else if (url.pathname.startsWith("/assets/")) {
2802
- filePath = path5.join(distDir, url.pathname);
2834
+ filePath = path6.join(distDir, url.pathname);
2803
2835
  } else if (url.pathname.startsWith("/")) {
2804
- filePath = path5.join(distDir, url.pathname);
2836
+ filePath = path6.join(distDir, url.pathname);
2805
2837
  } else {
2806
- filePath = path5.join(distDir, "index.html");
2838
+ filePath = path6.join(distDir, "index.html");
2807
2839
  }
2808
- const resolvedPath = path5.resolve(filePath);
2840
+ const resolvedPath = path6.resolve(filePath);
2809
2841
  if (!isInsideDist(resolvedPath, distDir)) {
2810
2842
  res.writeHead(403, { "Content-Type": "text/plain" });
2811
2843
  res.end("Forbidden");
2812
2844
  return;
2813
2845
  }
2814
- const ext = path5.extname(resolvedPath);
2846
+ const ext = path6.extname(resolvedPath);
2815
2847
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2816
2848
  res.setHeader("Content-Type", contentType);
2817
2849
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2823,18 +2855,18 @@ function createHttpServer(opts) {
2823
2855
  "Content-Security-Policy",
2824
2856
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2825
2857
  );
2826
- const html = await fs4.readFile(resolvedPath, "utf8");
2858
+ const html = await fs5.readFile(resolvedPath, "utf8");
2827
2859
  res.writeHead(200);
2828
2860
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2829
2861
  return;
2830
2862
  }
2831
- const fileContent = await fs4.readFile(resolvedPath);
2863
+ const fileContent = await fs5.readFile(resolvedPath);
2832
2864
  res.writeHead(200);
2833
2865
  res.end(fileContent);
2834
2866
  } catch (err) {
2835
2867
  if (err.code === "ENOENT") {
2836
2868
  try {
2837
- const html = await fs4.readFile(path5.join(distDir, "index.html"), "utf8");
2869
+ const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
2838
2870
  res.writeHead(200, {
2839
2871
  "Content-Type": "text/html",
2840
2872
  "X-Content-Type-Options": "nosniff",
@@ -2858,11 +2890,17 @@ function createHttpServer(opts) {
2858
2890
  }
2859
2891
  });
2860
2892
  }
2893
+
2894
+ // src/server/instance-registry.ts
2895
+ import * as os from "node:os";
2896
+ import * as path7 from "node:path";
2897
+ import * as fs6 from "node:fs/promises";
2898
+ import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2861
2899
  function defaultBaseDir() {
2862
- return path5.join(os.homedir(), ".wrongstack");
2900
+ return path7.join(os.homedir(), ".wrongstack");
2863
2901
  }
2864
2902
  function registryPath(baseDir = defaultBaseDir()) {
2865
- return path5.join(baseDir, "webui-instances.json");
2903
+ return path7.join(baseDir, "webui-instances.json");
2866
2904
  }
2867
2905
  function isPidAlive(pid) {
2868
2906
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2875,7 +2913,7 @@ function isPidAlive(pid) {
2875
2913
  }
2876
2914
  async function load(file) {
2877
2915
  try {
2878
- const raw = await fs4.readFile(file, "utf8");
2916
+ const raw = await fs6.readFile(file, "utf8");
2879
2917
  const parsed = JSON.parse(raw);
2880
2918
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2881
2919
  return parsed;
@@ -2885,7 +2923,7 @@ async function load(file) {
2885
2923
  return { version: 1, instances: [] };
2886
2924
  }
2887
2925
  async function save(file, instances) {
2888
- await atomicWrite(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
2926
+ await atomicWrite3(file, `${JSON.stringify({ version: 1, instances }, null, 2)}
2889
2927
  `, {
2890
2928
  mode: 384
2891
2929
  });
@@ -2930,6 +2968,19 @@ function formatInstances(instances) {
2930
2968
  }
2931
2969
  return lines.join("\n");
2932
2970
  }
2971
+
2972
+ // src/server/mcp-handlers.ts
2973
+ import { allServers } from "@wrongstack/core";
2974
+ import {
2975
+ addMcp,
2976
+ disableMcp,
2977
+ discoverMcp,
2978
+ enableMcp,
2979
+ listMcp,
2980
+ removeMcp,
2981
+ restartMcp,
2982
+ updateMcp
2983
+ } from "@wrongstack/mcp";
2933
2984
  function mapStatus(raw) {
2934
2985
  switch (raw) {
2935
2986
  case "connected":
@@ -2945,7 +2996,7 @@ function mapStatus(raw) {
2945
2996
  return "stopped";
2946
2997
  }
2947
2998
  }
2948
- function toView(info) {
2999
+ function toView(info, health) {
2949
3000
  const view = {
2950
3001
  name: info.name,
2951
3002
  transport: info.transport,
@@ -2957,6 +3008,7 @@ function toView(info) {
2957
3008
  };
2958
3009
  if (info.description !== void 0) view.description = info.description;
2959
3010
  if (info.lazy !== void 0) view.lazy = info.lazy;
3011
+ if (health !== void 0) view.health = health;
2960
3012
  return view;
2961
3013
  }
2962
3014
  function deps(ws, globalConfigPath, registry) {
@@ -2982,7 +3034,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
2982
3034
  registry: mcpRegistry,
2983
3035
  presets: allServers()
2984
3036
  });
2985
- send(ws, { type: "mcp.list", payload: { servers: servers.map(toView) } });
3037
+ const health = new Map(
3038
+ (typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
3039
+ );
3040
+ send(ws, {
3041
+ type: "mcp.list",
3042
+ payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
3043
+ });
2986
3044
  }
2987
3045
  async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
2988
3046
  const d = deps(ws, globalConfigPath, mcpRegistry);
@@ -3132,6 +3190,99 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
3132
3190
  payload: { success: result.ok, message: result.message }
3133
3191
  });
3134
3192
  }
3193
+ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3194
+ if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
3195
+ const payload = payloadRecord(msg);
3196
+ let serverName = "";
3197
+ try {
3198
+ serverName = requiredPayloadString(payload, "name");
3199
+ const refresh = payload["refresh"] === true;
3200
+ const [resources, resourceTemplates] = await Promise.all([
3201
+ mcpRegistry.listResources(serverName, { refresh }),
3202
+ mcpRegistry.listResourceTemplates(serverName, { refresh })
3203
+ ]);
3204
+ send(ws, {
3205
+ type: "mcp.resources",
3206
+ payload: { name: serverName, resources, resourceTemplates }
3207
+ });
3208
+ } catch (err) {
3209
+ sendContentError(ws, "resources", serverName, errorMessage(err));
3210
+ }
3211
+ }
3212
+ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3213
+ if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
3214
+ const payload = payloadRecord(msg);
3215
+ let serverName = "";
3216
+ try {
3217
+ serverName = requiredPayloadString(payload, "name");
3218
+ const prompts = await mcpRegistry.listPrompts(serverName, {
3219
+ refresh: payload["refresh"] === true
3220
+ });
3221
+ send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3222
+ } catch (err) {
3223
+ sendContentError(ws, "prompts", serverName, errorMessage(err));
3224
+ }
3225
+ }
3226
+ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3227
+ if (!mcpRegistry)
3228
+ return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
3229
+ const payload = payloadRecord(msg);
3230
+ let serverName = "";
3231
+ try {
3232
+ serverName = requiredPayloadString(payload, "name");
3233
+ const insertion = await mcpRegistry.selectResourceForInsertion(
3234
+ serverName,
3235
+ requiredPayloadString(payload, "uri")
3236
+ );
3237
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3238
+ } catch (err) {
3239
+ sendContentError(ws, "resource.read", serverName, errorMessage(err));
3240
+ }
3241
+ }
3242
+ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3243
+ if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
3244
+ const payload = payloadRecord(msg);
3245
+ let serverName = "";
3246
+ try {
3247
+ serverName = requiredPayloadString(payload, "name");
3248
+ const insertion = await mcpRegistry.selectPromptForInsertion(
3249
+ serverName,
3250
+ requiredPayloadString(payload, "prompt"),
3251
+ promptArguments(payload["arguments"])
3252
+ );
3253
+ send(ws, { type: "mcp.content.selected", payload: insertion });
3254
+ } catch (err) {
3255
+ sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3256
+ }
3257
+ }
3258
+ function payloadRecord(msg) {
3259
+ return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
3260
+ }
3261
+ function requiredPayloadString(payload, field) {
3262
+ const value = payload[field];
3263
+ if (typeof value !== "string" || value.length === 0) {
3264
+ throw new Error(`MCP payload field "${field}" must be a non-empty string`);
3265
+ }
3266
+ return value;
3267
+ }
3268
+ function promptArguments(value) {
3269
+ if (value === void 0) return void 0;
3270
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3271
+ throw new Error("MCP prompt arguments must be an object");
3272
+ }
3273
+ const args = {};
3274
+ for (const [key, item] of Object.entries(value)) {
3275
+ if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
3276
+ args[key] = item;
3277
+ }
3278
+ return args;
3279
+ }
3280
+ function sendContentError(ws, action, name2, error) {
3281
+ send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3282
+ }
3283
+ function errorMessage(err) {
3284
+ return err instanceof Error ? err.message : String(err);
3285
+ }
3135
3286
 
3136
3287
  // src/server/memory-handlers.ts
3137
3288
  async function handleMemoryList(ws, memoryStore) {
@@ -3167,6 +3318,9 @@ async function handleMemoryForget(ws, msg, memoryStore) {
3167
3318
  sendResult(ws, false, errMessage(err));
3168
3319
  }
3169
3320
  }
3321
+
3322
+ // src/server/open-browser.ts
3323
+ import { spawn } from "node:child_process";
3170
3324
  function browserOpenCommand(url, platform = process.platform) {
3171
3325
  if (platform === "win32") {
3172
3326
  return { command: "cmd", args: ["/c", "start", "", url] };
@@ -3185,7 +3339,7 @@ function openBrowser(url, platform = process.platform) {
3185
3339
  child.unref();
3186
3340
  if (child.pid) {
3187
3341
  try {
3188
- import('@wrongstack/tools').then(({ getProcessRegistry }) => {
3342
+ import("@wrongstack/tools").then(({ getProcessRegistry }) => {
3189
3343
  const pid = child.pid;
3190
3344
  if (pid === void 0) return;
3191
3345
  getProcessRegistry().register({
@@ -3207,6 +3361,10 @@ function openBrowser(url, platform = process.platform) {
3207
3361
  } catch {
3208
3362
  }
3209
3363
  }
3364
+
3365
+ // src/server/port-utils.ts
3366
+ import * as net from "node:net";
3367
+ import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3210
3368
  function isPortFree(host, port) {
3211
3369
  return new Promise((resolve10) => {
3212
3370
  const srv = net.createServer();
@@ -3232,7 +3390,7 @@ async function findFreePort(host, startPort, opts = {}) {
3232
3390
  }
3233
3391
  port++;
3234
3392
  }
3235
- throw new ToolValidationError({
3393
+ throw new ToolValidationError3({
3236
3394
  message: `No free port found near ${startPort} on ${host} after ${maxTries} attempts.`,
3237
3395
  field: "port"
3238
3396
  });
@@ -3442,10 +3600,19 @@ async function handlePromptsRecent(ws, ctx) {
3442
3600
  send(ws, { type: "prompts.recent", payload: { slugs: [], error: errMessage(err) } });
3443
3601
  }
3444
3602
  }
3603
+
3604
+ // src/server/provider-config-standalone.ts
3605
+ import * as path8 from "node:path";
3606
+ import { DefaultSecretVault } from "@wrongstack/core";
3607
+
3608
+ // src/server/provider-config-io.ts
3609
+ import * as fs7 from "node:fs/promises";
3610
+ import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3611
+ import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3445
3612
  async function loadSavedProviders(configPath, vault) {
3446
3613
  let raw;
3447
3614
  try {
3448
- raw = await fs4.readFile(configPath, "utf8");
3615
+ raw = await fs7.readFile(configPath, "utf8");
3449
3616
  } catch {
3450
3617
  return {};
3451
3618
  }
@@ -3462,7 +3629,7 @@ async function saveProviders(configPath, vault, providers) {
3462
3629
  let raw;
3463
3630
  let fileExists = true;
3464
3631
  try {
3465
- raw = await fs4.readFile(configPath, "utf8");
3632
+ raw = await fs7.readFile(configPath, "utf8");
3466
3633
  } catch (err) {
3467
3634
  if (err.code !== "ENOENT") {
3468
3635
  throw new ConfigError({
@@ -3491,16 +3658,21 @@ async function saveProviders(configPath, vault, providers) {
3491
3658
  }
3492
3659
  parsed.providers = providers;
3493
3660
  const encrypted = encryptConfigSecrets(parsed, vault);
3494
- await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3661
+ await atomicWrite4(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3495
3662
  }
3663
+
3664
+ // src/server/provider-config-standalone.ts
3496
3665
  function createProviderConfigIO(configPath) {
3497
- const keyFile = path5.join(path5.dirname(configPath), ".key");
3666
+ const keyFile = path8.join(path8.dirname(configPath), ".key");
3498
3667
  const vault = new DefaultSecretVault({ keyFile });
3499
3668
  return {
3500
3669
  load: () => loadSavedProviders(configPath, vault),
3501
3670
  save: (providers) => saveProviders(configPath, vault, providers)
3502
3671
  };
3503
3672
  }
3673
+
3674
+ // src/server/provider-keys.ts
3675
+ import { expectDefined } from "@wrongstack/core";
3504
3676
  function normalizeKeys(cfg) {
3505
3677
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3506
3678
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3594,6 +3766,12 @@ function removeProvider(providers, providerId) {
3594
3766
  delete providers[providerId];
3595
3767
  return { ok: true, message: `Provider "${providerId}" removed` };
3596
3768
  }
3769
+
3770
+ // src/server/sdd-board-ws-handler.ts
3771
+ import {
3772
+ applySddLifecycle,
3773
+ SddBoardStore
3774
+ } from "@wrongstack/sdd";
3597
3775
  var CONTROL_TYPES = /* @__PURE__ */ new Set([
3598
3776
  "pause",
3599
3777
  "resume",
@@ -3738,6 +3916,26 @@ var SddBoardWebSocketHandler = class {
3738
3916
  if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
3739
3917
  }
3740
3918
  };
3919
+
3920
+ // src/server/sdd-wizard-wiring.ts
3921
+ import * as path9 from "node:path";
3922
+ import { spawnSync as spawnSync2 } from "node:child_process";
3923
+ import {
3924
+ WorktreeManager as WorktreeManager2,
3925
+ ToolValidationError as ToolValidationError4
3926
+ } from "@wrongstack/core";
3927
+ import {
3928
+ cleanupStaleSddWorktrees,
3929
+ makeCommandVerifier,
3930
+ makeLlmSubtaskGenerator,
3931
+ SddBoardStore as SddBoardStore2,
3932
+ SddInterviewDriver,
3933
+ SddRunRegistry,
3934
+ SddSupervisor,
3935
+ SpecStore,
3936
+ startSddRun,
3937
+ TaskGraphStore
3938
+ } from "@wrongstack/sdd";
3741
3939
  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";
3742
3940
  function buildSddWizardDeps(opts) {
3743
3941
  const registry = new SddRunRegistry();
@@ -3761,14 +3959,14 @@ function buildSddWizardDeps(opts) {
3761
3959
  makeDriver: () => new SddInterviewDriver({
3762
3960
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
3763
3961
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
3764
- sessionPath: path5.join(opts.paths.projectDir, "sdd-wizard-session.json")
3962
+ sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
3765
3963
  }),
3766
3964
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
3767
3965
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
3768
3966
  const graph = driver.getGraph();
3769
3967
  const tracker = driver.getTracker();
3770
3968
  if (!graph || !tracker) {
3771
- throw new ToolValidationError({
3969
+ throw new ToolValidationError4({
3772
3970
  message: "No task graph to run \u2014 finish the interview first.",
3773
3971
  field: "taskGraph"
3774
3972
  });
@@ -3776,7 +3974,7 @@ function buildSddWizardDeps(opts) {
3776
3974
  const worktreesEnabled = useWorktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
3777
3975
  let worktrees;
3778
3976
  if (worktreesEnabled) {
3779
- const inGit = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
3977
+ const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
3780
3978
  cwd: opts.projectRoot,
3781
3979
  encoding: "utf8",
3782
3980
  windowsHide: true
@@ -3786,14 +3984,14 @@ function buildSddWizardDeps(opts) {
3786
3984
  projectRoot: opts.projectRoot,
3787
3985
  boardsDir: opts.paths.projectSddBoards
3788
3986
  }).catch(() => void 0);
3789
- worktrees = new WorktreeManager({
3987
+ worktrees = new WorktreeManager2({
3790
3988
  projectRoot: opts.projectRoot,
3791
3989
  events: opts.events,
3792
3990
  sessionId: () => opts.agent.ctx.session?.id
3793
3991
  });
3794
3992
  }
3795
3993
  }
3796
- const boardStore = new SddBoardStore({ baseDir: opts.paths.projectSddBoards });
3994
+ const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
3797
3995
  const verifyTask = makeCommandVerifier();
3798
3996
  const superviseFailure = opts.brain ? new SddSupervisor({
3799
3997
  brain: opts.brain,
@@ -3973,20 +4171,25 @@ var SddWizardWebSocketHandler = class {
3973
4171
  if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
3974
4172
  }
3975
4173
  };
4174
+
4175
+ // src/server/shell-open.ts
4176
+ import * as fs8 from "node:fs/promises";
4177
+ import * as path10 from "node:path";
4178
+ import { spawn as spawn2 } from "node:child_process";
3976
4179
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
3977
4180
  function shellQuote(s) {
3978
4181
  return s.replaceAll("'", `'"'"'`);
3979
4182
  }
3980
4183
  async function handleShellOpen(req, logger) {
3981
4184
  try {
3982
- const resolved = path5.resolve(req.path);
3983
- await fs4.access(resolved);
4185
+ const resolved = path10.resolve(req.path);
4186
+ await fs8.access(resolved);
3984
4187
  if (METACHAR_REGEX.test(resolved)) {
3985
4188
  return { success: false, message: "Path contains unsupported characters." };
3986
4189
  }
3987
4190
  const platform = process.platform;
3988
4191
  const launch = (cmd, args, onError) => {
3989
- const child = spawn(cmd, args, {
4192
+ const child = spawn2(cmd, args, {
3990
4193
  detached: true,
3991
4194
  stdio: "ignore",
3992
4195
  windowsHide: true
@@ -4030,6 +4233,12 @@ async function handleShellOpen(req, logger) {
4030
4233
  }
4031
4234
  }
4032
4235
 
4236
+ // src/server/skills-handlers.ts
4237
+ import { promises as fs9 } from "node:fs";
4238
+ import path11 from "node:path";
4239
+ import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4240
+ import { wstackGlobalRoot } from "@wrongstack/core/utils";
4241
+
4033
4242
  // src/server/ws-payload-validation.ts
4034
4243
  function isRecord(value) {
4035
4244
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4163,6 +4372,16 @@ function validateBrainAskPayload(payload) {
4163
4372
  }
4164
4373
  return { ok: true, value: { question: question.trim() } };
4165
4374
  }
4375
+ function validateBrainConfigSetPayload(payload) {
4376
+ if (!isRecord(payload)) {
4377
+ return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
4378
+ }
4379
+ const patch = payload["patch"];
4380
+ if (!isRecord(patch)) {
4381
+ return { ok: false, message: "brain.config.set payload.patch must be an object" };
4382
+ }
4383
+ return { ok: true, value: { patch } };
4384
+ }
4166
4385
  function validateAutonomySwitchPayload(payload) {
4167
4386
  if (!isRecord(payload)) {
4168
4387
  return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
@@ -4224,7 +4443,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4224
4443
  "hqEnabled",
4225
4444
  "hqRawContent",
4226
4445
  "fallbackAuto",
4227
- "favoriteModelsOnly"
4446
+ "favoriteModelsOnly",
4447
+ "breakerEnabled",
4448
+ "debugStream"
4228
4449
  ]);
4229
4450
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4230
4451
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
@@ -4235,9 +4456,17 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4235
4456
  "maxIterations",
4236
4457
  "maxConcurrent",
4237
4458
  "enhanceDelayMs",
4238
- "tgLongToolMs"
4459
+ "tgLongToolMs",
4460
+ "breakerAutoKillResetMs"
4461
+ ]);
4462
+ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4463
+ "hqUrl",
4464
+ "hqToken",
4465
+ "uiLocale",
4466
+ "thinkingWord",
4467
+ "refinerProvider",
4468
+ "refinerModel"
4239
4469
  ]);
4240
- var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
4241
4470
  var ENUM_PREF_KEYS = {
4242
4471
  autonomy: AUTONOMY_VALUES,
4243
4472
  contextStrategy: CONTEXT_STRATEGY_VALUES,
@@ -4248,7 +4477,10 @@ var ENUM_PREF_KEYS = {
4248
4477
  auditLevel: AUDIT_LEVEL_VALUES,
4249
4478
  reasoningMode: REASONING_MODE_VALUES,
4250
4479
  reasoningEffort: REASONING_EFFORT_VALUES,
4251
- cacheTtl: CACHE_TTL_VALUES
4480
+ cacheTtl: CACHE_TTL_VALUES,
4481
+ statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4482
+ animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4483
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
4252
4484
  };
4253
4485
  function validateModelRuntimeValue(modelRuntime, path23) {
4254
4486
  const reasoning = modelRuntime["reasoning"];
@@ -4624,6 +4856,123 @@ function validateProjectsSelectPayload(payload) {
4624
4856
  };
4625
4857
  }
4626
4858
 
4859
+ // src/server/zip.ts
4860
+ import { deflateRawSync, inflateRawSync } from "node:zlib";
4861
+ function createZipBuffer(entries) {
4862
+ const localParts = [];
4863
+ const centralParts = [];
4864
+ let offset = 0;
4865
+ const { date, time } = dosDateTime(/* @__PURE__ */ new Date());
4866
+ for (const entry of entries) {
4867
+ const name2 = Buffer.from(normalizeZipPath(entry.name), "utf8");
4868
+ const data = typeof entry.data === "string" ? Buffer.from(entry.data, "utf8") : Buffer.from(entry.data);
4869
+ const compressed = deflateRawSync(data);
4870
+ const crc = crc32(data);
4871
+ const local = Buffer.alloc(30);
4872
+ local.writeUInt32LE(67324752, 0);
4873
+ local.writeUInt16LE(20, 4);
4874
+ local.writeUInt16LE(2048, 6);
4875
+ local.writeUInt16LE(8, 8);
4876
+ local.writeUInt16LE(time, 10);
4877
+ local.writeUInt16LE(date, 12);
4878
+ local.writeUInt32LE(crc, 14);
4879
+ local.writeUInt32LE(compressed.length, 18);
4880
+ local.writeUInt32LE(data.length, 22);
4881
+ local.writeUInt16LE(name2.length, 26);
4882
+ localParts.push(local, name2, compressed);
4883
+ const central = Buffer.alloc(46);
4884
+ central.writeUInt32LE(33639248, 0);
4885
+ central.writeUInt16LE(798, 4);
4886
+ central.writeUInt16LE(20, 6);
4887
+ central.writeUInt16LE(2048, 8);
4888
+ central.writeUInt16LE(8, 10);
4889
+ central.writeUInt16LE(time, 12);
4890
+ central.writeUInt16LE(date, 14);
4891
+ central.writeUInt32LE(crc, 16);
4892
+ central.writeUInt32LE(compressed.length, 20);
4893
+ central.writeUInt32LE(data.length, 24);
4894
+ central.writeUInt16LE(name2.length, 28);
4895
+ central.writeUInt32LE(offset, 42);
4896
+ centralParts.push(central, name2);
4897
+ offset += local.length + name2.length + compressed.length;
4898
+ }
4899
+ const centralDirectory = Buffer.concat(centralParts);
4900
+ const end = Buffer.alloc(22);
4901
+ end.writeUInt32LE(101010256, 0);
4902
+ end.writeUInt16LE(entries.length, 8);
4903
+ end.writeUInt16LE(entries.length, 10);
4904
+ end.writeUInt32LE(centralDirectory.length, 12);
4905
+ end.writeUInt32LE(offset, 16);
4906
+ return Buffer.concat([...localParts, centralDirectory, end]);
4907
+ }
4908
+ function readZipEntries(archive) {
4909
+ const maxEntries = 1e3;
4910
+ const maxEntryBytes = 64 * 1024 * 1024;
4911
+ const maxTotalBytes = 128 * 1024 * 1024;
4912
+ const buffer = Buffer.from(archive);
4913
+ const result = /* @__PURE__ */ new Map();
4914
+ let totalBytes = 0;
4915
+ let offset = 0;
4916
+ while (offset + 4 <= buffer.length && buffer.readUInt32LE(offset) === 67324752) {
4917
+ if (offset + 30 > buffer.length) throw new Error("Truncated ZIP local header.");
4918
+ const flags = buffer.readUInt16LE(offset + 6);
4919
+ const method = buffer.readUInt16LE(offset + 8);
4920
+ if ((flags & 1) !== 0) throw new Error("Encrypted ZIP entries are not supported.");
4921
+ if ((flags & 8) !== 0) throw new Error("ZIP data descriptors are not supported.");
4922
+ const compressedSize = buffer.readUInt32LE(offset + 18);
4923
+ const expectedSize = buffer.readUInt32LE(offset + 22);
4924
+ if (result.size >= maxEntries) throw new Error("ZIP contains too many entries.");
4925
+ if (expectedSize > maxEntryBytes || totalBytes + expectedSize > maxTotalBytes) {
4926
+ throw new Error("ZIP expands beyond the configured size limit.");
4927
+ }
4928
+ if (compressedSize > 0 && expectedSize / compressedSize > 1e3) {
4929
+ throw new Error("ZIP entry has a suspicious compression ratio.");
4930
+ }
4931
+ const nameLength = buffer.readUInt16LE(offset + 26);
4932
+ const extraLength = buffer.readUInt16LE(offset + 28);
4933
+ const nameStart = offset + 30;
4934
+ const dataStart = nameStart + nameLength + extraLength;
4935
+ const dataEnd = dataStart + compressedSize;
4936
+ if (dataEnd > buffer.length) throw new Error("Truncated ZIP entry.");
4937
+ const name2 = buffer.subarray(nameStart, nameStart + nameLength).toString("utf8");
4938
+ if (!isSafeZipPath(name2)) throw new Error(`Unsafe ZIP entry path: ${name2}`);
4939
+ const compressed = buffer.subarray(dataStart, dataEnd);
4940
+ const data = method === 0 ? Buffer.from(compressed) : method === 8 ? inflateRawSync(compressed) : void 0;
4941
+ if (!data) throw new Error(`Unsupported ZIP compression method: ${method}`);
4942
+ if (data.length !== expectedSize) throw new Error(`ZIP size mismatch for ${name2}`);
4943
+ if (crc32(data) !== buffer.readUInt32LE(offset + 14)) throw new Error(`ZIP CRC mismatch for ${name2}`);
4944
+ result.set(name2, data);
4945
+ totalBytes += data.length;
4946
+ offset = dataEnd;
4947
+ }
4948
+ return result;
4949
+ }
4950
+ function normalizeZipPath(value) {
4951
+ const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
4952
+ if (!isSafeZipPath(normalized)) throw new Error(`Unsafe ZIP entry path: ${value}`);
4953
+ return normalized;
4954
+ }
4955
+ function isSafeZipPath(value) {
4956
+ return value.length > 0 && !value.includes("\0") && !value.startsWith("/") && !/^[a-z]:/i.test(value) && !value.split("/").includes("..");
4957
+ }
4958
+ function dosDateTime(value) {
4959
+ const year = Math.max(1980, Math.min(2107, value.getFullYear()));
4960
+ return {
4961
+ date: year - 1980 << 9 | value.getMonth() + 1 << 5 | value.getDate(),
4962
+ time: value.getHours() << 11 | value.getMinutes() << 5 | Math.floor(value.getSeconds() / 2)
4963
+ };
4964
+ }
4965
+ var CRC_TABLE = Array.from({ length: 256 }, (_, index) => {
4966
+ let crc = index;
4967
+ for (let bit = 0; bit < 8; bit++) crc = (crc & 1) !== 0 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
4968
+ return crc >>> 0;
4969
+ });
4970
+ function crc32(data) {
4971
+ let crc = 4294967295;
4972
+ for (const byte of data) crc = crc >>> 8 ^ (CRC_TABLE[(crc ^ byte) & 255] ?? 0);
4973
+ return (crc ^ 4294967295) >>> 0;
4974
+ }
4975
+
4627
4976
  // src/server/skills-handlers.ts
4628
4977
  async function handleSkillsList(ws, ctx) {
4629
4978
  if (!ctx.skillLoader) {
@@ -4692,19 +5041,19 @@ async function handleSkillsContent(ws, ctx, msg) {
4692
5041
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
4693
5042
  return;
4694
5043
  }
4695
- const body = await promises.readFile(entry.path, "utf8");
4696
- const skillDir = path5__default.dirname(entry.path);
5044
+ const body = await fs9.readFile(entry.path, "utf8");
5045
+ const skillDir = path11.dirname(entry.path);
4697
5046
  let relatedFiles = [];
4698
5047
  try {
4699
- const files = await promises.readdir(skillDir);
4700
- relatedFiles = files.filter((f) => f !== path5__default.basename(entry.path)).map((f) => path5__default.join(skillDir, f));
5048
+ const files = await fs9.readdir(skillDir);
5049
+ relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
4701
5050
  } catch {
4702
5051
  }
4703
5052
  const nameLower = name2.toLowerCase();
4704
5053
  const refResults = await Promise.all(
4705
5054
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
4706
5055
  try {
4707
- const content = await promises.readFile(e.path, "utf8");
5056
+ const content = await fs9.readFile(e.path, "utf8");
4708
5057
  return [e.name, content.toLowerCase().includes(nameLower)];
4709
5058
  } catch {
4710
5059
  return [e.name, false];
@@ -4794,20 +5143,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
4794
5143
  }
4795
5144
  const createPayload = parsed.value;
4796
5145
  try {
4797
- const targetDir = createPayload.scope === "global" ? path5__default.join(
4798
- ctx.globalSkillsDir ?? path5__default.join(wstackGlobalRoot(), "skills"),
5146
+ const targetDir = createPayload.scope === "global" ? path11.join(
5147
+ ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
4799
5148
  createPayload.name.trim()
4800
- ) : path5__default.join(
4801
- ctx.projectSkillsDir ?? path5__default.join(ctx.projectRoot, ".wrongstack", "skills"),
5149
+ ) : path11.join(
5150
+ ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
4802
5151
  createPayload.name.trim()
4803
5152
  );
4804
5153
  try {
4805
- await promises.access(targetDir);
5154
+ await fs9.access(targetDir);
4806
5155
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
4807
5156
  return;
4808
5157
  } catch {
4809
5158
  }
4810
- await promises.mkdir(targetDir, { recursive: true });
5159
+ await fs9.mkdir(targetDir, { recursive: true });
4811
5160
  const lines = createPayload.description.trim().split("\n");
4812
5161
  const firstLine = (lines[0] ?? "").trim();
4813
5162
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -4855,13 +5204,13 @@ ${trigger}
4855
5204
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
4856
5205
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
4857
5206
  ].join("\n");
4858
- await atomicWrite(path5__default.join(targetDir, "SKILL.md"), skillContent);
5207
+ await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
4859
5208
  send(ws, {
4860
5209
  type: "skills.created",
4861
5210
  payload: {
4862
5211
  success: true,
4863
5212
  error: null,
4864
- skill: { name: createPayload.name.trim(), path: path5__default.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5213
+ skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
4865
5214
  }
4866
5215
  });
4867
5216
  } catch (err) {
@@ -4891,7 +5240,7 @@ async function handleSkillsEdit(ws, ctx, msg) {
4891
5240
  send(ws, { type: "skills.edited", payload: { success: false, error: `${label} skills cannot be edited` } });
4892
5241
  return;
4893
5242
  }
4894
- await atomicWrite(entry.path, editPayload.body);
5243
+ await atomicWrite5(entry.path, editPayload.body);
4895
5244
  send(ws, { type: "skills.edited", payload: { success: true, error: null } });
4896
5245
  } catch (err) {
4897
5246
  send(ws, { type: "skills.edited", payload: { success: false, error: errMessage(err) } });
@@ -4904,29 +5253,35 @@ async function handleSkillsExport(ws, ctx) {
4904
5253
  }
4905
5254
  try {
4906
5255
  const entries = await ctx.skillLoader.listEntries();
4907
- const zip = new JSZip();
5256
+ const zipEntries = [];
4908
5257
  for (const entry of entries) {
4909
5258
  try {
4910
5259
  const body = await ctx.skillLoader.readBody(entry.name);
4911
5260
  const safeName = entry.name.replace(/\//g, "_");
4912
- zip.file(`${safeName}/SKILL.md`, body);
5261
+ zipEntries.push({ name: `${safeName}/SKILL.md`, data: body });
4913
5262
  } catch {
4914
5263
  }
4915
5264
  }
4916
- const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
5265
+ const zipBuffer = createZipBuffer(zipEntries);
4917
5266
  const zipBase64 = zipBuffer.toString("base64");
4918
5267
  send(ws, { type: "skills.exported", payload: { zipBase64, skillCount: entries.length, error: void 0 } });
4919
5268
  } catch (err) {
4920
5269
  send(ws, { type: "skills.exported", payload: { zipBase64: "", skillCount: 0, error: errMessage(err) } });
4921
5270
  }
4922
5271
  }
5272
+
5273
+ // src/server/specs-ws-handler.ts
5274
+ import {
5275
+ computeTaskProgress
5276
+ } from "@wrongstack/core";
5277
+ import { SpecStore as SpecStore2, TaskGraphStore as TaskGraphStore2 } from "@wrongstack/sdd";
4923
5278
  var SpecsWebSocketHandler = class {
4924
5279
  specStore;
4925
5280
  graphStore;
4926
5281
  clients = /* @__PURE__ */ new Set();
4927
5282
  constructor(specsDir, taskGraphsDir) {
4928
- this.specStore = new SpecStore({ baseDir: specsDir });
4929
- this.graphStore = new TaskGraphStore({ baseDir: taskGraphsDir });
5283
+ this.specStore = new SpecStore2({ baseDir: specsDir });
5284
+ this.graphStore = new TaskGraphStore2({ baseDir: taskGraphsDir });
4930
5285
  }
4931
5286
  addClient(ws) {
4932
5287
  const client = { ws, id: crypto.randomUUID() };
@@ -5130,6 +5485,13 @@ function estimateContextBreakdown(input) {
5130
5485
  messages: { total: msgTokens, count: input.messages.length, breakdown: messageBreakdown }
5131
5486
  };
5132
5487
  }
5488
+
5489
+ // src/server/worktree-ws-handler.ts
5490
+ import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
5491
+ import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5492
+ import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5493
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
5494
+ var MAX_ACTIVITY = 6;
5133
5495
  var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
5134
5496
  var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
5135
5497
  var WorktreeWebSocketHandler = class {
@@ -5186,13 +5548,13 @@ var WorktreeWebSocketHandler = class {
5186
5548
  // ── orphan management ─────────────────────────────────────────────────────
5187
5549
  /** Absolute managed-worktrees root for this project. */
5188
5550
  worktreesRoot() {
5189
- return resolve(join(this.management.projectRoot, ".wrongstack", "worktrees"));
5551
+ return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
5190
5552
  }
5191
5553
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5192
5554
  underRoot(dir) {
5193
- const abs = resolve(dir);
5555
+ const abs = resolve6(dir);
5194
5556
  const root = this.worktreesRoot();
5195
- return abs !== root && abs.startsWith(root + sep);
5557
+ return abs !== root && abs.startsWith(root + sep3);
5196
5558
  }
5197
5559
  /** Branches of worktrees a live in-session run currently owns. */
5198
5560
  liveActiveBranches() {
@@ -5213,7 +5575,7 @@ var WorktreeWebSocketHandler = class {
5213
5575
  return;
5214
5576
  }
5215
5577
  try {
5216
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5578
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5217
5579
  const { worktrees, branches } = await wt.listManaged();
5218
5580
  const live = this.liveActiveBranches();
5219
5581
  const orphans = [];
@@ -5237,7 +5599,7 @@ var WorktreeWebSocketHandler = class {
5237
5599
  }
5238
5600
  });
5239
5601
  } catch (err) {
5240
- this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage(err)}`);
5602
+ this.logger.debug?.(`worktree orphan scan failed: ${toErrorMessage2(err)}`);
5241
5603
  this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
5242
5604
  }
5243
5605
  }
@@ -5261,7 +5623,7 @@ var WorktreeWebSocketHandler = class {
5261
5623
  });
5262
5624
  return;
5263
5625
  }
5264
- const res = await cleanupStaleSddWorktrees({
5626
+ const res = await cleanupStaleSddWorktrees2({
5265
5627
  projectRoot: this.management.projectRoot,
5266
5628
  boardsDir: this.management.boardsDir
5267
5629
  });
@@ -5300,7 +5662,7 @@ var WorktreeWebSocketHandler = class {
5300
5662
  }
5301
5663
  let removed = false;
5302
5664
  if (dir) {
5303
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5665
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5304
5666
  ({ removed } = await wt.removeOne(dir, branch));
5305
5667
  }
5306
5668
  for (const [id, h] of [...this.handles]) {
@@ -5324,7 +5686,7 @@ var WorktreeWebSocketHandler = class {
5324
5686
  this.broadcast({ type: "worktree.merge_result", payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" } });
5325
5687
  return;
5326
5688
  }
5327
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5689
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5328
5690
  const res = await wt.mergeBranch(branch);
5329
5691
  this.broadcast({
5330
5692
  type: "worktree.merge_result",
@@ -5339,8 +5701,8 @@ var WorktreeWebSocketHandler = class {
5339
5701
  return;
5340
5702
  }
5341
5703
  const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
5342
- const wt = new WorktreeManager({ projectRoot: this.management.projectRoot });
5343
- const summary = await wt.diffSummary(resolve(dir), base);
5704
+ const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
5705
+ const summary = await wt.diffSummary(resolve6(dir), base);
5344
5706
  this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
5345
5707
  }
5346
5708
  // ── internals ───────────────────────────────────────────────────────────
@@ -5412,7 +5774,7 @@ var WorktreeWebSocketHandler = class {
5412
5774
  activity(id, kind, text) {
5413
5775
  const cur = this.handles.get(id);
5414
5776
  if (cur) {
5415
- const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-6);
5777
+ const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(-MAX_ACTIVITY);
5416
5778
  this.handles.set(id, { ...cur, recentActivity });
5417
5779
  }
5418
5780
  this.broadcast({ type: "worktree.event", payload: { kind, handleId: id, text, at: Date.now() } });
@@ -5444,7 +5806,7 @@ var WorktreeWebSocketHandler = class {
5444
5806
  try {
5445
5807
  if (ws.readyState === 1) ws.send(data);
5446
5808
  } catch (err) {
5447
- this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage(err)}`);
5809
+ this.logger.debug?.(`worktree broadcast failed: ${toErrorMessage2(err)}`);
5448
5810
  }
5449
5811
  }
5450
5812
  }
@@ -5456,6 +5818,12 @@ var WorktreeWebSocketHandler = class {
5456
5818
  }
5457
5819
  };
5458
5820
 
5821
+ // src/server/server-runtime.ts
5822
+ import * as path13 from "node:path";
5823
+ import { createRequire } from "node:module";
5824
+ import { fileURLToPath } from "node:url";
5825
+ import { WebSocketServer } from "ws";
5826
+
5459
5827
  // src/server/lifecycle.ts
5460
5828
  function createShutdown(res) {
5461
5829
  const log = res.log ?? ((m) => console.log(m));
@@ -5491,9 +5859,14 @@ function registerShutdownHandlers(res) {
5491
5859
  process.off("SIGTERM", shutdown);
5492
5860
  };
5493
5861
  }
5862
+
5863
+ // src/server/setup-events.ts
5864
+ import * as fs10 from "node:fs/promises";
5865
+ import { watch as fsWatch } from "node:fs";
5866
+ import * as path12 from "node:path";
5494
5867
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
5495
5868
  const raw = String(filename);
5496
- const relative4 = path5.isAbsolute(raw) ? path5.relative(projectsDir, raw) : raw;
5869
+ const relative4 = path12.isAbsolute(raw) ? path12.relative(projectsDir, raw) : raw;
5497
5870
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
5498
5871
  if (parts.length < 2) return null;
5499
5872
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -5506,6 +5879,10 @@ function shouldLogWatcherStats() {
5506
5879
  function setupEvents(deps2) {
5507
5880
  const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
5508
5881
  const disposers = [];
5882
+ let disposed = false;
5883
+ const on = (event, listener) => {
5884
+ disposers.push(events.on(event, listener));
5885
+ };
5509
5886
  const currentSessionId = () => context.session?.id ?? "";
5510
5887
  const sessionPayload2 = (payload) => {
5511
5888
  const provided = payload["sessionId"];
@@ -5521,20 +5898,20 @@ function setupEvents(deps2) {
5521
5898
  sessionBridge?.append(event).catch(() => {
5522
5899
  });
5523
5900
  };
5524
- events.on("iteration.started", (e) => {
5901
+ on("iteration.started", (e) => {
5525
5902
  const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
5526
5903
  broadcast2(clients, {
5527
5904
  type: "iteration.started",
5528
5905
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
5529
5906
  });
5530
5907
  });
5531
- events.on("iteration.completed", (e) => {
5908
+ on("iteration.completed", (e) => {
5532
5909
  broadcast2(clients, {
5533
5910
  type: "iteration.completed",
5534
5911
  payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
5535
5912
  });
5536
5913
  });
5537
- events.on("iteration.limit_reached", (e) => {
5914
+ on("iteration.limit_reached", (e) => {
5538
5915
  broadcast2(clients, {
5539
5916
  type: "iteration.limit_reached",
5540
5917
  payload: sessionPayload2({
@@ -5544,19 +5921,19 @@ function setupEvents(deps2) {
5544
5921
  })
5545
5922
  });
5546
5923
  });
5547
- events.on("provider.text_delta", (e) => {
5924
+ on("provider.text_delta", (e) => {
5548
5925
  broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
5549
5926
  });
5550
- events.on("provider.thinking_delta", (e) => {
5927
+ on("provider.thinking_delta", (e) => {
5551
5928
  broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
5552
5929
  });
5553
- events.on("provider.stream_error", (e) => {
5930
+ on("provider.stream_error", (e) => {
5554
5931
  broadcast2(clients, {
5555
5932
  type: "provider.stream_error",
5556
5933
  payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
5557
5934
  });
5558
5935
  });
5559
- events.on("tool.started", (e) => {
5936
+ on("tool.started", (e) => {
5560
5937
  broadcast2(clients, {
5561
5938
  type: "tool.started",
5562
5939
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
@@ -5569,7 +5946,7 @@ function setupEvents(deps2) {
5569
5946
  input: e.input
5570
5947
  });
5571
5948
  });
5572
- events.on("tool.progress", (e) => {
5949
+ on("tool.progress", (e) => {
5573
5950
  broadcast2(clients, {
5574
5951
  type: "tool.progress",
5575
5952
  // Nested `event` shape — the client handler reads `payload.event?.text`
@@ -5590,7 +5967,7 @@ function setupEvents(deps2) {
5590
5967
  }
5591
5968
  });
5592
5969
  });
5593
- events.on("tool.executed", (e) => {
5970
+ on("tool.executed", (e) => {
5594
5971
  broadcast2(clients, {
5595
5972
  type: "tool.executed",
5596
5973
  payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
@@ -5630,7 +6007,7 @@ function setupEvents(deps2) {
5630
6007
  try {
5631
6008
  const taskPath = context.meta["task.path"];
5632
6009
  if (typeof taskPath === "string" && taskPath) {
5633
- const { loadTasks } = await import('@wrongstack/core');
6010
+ const { loadTasks } = await import("@wrongstack/core");
5634
6011
  const file = await loadTasks(taskPath);
5635
6012
  broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
5636
6013
  }
@@ -5639,7 +6016,7 @@ function setupEvents(deps2) {
5639
6016
  try {
5640
6017
  const planPath = context.meta["plan.path"];
5641
6018
  if (typeof planPath === "string" && planPath) {
5642
- const { loadPlan } = await import('@wrongstack/core');
6019
+ const { loadPlan } = await import("@wrongstack/core");
5643
6020
  const plan = await loadPlan(planPath);
5644
6021
  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: [] } }) });
5645
6022
  }
@@ -5648,7 +6025,7 @@ function setupEvents(deps2) {
5648
6025
  })();
5649
6026
  }
5650
6027
  });
5651
- events.on("tool.loop_detected", (e) => {
6028
+ on("tool.loop_detected", (e) => {
5652
6029
  broadcast2(clients, {
5653
6030
  type: "tool.loop_detected",
5654
6031
  payload: sessionPayload2({
@@ -5660,19 +6037,19 @@ function setupEvents(deps2) {
5660
6037
  })
5661
6038
  });
5662
6039
  });
5663
- events.on("trust.persisted", (e) => {
6040
+ on("trust.persisted", (e) => {
5664
6041
  broadcast2(clients, {
5665
6042
  type: "trust.persisted",
5666
6043
  payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
5667
6044
  });
5668
6045
  });
5669
- events.on("delegate.started", (e) => {
6046
+ on("delegate.started", (e) => {
5670
6047
  broadcast2(clients, {
5671
6048
  type: "delegate.started",
5672
6049
  payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
5673
6050
  });
5674
6051
  });
5675
- events.on("delegate.completed", (e) => {
6052
+ on("delegate.completed", (e) => {
5676
6053
  broadcast2(clients, {
5677
6054
  type: "delegate.completed",
5678
6055
  payload: sessionPayload2({
@@ -5690,7 +6067,7 @@ function setupEvents(deps2) {
5690
6067
  })
5691
6068
  });
5692
6069
  });
5693
- events.on("provider.response", (e) => {
6070
+ on("provider.response", (e) => {
5694
6071
  broadcast2(clients, {
5695
6072
  type: "provider.response",
5696
6073
  payload: sessionPayload2({
@@ -5702,7 +6079,7 @@ function setupEvents(deps2) {
5702
6079
  })
5703
6080
  });
5704
6081
  });
5705
- events.on("ctx.pct", (e) => {
6082
+ on("ctx.pct", (e) => {
5706
6083
  broadcast2(clients, {
5707
6084
  type: "ctx.pct",
5708
6085
  payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
@@ -5719,28 +6096,28 @@ function setupEvents(deps2) {
5719
6096
  })
5720
6097
  });
5721
6098
  });
5722
- events.on("ctx.max_context", (e) => {
6099
+ on("ctx.max_context", (e) => {
5723
6100
  broadcast2(clients, {
5724
6101
  type: "ctx.max_context",
5725
6102
  payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
5726
6103
  });
5727
6104
  });
5728
- events.on("token.threshold", (e) => {
6105
+ on("token.threshold", (e) => {
5729
6106
  broadcast2(clients, {
5730
6107
  type: "token.threshold",
5731
6108
  payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
5732
6109
  });
5733
6110
  });
5734
- events.on("token.cost_estimate_unavailable", (e) => {
6111
+ on("token.cost_estimate_unavailable", (e) => {
5735
6112
  broadcast2(clients, {
5736
6113
  type: "token.cost_estimate_unavailable",
5737
6114
  payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
5738
6115
  });
5739
6116
  });
5740
- events.on("context.repaired", (e) => {
6117
+ on("context.repaired", (e) => {
5741
6118
  broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
5742
6119
  });
5743
- events.on("tool.confirm_needed", (e) => {
6120
+ on("tool.confirm_needed", (e) => {
5744
6121
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
5745
6122
  const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
5746
6123
  pendingConfirms.set(id, {
@@ -5751,7 +6128,7 @@ function setupEvents(deps2) {
5751
6128
  });
5752
6129
  broadcast2(clients, { type: "tool.confirm_needed", payload });
5753
6130
  });
5754
- events.on("error", (e) => {
6131
+ on("error", (e) => {
5755
6132
  broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
5756
6133
  appendForCurrentSession(e.sessionId, {
5757
6134
  type: "error",
@@ -5760,13 +6137,13 @@ function setupEvents(deps2) {
5760
6137
  phase: e.phase
5761
6138
  });
5762
6139
  });
5763
- events.on("session.damaged", (e) => {
6140
+ on("session.damaged", (e) => {
5764
6141
  broadcast2(clients, {
5765
6142
  type: "session.damaged",
5766
6143
  payload: { sessionId: e.sessionId, detail: e.detail }
5767
6144
  });
5768
6145
  });
5769
- events.on("session.rewound", (e) => {
6146
+ on("session.rewound", (e) => {
5770
6147
  broadcast2(clients, {
5771
6148
  type: "session.rewound",
5772
6149
  payload: sessionPayload2({
@@ -5777,7 +6154,7 @@ function setupEvents(deps2) {
5777
6154
  })
5778
6155
  });
5779
6156
  });
5780
- events.on("checkpoint.written", (e) => {
6157
+ on("checkpoint.written", (e) => {
5781
6158
  broadcast2(clients, {
5782
6159
  type: "checkpoint.written",
5783
6160
  payload: sessionPayload2({
@@ -5789,19 +6166,19 @@ function setupEvents(deps2) {
5789
6166
  })
5790
6167
  });
5791
6168
  });
5792
- events.on("in_flight.started", (e) => {
6169
+ on("in_flight.started", (e) => {
5793
6170
  broadcast2(clients, {
5794
6171
  type: "in_flight.started",
5795
6172
  payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
5796
6173
  });
5797
6174
  });
5798
- events.on("in_flight.ended", (e) => {
6175
+ on("in_flight.ended", (e) => {
5799
6176
  broadcast2(clients, {
5800
6177
  type: "in_flight.ended",
5801
6178
  payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
5802
6179
  });
5803
6180
  });
5804
- events.on("provider.retry", (e) => {
6181
+ on("provider.retry", (e) => {
5805
6182
  broadcast2(clients, {
5806
6183
  type: "provider.retry",
5807
6184
  payload: sessionPayload2({
@@ -5823,7 +6200,7 @@ function setupEvents(deps2) {
5823
6200
  description: e.description
5824
6201
  });
5825
6202
  });
5826
- events.on("provider.error", (e) => {
6203
+ on("provider.error", (e) => {
5827
6204
  broadcast2(clients, {
5828
6205
  type: "provider.error",
5829
6206
  payload: sessionPayload2({
@@ -5843,7 +6220,7 @@ function setupEvents(deps2) {
5843
6220
  retryable: e.retryable
5844
6221
  });
5845
6222
  });
5846
- events.on("provider.fallback", (e) => {
6223
+ on("provider.fallback", (e) => {
5847
6224
  broadcast2(clients, {
5848
6225
  type: "provider.fallback",
5849
6226
  payload: sessionPayload2({
@@ -5855,7 +6232,7 @@ function setupEvents(deps2) {
5855
6232
  })
5856
6233
  });
5857
6234
  });
5858
- events.on("compaction.fired", (e) => {
6235
+ on("compaction.fired", (e) => {
5859
6236
  broadcast2(clients, {
5860
6237
  type: "context.compacted",
5861
6238
  payload: sessionPayload2({
@@ -5867,7 +6244,7 @@ function setupEvents(deps2) {
5867
6244
  })
5868
6245
  });
5869
6246
  });
5870
- events.on("compaction.failed", (e) => {
6247
+ on("compaction.failed", (e) => {
5871
6248
  broadcast2(clients, {
5872
6249
  type: "compaction.failed",
5873
6250
  payload: sessionPayload2({
@@ -5882,25 +6259,25 @@ function setupEvents(deps2) {
5882
6259
  })
5883
6260
  });
5884
6261
  });
5885
- events.on("mcp.server.connected", (e) => {
6262
+ on("mcp.server.connected", (e) => {
5886
6263
  broadcast2(clients, {
5887
6264
  type: "mcp.server.connected",
5888
6265
  payload: { name: e.name, toolCount: e.toolCount }
5889
6266
  });
5890
6267
  });
5891
- events.on("mcp.server.reconnected", (e) => {
6268
+ on("mcp.server.reconnected", (e) => {
5892
6269
  broadcast2(clients, {
5893
6270
  type: "mcp.server.reconnected",
5894
6271
  payload: { name: e.name, toolCount: e.toolCount }
5895
6272
  });
5896
6273
  });
5897
- events.on("mcp.server.disconnected", (e) => {
6274
+ on("mcp.server.disconnected", (e) => {
5898
6275
  broadcast2(clients, {
5899
6276
  type: "mcp.server.disconnected",
5900
6277
  payload: { name: e.name, reason: e.reason }
5901
6278
  });
5902
6279
  });
5903
- events.on("coordinator.stats", (e) => {
6280
+ on("coordinator.stats", (e) => {
5904
6281
  broadcast2(clients, {
5905
6282
  type: "coordinator.stats",
5906
6283
  payload: sessionPayload2({
@@ -5928,15 +6305,16 @@ function setupEvents(deps2) {
5928
6305
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
5929
6306
  });
5930
6307
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
5931
- 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 }));
5932
- events.on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
5933
- events.on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
5934
- 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 }));
5935
- events.on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
5936
- events.on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
5937
- events.on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
5938
- 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 }));
5939
- events.on("agent.timeline.message", (e) => {
6308
+ 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 }));
6309
+ on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6310
+ on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6311
+ 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 }));
6312
+ on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6313
+ on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6314
+ on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6315
+ 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 }));
6316
+ on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
6317
+ on("agent.timeline.message", (e) => {
5940
6318
  broadcast2(clients, {
5941
6319
  type: "agent.timeline.message",
5942
6320
  payload: sessionPayload2({
@@ -5952,7 +6330,7 @@ function setupEvents(deps2) {
5952
6330
  })
5953
6331
  });
5954
6332
  });
5955
- events.on("agent.status_changed", (e) => {
6333
+ on("agent.status_changed", (e) => {
5956
6334
  broadcast2(clients, {
5957
6335
  type: "agent.status_changed",
5958
6336
  payload: sessionPayload2({
@@ -5967,7 +6345,7 @@ function setupEvents(deps2) {
5967
6345
  });
5968
6346
  });
5969
6347
  let leaderSpawned = false;
5970
- events.on("iteration.started", (e) => {
6348
+ on("iteration.started", (e) => {
5971
6349
  if (!leaderSpawned) {
5972
6350
  leaderSpawned = true;
5973
6351
  const provider = context.provider?.id ?? "unknown";
@@ -5981,7 +6359,7 @@ function setupEvents(deps2) {
5981
6359
  });
5982
6360
  }
5983
6361
  });
5984
- events.on("tool.executed", (e) => {
6362
+ on("tool.executed", (e) => {
5985
6363
  forwardSubagent("tool_executed", {
5986
6364
  sessionId: e.sessionId,
5987
6365
  subagentId: "leader",
@@ -5990,7 +6368,7 @@ function setupEvents(deps2) {
5990
6368
  ok: e.ok
5991
6369
  });
5992
6370
  });
5993
- events.on("provider.response", (e) => {
6371
+ on("provider.response", (e) => {
5994
6372
  if (e.usage?.input != null) {
5995
6373
  const maxCtx = context.provider.capabilities.maxContext;
5996
6374
  const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
@@ -6007,7 +6385,7 @@ function setupEvents(deps2) {
6007
6385
  });
6008
6386
  }
6009
6387
  });
6010
- events.on("iteration.completed", (e) => {
6388
+ on("iteration.completed", (e) => {
6011
6389
  if (!leaderSpawned) {
6012
6390
  leaderSpawned = true;
6013
6391
  const provider = context.provider?.id ?? "unknown";
@@ -6033,14 +6411,20 @@ function setupEvents(deps2) {
6033
6411
  payload: sessionPayload2({ event: eventName, ...payload })
6034
6412
  });
6035
6413
  });
6036
- events.on("client.status", async (e) => {
6414
+ events.onPattern("memory.*", (eventName, payload) => {
6415
+ broadcast2(clients, {
6416
+ type: "memory.event",
6417
+ payload: sessionPayload2({ event: eventName, ...payload })
6418
+ });
6419
+ });
6420
+ on("client.status", async (e) => {
6037
6421
  broadcast2(clients, { type: "client.status_update", payload: e });
6038
6422
  if (wpaths?.projectStatus) {
6039
6423
  try {
6040
6424
  const statusFile = wpaths.projectStatus(e.projectHash);
6041
- const dir = path5.dirname(statusFile);
6042
- await fs4.mkdir(dir, { recursive: true });
6043
- await fs4.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6425
+ const dir = path12.dirname(statusFile);
6426
+ await fs10.mkdir(dir, { recursive: true });
6427
+ await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6044
6428
  } catch (err) {
6045
6429
  console.error(
6046
6430
  JSON.stringify({
@@ -6054,7 +6438,7 @@ function setupEvents(deps2) {
6054
6438
  }
6055
6439
  });
6056
6440
  if (wpaths?.projectStatus && wpaths.configDir) {
6057
- const projectsDir = path5.join(wpaths.configDir, "projects");
6441
+ const projectsDir = path12.join(wpaths.configDir, "projects");
6058
6442
  const knownProjectHashes = /* @__PURE__ */ new Set();
6059
6443
  const debounceTimers = /* @__PURE__ */ new Map();
6060
6444
  const DEBOUNCE_MS = 150;
@@ -6118,8 +6502,9 @@ function setupEvents(deps2) {
6118
6502
  let watcher;
6119
6503
  const startWatcher = async () => {
6120
6504
  try {
6121
- await fs4.mkdir(projectsDir, { recursive: true });
6122
- watcher = watch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6505
+ await fs10.mkdir(projectsDir, { recursive: true });
6506
+ if (disposed) return;
6507
+ watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6123
6508
  if (eventType !== "change" && eventType !== "rename") return;
6124
6509
  if (filename == null) return;
6125
6510
  const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
@@ -6128,8 +6513,8 @@ function setupEvents(deps2) {
6128
6513
  if (!knownProjectHashes.has(projectHash)) return;
6129
6514
  if (watcherMetrics) watcherMetrics.filesProcessed++;
6130
6515
  try {
6131
- const targetFile = path5.join(projectsDir, projectHash, "status.json");
6132
- const content = await fs4.readFile(targetFile, "utf-8");
6516
+ const targetFile = path12.join(projectsDir, projectHash, "status.json");
6517
+ const content = await fs10.readFile(targetFile, "utf-8");
6133
6518
  const statusData = JSON.parse(content);
6134
6519
  scheduleBroadcast(projectHash, statusData);
6135
6520
  } catch {
@@ -6149,7 +6534,7 @@ function setupEvents(deps2) {
6149
6534
  );
6150
6535
  }
6151
6536
  };
6152
- events.on("client.status", (e) => {
6537
+ on("client.status", (e) => {
6153
6538
  if (e.projectHash) {
6154
6539
  const hash = String(e.projectHash);
6155
6540
  if (!knownProjectHashes.has(hash)) {
@@ -6181,15 +6566,17 @@ function setupEvents(deps2) {
6181
6566
  }
6182
6567
  });
6183
6568
  }
6184
- const globalRoot = globalConfigPath ? path5.dirname(globalConfigPath) : void 0;
6569
+ const globalRoot = globalConfigPath ? path12.dirname(globalConfigPath) : void 0;
6185
6570
  if (globalRoot) {
6186
6571
  const broadcastSessions = async () => {
6187
6572
  try {
6188
- const { SessionRegistry } = await import('@wrongstack/core');
6573
+ const { SessionRegistry } = await import("@wrongstack/core");
6189
6574
  const registry = new SessionRegistry(globalRoot);
6190
6575
  const sessions = await registry.list();
6191
6576
  const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6192
- const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6577
+ const live = sessions.filter(
6578
+ (s) => s.status === "active" || s.status === "idle"
6579
+ ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
6193
6580
  sessionId: s.sessionId,
6194
6581
  projectName: s.projectName,
6195
6582
  projectSlug: s.projectSlug,
@@ -6228,7 +6615,7 @@ function setupEvents(deps2) {
6228
6615
  disposers.push(() => clearInterval(statusInterval));
6229
6616
  let regDebounce;
6230
6617
  try {
6231
- const regWatcher = watch(globalRoot, { persistent: false }, (_event, filename) => {
6618
+ const regWatcher = fsWatch(globalRoot, { persistent: false }, (_event, filename) => {
6232
6619
  const name2 = filename ? String(filename) : "";
6233
6620
  if (!name2.startsWith("session-registry.json") || name2.endsWith(".lock")) return;
6234
6621
  if (regDebounce) clearTimeout(regDebounce);
@@ -6243,6 +6630,8 @@ function setupEvents(deps2) {
6243
6630
  void broadcastSessions();
6244
6631
  }
6245
6632
  return () => {
6633
+ if (disposed) return;
6634
+ disposed = true;
6246
6635
  for (const dispose of disposers) {
6247
6636
  try {
6248
6637
  dispose();
@@ -6264,6 +6653,9 @@ function getCostRates(model) {
6264
6653
  function computeUsageCost(usage, rates) {
6265
6654
  return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
6266
6655
  }
6656
+
6657
+ // src/server/model-catalog.ts
6658
+ import { capabilitiesFor } from "@wrongstack/providers";
6267
6659
  var SIBLING_CATALOG = {
6268
6660
  "anthropic-oauth": "anthropic",
6269
6661
  "openai-codex": "openai",
@@ -6370,6 +6762,9 @@ function readConfiguredBool(providerConfig, key) {
6370
6762
  function positiveNumber(value) {
6371
6763
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
6372
6764
  }
6765
+
6766
+ // src/server/server-runtime.ts
6767
+ import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6373
6768
  async function resolvePorts(opts) {
6374
6769
  const requestedWsPort = opts.wsPort ?? 3457;
6375
6770
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
@@ -6430,7 +6825,7 @@ function createSessionStartPayload(g) {
6430
6825
  inputCost,
6431
6826
  outputCost,
6432
6827
  cacheReadCost,
6433
- projectName: path5.basename(projectRoot) || projectRoot,
6828
+ projectName: path13.basename(projectRoot) || projectRoot,
6434
6829
  projectRoot,
6435
6830
  cwd: g.getWorkingDir(),
6436
6831
  mode: g.getModeId(),
@@ -6463,18 +6858,31 @@ function createWsServers(ports, accessToken) {
6463
6858
  allowedHostnames: publicHostnames,
6464
6859
  allowBrowserUrlToken: Boolean(ports.publicWsUrl)
6465
6860
  });
6466
- const WS_MAX_PAYLOAD = 8 * 1024 * 1024;
6861
+ const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
6467
6862
  const wssPrimary = new WebSocketServer({
6468
6863
  port: ports.wsPort,
6469
6864
  host: ports.wsHost,
6470
6865
  verifyClient: verifyClient2,
6471
- maxPayload: WS_MAX_PAYLOAD
6866
+ maxPayload: WS_MAX_PAYLOAD,
6867
+ // Send a ping every 15s to keep idle connections alive. Without this,
6868
+ // network equipment (routers, proxies, NAT gateways) may drop idle TCP
6869
+ // connections, causing the browser to see a close event and show the
6870
+ // "reconnecting" banner. The browser responds with a pong automatically
6871
+ // (the WebSocket API handles this at the transport level).
6872
+ pingInterval: 15e3,
6873
+ // Wait 5s for a pong before considering the connection dead. A client
6874
+ // that hasn't responded to 3 consecutive pings in a row (45s total
6875
+ // quiet) is almost certainly unreachable — close the socket so the
6876
+ // client can reconnect cleanly rather than hanging indefinitely.
6877
+ pingTimeout: 5e3
6472
6878
  });
6473
6879
  const wssSecondary = ports.wsHost === "127.0.0.1" ? new WebSocketServer({
6474
6880
  port: ports.wsPort,
6475
6881
  host: "::1",
6476
6882
  verifyClient: verifyClient2,
6477
- maxPayload: WS_MAX_PAYLOAD
6883
+ maxPayload: WS_MAX_PAYLOAD,
6884
+ pingInterval: 15e3,
6885
+ pingTimeout: 5e3
6478
6886
  }) : null;
6479
6887
  const clients = /* @__PURE__ */ new Map();
6480
6888
  console.log(
@@ -6496,7 +6904,7 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
6496
6904
  };
6497
6905
  wssPrimary.on("listening", () => arm(`${wsHost}:${wsPort}`));
6498
6906
  wssPrimary.on("error", (err) => {
6499
- console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6907
+ console.error(JSON.stringify({ level: "error", event: "webui.ws_server_error", host: wsHost, message: toErrorMessage3(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6500
6908
  });
6501
6909
  if (wssSecondary) {
6502
6910
  wssSecondary.on("listening", () => arm(`::1:${wsPort}`));
@@ -6515,13 +6923,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
6515
6923
  };
6516
6924
  }
6517
6925
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
6518
- if (explicitDistDir) return path5.resolve(explicitDistDir);
6926
+ if (explicitDistDir) return path13.resolve(explicitDistDir);
6519
6927
  try {
6520
6928
  const requireFromHere2 = createRequire(fromUrl);
6521
6929
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui/server");
6522
- return path5.resolve(path5.dirname(serverEntry), "..");
6930
+ return path13.resolve(path13.dirname(serverEntry), "..");
6523
6931
  } catch {
6524
- return path5.resolve(path5.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6932
+ return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6525
6933
  }
6526
6934
  }
6527
6935
  function startHttpServer(opts) {
@@ -6536,13 +6944,13 @@ function startHttpServer(opts) {
6536
6944
  watcherMetrics: opts.watcherMetrics,
6537
6945
  onFleetPing: opts.onFleetPing
6538
6946
  });
6539
- const registryBaseDir = path5.dirname(opts.globalConfigPath);
6947
+ const registryBaseDir = path13.dirname(opts.globalConfigPath);
6540
6948
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
6541
6949
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
6542
6950
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
6543
6951
  if (opts.openBrowser) openBrowser(openUrl);
6544
6952
  void registerInstance(
6545
- { 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 }) },
6953
+ { pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
6546
6954
  registryBaseDir
6547
6955
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
6548
6956
  });
@@ -6556,6 +6964,65 @@ function registerShutdown(deps2) {
6556
6964
  onShutdown: deps2.onShutdown
6557
6965
  });
6558
6966
  }
6967
+
6968
+ // src/server/pre-context-services.ts
6969
+ import * as path16 from "node:path";
6970
+ import { createRequire as createRequire2 } from "node:module";
6971
+ import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
6972
+ import {
6973
+ AnnotationsStore,
6974
+ DEFAULT_SESSION_PRUNE_DAYS,
6975
+ DefaultModelsRegistry,
6976
+ DefaultModeStore,
6977
+ DefaultPromptLoader,
6978
+ DefaultSessionReader,
6979
+ DefaultSessionStore,
6980
+ DefaultSkillLoader,
6981
+ DefaultSystemPromptBuilder,
6982
+ EventBus,
6983
+ GlobalMailbox,
6984
+ PromptUsageStore,
6985
+ ProviderRegistry,
6986
+ SkillInstaller,
6987
+ TOKENS,
6988
+ ToolRegistry,
6989
+ Context,
6990
+ applyToolDescriptionModes,
6991
+ applyToolResultRenderModes,
6992
+ configureChildEnvGitIdentity,
6993
+ getSessionRegistry as getSessionRegistry2,
6994
+ resolveContextWindowPolicy,
6995
+ makeMailboxTool,
6996
+ makeMailInboxTool,
6997
+ makeFleetStatusTool,
6998
+ makeMailSendTool
6999
+ } from "@wrongstack/core";
7000
+ import {
7001
+ createSuperMemoryTools
7002
+ } from "@wrongstack/super-memory";
7003
+ import {
7004
+ MCPAuthorizationManager,
7005
+ MCPRegistry,
7006
+ MCPVaultTokenStore,
7007
+ createVaultBackedMcpAuthorizationProviderFactory
7008
+ } from "@wrongstack/mcp";
7009
+ import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
7010
+ import { createDefaultContainer } from "@wrongstack/runtime";
7011
+ import {
7012
+ builtinToolsPack,
7013
+ configureDangerBypass,
7014
+ configureExecPolicy,
7015
+ forgetTool,
7016
+ relatedMemoryTool,
7017
+ rememberTool,
7018
+ searchMemoryTool
7019
+ } from "@wrongstack/tools";
7020
+ import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
7021
+
7022
+ // src/server/setup-screen.ts
7023
+ import { expectDefined as expectDefined2 } from "@wrongstack/core";
7024
+ import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
7025
+ import { makeProviderFromConfig } from "@wrongstack/providers";
6559
7026
  var UNCONFIGURED_CAPABILITIES = {
6560
7027
  tools: false,
6561
7028
  parallelTools: false,
@@ -6595,7 +7062,7 @@ function logCreateFailure(event, err) {
6595
7062
  JSON.stringify({
6596
7063
  level: "error",
6597
7064
  event,
6598
- message: toErrorMessage(err),
7065
+ message: toErrorMessage4(err),
6599
7066
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6600
7067
  })
6601
7068
  );
@@ -6620,7 +7087,7 @@ function resolveSetupProvider(opts) {
6620
7087
  const savedProviders = config.providers ?? {};
6621
7088
  const firstKey = Object.keys(savedProviders)[0];
6622
7089
  if (firstKey) {
6623
- const firstProvider = expectDefined(savedProviders[firstKey]);
7090
+ const firstProvider = expectDefined2(savedProviders[firstKey]);
6624
7091
  try {
6625
7092
  const provider2 = makeProviderFromConfig(firstKey, {
6626
7093
  ...firstProvider,
@@ -6688,6 +7155,20 @@ function seedContextMeta(config, context) {
6688
7155
  meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
6689
7156
  meta["cacheTtl"] = mr.cache?.ttl ?? "default";
6690
7157
  }
7158
+ meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
7159
+ meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
7160
+ meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7161
+ meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7162
+ meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
7163
+ meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7164
+ meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7165
+ {
7166
+ const featuresAllow = config.features?.allowOutsideProjectRoot;
7167
+ const toolsRestrict = config.tools?.restrictToProjectRoot;
7168
+ const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
7169
+ meta["fsAccess"] = allow ? "unrestricted" : "project";
7170
+ }
7171
+ meta["debugStream"] = config.debugStream === true;
6691
7172
  const hqConfig = config.hq;
6692
7173
  meta["hqEnabled"] = hqConfig?.enabled === true;
6693
7174
  meta["hqUrl"] = hqConfig?.url ?? "";
@@ -6700,6 +7181,11 @@ function seedContextMeta(config, context) {
6700
7181
  const tgMs = tgExt?.["longToolThresholdMs"];
6701
7182
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
6702
7183
  }
7184
+
7185
+ // src/server/model-auto-discovery.ts
7186
+ import * as fs11 from "node:fs/promises";
7187
+ import * as path14 from "node:path";
7188
+ import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
6703
7189
  function isOverlayRegistry(value) {
6704
7190
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
6705
7191
  }
@@ -6724,7 +7210,7 @@ function eligibleProviders(config) {
6724
7210
  }
6725
7211
  async function readCache(file) {
6726
7212
  try {
6727
- return JSON.parse(await fs4.readFile(file, "utf8"));
7213
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6728
7214
  } catch {
6729
7215
  return {};
6730
7216
  }
@@ -6734,7 +7220,7 @@ async function discoverAndMergeWebuiProviders(opts) {
6734
7220
  if (!isOverlayRegistry(registry)) return;
6735
7221
  const targets = eligibleProviders(opts.config);
6736
7222
  if (targets.length === 0) return;
6737
- const cacheFile = path5.join(opts.cacheDir, "discovered-models-cache.json");
7223
+ const cacheFile = path14.join(opts.cacheDir, "discovered-models-cache.json");
6738
7224
  const cache = await readCache(cacheFile);
6739
7225
  let cacheDirty = false;
6740
7226
  await Promise.all(
@@ -6771,18 +7257,196 @@ async function discoverAndMergeWebuiProviders(opts) {
6771
7257
  );
6772
7258
  if (cacheDirty) {
6773
7259
  try {
6774
- await fs4.mkdir(path5.dirname(cacheFile), { recursive: true });
6775
- await fs4.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7260
+ await fs11.mkdir(path14.dirname(cacheFile), { recursive: true });
7261
+ await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
6776
7262
  } catch {
6777
7263
  opts.logger?.debug?.("provider auto-discovery cache write failed");
6778
7264
  }
6779
7265
  }
6780
7266
  }
6781
7267
 
7268
+ // src/server/standalone-session-identity.ts
7269
+ import * as path15 from "node:path";
7270
+ import {
7271
+ AgentStatusTracker,
7272
+ FleetNotifier,
7273
+ getSessionRegistry,
7274
+ RecoveryLock
7275
+ } from "@wrongstack/core";
7276
+ import { WebSocket as WebSocket2 } from "ws";
7277
+ async function createStandaloneSessionIdentityLifecycle(opts) {
7278
+ const { paths, events, logger } = opts;
7279
+ const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
7280
+ const fleetNotifier = new FleetNotifier({
7281
+ baseDir: paths.globalRoot,
7282
+ projectRoot: paths.projectRoot,
7283
+ selfPid: process.pid
7284
+ });
7285
+ let activeSessionId = opts.initialSessionId;
7286
+ let stopped = false;
7287
+ let transition = Promise.resolve();
7288
+ const statusTracker = new AgentStatusTracker({
7289
+ events,
7290
+ registry,
7291
+ sessionId: () => activeSessionId,
7292
+ onUpdate: () => fleetNotifier.notify()
7293
+ });
7294
+ const register = async (sessionId) => {
7295
+ try {
7296
+ await registry.register({
7297
+ sessionId,
7298
+ projectSlug: paths.projectSlug,
7299
+ projectRoot: paths.projectRoot,
7300
+ projectName: path15.basename(paths.projectRoot),
7301
+ workingDir: opts.workingDir,
7302
+ clientType: "webui",
7303
+ pid: process.pid,
7304
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7305
+ agents: statusTracker.getAgents()
7306
+ });
7307
+ fleetNotifier.notify();
7308
+ } catch (err) {
7309
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
7310
+ }
7311
+ };
7312
+ await register(activeSessionId);
7313
+ statusTracker.start();
7314
+ const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
7315
+ let ownsRecoveryLock = false;
7316
+ if (recoveryLock) {
7317
+ try {
7318
+ await recoveryLock.write(activeSessionId);
7319
+ ownsRecoveryLock = true;
7320
+ } catch {
7321
+ }
7322
+ }
7323
+ let stopHqBridges = () => {
7324
+ };
7325
+ let closeHqPublisher = () => {
7326
+ };
7327
+ let restartHqBridges = (_sessionId) => {
7328
+ };
7329
+ if (opts.enableHqTelemetry !== false) {
7330
+ try {
7331
+ const core = await import("@wrongstack/core");
7332
+ const publisher = core.createHqPublisherFromEnv({
7333
+ clientKind: "webui",
7334
+ projectRoot: paths.projectRoot,
7335
+ projectName: path15.basename(paths.projectRoot),
7336
+ appConfig: opts.config,
7337
+ socketFactory: (url) => new WebSocket2(url)
7338
+ });
7339
+ if (publisher) {
7340
+ publisher.connect();
7341
+ closeHqPublisher = () => publisher.close();
7342
+ restartHqBridges = (sessionId) => {
7343
+ stopHqBridges();
7344
+ const stops = [];
7345
+ const add = (start) => {
7346
+ try {
7347
+ stops.push(start());
7348
+ } catch {
7349
+ }
7350
+ };
7351
+ add(
7352
+ () => core.startSessionTelemetryBridge({
7353
+ publisher,
7354
+ events,
7355
+ sessionId,
7356
+ projectRoot: paths.projectRoot,
7357
+ projectName: path15.basename(paths.projectRoot),
7358
+ globalRoot: paths.globalRoot,
7359
+ initialAgents: statusTracker.getAgents(),
7360
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
7361
+ })
7362
+ );
7363
+ add(
7364
+ () => core.startFleetTelemetryBridge({
7365
+ events,
7366
+ publisher,
7367
+ runId: sessionId,
7368
+ sessionId
7369
+ })
7370
+ );
7371
+ add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
7372
+ add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
7373
+ add(
7374
+ () => core.startToolTelemetryBridge({
7375
+ events,
7376
+ publisher,
7377
+ projectRoot: paths.projectRoot,
7378
+ sessionId
7379
+ })
7380
+ );
7381
+ add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
7382
+ stopHqBridges = () => {
7383
+ for (const stop2 of stops.splice(0)) {
7384
+ try {
7385
+ stop2();
7386
+ } catch {
7387
+ }
7388
+ }
7389
+ };
7390
+ };
7391
+ restartHqBridges(activeSessionId);
7392
+ }
7393
+ } catch (err) {
7394
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
7395
+ }
7396
+ }
7397
+ const repointRecovery = async (sessionId) => {
7398
+ if (!recoveryLock) return;
7399
+ try {
7400
+ if (ownsRecoveryLock) await recoveryLock.clear();
7401
+ await recoveryLock.write(sessionId);
7402
+ ownsRecoveryLock = true;
7403
+ } catch {
7404
+ ownsRecoveryLock = false;
7405
+ }
7406
+ };
7407
+ const activate = async (sessionId) => {
7408
+ if (stopped || sessionId === activeSessionId) return;
7409
+ transition = transition.then(async () => {
7410
+ if (stopped || sessionId === activeSessionId) return;
7411
+ activeSessionId = sessionId;
7412
+ await register(sessionId);
7413
+ await repointRecovery(sessionId);
7414
+ try {
7415
+ restartHqBridges(sessionId);
7416
+ } catch (err) {
7417
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
7418
+ }
7419
+ });
7420
+ await transition;
7421
+ };
7422
+ const stop = async () => {
7423
+ if (stopped) return;
7424
+ stopped = true;
7425
+ await transition.catch(() => void 0);
7426
+ statusTracker.stop();
7427
+ stopHqBridges();
7428
+ closeHqPublisher();
7429
+ fleetNotifier.dispose();
7430
+ try {
7431
+ await registry.markClosing();
7432
+ await registry.unregister();
7433
+ } catch {
7434
+ }
7435
+ if (recoveryLock && ownsRecoveryLock) {
7436
+ await recoveryLock.clear().catch(() => void 0);
7437
+ ownsRecoveryLock = false;
7438
+ }
7439
+ };
7440
+ return { statusTracker, activate, stop };
7441
+ }
7442
+ function errorMessage2(err) {
7443
+ return err instanceof Error ? err.message : String(err);
7444
+ }
7445
+
6782
7446
  // src/server/pre-context-services.ts
6783
7447
  var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
6784
7448
  async function createPreContextServices(input) {
6785
- const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
7449
+ const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
6786
7450
  const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
6787
7451
  cacheFile: wpaths.modelsCache,
6788
7452
  ttlSeconds: 0,
@@ -6794,20 +7458,22 @@ async function createPreContextServices(input) {
6794
7458
  await modelsRegistry.refresh();
6795
7459
  logger.info("models.dev catalog refreshed");
6796
7460
  } catch (err) {
6797
- logger.warn(`models.dev refresh failed (${toErrorMessage(err)}); using cached catalog`);
7461
+ logger.warn(`models.dev refresh failed (${toErrorMessage5(err)}); using cached catalog`);
6798
7462
  }
6799
7463
  }
6800
7464
  try {
6801
7465
  await discoverAndMergeWebuiProviders({
6802
7466
  config,
6803
7467
  registry: modelsRegistry,
6804
- cacheDir: path5.dirname(wpaths.modelsCache),
7468
+ cacheDir: path16.dirname(wpaths.modelsCache),
6805
7469
  logger
6806
7470
  });
6807
7471
  } catch (err) {
6808
- logger.debug(`provider auto-discovery skipped: ${toErrorMessage(err)}`);
7472
+ logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
6809
7473
  }
6810
- const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
7474
+ const events = opts.services?.events ?? new EventBus();
7475
+ events.setLogger(logger);
7476
+ const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry, events });
6811
7477
  const configStore = opts.services?.configStore ?? container.resolve(TOKENS.ConfigStore);
6812
7478
  const providerRegistry = new ProviderRegistry();
6813
7479
  try {
@@ -6815,22 +7481,23 @@ async function createPreContextServices(input) {
6815
7481
  for (const f of factories) providerRegistry.register(f);
6816
7482
  console.log("[WebUI] Provider registry loaded:", providerRegistry.list().length, "providers");
6817
7483
  } catch (err) {
6818
- console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message: toErrorMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
7484
+ console.warn(JSON.stringify({ level: "warn", event: "webui.provider_registry_load_failed", message: toErrorMessage5(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6819
7485
  }
6820
7486
  const toolRegistry = opts.services?.toolRegistry ?? (() => {
6821
7487
  const r = new ToolRegistry();
6822
7488
  r.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
6823
7489
  return r;
6824
7490
  })();
6825
- const memoryStore = new DefaultMemoryStore({ paths: wpaths });
7491
+ const memoryStore = container.resolve(TOKENS.MemoryStore);
6826
7492
  if (config.features.memory) {
6827
7493
  toolRegistry.register(rememberTool(memoryStore));
6828
7494
  toolRegistry.register(forgetTool(memoryStore));
6829
7495
  toolRegistry.register(searchMemoryTool(memoryStore));
6830
7496
  toolRegistry.register(relatedMemoryTool(memoryStore));
7497
+ if (isSuperMemoryService(memoryStore)) {
7498
+ for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
7499
+ }
6831
7500
  }
6832
- const events = opts.services?.events ?? new EventBus();
6833
- events.setLogger(logger);
6834
7501
  toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
6835
7502
  toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
6836
7503
  toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
@@ -6839,11 +7506,25 @@ async function createPreContextServices(input) {
6839
7506
  applyToolResultRenderModes(toolRegistry, config.tools?.resultRenderMode);
6840
7507
  configureExecPolicy(config.tools?.exec ?? {});
6841
7508
  configureDangerBypass(config.tools?.exec?.danger ?? {});
7509
+ configureChildEnvGitIdentity(config.git?.identity ?? null);
6842
7510
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
6843
- const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger, cacheDir: wpaths.cacheDir });
7511
+ const mcpTokenStore = new MCPVaultTokenStore(
7512
+ path16.join(wpaths.projectDir, "mcp-auth.json"),
7513
+ vault
7514
+ );
7515
+ const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
7516
+ const mcpRegistry = new MCPRegistry({
7517
+ toolRegistry,
7518
+ events,
7519
+ log: logger,
7520
+ cacheDir: wpaths.cacheDir,
7521
+ authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
7522
+ store: mcpTokenStore
7523
+ }),
7524
+ authorizationManager: mcpAuthorizationManager
7525
+ });
6844
7526
  if (config.features.mcp && config.mcpServers) {
6845
7527
  for (const [name2, cfg] of Object.entries(config.mcpServers)) {
6846
- if (cfg.enabled === false) continue;
6847
7528
  void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
6848
7529
  logger.warn(`MCP server "${name2}" failed to start at boot`, err);
6849
7530
  });
@@ -6858,7 +7539,7 @@ async function createPreContextServices(input) {
6858
7539
  // surface via the SessionRegistry.
6859
7540
  isSessionInUse: async (sessionId) => {
6860
7541
  try {
6861
- const registry = getSessionRegistry(wpaths.globalRoot);
7542
+ const registry = getSessionRegistry2(wpaths.globalRoot);
6862
7543
  const live = await registry.listByProject(wpaths.projectSlug);
6863
7544
  const hit = live.find((e) => e.sessionId === sessionId);
6864
7545
  if (hit) {
@@ -6883,120 +7564,22 @@ async function createPreContextServices(input) {
6883
7564
  await input.touchProject(projectRoot, workingDir);
6884
7565
  } catch {
6885
7566
  }
6886
- let statusTracker;
6887
- try {
6888
- const registry = getSessionRegistry(wpaths.globalRoot);
6889
- await registry.register({
6890
- sessionId: session.id,
6891
- projectSlug: wpaths.projectSlug,
7567
+ const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
7568
+ config,
7569
+ events,
7570
+ logger,
7571
+ paths: {
7572
+ globalRoot: wpaths.globalRoot,
6892
7573
  projectRoot,
6893
- projectName: path5.basename(projectRoot),
6894
- workingDir,
6895
- clientType: "webui",
6896
- pid: process.pid,
6897
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
6898
- });
6899
- const fleetNotifier = new FleetNotifier({ baseDir: wpaths.globalRoot, projectRoot, selfPid: process.pid });
6900
- statusTracker = new AgentStatusTracker({
6901
- events,
6902
- registry,
6903
- sessionId: () => session.id,
6904
- onUpdate: () => fleetNotifier.notify()
6905
- });
6906
- statusTracker.start();
6907
- let stopHqSessionBridge;
6908
- let hqTelemetryPublisher;
6909
- const stopHqAuxBridges = [];
6910
- try {
6911
- const {
6912
- createHqPublisherFromEnv,
6913
- startSessionTelemetryBridge,
6914
- startFleetTelemetryBridge,
6915
- startBrainTelemetryBridge,
6916
- startWorktreeTelemetryBridge,
6917
- startToolTelemetryBridge,
6918
- startCostTelemetryBridge
6919
- } = await import('@wrongstack/core');
6920
- const hqTelemetry = createHqPublisherFromEnv({
6921
- clientKind: "webui",
6922
- projectRoot,
6923
- projectName: path5.basename(projectRoot),
6924
- appConfig: config,
6925
- socketFactory: (url) => new WebSocket(url)
6926
- });
6927
- if (hqTelemetry) {
6928
- hqTelemetry.connect();
6929
- hqTelemetryPublisher = hqTelemetry;
6930
- stopHqSessionBridge = startSessionTelemetryBridge({
6931
- publisher: hqTelemetry,
6932
- events,
6933
- sessionId: session.id,
6934
- projectRoot,
6935
- projectName: path5.basename(projectRoot),
6936
- globalRoot: wpaths.globalRoot,
6937
- initialAgents: statusTracker?.getAgents(),
6938
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
6939
- });
6940
- try {
6941
- stopHqAuxBridges.push(
6942
- startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
6943
- );
6944
- } catch {
6945
- }
6946
- try {
6947
- stopHqAuxBridges.push(
6948
- startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6949
- );
6950
- } catch {
6951
- }
6952
- try {
6953
- stopHqAuxBridges.push(
6954
- startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6955
- );
6956
- } catch {
6957
- }
6958
- try {
6959
- stopHqAuxBridges.push(
6960
- startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
6961
- );
6962
- } catch {
6963
- }
6964
- try {
6965
- stopHqAuxBridges.push(
6966
- startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
6967
- );
6968
- } catch {
6969
- }
6970
- }
6971
- } catch {
6972
- }
6973
- const stopTracking = async () => {
6974
- try {
6975
- fleetNotifier.dispose();
6976
- await registry.markClosing();
6977
- statusTracker?.stop();
6978
- stopHqSessionBridge?.();
6979
- for (const stop of stopHqAuxBridges) {
6980
- try {
6981
- stop();
6982
- } catch {
6983
- }
6984
- }
6985
- hqTelemetryPublisher?.close();
6986
- } catch {
6987
- }
6988
- };
6989
- process.once("beforeExit", () => {
6990
- void stopTracking();
6991
- });
6992
- process.once("SIGINT", () => {
6993
- void stopTracking();
6994
- });
6995
- process.once("SIGTERM", () => {
6996
- void stopTracking();
6997
- });
6998
- } catch {
6999
- }
7574
+ projectSlug: wpaths.projectSlug,
7575
+ projectSessions: wpaths.projectSessions
7576
+ },
7577
+ workingDir,
7578
+ initialSessionId: session.id,
7579
+ sessionStore,
7580
+ manageRecoveryLock: !opts.services?.session
7581
+ });
7582
+ const statusTracker = sessionIdentity.statusTracker;
7000
7583
  let context;
7001
7584
  const tokenCounter = new DefaultTokenCounter({
7002
7585
  registry: modelsRegistry,
@@ -7021,7 +7604,7 @@ async function createPreContextServices(input) {
7021
7604
  const modelCapabilitiesRef = { current: modelCapabilities };
7022
7605
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7023
7606
  const skillInstaller = config.features.skills ? new SkillInstaller({
7024
- manifestPath: path5.join(wpaths.globalRoot, "installed-skills.json"),
7607
+ manifestPath: path16.join(wpaths.globalRoot, "installed-skills.json"),
7025
7608
  projectSkillsDir: wpaths.inProjectSkills,
7026
7609
  globalSkillsDir: wpaths.globalSkills,
7027
7610
  projectHash: wpaths.projectHash,
@@ -7030,8 +7613,8 @@ async function createPreContextServices(input) {
7030
7613
  const promptsEnabled = config.features.prompts !== false;
7031
7614
  const bundledPromptsDir = promptsEnabled ? (() => {
7032
7615
  try {
7033
- const req = createRequire(import.meta.url);
7034
- return path5.join(path5.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7616
+ const req = createRequire2(import.meta.url);
7617
+ return path16.join(path16.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7035
7618
  } catch {
7036
7619
  return void 0;
7037
7620
  }
@@ -7083,6 +7666,14 @@ async function createPreContextServices(input) {
7083
7666
  const initialContextPolicy = resolveContextWindowPolicy(config.context);
7084
7667
  context.meta["contextWindowMode"] = initialContextPolicy.id;
7085
7668
  context.meta["contextWindowPolicy"] = initialContextPolicy;
7669
+ context.state.setMeta(
7670
+ "plan.path",
7671
+ sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
7672
+ );
7673
+ context.state.setMeta(
7674
+ "task.path",
7675
+ sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
7676
+ );
7086
7677
  seedContextMeta(config, context);
7087
7678
  return {
7088
7679
  modelsRegistry,
@@ -7099,6 +7690,7 @@ async function createPreContextServices(input) {
7099
7690
  session,
7100
7691
  sessionStartedAt,
7101
7692
  statusTracker,
7693
+ sessionIdentity,
7102
7694
  tokenCounter,
7103
7695
  modeStore,
7104
7696
  modeId,
@@ -7112,8 +7704,33 @@ async function createPreContextServices(input) {
7112
7704
  needsSetup
7113
7705
  };
7114
7706
  }
7707
+ function isSuperMemoryService(memoryStore) {
7708
+ const value = memoryStore;
7709
+ 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";
7710
+ }
7711
+
7712
+ // src/server/start-webui.ts
7713
+ import * as path22 from "node:path";
7714
+ import {
7715
+ createDefaultPipelines,
7716
+ createSessionEventBridge,
7717
+ DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
7718
+ expectDefined as expectDefined3,
7719
+ resolveSessionLoggingConfig,
7720
+ watchProviderConfig
7721
+ } from "@wrongstack/core";
7722
+ import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
7723
+ import { makeProviderFromConfig as makeProviderFromConfig3 } from "@wrongstack/providers";
7724
+ import {
7725
+ ensureSessionShell
7726
+ } from "@wrongstack/tools";
7727
+
7728
+ // src/server/boot.ts
7729
+ import {
7730
+ bootConfig as coreBootConfig
7731
+ } from "@wrongstack/core";
7115
7732
  async function bootConfig() {
7116
- const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await bootConfig$1({
7733
+ const { config, vault, globalConfigPath, projectRoot, wpaths, logger } = await coreBootConfig({
7117
7734
  appLabel: "WebUI"
7118
7735
  });
7119
7736
  return { config, vault, globalConfigPath, projectRoot, wpaths, logger };
@@ -7121,14 +7738,46 @@ async function bootConfig() {
7121
7738
  function patchConfig(config, updates) {
7122
7739
  return Object.freeze({ ...config, ...updates });
7123
7740
  }
7741
+
7742
+ // src/server/backend-services.ts
7743
+ import { join as join13 } from "node:path";
7744
+ import {
7745
+ Agent,
7746
+ AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
7747
+ BrainMonitor,
7748
+ CollaborationBus as CollaborationBusCtor,
7749
+ DEFAULT_TOOLS_CONFIG,
7750
+ createStrategyCompactor,
7751
+ collabInjectMiddleware,
7752
+ collabPauseMiddleware,
7753
+ estimateRequestTokensCalibrated,
7754
+ installDesignStudioMiddleware,
7755
+ ObservableBrainArbiter as ObservableBrainArbiterCtor,
7756
+ resolveContextWindowPolicy as resolveContextWindowPolicy2,
7757
+ BrainDecisionLedger,
7758
+ createBrainRuntime,
7759
+ resolveBrainConfigDefaults,
7760
+ EscalationRoutingBrainArbiter,
7761
+ GlobalMailbox as GlobalMailbox2,
7762
+ mailboxSessionTag,
7763
+ SessionMemoryConsolidator,
7764
+ TOKENS as TOKENS2,
7765
+ ToolExecutor
7766
+ } from "@wrongstack/core";
7767
+
7768
+ // src/server/collaboration-ws-handler.ts
7769
+ import { randomUUID } from "node:crypto";
7770
+ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7771
+ var REPLAY_LIMIT = 50;
7124
7772
  var PAUSE_TIMEOUT_MS = 6e4;
7125
7773
  var CollaborationWebSocketHandler = class {
7126
- constructor(events, logger, reader, annotations, bus) {
7774
+ constructor(events, logger, reader, annotations, bus, options = {}) {
7127
7775
  this.events = events;
7128
7776
  this.logger = logger;
7129
7777
  this.reader = reader;
7130
7778
  this.annotations = annotations;
7131
7779
  this.bus = bus;
7780
+ this.options = options;
7132
7781
  this.subscribe();
7133
7782
  this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
7134
7783
  }
@@ -7137,6 +7786,7 @@ var CollaborationWebSocketHandler = class {
7137
7786
  reader;
7138
7787
  annotations;
7139
7788
  bus;
7789
+ options;
7140
7790
  clients = /* @__PURE__ */ new Set();
7141
7791
  /** sessionId → participants currently watching it. */
7142
7792
  bySession = /* @__PURE__ */ new Map();
@@ -7164,11 +7814,16 @@ var CollaborationWebSocketHandler = class {
7164
7814
  handleMessage(ws, msg) {
7165
7815
  if (msg.type === "collab.join") {
7166
7816
  const payload = msg.payload;
7167
- if (!payload?.sessionId) {
7817
+ if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
7168
7818
  this.send(ws, this.errorMessage("collab.join requires sessionId"));
7169
7819
  return true;
7170
7820
  }
7171
- this.join(ws, payload.sessionId, payload.role ?? "observer");
7821
+ const role = payload.role ?? "observer";
7822
+ if (role !== "observer" && role !== "annotator" && role !== "controller") {
7823
+ this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
7824
+ return true;
7825
+ }
7826
+ this.join(ws, payload.sessionId, role);
7172
7827
  return true;
7173
7828
  }
7174
7829
  if (msg.type === "collab.leave") {
@@ -7203,6 +7858,20 @@ var CollaborationWebSocketHandler = class {
7203
7858
  }
7204
7859
  // ── Join / leave flow ──────────────────────────────────────────────────
7205
7860
  join(ws, sessionId, role) {
7861
+ const activeSessionId = this.options.getActiveSessionId?.();
7862
+ if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
7863
+ this.send(
7864
+ ws,
7865
+ this.errorMessage(
7866
+ `collab.join sessionId mismatch (active: ${activeSessionId})`
7867
+ )
7868
+ );
7869
+ return;
7870
+ }
7871
+ if (this.findParticipant(ws)) {
7872
+ this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
7873
+ return;
7874
+ }
7206
7875
  if (role === "controller" && !this.bus) {
7207
7876
  this.send(
7208
7877
  ws,
@@ -7221,6 +7890,13 @@ var CollaborationWebSocketHandler = class {
7221
7890
  );
7222
7891
  return;
7223
7892
  }
7893
+ if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
7894
+ this.send(
7895
+ ws,
7896
+ this.errorMessage(`role '${role}' requires explicit server authorization`)
7897
+ );
7898
+ return;
7899
+ }
7224
7900
  const participant = {
7225
7901
  participantId: randomUUID(),
7226
7902
  ws,
@@ -7248,7 +7924,7 @@ var CollaborationWebSocketHandler = class {
7248
7924
  if (this.reader) {
7249
7925
  this.replayHistory(ws, sessionId).catch((err) => {
7250
7926
  this.logger.debug?.(
7251
- `collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`
7927
+ `collab: replay failed for ${sessionId}: ${toErrorMessage6(err)}`
7252
7928
  );
7253
7929
  });
7254
7930
  }
@@ -7366,7 +8042,7 @@ var CollaborationWebSocketHandler = class {
7366
8042
  this.send(
7367
8043
  ws,
7368
8044
  this.errorMessage(
7369
- `annotation rejected: ${toErrorMessage(err)}`
8045
+ `annotation rejected: ${toErrorMessage6(err)}`
7370
8046
  )
7371
8047
  );
7372
8048
  }
@@ -7433,7 +8109,7 @@ var CollaborationWebSocketHandler = class {
7433
8109
  this.send(
7434
8110
  ws,
7435
8111
  this.errorMessage(
7436
- `resolve failed: ${toErrorMessage(err)}`
8112
+ `resolve failed: ${toErrorMessage6(err)}`
7437
8113
  )
7438
8114
  );
7439
8115
  }
@@ -7474,17 +8150,13 @@ var CollaborationWebSocketHandler = class {
7474
8150
  type: "collab.event",
7475
8151
  payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
7476
8152
  };
7477
- const data = JSON.stringify(msg);
7478
- for (const bucket of this.bySession.values()) {
7479
- for (const p of bucket) {
7480
- try {
7481
- if (p.ws.readyState === 1) p.ws.send(data);
7482
- } catch (err) {
7483
- this.logger.debug?.(
7484
- `collab broadcast failed: ${toErrorMessage(err)}`
7485
- );
7486
- }
7487
- }
8153
+ const activeSessionId = this.options.getActiveSessionId?.();
8154
+ if (activeSessionId !== void 0) {
8155
+ this.broadcast(activeSessionId, msg);
8156
+ return;
8157
+ }
8158
+ for (const sessionId of this.bySession.keys()) {
8159
+ this.broadcast(sessionId, msg);
7488
8160
  }
7489
8161
  }
7490
8162
  /**
@@ -7508,11 +8180,11 @@ var CollaborationWebSocketHandler = class {
7508
8180
  }
7509
8181
  } catch (err) {
7510
8182
  this.logger.debug?.(
7511
- `collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`
8183
+ `collab: session reader rejected ${sessionId}: ${toErrorMessage6(err)}`
7512
8184
  );
7513
8185
  return;
7514
8186
  }
7515
- const tail2 = all.slice(-50);
8187
+ const tail2 = all.slice(-REPLAY_LIMIT);
7516
8188
  if (tail2.length === 0) return;
7517
8189
  for (const raw of tail2) {
7518
8190
  const ev = raw;
@@ -7589,7 +8261,7 @@ var CollaborationWebSocketHandler = class {
7589
8261
  if (p.ws.readyState === 1) p.ws.send(data);
7590
8262
  } catch (err) {
7591
8263
  this.logger.debug?.(
7592
- `collab broadcast failed: ${toErrorMessage(err)}`
8264
+ `collab broadcast failed: ${toErrorMessage6(err)}`
7593
8265
  );
7594
8266
  }
7595
8267
  }
@@ -7808,7 +8480,8 @@ var CollaborationWebSocketHandler = class {
7808
8480
  * Bus callback: a queued injection was spliced into a real tool call. Re-emit
7809
8481
  * `collab.injection.granted` with phase `'consumed'` and the now-known tool
7810
8482
  * name. The injection carries no sessionId, so resolve it from the author's
7811
- * current session; if they've already left, fall back to every live session.
8483
+ * current participant. If the author has left, fail closed rather than leak
8484
+ * the payload to unrelated sessions.
7812
8485
  */
7813
8486
  broadcastInjectionConsumed(info) {
7814
8487
  let sessionId = null;
@@ -7837,10 +8510,23 @@ var CollaborationWebSocketHandler = class {
7837
8510
  if (sessionId) {
7838
8511
  this.broadcast(sessionId, message(sessionId));
7839
8512
  } else {
7840
- for (const sid of this.bySession.keys()) this.broadcast(sid, message(sid));
8513
+ this.logger.debug?.(
8514
+ `collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
8515
+ );
7841
8516
  }
7842
8517
  }
7843
8518
  };
8519
+
8520
+ // src/server/codebase-indexing.ts
8521
+ import * as fs12 from "node:fs";
8522
+ import * as path17 from "node:path";
8523
+ import {
8524
+ cancelPendingReindexes,
8525
+ enqueueReindex,
8526
+ isIndexableFile,
8527
+ runStartupIndex,
8528
+ shutdownCodebaseIndexHost
8529
+ } from "@wrongstack/tools";
7844
8530
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
7845
8531
  "node_modules",
7846
8532
  ".git",
@@ -7886,7 +8572,7 @@ function setupWebUICodebaseIndexing(deps2) {
7886
8572
  if (!filename) return;
7887
8573
  const rel = filename.toString();
7888
8574
  if (isIgnored(rel)) return;
7889
- const abs = path5.resolve(deps2.projectRoot, rel);
8575
+ const abs = path17.resolve(deps2.projectRoot, rel);
7890
8576
  enqueueFile(abs);
7891
8577
  });
7892
8578
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
@@ -7899,7 +8585,7 @@ function setupWebUICodebaseIndexing(deps2) {
7899
8585
  }
7900
8586
  function enqueueFile(filePath) {
7901
8587
  if (!idx.onEdit && !idx.watchExternal) return;
7902
- const abs = path5.isAbsolute(filePath) ? path5.normalize(filePath) : path5.resolve(deps2.projectRoot, filePath);
8588
+ const abs = path17.isAbsolute(filePath) ? path17.normalize(filePath) : path17.resolve(deps2.projectRoot, filePath);
7903
8589
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
7904
8590
  enqueueReindex({
7905
8591
  projectRoot: deps2.projectRoot,
@@ -7936,15 +8622,23 @@ function isIgnored(rel) {
7936
8622
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
7937
8623
  }
7938
8624
  function isInside2(root, target) {
7939
- const normalizedRoot = path5.resolve(root);
7940
- const normalizedTarget = path5.resolve(target);
7941
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path5.sep);
8625
+ const normalizedRoot = path17.resolve(root);
8626
+ const normalizedTarget = path17.resolve(target);
8627
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path17.sep);
7942
8628
  }
8629
+
8630
+ // src/server/discover-mailbox-bridge.ts
8631
+ import { spawn as spawn3 } from "node:child_process";
8632
+ import { createRequire as createRequire3 } from "node:module";
8633
+ import { existsSync } from "node:fs";
8634
+ import { dirname as dirname8, join as join12 } from "node:path";
8635
+ import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8636
+ import { readLiveLock } from "@wrongstack/core/coordination";
7943
8637
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
7944
8638
  async function discoverMailboxBridgeForWebui(params) {
7945
8639
  const mode = params.config?.features?.mailboxBridge ?? "auto";
7946
8640
  if (mode === "off") return;
7947
- const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot$1());
8641
+ const projectDir = resolveProjectDir(params.projectRoot, wstackGlobalRoot2());
7948
8642
  let result = await readLiveLock(projectDir);
7949
8643
  let spawnedPid = null;
7950
8644
  if (result.kind !== "live") {
@@ -8010,7 +8704,7 @@ async function waitForLiveMailboxBridge(projectDir, timeoutMs) {
8010
8704
  function spawnMailboxBridge(projectRoot, logger) {
8011
8705
  const invocation = mailboxServeInvocation(projectRoot);
8012
8706
  try {
8013
- const child = spawn(invocation.command, invocation.args, {
8707
+ const child = spawn3(invocation.command, invocation.args, {
8014
8708
  cwd: projectRoot,
8015
8709
  detached: process.platform !== "win32" && !invocation.useShell,
8016
8710
  env: process.env,
@@ -8041,7 +8735,7 @@ function mailboxServeInvocation(projectRoot) {
8041
8735
  return { command: process.execPath, args: [explicitCliEntry, "mailbox", "serve"] };
8042
8736
  }
8043
8737
  try {
8044
- const require2 = createRequire(import.meta.url);
8738
+ const require2 = createRequire3(import.meta.url);
8045
8739
  const cliEntry = require2.resolve("@wrongstack/cli");
8046
8740
  return { command: process.execPath, args: [cliEntry, "mailbox", "serve"] };
8047
8741
  } catch {
@@ -8065,9 +8759,9 @@ function mailboxServeInvocation(projectRoot) {
8065
8759
  function findWorkspaceCliEntry(projectRoot) {
8066
8760
  let dir = projectRoot;
8067
8761
  for (let i = 0; i < 6; i++) {
8068
- const candidate = join(dir, "packages", "cli", "dist", "index.js");
8762
+ const candidate = join12(dir, "packages", "cli", "dist", "index.js");
8069
8763
  if (existsSync(candidate)) return candidate;
8070
- const parent = dirname(dir);
8764
+ const parent = dirname8(dir);
8071
8765
  if (parent === dir) return null;
8072
8766
  dir = parent;
8073
8767
  }
@@ -8076,10 +8770,15 @@ function findWorkspaceCliEntry(projectRoot) {
8076
8770
  function sleep(ms) {
8077
8771
  return new Promise((resolve10) => setTimeout(resolve10, ms));
8078
8772
  }
8773
+
8774
+ // src/server/terminal-ws-handler.ts
8775
+ import { spawn as spawnChild } from "node:child_process";
8776
+ import { createRequire as createRequire4 } from "node:module";
8777
+ import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
8079
8778
  var MAX_SESSIONS_PER_CLIENT = 8;
8080
8779
  var DEFAULT_COLS = 80;
8081
8780
  var DEFAULT_ROWS = 24;
8082
- var requireFromHere = createRequire(import.meta.url);
8781
+ var requireFromHere = createRequire4(import.meta.url);
8083
8782
  var cachedNodePty;
8084
8783
  var TerminalWebSocketHandler = class {
8085
8784
  constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
@@ -8157,8 +8856,8 @@ var TerminalWebSocketHandler = class {
8157
8856
  ...windowsPtyOptions()
8158
8857
  });
8159
8858
  } catch (err) {
8160
- const msg = `Integrated terminal failed to start: ${toErrorMessage(err)}`;
8161
- this.logger.warn?.(`terminal spawn failed: ${toErrorMessage(err)}`);
8859
+ const msg = `Integrated terminal failed to start: ${toErrorMessage7(err)}`;
8860
+ this.logger.warn?.(`terminal spawn failed: ${toErrorMessage7(err)}`);
8162
8861
  this.send(ws, { type: "terminal.output", payload: { id: payload.id, data: `${msg}\r
8163
8862
  ` } });
8164
8863
  this.send(ws, { type: "terminal.exit", payload: { id: payload.id, exitCode: -1 } });
@@ -8211,7 +8910,7 @@ var TerminalWebSocketHandler = class {
8211
8910
  try {
8212
8911
  pty.kill();
8213
8912
  } catch (err) {
8214
- this.logger.warn?.(`${reason} failed: ${toErrorMessage(err)}`);
8913
+ this.logger.warn?.(`${reason} failed: ${toErrorMessage7(err)}`);
8215
8914
  }
8216
8915
  }
8217
8916
  killWindowsProcessTree(pty, reason) {
@@ -8223,7 +8922,7 @@ var TerminalWebSocketHandler = class {
8223
8922
  } catch {
8224
8923
  }
8225
8924
  } catch (err) {
8226
- this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage(err)}`);
8925
+ this.logger.warn?.(`${reason} taskkill failed: ${toErrorMessage7(err)}`);
8227
8926
  }
8228
8927
  }
8229
8928
  send(ws, msg) {
@@ -8254,7 +8953,7 @@ function windowsPtyOptions() {
8254
8953
  return { useConptyDll: true };
8255
8954
  }
8256
8955
  function defaultKillProcessTree(pid) {
8257
- const child = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
8956
+ const child = spawnChild("taskkill", ["/PID", String(pid), "/T", "/F"], {
8258
8957
  stdio: "ignore",
8259
8958
  windowsHide: true
8260
8959
  });
@@ -8267,6 +8966,14 @@ function clampDim(value, fallback) {
8267
8966
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
8268
8967
  return Math.max(1, Math.min(1e3, Math.floor(value)));
8269
8968
  }
8969
+
8970
+ // src/server/backend-services.ts
8971
+ import { makeLightSubagentFactory } from "@wrongstack/runtime";
8972
+ import {
8973
+ createSuperMemoryToolCallMiddleware,
8974
+ createSuperMemoryTurnMiddleware
8975
+ } from "@wrongstack/super-memory";
8976
+ import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
8270
8977
  async function createAgentServices(input) {
8271
8978
  const {
8272
8979
  config,
@@ -8281,15 +8988,37 @@ async function createAgentServices(input) {
8281
8988
  providerRegistry,
8282
8989
  modelsRegistry,
8283
8990
  events,
8991
+ memoryStore,
8284
8992
  modelCapabilitiesRef
8285
8993
  } = input;
8286
- const collabBus = new CollaborationBus();
8994
+ const collabBus = new CollaborationBusCtor();
8287
8995
  const pipelines = input.pipelines;
8288
8996
  const collabInject = collabInjectMiddleware(collabBus, { logger });
8289
8997
  pipelines.toolCall.prepend(collabInject);
8290
8998
  const collabPause = collabPauseMiddleware(collabBus, { logger });
8291
8999
  pipelines.toolCall.prepend(collabPause);
8292
9000
  installDesignStudioMiddleware({ pipelines, ctx: context });
9001
+ if (config.features.memory !== false && config.superMemory?.enabled !== false && isSuperMemoryRetriever(memoryStore)) {
9002
+ if (config.superMemory?.inject?.toolResults !== false) {
9003
+ pipelines.toolCall.use(createSuperMemoryToolCallMiddleware({
9004
+ memory: memoryStore,
9005
+ maxHintsPerTool: config.superMemory?.inject?.maxHintsPerTool,
9006
+ maxCharsPerTool: config.superMemory?.inject?.maxCharsPerTool,
9007
+ minScore: config.superMemory?.inject?.minScore,
9008
+ repeatCooldownMs: config.superMemory?.inject?.repeatCooldownMs,
9009
+ verifyOnMutation: config.superMemory?.hygiene?.autoOnFileChange,
9010
+ triggers: config.superMemory?.inject?.triggers
9011
+ }));
9012
+ }
9013
+ if (config.superMemory?.inject?.turnContext !== false) {
9014
+ pipelines.request.use(createSuperMemoryTurnMiddleware({
9015
+ memory: memoryStore,
9016
+ maxMemories: config.superMemory?.inject?.maxTurnMemories,
9017
+ maxChars: config.superMemory?.inject?.maxCharsPerTurn,
9018
+ minScore: config.superMemory?.inject?.minScore
9019
+ }));
9020
+ }
9021
+ }
8293
9022
  const codebaseIndexing = setupWebUICodebaseIndexing({
8294
9023
  config,
8295
9024
  context,
@@ -8303,7 +9032,7 @@ async function createAgentServices(input) {
8303
9032
  summarizerModel: config.context?.summarizerModel,
8304
9033
  llmSelector: config.context?.llmSelector
8305
9034
  });
8306
- const initialContextPolicy = resolveContextWindowPolicy(config.context);
9035
+ const initialContextPolicy = resolveContextWindowPolicy2(config.context);
8307
9036
  let autoCompactor;
8308
9037
  if (config.context?.autoCompact !== false) {
8309
9038
  let effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
@@ -8320,7 +9049,7 @@ async function createAgentServices(input) {
8320
9049
  }
8321
9050
  }
8322
9051
  if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
8323
- autoCompactor = new AutoCompactionMiddleware(
9052
+ autoCompactor = new AutoCompactionMiddlewareCtor(
8324
9053
  compactor,
8325
9054
  effectiveMaxContext,
8326
9055
  (ctx) => estimateRequestTokensCalibrated(
@@ -8348,7 +9077,7 @@ async function createAgentServices(input) {
8348
9077
  const updateAutoCompactionMaxContext = async (newProvider, providerId = newProvider.id, providerCfg) => {
8349
9078
  await modelsRegistry.refresh().catch((err) => {
8350
9079
  logger.warn(
8351
- `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage(err)}; using cached catalog`
9080
+ `models.dev refresh failed for ${providerId}/${context.model}: ${toErrorMessage8(err)}; using cached catalog`
8352
9081
  );
8353
9082
  });
8354
9083
  const currentConfig = input.config;
@@ -8385,9 +9114,9 @@ async function createAgentServices(input) {
8385
9114
  maxContext: newMaxContext
8386
9115
  });
8387
9116
  };
8388
- const secretScrubber = container.resolve(TOKENS.SecretScrubber);
8389
- const renderer = container.has(TOKENS.Renderer) ? container.resolve(TOKENS.Renderer) : void 0;
8390
- const permissionPolicy = container.resolve(TOKENS.PermissionPolicy);
9117
+ const secretScrubber = container.resolve(TOKENS2.SecretScrubber);
9118
+ const renderer = container.has(TOKENS2.Renderer) ? container.resolve(TOKENS2.Renderer) : void 0;
9119
+ const permissionPolicy = container.resolve(TOKENS2.PermissionPolicy);
8391
9120
  const toolExecutor = new ToolExecutor(toolRegistry, {
8392
9121
  permissionPolicy,
8393
9122
  secretScrubber,
@@ -8398,7 +9127,7 @@ async function createAgentServices(input) {
8398
9127
  perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
8399
9128
  tracer: void 0
8400
9129
  });
8401
- const webuiLogger = container.resolve(TOKENS.Logger);
9130
+ const webuiLogger = container.resolve(TOKENS2.Logger);
8402
9131
  void discoverMailboxBridgeForWebui({
8403
9132
  projectRoot,
8404
9133
  config,
@@ -8424,25 +9153,66 @@ async function createAgentServices(input) {
8424
9153
  confirmAwaiter: void 0,
8425
9154
  toolExecutor
8426
9155
  });
9156
+ if (config.features.memory && config.features.memoryConsolidation !== false) {
9157
+ agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
9158
+ }
8427
9159
  console.log("[WebUI] Agent initialized");
8428
- const brainSettings = { maxAutoRisk: "medium" };
8429
- const autonomousBrain = {
8430
- decide: (request) => createAutonomyBrain({
8431
- provider,
8432
- model: context.model,
8433
- maxAutoRisk: "all"
8434
- // the tiered ceiling gates risk — keep inner permissive
8435
- }).decide(request)
9160
+ const brainCfg = resolveBrainConfigDefaults(config.brain, {
9161
+ fallbackModels: config.fallbackModels
9162
+ });
9163
+ const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
9164
+ let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9165
+ let brainLedger;
9166
+ const startBrainLedger = () => {
9167
+ if (brainLedger) return;
9168
+ brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
9169
+ void brainLedger.start();
8436
9170
  };
8437
- const brain = new ObservableBrainArbiter(
8438
- createTieredBrainArbiter({
8439
- policy: new DefaultBrainArbiter(),
8440
- autonomous: autonomousBrain,
8441
- getMaxAutoRisk: () => brainSettings.maxAutoRisk
8442
- }),
9171
+ if (brainLedgerEnabled) startBrainLedger();
9172
+ const brainRuntime = createBrainRuntime({
9173
+ initialConfig: brainCfg,
9174
+ defaultProviderId: config.provider,
9175
+ sessionProvider: () => provider,
9176
+ sessionModel: () => context.model,
9177
+ resolveProvider: (providerId) => {
9178
+ const savedCfg = config.providers?.[providerId] ?? {};
9179
+ return providerRegistry.create({
9180
+ ...savedCfg,
9181
+ apiKey: savedCfg.apiKey ?? config.apiKey,
9182
+ baseUrl: savedCfg.baseUrl ?? config.baseUrl,
9183
+ type: providerId
9184
+ });
9185
+ },
9186
+ ledger: {
9187
+ getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
9188
+ isEnabled: () => brainLedgerEnabled,
9189
+ setEnabled: (on) => {
9190
+ brainLedgerEnabled = on;
9191
+ if (on) {
9192
+ startBrainLedger();
9193
+ } else {
9194
+ void brainLedger?.stop();
9195
+ brainLedger = void 0;
9196
+ }
9197
+ },
9198
+ failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
9199
+ getDecisionDigest: (request) => brainLedger?.digestFor(request)
9200
+ },
9201
+ persist: input.persistBrainConfig
9202
+ });
9203
+ const brainSettings = {
9204
+ get maxAutoRisk() {
9205
+ return brainRuntime.getMaxAutoRisk();
9206
+ },
9207
+ set maxAutoRisk(level) {
9208
+ void brainRuntime.apply({ maxAutoRisk: level }).persisted;
9209
+ }
9210
+ };
9211
+ const brain = new ObservableBrainArbiterCtor(
9212
+ new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
8443
9213
  events
8444
9214
  );
8445
- container.bind(TOKENS.BrainArbiter, () => brain);
9215
+ container.bind(TOKENS2.BrainArbiter, () => brain);
8446
9216
  const brainLog = [];
8447
9217
  const pushBrainLog = (entry) => {
8448
9218
  brainLog.push(entry);
@@ -8475,10 +9245,16 @@ async function createAgentServices(input) {
8475
9245
  outcome: e.decision.type === "deny" ? e.decision.reason : ""
8476
9246
  })
8477
9247
  );
8478
- const brainMailbox = new GlobalMailbox(wpaths.projectDir, events);
9248
+ const brainMailbox = new GlobalMailbox2(wpaths.projectDir, events);
8479
9249
  const brainMonitor = new BrainMonitor({
8480
9250
  events,
8481
9251
  brain,
9252
+ toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
9253
+ errorStormCount: brainCfg.monitor?.errorStormCount,
9254
+ stallMs: brainCfg.monitor?.stallMs,
9255
+ fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
9256
+ fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
9257
+ cooldownMs: brainCfg.monitor?.cooldownMs,
8482
9258
  sessionId: () => context.session?.id,
8483
9259
  intervene: async ({ subject, body }) => {
8484
9260
  const tag = mailboxSessionTag(input.sessionGetter().id);
@@ -8543,7 +9319,10 @@ async function createAgentServices(input) {
8543
9319
  logger,
8544
9320
  input.sessionReader,
8545
9321
  input.annotationsStore,
8546
- collabBus
9322
+ collabBus,
9323
+ {
9324
+ getActiveSessionId: () => context.session.id
9325
+ }
8547
9326
  );
8548
9327
  return {
8549
9328
  collabBus,
@@ -8555,8 +9334,13 @@ async function createAgentServices(input) {
8555
9334
  pipelines,
8556
9335
  brain,
8557
9336
  brainSettings,
9337
+ brainRuntime,
8558
9338
  brainLog,
8559
9339
  brainMonitor,
9340
+ // Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
9341
+ get brainLedger() {
9342
+ return brainLedger;
9343
+ },
8560
9344
  codebaseIndexing,
8561
9345
  autoPhaseHandler,
8562
9346
  specsHandler,
@@ -8568,14 +9352,14 @@ async function createAgentServices(input) {
8568
9352
  updateAutoCompactionMaxContext
8569
9353
  };
8570
9354
  }
9355
+ function isSuperMemoryRetriever(memoryStore) {
9356
+ const value = memoryStore;
9357
+ return typeof value["retrieveForPath"] === "function" && typeof value["searchSuper"] === "function";
9358
+ }
8571
9359
 
8572
9360
  // src/server/pending-confirms.ts
8573
- function isDestructivePendingConfirm(confirm) {
8574
- return confirm.riskTier === "destructive" || confirm.decisionSource === "yolo_destructive";
8575
- }
8576
9361
  function resolveYoloEligiblePendingConfirms(pendingConfirms) {
8577
9362
  for (const [id, confirm] of pendingConfirms) {
8578
- if (isDestructivePendingConfirm(confirm)) continue;
8579
9363
  pendingConfirms.delete(id);
8580
9364
  confirm.resolve("yes");
8581
9365
  }
@@ -8731,6 +9515,20 @@ function createConnectionHandler(opts) {
8731
9515
  };
8732
9516
  }
8733
9517
 
9518
+ // src/server/message-dispatcher.ts
9519
+ import path18 from "node:path";
9520
+ import {
9521
+ buildUserContentBlocks,
9522
+ IncomingImageError,
9523
+ parseIncomingImages
9524
+ } from "@wrongstack/core/utils";
9525
+ import {
9526
+ createToolVisionAdapters,
9527
+ ImageInputUnsupportedError,
9528
+ routeImagesForModel,
9529
+ VisionUrlBlockedError
9530
+ } from "@wrongstack/runtime/vision";
9531
+
8734
9532
  // src/server/autophase-routes.ts
8735
9533
  async function handleAutoPhaseRoute(_ws, msg, handlers) {
8736
9534
  if (!msg.type.startsWith("autophase.")) return false;
@@ -8750,14 +9548,23 @@ async function handleBrainRoute(ws, msg, handlers) {
8750
9548
  case "brain.ask":
8751
9549
  await handlers.ask(ws, msg);
8752
9550
  return true;
9551
+ case "brain.config.get":
9552
+ await handlers.configGet(ws, msg);
9553
+ return true;
9554
+ case "brain.config.set":
9555
+ await handlers.configSet(ws, msg);
9556
+ return true;
8753
9557
  default:
8754
9558
  return false;
8755
9559
  }
8756
9560
  }
9561
+
9562
+ // src/server/goal-handlers.ts
9563
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
8757
9564
  async function handleGoalGet(projectRoot, broadcast2) {
8758
9565
  try {
8759
9566
  const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
8760
- const { readFile: readFile11 } = await import('fs/promises');
9567
+ const { readFile: readFile11 } = await import("node:fs/promises");
8761
9568
  const raw = await readFile11(goalPath, "utf8");
8762
9569
  const goal = JSON.parse(raw);
8763
9570
  broadcast2({ type: "goal.updated", payload: goal });
@@ -8811,7 +9618,7 @@ async function handleTasksGet(ctx, ws) {
8811
9618
  const taskPath = ctx.context.meta["task.path"];
8812
9619
  if (typeof taskPath === "string" && taskPath) {
8813
9620
  try {
8814
- const { loadTasks } = await import('@wrongstack/core');
9621
+ const { loadTasks } = await import("@wrongstack/core");
8815
9622
  const file = await loadTasks(taskPath);
8816
9623
  ctx.send(ws, { type: "tasks.updated", payload: sessionPayload(ctx, { tasks: file?.tasks ?? [] }) });
8817
9624
  } catch {
@@ -8831,7 +9638,7 @@ async function handleTaskUpdate(ctx, ws, payload) {
8831
9638
  return;
8832
9639
  }
8833
9640
  try {
8834
- const { loadTasks, saveTasks } = await import('@wrongstack/core');
9641
+ const { loadTasks, saveTasks } = await import("@wrongstack/core");
8835
9642
  const file = await loadTasks(taskPath);
8836
9643
  if (!file) {
8837
9644
  sendResult2(ws, ctx, false, "No task file found.");
@@ -8860,7 +9667,7 @@ async function handlePlanGet(ctx, ws) {
8860
9667
  const sessionId = ctx.context.session?.id ?? "";
8861
9668
  if (typeof planPath === "string" && planPath) {
8862
9669
  try {
8863
- const { loadPlan } = await import('@wrongstack/core');
9670
+ const { loadPlan } = await import("@wrongstack/core");
8864
9671
  const plan = await loadPlan(planPath);
8865
9672
  ctx.send(ws, {
8866
9673
  type: "plan.updated",
@@ -8901,7 +9708,7 @@ async function handlePlanTemplateUse(ctx, ws, template) {
8901
9708
  return;
8902
9709
  }
8903
9710
  try {
8904
- const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import('@wrongstack/core');
9711
+ const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import("@wrongstack/core");
8905
9712
  const tpl = getPlanTemplate(template);
8906
9713
  if (!tpl) {
8907
9714
  sendResult2(ws, ctx, false, `Unknown template "${template}".`);
@@ -8926,7 +9733,7 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
8926
9733
  return;
8927
9734
  }
8928
9735
  try {
8929
- const { mutatePlan, setPlanItemStatus } = await import('@wrongstack/core');
9736
+ const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
8930
9737
  let changed = false;
8931
9738
  const plan = await mutatePlan(planPath, sessionId, async (p) => {
8932
9739
  const before = p.updatedAt;
@@ -8993,6 +9800,45 @@ async function handleWorklistMessage(ctx, ws, msg) {
8993
9800
  return;
8994
9801
  }
8995
9802
  }
9803
+
9804
+ // src/server/kanban-routes.ts
9805
+ import {
9806
+ deserializeTaskGraph,
9807
+ serializeTaskGraph
9808
+ } from "@wrongstack/core";
9809
+ import {
9810
+ addColumn,
9811
+ addGoalMetricToTask,
9812
+ addTask,
9813
+ assignTask,
9814
+ claimReadyTask,
9815
+ copyTaskToBoard,
9816
+ createBoard,
9817
+ duplicateBoard,
9818
+ exportBoardToTaskGraph,
9819
+ generateBoardFromDescription,
9820
+ getBoard,
9821
+ getKanbanOrchestrationSnapshot,
9822
+ getKanbanQueueHealth,
9823
+ getTask,
9824
+ getTaskChain,
9825
+ listReadyTasks,
9826
+ listBoards,
9827
+ mergeTasks,
9828
+ moveTask,
9829
+ parseLinesIntoTasks,
9830
+ removeBoard,
9831
+ removeColumn,
9832
+ removeTask,
9833
+ releaseTaskClaim,
9834
+ setTaskChain,
9835
+ splitTask,
9836
+ syncBoardFromTaskGraph,
9837
+ transferTaskToBoard,
9838
+ updateBoard,
9839
+ updateGoalMetricOnTask,
9840
+ updateTask
9841
+ } from "@wrongstack/kanban";
8996
9842
  function ok(ws, type, data) {
8997
9843
  send(ws, { type, payload: { success: true, data: data ?? null } });
8998
9844
  }
@@ -9438,6 +10284,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
9438
10284
  "Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
9439
10285
  );
9440
10286
  return true;
10287
+ case "kanban.capabilities":
10288
+ ok(ws, type, { dispatchSupported: false });
10289
+ return true;
9441
10290
  case "kanban.task.remove": {
9442
10291
  const boardId = payload?.boardId;
9443
10292
  const taskId = payload?.taskId;
@@ -9517,6 +10366,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
9517
10366
  case "mailbox.purge":
9518
10367
  await handlers.purge(ws, msg);
9519
10368
  return true;
10369
+ case "mailbox.compact":
10370
+ await handlers.compact(ws, msg);
10371
+ return true;
9520
10372
  default:
9521
10373
  return false;
9522
10374
  }
@@ -9555,6 +10407,18 @@ async function handleMcpRoute(ws, msg, handlers) {
9555
10407
  case "mcp.discover":
9556
10408
  await handlers.discover(ws, msg);
9557
10409
  return true;
10410
+ case "mcp.resources":
10411
+ await handlers.resources(ws, msg);
10412
+ return true;
10413
+ case "mcp.prompts":
10414
+ await handlers.prompts(ws, msg);
10415
+ return true;
10416
+ case "mcp.resource.read":
10417
+ await handlers.resourceRead(ws, msg);
10418
+ return true;
10419
+ case "mcp.prompt.get":
10420
+ await handlers.promptGet(ws, msg);
10421
+ return true;
9558
10422
  default:
9559
10423
  return false;
9560
10424
  }
@@ -9593,7 +10457,7 @@ async function handlePrefsRoute(ws, msg, handlers) {
9593
10457
  // src/server/process-handlers.ts
9594
10458
  async function handleProcessList(ws) {
9595
10459
  try {
9596
- const { getProcessRegistry } = await import('@wrongstack/tools');
10460
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9597
10461
  const procs = getProcessRegistry().list();
9598
10462
  send(ws, {
9599
10463
  type: "process.list",
@@ -9620,7 +10484,7 @@ async function handleProcessKill(ws, payload) {
9620
10484
  }
9621
10485
  const { pid } = parsed.value;
9622
10486
  try {
9623
- const { getProcessRegistry } = await import('@wrongstack/tools');
10487
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9624
10488
  const proc = getProcessRegistry().get(pid);
9625
10489
  if (proc?.protected) {
9626
10490
  sendResult(ws, false, `Cannot kill protected process (PID ${pid})`);
@@ -9634,7 +10498,7 @@ async function handleProcessKill(ws, payload) {
9634
10498
  }
9635
10499
  async function handleProcessKillAll(ws) {
9636
10500
  try {
9637
- const { getProcessRegistry } = await import('@wrongstack/tools');
10501
+ const { getProcessRegistry } = await import("@wrongstack/tools");
9638
10502
  getProcessRegistry().killAll();
9639
10503
  sendResult(ws, true, "All processes killed");
9640
10504
  } catch (err) {
@@ -9944,7 +10808,7 @@ function createMessageDispatcher(opts) {
9944
10808
  skillLoader: deps2.skillLoader,
9945
10809
  skillInstaller: deps2.skillInstaller,
9946
10810
  projectRoot,
9947
- projectSkillsDir: path5__default.join(projectRoot, ".wrongstack", "skills"),
10811
+ projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
9948
10812
  globalSkillsDir: deps2.wpaths.globalSkills
9949
10813
  };
9950
10814
  }
@@ -10017,7 +10881,8 @@ function createMessageDispatcher(opts) {
10017
10881
  }
10018
10882
  case "user_message": {
10019
10883
  if (!ensureCurrentSession(ws, msg, "user_message")) return;
10020
- const content = msg.payload.content;
10884
+ const userPayload = msg.payload;
10885
+ const content = userPayload.content;
10021
10886
  if (runLock.get()) {
10022
10887
  send(ws, {
10023
10888
  type: "error",
@@ -10031,8 +10896,24 @@ function createMessageDispatcher(opts) {
10031
10896
  const thisRun = new AbortController();
10032
10897
  runLock.set(thisRun);
10033
10898
  try {
10899
+ let input = content;
10900
+ const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
10901
+ if (imageBlocks.length > 0) {
10902
+ const routed = await routeImagesForModel(
10903
+ buildUserContentBlocks(content, imageBlocks),
10904
+ {
10905
+ supportsVision: deps2.agent.ctx.provider.capabilities.vision,
10906
+ adapters: () => createToolVisionAdapters(deps2.agent.tools),
10907
+ ctx: deps2.agent.ctx,
10908
+ signal: thisRun.signal,
10909
+ providerId: deps2.agent.ctx.provider.id,
10910
+ model: deps2.agent.ctx.model
10911
+ }
10912
+ );
10913
+ input = routed.blocks;
10914
+ }
10034
10915
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
10035
- const result = await deps2.agent.run(content, {
10916
+ const result = await deps2.agent.run(input, {
10036
10917
  signal: thisRun.signal,
10037
10918
  maxIterations: maxIt
10038
10919
  });
@@ -10050,13 +10931,24 @@ function createMessageDispatcher(opts) {
10050
10931
  })
10051
10932
  });
10052
10933
  } catch (err) {
10053
- send(ws, {
10054
- type: "error",
10055
- payload: sessionPayload2({
10056
- phase: "agent.run",
10057
- message: errMessage(err)
10058
- })
10059
- });
10934
+ if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
10935
+ send(ws, {
10936
+ type: "error",
10937
+ payload: sessionPayload2({
10938
+ phase: "user_message",
10939
+ ...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
10940
+ message: err.message
10941
+ })
10942
+ });
10943
+ } else {
10944
+ send(ws, {
10945
+ type: "error",
10946
+ payload: sessionPayload2({
10947
+ phase: "agent.run",
10948
+ message: errMessage(err)
10949
+ })
10950
+ });
10951
+ }
10060
10952
  } finally {
10061
10953
  if (runLock.get() === thisRun) {
10062
10954
  runLock.set(null);
@@ -10104,7 +10996,7 @@ function createMessageDispatcher(opts) {
10104
10996
  break;
10105
10997
  }
10106
10998
  case "tool.disable": {
10107
- const name2 = (msg.payload ?? {}).name;
10999
+ const name2 = msg.payload?.name;
10108
11000
  if (!name2) {
10109
11001
  send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
10110
11002
  break;
@@ -10119,7 +11011,7 @@ function createMessageDispatcher(opts) {
10119
11011
  break;
10120
11012
  }
10121
11013
  case "tool.enable": {
10122
- const name2 = (msg.payload ?? {}).name;
11014
+ const name2 = msg.payload?.name;
10123
11015
  if (!name2) {
10124
11016
  send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
10125
11017
  break;
@@ -10163,6 +11055,14 @@ function createMessageDispatcher(opts) {
10163
11055
  throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
10164
11056
  case "mcp.discover":
10165
11057
  throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
11058
+ case "mcp.resources":
11059
+ throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
11060
+ case "mcp.prompts":
11061
+ throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
11062
+ case "mcp.resource.read":
11063
+ throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
11064
+ case "mcp.prompt.get":
11065
+ throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
10166
11066
  // Skills — full request→response cycle lives in skills-handlers.ts.
10167
11067
  case "skills.list":
10168
11068
  await handleSkillsList(ws, makeSkillsContext());
@@ -10399,6 +11299,7 @@ function createMessageDispatcher(opts) {
10399
11299
  break;
10400
11300
  }
10401
11301
  case "prefs.update": {
11302
+ void ws;
10402
11303
  throw new Error("handlePrefsRoute did not claim prefs.update \u2014 check chain order");
10403
11304
  }
10404
11305
  case "prefs.get": {
@@ -10412,6 +11313,11 @@ function createMessageDispatcher(opts) {
10412
11313
  }
10413
11314
  };
10414
11315
  }
11316
+
11317
+ // src/server/pref-helpers.ts
11318
+ import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
11319
+ import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11320
+ import * as fs13 from "node:fs/promises";
10415
11321
  var PREF_KEYS = [
10416
11322
  "autonomy",
10417
11323
  "autonomyDelayMs",
@@ -10457,7 +11363,19 @@ var PREF_KEYS = [
10457
11363
  "favoriteModels",
10458
11364
  "favoriteModelsOnly",
10459
11365
  "modelMatrix",
10460
- "fallbackAuto"
11366
+ "fallbackAuto",
11367
+ // Refiner + TUI visual prefs (parity with the CLI's embedded server —
11368
+ // these were browser-editable there but rejected as unknown keys here).
11369
+ "refinerProvider",
11370
+ "refinerModel",
11371
+ "thinkingWord",
11372
+ "statuslineMode",
11373
+ "animationStyle",
11374
+ // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11375
+ "breakerEnabled",
11376
+ "breakerAutoKillResetMs",
11377
+ "fsAccess",
11378
+ "debugStream"
10461
11379
  ];
10462
11380
  function prefSnapshot(contextMeta) {
10463
11381
  const snapshot = {};
@@ -10471,7 +11389,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
10471
11389
  const write = async () => {
10472
11390
  let raw;
10473
11391
  try {
10474
- raw = await fs4.readFile(globalConfigPath, "utf8");
11392
+ raw = await fs13.readFile(globalConfigPath, "utf8");
10475
11393
  } catch {
10476
11394
  raw = "{}";
10477
11395
  }
@@ -10482,10 +11400,10 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
10482
11400
  logger.warn(`${errorLabel}: refusing to overwrite corrupt config at ${globalConfigPath}`);
10483
11401
  return;
10484
11402
  }
10485
- const decrypted = decryptConfigSecrets(parsed, vault);
11403
+ const decrypted = decryptConfigSecrets2(parsed, vault);
10486
11404
  mutate(decrypted);
10487
- const encrypted = encryptConfigSecrets(decrypted, vault);
10488
- await atomicWrite$1(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
11405
+ const encrypted = encryptConfigSecrets2(decrypted, vault);
11406
+ await atomicWrite6(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
10489
11407
  };
10490
11408
  const next = holder.lock.then(write);
10491
11409
  holder.lock = next.then(
@@ -10528,6 +11446,16 @@ async function persistPrefsToConfig(deps2, holder, payload) {
10528
11446
  setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
10529
11447
  if (typeof payload["enhanceLanguage"] === "string")
10530
11448
  setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
11449
+ if (typeof payload["refinerProvider"] === "string")
11450
+ setAutonomy("refinerProvider", payload["refinerProvider"]);
11451
+ if (typeof payload["refinerModel"] === "string")
11452
+ setAutonomy("refinerModel", payload["refinerModel"]);
11453
+ if (typeof payload["thinkingWord"] === "string")
11454
+ setAutonomy("thinkingWord", payload["thinkingWord"]);
11455
+ if (typeof payload["statuslineMode"] === "string")
11456
+ setAutonomy("statuslineMode", payload["statuslineMode"]);
11457
+ if (typeof payload["animationStyle"] === "string")
11458
+ setAutonomy("animationStyle", payload["animationStyle"]);
10531
11459
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
10532
11460
  if (typeof payload["nextPrediction"] === "boolean")
10533
11461
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -10645,15 +11573,38 @@ async function persistPrefsToConfig(deps2, holder, payload) {
10645
11573
  }
10646
11574
  decrypted.modelRuntime = mr;
10647
11575
  }
11576
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
11577
+ const cb = decrypted.circuitBreaker ?? {};
11578
+ if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
11579
+ if (typeof payload["breakerAutoKillResetMs"] === "number")
11580
+ cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
11581
+ decrypted.circuitBreaker = cb;
11582
+ }
11583
+ if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
11584
+ const restrict = payload["fsAccess"] === "project";
11585
+ const toolsCfg = decrypted.tools ?? {};
11586
+ toolsCfg.restrictToProjectRoot = restrict;
11587
+ decrypted.tools = toolsCfg;
11588
+ const featsCfg = decrypted.features ?? {};
11589
+ featsCfg.allowOutsideProjectRoot = !restrict;
11590
+ decrypted.features = featsCfg;
11591
+ }
11592
+ if (typeof payload["debugStream"] === "boolean")
11593
+ decrypted.debugStream = payload["debugStream"];
10648
11594
  }, "prefs");
10649
11595
  }
11596
+
11597
+ // src/server/projects-manifest.ts
11598
+ import * as fs14 from "node:fs/promises";
11599
+ import * as path19 from "node:path";
11600
+ import { projectSlug } from "@wrongstack/core";
10650
11601
  function projectsJsonPath(globalConfigPath) {
10651
- const base = path5.dirname(globalConfigPath);
10652
- return path5.join(base, "projects.json");
11602
+ const base = path19.dirname(globalConfigPath);
11603
+ return path19.join(base, "projects.json");
10653
11604
  }
10654
11605
  async function loadManifest(globalConfigPath) {
10655
11606
  try {
10656
- const raw = await fs4.readFile(projectsJsonPath(globalConfigPath), "utf8");
11607
+ const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
10657
11608
  const parsed = JSON.parse(raw);
10658
11609
  return { projects: parsed.projects ?? [] };
10659
11610
  } catch {
@@ -10662,18 +11613,26 @@ async function loadManifest(globalConfigPath) {
10662
11613
  }
10663
11614
  async function saveManifest(manifest, globalConfigPath) {
10664
11615
  const file = projectsJsonPath(globalConfigPath);
10665
- await fs4.mkdir(path5.dirname(file), { recursive: true });
10666
- await fs4.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11616
+ await fs14.mkdir(path19.dirname(file), { recursive: true });
11617
+ await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
10667
11618
  }
10668
11619
  function generateProjectSlug(rootPath) {
10669
11620
  return projectSlug(rootPath);
10670
11621
  }
10671
11622
  async function ensureProjectDataDir(slug, globalConfigPath) {
10672
- const base = path5.dirname(globalConfigPath);
10673
- const dir = path5.join(base, "projects", slug);
10674
- await fs4.mkdir(dir, { recursive: true });
11623
+ const base = path19.dirname(globalConfigPath);
11624
+ const dir = path19.join(base, "projects", slug);
11625
+ await fs14.mkdir(dir, { recursive: true });
10675
11626
  return dir;
10676
11627
  }
11628
+
11629
+ // src/server/provider-handlers.ts
11630
+ import { DefaultSecretScrubber } from "@wrongstack/core";
11631
+ import {
11632
+ beginOAuthLogin
11633
+ } from "@wrongstack/providers/oauth";
11634
+ import { probeLocalLlm } from "@wrongstack/runtime/probe";
11635
+ import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
10677
11636
  function projectSavedProviders(providers) {
10678
11637
  return Object.entries(providers).map(([id, cfg]) => {
10679
11638
  const keys = normalizeKeys(cfg);
@@ -10704,7 +11663,7 @@ function createProviderHandlers(deps2) {
10704
11663
  }
10705
11664
  async function saveConfigProviders(providers) {
10706
11665
  const next = configWriteLock.then(() => saveProviders(globalConfigPath, vault, providers)).catch((err) => {
10707
- const msg = toErrorMessage(err);
11666
+ const msg = toErrorMessage9(err);
10708
11667
  console.error(JSON.stringify({
10709
11668
  level: "error",
10710
11669
  event: "webui.provider_save_failed",
@@ -10976,10 +11935,23 @@ function createProviderHandlers(deps2) {
10976
11935
  loadConfigProviders
10977
11936
  };
10978
11937
  }
11938
+
11939
+ // src/server/routes.ts
11940
+ import path21 from "node:path";
11941
+ import {
11942
+ enhanceUserPrompt,
11943
+ gatedEnhancerReasoning,
11944
+ recentTextTurns,
11945
+ resolveProviderModelList
11946
+ } from "@wrongstack/core";
11947
+ import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
11948
+
11949
+ // src/server/mailbox-handlers.ts
11950
+ import { GlobalMailbox as GlobalMailbox3, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core";
10979
11951
  async function handleMailboxMessages(ws, deps2, payload) {
10980
11952
  try {
10981
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
10982
- const mb = new GlobalMailbox(dir);
11953
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11954
+ const mb = new GlobalMailbox3(dir);
10983
11955
  const limit = payload?.limit ?? 30;
10984
11956
  const unreadForAgent = payload?.unreadOnly === true && payload.agentId !== void 0;
10985
11957
  const messages = await mb.query({
@@ -11019,8 +11991,8 @@ async function handleMailboxMessages(ws, deps2, payload) {
11019
11991
  }
11020
11992
  async function handleMailboxAgents(ws, deps2, payload) {
11021
11993
  try {
11022
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
11023
- const mb = new GlobalMailbox(dir);
11994
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
11995
+ const mb = new GlobalMailbox3(dir);
11024
11996
  const agents = payload?.onlineOnly ? await mb.getOnlineAgents() : await mb.getAgentStatuses();
11025
11997
  send(ws, {
11026
11998
  type: "mailbox.agents",
@@ -11048,8 +12020,8 @@ async function handleMailboxAgents(ws, deps2, payload) {
11048
12020
  }
11049
12021
  async function handleMailboxClear(ws, deps2) {
11050
12022
  try {
11051
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
11052
- const mb = new GlobalMailbox(dir);
12023
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
12024
+ const mb = new GlobalMailbox3(dir);
11053
12025
  await mb.clearAll();
11054
12026
  send(ws, { type: "mailbox.cleared", payload: {} });
11055
12027
  } catch (err) {
@@ -11058,14 +12030,31 @@ async function handleMailboxClear(ws, deps2) {
11058
12030
  }
11059
12031
  async function handleMailboxPurge(ws, deps2, opts) {
11060
12032
  try {
11061
- const dir = resolveProjectDir(deps2.projectRoot, deps2.globalRoot);
11062
- const mb = new GlobalMailbox(dir);
12033
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
12034
+ const mb = new GlobalMailbox3(dir);
11063
12035
  const result = await mb.purgeStale(opts);
11064
12036
  send(ws, { type: "mailbox.purged", payload: result });
11065
12037
  } catch (err) {
11066
12038
  send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
11067
12039
  }
11068
12040
  }
12041
+ async function handleMailboxCompact(ws, deps2, opts) {
12042
+ try {
12043
+ const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
12044
+ const mb = new GlobalMailbox3(dir);
12045
+ const result = await mb.autoCompact(opts);
12046
+ send(ws, { type: "mailbox.compacted", payload: result });
12047
+ } catch (err) {
12048
+ send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
12049
+ }
12050
+ }
12051
+
12052
+ // src/server/mode-handlers.ts
12053
+ import {
12054
+ DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
12055
+ resolveWstackPaths as resolveWstackPaths2,
12056
+ ToolValidationError as ToolValidationError5
12057
+ } from "@wrongstack/core";
11069
12058
  function createModeHandlers(ctx) {
11070
12059
  return {
11071
12060
  listModes: async (ws) => {
@@ -11108,14 +12097,14 @@ function createModeHandlers(ctx) {
11108
12097
  } else {
11109
12098
  const found = await ctx.modeStore.getMode(id);
11110
12099
  if (!found) {
11111
- throw new ToolValidationError({ message: `Unknown mode "${id}"`, field: "modeId" });
12100
+ throw new ToolValidationError5({ message: `Unknown mode "${id}"`, field: "modeId" });
11112
12101
  }
11113
12102
  await ctx.modeStore.setActiveMode(id);
11114
12103
  }
11115
12104
  ctx.setModeId(id);
11116
12105
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
11117
- const paths = resolveWstackPaths$1({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
11118
- const freshBuilder = new DefaultSystemPromptBuilder({
12106
+ const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12107
+ const freshBuilder = new DefaultSystemPromptBuilder2({
11119
12108
  memoryStore: ctx.memoryStore,
11120
12109
  skillLoader: ctx.skillLoader,
11121
12110
  modeStore: ctx.modeStore,
@@ -11145,6 +12134,9 @@ function createModeHandlers(ctx) {
11145
12134
  }
11146
12135
  };
11147
12136
  }
12137
+
12138
+ // src/server/project-handlers.ts
12139
+ import * as path20 from "node:path";
11148
12140
  function createProjectHandlers(ctx) {
11149
12141
  return {
11150
12142
  listProjects: async (ws) => {
@@ -11170,7 +12162,7 @@ function createProjectHandlers(ctx) {
11170
12162
  selectProject: async (ws, msg) => {
11171
12163
  const payload = msg.payload;
11172
12164
  const root = typeof payload?.root === "string" ? payload.root : "";
11173
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path5.basename(root) : "";
12165
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
11174
12166
  send(ws, {
11175
12167
  type: "projects.selected",
11176
12168
  payload: {
@@ -11203,6 +12195,14 @@ function createProjectHandlers(ctx) {
11203
12195
  }
11204
12196
  };
11205
12197
  }
12198
+
12199
+ // src/server/session-handlers.ts
12200
+ import {
12201
+ DEFAULT_CONTEXT_WINDOW_MODE_ID,
12202
+ repairToolUseAdjacency,
12203
+ resolveContextWindowPolicy as resolveContextWindowPolicy3
12204
+ } from "@wrongstack/core";
12205
+ import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
11206
12206
  function createSessionHandlers(ctx) {
11207
12207
  const currentSessionId = () => ctx.getSession().id;
11208
12208
  const sessionPayload2 = (payload) => {
@@ -11228,33 +12228,46 @@ function createSessionHandlers(ctx) {
11228
12228
  });
11229
12229
  return false;
11230
12230
  };
12231
+ const finalizeSession = async (writer) => {
12232
+ await writer.append({
12233
+ type: "session_end",
12234
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12235
+ usage: ctx.tokenCounter.total()
12236
+ }).catch(() => void 0);
12237
+ await writer.close().catch(() => void 0);
12238
+ };
12239
+ const activateSession = async (next, messages, usage) => {
12240
+ const current = ctx.getSession();
12241
+ if (current !== next) await finalizeSession(current);
12242
+ ctx.setSession(next);
12243
+ ctx.context.session = next;
12244
+ ctx.context.state.replaceMessages(messages);
12245
+ ctx.context.state.replaceTodos([]);
12246
+ ctx.context.readFiles.clear();
12247
+ ctx.context.fileMtimes.clear();
12248
+ ctx.context.state.setMeta(
12249
+ "plan.path",
12250
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
12251
+ );
12252
+ ctx.context.state.setMeta(
12253
+ "task.path",
12254
+ sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
12255
+ );
12256
+ ctx.tokenCounter.reset();
12257
+ if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
12258
+ ctx.setSessionStartedAt(Date.now());
12259
+ await ctx.onSessionSwapped(next.id);
12260
+ };
11231
12261
  return {
11232
12262
  newSession: async (ws, msg) => {
11233
12263
  if (!ensureCurrentSession(ws, msg, "session.new")) return;
11234
- const session = ctx.getSession();
11235
- try {
11236
- await session.append({
11237
- type: "session_end",
11238
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11239
- usage: ctx.tokenCounter.total()
11240
- });
11241
- await session.close();
11242
- } catch {
11243
- }
11244
12264
  const next = await ctx.getSessionStore().create({
11245
12265
  id: "",
11246
12266
  title: "",
11247
12267
  model: ctx.config.model,
11248
12268
  provider: ctx.config.provider
11249
12269
  });
11250
- ctx.setSession(next);
11251
- ctx.context.session = next;
11252
- ctx.context.state.replaceMessages([]);
11253
- ctx.context.state.replaceTodos([]);
11254
- ctx.context.readFiles.clear();
11255
- ctx.context.fileMtimes.clear();
11256
- ctx.tokenCounter.reset();
11257
- ctx.setSessionStartedAt(Date.now());
12270
+ await activateSession(next, []);
11258
12271
  broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
11259
12272
  },
11260
12273
  clearContext: async (ws, msg) => {
@@ -11356,7 +12369,7 @@ function createSessionHandlers(ctx) {
11356
12369
  return;
11357
12370
  }
11358
12371
  const { id } = parsed.value;
11359
- let policy = resolveContextWindowPolicy({}, id);
12372
+ let policy = resolveContextWindowPolicy3({}, id);
11360
12373
  if (policy.id !== id) {
11361
12374
  const customModes = ctx.customModeStore.list().filter((m) => m.custom === true);
11362
12375
  const custom = customModes.find((m) => m.id === id);
@@ -11428,7 +12441,7 @@ function createSessionHandlers(ctx) {
11428
12441
  const { id } = parsed.value;
11429
12442
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
11430
12443
  ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
11431
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
12444
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
11432
12445
  }
11433
12446
  const result = ctx.customModeStore.remove(id);
11434
12447
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -11510,23 +12523,7 @@ function createSessionHandlers(ctx) {
11510
12523
  return;
11511
12524
  }
11512
12525
  const resumed = await ctx.getSessionStore().resume(id);
11513
- try {
11514
- await current.append({
11515
- type: "session_end",
11516
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11517
- usage: ctx.tokenCounter.total()
11518
- });
11519
- await current.close();
11520
- } catch {
11521
- }
11522
- ctx.setSession(resumed.writer);
11523
- ctx.context.session = resumed.writer;
11524
- ctx.context.state.replaceMessages(resumed.data.messages);
11525
- ctx.context.readFiles.clear();
11526
- ctx.context.fileMtimes.clear();
11527
- ctx.tokenCounter.reset();
11528
- ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
11529
- ctx.setSessionStartedAt(Date.now());
12526
+ await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
11530
12527
  broadcast(ctx.clients, {
11531
12528
  type: "session.start",
11532
12529
  payload: {
@@ -11548,12 +12545,9 @@ function createSessionHandlers(ctx) {
11548
12545
  listCheckpoints: async (ws, msg) => {
11549
12546
  if (!ensureCurrentSession(ws, msg, "session.checkpoints")) return;
11550
12547
  try {
11551
- const { DefaultSessionRewinder } = await import('@wrongstack/core');
12548
+ const { DefaultSessionRewinder } = await import("@wrongstack/core");
11552
12549
  const projectRoot = ctx.getProjectRoot();
11553
- const rewinder = new DefaultSessionRewinder(
11554
- path5.join(projectRoot, ".wrongstack", "sessions"),
11555
- projectRoot
11556
- );
12550
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
11557
12551
  const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
11558
12552
  send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
11559
12553
  } catch {
@@ -11564,12 +12558,9 @@ function createSessionHandlers(ctx) {
11564
12558
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
11565
12559
  const { checkpointIndex } = msg.payload;
11566
12560
  try {
11567
- const { DefaultSessionRewinder } = await import('@wrongstack/core');
12561
+ const { DefaultSessionRewinder } = await import("@wrongstack/core");
11568
12562
  const projectRoot = ctx.getProjectRoot();
11569
- const rewinder = new DefaultSessionRewinder(
11570
- path5.join(projectRoot, ".wrongstack", "sessions"),
11571
- projectRoot
11572
- );
12563
+ const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
11573
12564
  await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
11574
12565
  await ctx.context.session.truncateToCheckpoint(checkpointIndex);
11575
12566
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
@@ -11652,11 +12643,13 @@ function buildRoutes(state, deps2, cb) {
11652
12643
  const saved = await providerHandlers.loadConfigProviders();
11653
12644
  const cfg = saved[providerId];
11654
12645
  const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
12646
+ const siblingId = SIBLING_CATALOG[providerId];
12647
+ const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
11655
12648
  const models = await enrichProviderModelDescriptors(
11656
12649
  deps2.modelsRegistry,
11657
12650
  providerId,
11658
12651
  cfg,
11659
- resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId)
12652
+ resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId, siblingCatalog)
11660
12653
  );
11661
12654
  send(ws, {
11662
12655
  type: "provider.models",
@@ -11682,7 +12675,7 @@ function buildRoutes(state, deps2, cb) {
11682
12675
  const providerCfg = newCfg.providers?.[newProvider] ?? {
11683
12676
  type: newProvider
11684
12677
  };
11685
- const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig(newProvider, providerCfg);
12678
+ const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
11686
12679
  deps2.context.provider = newProv;
11687
12680
  await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
11688
12681
  await cb.updateGlobalConfig((config) => {
@@ -11783,8 +12776,10 @@ function buildRoutes(state, deps2, cb) {
11783
12776
  getProjectRoot: state.getProjectRoot,
11784
12777
  getSession: state.getSession,
11785
12778
  getSessionStore: state.getSessionStore,
12779
+ sessionsDir: deps2.wpaths.projectSessions,
11786
12780
  setSession: state.setSession,
11787
12781
  setSessionStartedAt: state.setSessionStartedAt,
12782
+ onSessionSwapped: cb.onSessionSwapped,
11788
12783
  sessionStartPayload: cb.sessionStartPayload
11789
12784
  });
11790
12785
  const projectRoutes = createProjectHandlers({
@@ -11879,6 +12874,17 @@ function buildRoutes(state, deps2, cb) {
11879
12874
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
11880
12875
  }
11881
12876
  }
12877
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
12878
+ const { getProcessRegistry } = await import("@wrongstack/tools");
12879
+ getProcessRegistry().setBreakerConfig({
12880
+ ...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
12881
+ ...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
12882
+ });
12883
+ }
12884
+ if (typeof payload["debugStream"] === "boolean") {
12885
+ const { setDebugStreamEnabled } = await import("@wrongstack/providers");
12886
+ setDebugStreamEnabled(payload["debugStream"]);
12887
+ }
11882
12888
  if (typeof payload["logLevel"] === "string") {
11883
12889
  const valid = ["debug", "info", "warn", "error"];
11884
12890
  if (valid.includes(payload["logLevel"])) {
@@ -11925,7 +12931,7 @@ function buildRoutes(state, deps2, cb) {
11925
12931
  }
11926
12932
  return handleMailboxMessages(
11927
12933
  ws,
11928
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12934
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11929
12935
  parsed.value
11930
12936
  );
11931
12937
  },
@@ -11937,13 +12943,13 @@ function buildRoutes(state, deps2, cb) {
11937
12943
  }
11938
12944
  return handleMailboxAgents(
11939
12945
  ws,
11940
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12946
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11941
12947
  parsed.value
11942
12948
  );
11943
12949
  },
11944
12950
  clear: (ws) => handleMailboxClear(ws, {
11945
12951
  projectRoot: state.getProjectRoot(),
11946
- globalRoot: path5__default.dirname(deps2.globalConfigPath)
12952
+ globalRoot: path21.dirname(deps2.globalConfigPath)
11947
12953
  }),
11948
12954
  purge: (ws, msg) => {
11949
12955
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -11953,9 +12959,16 @@ function buildRoutes(state, deps2, cb) {
11953
12959
  }
11954
12960
  return handleMailboxPurge(
11955
12961
  ws,
11956
- { projectRoot: state.getProjectRoot(), globalRoot: path5__default.dirname(deps2.globalConfigPath) },
12962
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
11957
12963
  parsed.value
11958
12964
  );
12965
+ },
12966
+ compact: (ws, msg) => {
12967
+ return handleMailboxCompact(
12968
+ ws,
12969
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
12970
+ msg.payload ?? {}
12971
+ );
11959
12972
  }
11960
12973
  };
11961
12974
  const mcpRoutes = {
@@ -11968,14 +12981,29 @@ function buildRoutes(state, deps2, cb) {
11968
12981
  sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11969
12982
  wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11970
12983
  restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
11971
- discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12984
+ discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12985
+ resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12986
+ prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12987
+ resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
12988
+ promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
12989
+ };
12990
+ const sendBrainStatus = (ws) => {
12991
+ const snapshot = deps2.brainRuntime.getSnapshot();
12992
+ send(ws, {
12993
+ type: "brain.status",
12994
+ payload: {
12995
+ maxAutoRisk: deps2.brainSettings.maxAutoRisk,
12996
+ log: deps2.brainLog,
12997
+ mode: snapshot.mode,
12998
+ poolLabels: snapshot.poolLabels,
12999
+ councilLabels: snapshot.councilLabels,
13000
+ ledgerPath: snapshot.ledger.path
13001
+ }
13002
+ });
11972
13003
  };
11973
13004
  const brainRoutes = {
11974
13005
  status: (ws) => {
11975
- send(ws, {
11976
- type: "brain.status",
11977
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
11978
- });
13006
+ sendBrainStatus(ws);
11979
13007
  },
11980
13008
  risk: (ws, msg) => {
11981
13009
  const parsed = validateBrainRiskPayload(msg.payload);
@@ -11985,11 +13013,43 @@ function buildRoutes(state, deps2, cb) {
11985
13013
  }
11986
13014
  const { level } = parsed.value;
11987
13015
  deps2.brainSettings.maxAutoRisk = level;
13016
+ sendBrainStatus(ws);
13017
+ },
13018
+ configGet: (ws) => {
11988
13019
  send(ws, {
11989
- type: "brain.status",
11990
- payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
13020
+ type: "brain.config",
13021
+ payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
11991
13022
  });
11992
13023
  },
13024
+ configSet: async (ws, msg) => {
13025
+ const parsed = validateBrainConfigSetPayload(msg.payload);
13026
+ if (!parsed.ok) {
13027
+ sendResult(ws, false, parsed.message);
13028
+ return;
13029
+ }
13030
+ try {
13031
+ const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
13032
+ const result = await persisted;
13033
+ send(ws, {
13034
+ type: "brain.config",
13035
+ payload: {
13036
+ config: deps2.brainRuntime.getSnapshot(),
13037
+ persisted: result.ok,
13038
+ ...result.ok ? {} : { error: result.error ?? "Persist failed." }
13039
+ }
13040
+ });
13041
+ sendBrainStatus(ws);
13042
+ } catch (err) {
13043
+ send(ws, {
13044
+ type: "brain.config",
13045
+ payload: {
13046
+ config: deps2.brainRuntime.getSnapshot(),
13047
+ persisted: false,
13048
+ error: `Invalid Brain setting: ${errMessage(err)}`
13049
+ }
13050
+ });
13051
+ }
13052
+ },
11993
13053
  ask: async (ws, msg) => {
11994
13054
  const parsed = validateBrainAskPayload(msg.payload);
11995
13055
  if (!parsed.ok) {
@@ -12061,7 +13121,7 @@ async function startWebUI(opts = {}) {
12061
13121
  const updateGlobalConfig2 = async (mutate, errorLabel) => updateGlobalConfig(prefHelperDeps, configWriteLock, mutate, errorLabel);
12062
13122
  console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
12063
13123
  if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
12064
- const firstKey = expectDefined(Object.keys(config.providers)[0]);
13124
+ const firstKey = expectDefined3(Object.keys(config.providers)[0]);
12065
13125
  config = patchConfig(config, { provider: firstKey });
12066
13126
  console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
12067
13127
  }
@@ -12097,7 +13157,8 @@ async function startWebUI(opts = {}) {
12097
13157
  promptsCtx,
12098
13158
  modelCapabilitiesRef,
12099
13159
  provider,
12100
- context
13160
+ context,
13161
+ sessionIdentity
12101
13162
  } = preContext;
12102
13163
  let sessionStore = preContext.sessionStore;
12103
13164
  let session = preContext.session;
@@ -12130,7 +13191,12 @@ async function startWebUI(opts = {}) {
12130
13191
  modelCapabilitiesRef,
12131
13192
  sessionGetter: () => session,
12132
13193
  sessionReader,
12133
- annotationsStore
13194
+ annotationsStore,
13195
+ // Brain settings persist to the GLOBAL config only (config.brain is on
13196
+ // the in-project deny list), serialized behind the shared write lock.
13197
+ persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
13198
+ decrypted["brain"] = brainConfig;
13199
+ }, "brain.config")
12134
13200
  });
12135
13201
  const {
12136
13202
  compactor,
@@ -12140,6 +13206,7 @@ async function startWebUI(opts = {}) {
12140
13206
  pipelines,
12141
13207
  brain,
12142
13208
  brainSettings,
13209
+ brainRuntime,
12143
13210
  brainLog,
12144
13211
  brainMonitor,
12145
13212
  codebaseIndexing,
@@ -12161,7 +13228,7 @@ async function startWebUI(opts = {}) {
12161
13228
  getProjectRoot: () => projectRoot,
12162
13229
  getWorkingDir: () => workingDir,
12163
13230
  getModeId: () => modeId,
12164
- getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID),
13231
+ getContextMode: () => String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID2),
12165
13232
  getNeedsSetup: () => needsSetup,
12166
13233
  modelsRegistry
12167
13234
  });
@@ -12204,21 +13271,21 @@ async function startWebUI(opts = {}) {
12204
13271
  wpaths
12205
13272
  }, watcherMetricsRef);
12206
13273
  async function touchProjectEntry(root, workDir) {
12207
- const resolved = path5.resolve(root);
13274
+ const resolved = path22.resolve(root);
12208
13275
  const manifest = await loadManifest(globalConfigPath);
12209
13276
  const now = (/* @__PURE__ */ new Date()).toISOString();
12210
- const existing = manifest.projects.find((p) => path5.resolve(p.root) === resolved);
13277
+ const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
12211
13278
  if (existing) {
12212
13279
  existing.lastSeen = now;
12213
- if (workDir) existing.lastWorkingDir = path5.resolve(workDir);
13280
+ if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
12214
13281
  } else {
12215
13282
  manifest.projects.push({
12216
- name: path5.basename(resolved),
13283
+ name: path22.basename(resolved),
12217
13284
  root: resolved,
12218
13285
  slug: generateProjectSlug(resolved),
12219
13286
  createdAt: now,
12220
13287
  lastSeen: now,
12221
- lastWorkingDir: workDir ? path5.resolve(workDir) : void 0
13288
+ lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
12222
13289
  });
12223
13290
  }
12224
13291
  await saveManifest(manifest, globalConfigPath);
@@ -12310,10 +13377,12 @@ async function startWebUI(opts = {}) {
12310
13377
  terminalHandler,
12311
13378
  brain,
12312
13379
  brainSettings,
13380
+ brainRuntime,
12313
13381
  brainLog
12314
13382
  };
12315
13383
  const cb = {
12316
13384
  sessionStartPayload,
13385
+ onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
12317
13386
  updateAutoCompactionMaxContext,
12318
13387
  updateGlobalConfig: updateGlobalConfig2,
12319
13388
  persistPrefsToConfig: persistPrefsToConfig2,
@@ -12363,13 +13432,13 @@ async function startWebUI(opts = {}) {
12363
13432
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
12364
13433
  ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
12365
13434
  };
12366
- const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig(activeId, { ...providerCfg, type: activeId });
13435
+ const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...providerCfg, type: activeId });
12367
13436
  deps2.context.provider = newProv;
12368
13437
  void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
12369
13438
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
12370
13439
  } catch (err) {
12371
13440
  console.warn(
12372
- `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage(err)}`
13441
+ `[WebUI] Credential hot-reload failed for ${activeId}: ${toErrorMessage10(err)}`
12373
13442
  );
12374
13443
  }
12375
13444
  },
@@ -12436,20 +13505,36 @@ async function startWebUI(opts = {}) {
12436
13505
  },
12437
13506
  clients: () => clients.keys(),
12438
13507
  servers: [httpServer, wssPrimary, ...wssSecondary ? [wssSecondary] : []],
12439
- onShutdown: () => {
13508
+ onShutdown: async () => {
12440
13509
  credentialWatcherClose?.();
12441
13510
  brainMonitor.stop();
12442
- void mcpRegistry.stopAll().catch(() => void 0);
13511
+ await agentServices.brainLedger?.stop().catch(() => {
13512
+ });
13513
+ await mcpRegistry.stopAll().catch(() => void 0);
13514
+ await sessionIdentity.stop();
12443
13515
  eventArming.getDispose()?.();
12444
13516
  if (eternalSubscription) {
12445
13517
  eternalSubscription.dispose();
12446
13518
  eternalSubscription = null;
12447
13519
  }
12448
13520
  codebaseIndexing.dispose();
12449
- return unregisterInstance(process.pid, path5.dirname(globalConfigPath));
13521
+ if (config.superMemory?.enabled !== false && config.superMemory?.hygiene?.autoAfterSession !== false) {
13522
+ const candidate = memoryStore;
13523
+ await candidate.hygiene?.({
13524
+ retentionDays: config.superMemory?.hygiene?.retentionDays,
13525
+ archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
13526
+ }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
13527
+ }
13528
+ await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
12450
13529
  }
12451
13530
  });
12452
13531
  }
13532
+
13533
+ // src/server/provider-store.ts
13534
+ import { expectDefined as expectDefined4 } from "@wrongstack/core";
13535
+ import * as fs15 from "node:fs/promises";
13536
+ import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets3 } from "@wrongstack/core/security";
13537
+ import { atomicWrite as atomicWrite7 } from "@wrongstack/core";
12453
13538
  function createConfigWriteLock() {
12454
13539
  let lock = Promise.resolve();
12455
13540
  return {
@@ -12471,10 +13556,10 @@ function createProviderStore(deps2) {
12471
13556
  const configWriteLock = createConfigWriteLock();
12472
13557
  async function loadSavedProviders2() {
12473
13558
  try {
12474
- const raw = await fs4.readFile(globalConfigPath, "utf8");
13559
+ const raw = await fs15.readFile(globalConfigPath, "utf8");
12475
13560
  const parsed = JSON.parse(raw);
12476
13561
  if (!parsed.providers) return {};
12477
- return decryptConfigSecrets(parsed.providers, vault);
13562
+ return decryptConfigSecrets3(parsed.providers, vault);
12478
13563
  } catch {
12479
13564
  return {};
12480
13565
  }
@@ -12485,14 +13570,14 @@ function createProviderStore(deps2) {
12485
13570
  await prev;
12486
13571
  let parsed;
12487
13572
  try {
12488
- const raw = await fs4.readFile(globalConfigPath, "utf8");
13573
+ const raw = await fs15.readFile(globalConfigPath, "utf8");
12489
13574
  parsed = JSON.parse(raw);
12490
13575
  } catch {
12491
13576
  parsed = {};
12492
13577
  }
12493
13578
  parsed["providers"] = providers;
12494
- const encrypted = encryptConfigSecrets(parsed, vault);
12495
- await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
13579
+ const encrypted = encryptConfigSecrets3(parsed, vault);
13580
+ await atomicWrite7(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
12496
13581
  } finally {
12497
13582
  release();
12498
13583
  }
@@ -12514,7 +13599,7 @@ function createProviderStore(deps2) {
12514
13599
  return;
12515
13600
  }
12516
13601
  cfg.apiKeys = keys;
12517
- const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
13602
+ const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined4(keys[0]);
12518
13603
  delete cfg.apiKey;
12519
13604
  if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
12520
13605
  cfg.activeKey = active.label;
@@ -12533,7 +13618,190 @@ function createProviderStore(deps2) {
12533
13618
  maskedKey: maskedKey2
12534
13619
  };
12535
13620
  }
12536
-
12537
- export { AutoPhaseWebSocketHandler, CollaborationWebSocketHandler, SKIP_DIRS, SddBoardWebSocketHandler, SddWizardWebSocketHandler, SpecsWebSocketHandler, TerminalWebSocketHandler, WorktreeWebSocketHandler, addProvider, bootConfig, broadcast, browserOpenCommand, buildCspHeader, buildSddWizardDeps, buildWebUIAccessUrl, clearAnalyticsBuffer, computeUsageCost, createConfigWriteLock, createCustomModeStore, createEternalSubscription, createHttpServer, createProviderConfigIO, createProviderHandlers, createProviderStore, createShutdown, createToolLspCompletionSource, decodeSessionId, defaultBaseDir, deleteKey, ensureProjectDataDir, envFlag, errMessage, estimateContextBreakdown, estimateTokens, extractToken, extractTokenFromCookie, findFreePort, formatInstances, generateAuthToken, getAnalyticsBuffer, getCostRates, handleApiAnalyticsGet, handleApiAnalyticsPost, handleApiAnalyticsSummary, handleAutoPhaseRoute, handleBrainRoute, handleCompletionRequest, handleDesignList, handleDesignMaterialize, handleDesignSet, handleDesignState, handleDesignUse, handleDesignVerify, handleFilesList, handleFilesRead, handleFilesTree, handleFilesWrite, handleGitChanges, handleGitDiff, handleGitInfo, handleGoalGet, handleMailboxMessages, handleMailboxRoute, handleMcpAdd, handleMcpDisable, handleMcpDiscover, handleMcpEnable, handleMcpList, handleMcpRemove, handleMcpRestart, handleMcpRoute, handleMcpSleep, handleMcpUpdate, handleMcpWake, handleMemoryForget, handleMemoryList, handleMemoryRemember, handleModeRoute, handlePrefsRoute, handleProcessKill, handleProcessKillAll, handleProcessList, handleProjectRoute, handlePromptsContent, handlePromptsCreate, handlePromptsFavorite, handlePromptsList, handlePromptsRecent, handlePromptsSearch, handlePromptsUsed, handleProviderRoute, handleSddBoardRoute, handleSddWizardRoute, handleSessionRoute, handleShellGitRoute, handleShellOpen, handleSkillsContent, handleSkillsCreate, handleSkillsEdit, handleSkillsExport, handleSkillsInstall, handleSkillsList, handleSkillsUninstall, handleSkillsUpdate, handleSpecsRoute, handleWorklistMessage, hostForBrowserUrl, hostHeaderOk, injectWsConfig, injectWsPort, isHiddenEntry, isInsideDist, isLoopbackBind, isLoopbackHostname, isPathInside, isPidAlive, isPortFree, isWildcardBind, listInstances, loadManifest, loadSavedProviders, maskedKey, messagePreview, messageTokens, normalizeKeys, openBrowser, patchConfig, persistPrefsToConfig, projectSavedProviders, projectsJsonPath, rankFiles, registerInstance, registerShutdownHandlers, registryPath, removeProvider, resolveAllPendingConfirms, resolveAuthToken, resolveProviderCatalogForModels, resolveProviderModelMetadata, resolveWorkingDirInsideProject, resolveYoloEligiblePendingConfirms, saveManifest, saveProviders, send, sendResult, setActiveKey, setupEvents, setupWebUICodebaseIndexing, startWebUI, statusProjectHashFromWatchFilename, stringifyContent, tokenMatches, unregisterInstance, upsertKey, validateAutonomySwitchPayload, validateBrainAskPayload, validateBrainRiskPayload, validateContextModeCreatePayload, validateContextModeDeletePayload, validateContextModeSwitchPayload, validateContextModeUpdatePayload, validateGitDiffPayload, validateMailboxAgentsPayload, validateMailboxMessagesPayload, validateMailboxPurgePayload, validateModeSwitchPayload, validateModelSwitchPayload, validatePlanTemplateUsePayload, validatePrefsUpdatePayload, validateProcessKillPayload, validateProjectsAddPayload, validateProjectsSelectPayload, validateShellOpenPayload, validateSkillsCreatePayload, validateSkillsEditPayload, validateWorkingDirSetPayload, verifyClient, writeKeysBack };
13621
+ export {
13622
+ AutoPhaseWebSocketHandler,
13623
+ CollaborationWebSocketHandler,
13624
+ SKIP_DIRS,
13625
+ SddBoardWebSocketHandler,
13626
+ SddWizardWebSocketHandler,
13627
+ SpecsWebSocketHandler,
13628
+ TerminalWebSocketHandler,
13629
+ WorktreeWebSocketHandler,
13630
+ addProvider,
13631
+ bootConfig,
13632
+ broadcast,
13633
+ browserOpenCommand,
13634
+ buildCspHeader,
13635
+ buildSddWizardDeps,
13636
+ buildWebUIAccessUrl,
13637
+ clearAnalyticsBuffer,
13638
+ computeUsageCost,
13639
+ createConfigWriteLock,
13640
+ createCustomModeStore,
13641
+ createEternalSubscription,
13642
+ createHttpServer,
13643
+ createProviderConfigIO,
13644
+ createProviderHandlers,
13645
+ createProviderStore,
13646
+ createShutdown,
13647
+ createToolLspCompletionSource,
13648
+ createZipBuffer,
13649
+ decodeSessionId,
13650
+ defaultBaseDir,
13651
+ deleteKey,
13652
+ ensureProjectDataDir,
13653
+ envFlag,
13654
+ errMessage,
13655
+ estimateContextBreakdown,
13656
+ estimateTokens,
13657
+ extractToken,
13658
+ extractTokenFromCookie,
13659
+ findFreePort,
13660
+ formatInstances,
13661
+ generateAuthToken,
13662
+ getAnalyticsBuffer,
13663
+ getCostRates,
13664
+ handleApiAnalyticsGet,
13665
+ handleApiAnalyticsPost,
13666
+ handleApiAnalyticsSummary,
13667
+ handleAutoPhaseRoute,
13668
+ handleBrainRoute,
13669
+ handleCompletionRequest,
13670
+ handleDesignList,
13671
+ handleDesignMaterialize,
13672
+ handleDesignSet,
13673
+ handleDesignState,
13674
+ handleDesignUse,
13675
+ handleDesignVerify,
13676
+ handleFilesList,
13677
+ handleFilesRead,
13678
+ handleFilesTree,
13679
+ handleFilesWrite,
13680
+ handleGitChanges,
13681
+ handleGitDiff,
13682
+ handleGitInfo,
13683
+ handleGoalGet,
13684
+ handleMailboxMessages,
13685
+ handleMailboxRoute,
13686
+ handleMcpAdd,
13687
+ handleMcpDisable,
13688
+ handleMcpDiscover,
13689
+ handleMcpEnable,
13690
+ handleMcpList,
13691
+ handleMcpPromptGet,
13692
+ handleMcpPrompts,
13693
+ handleMcpRemove,
13694
+ handleMcpResourceRead,
13695
+ handleMcpResources,
13696
+ handleMcpRestart,
13697
+ handleMcpRoute,
13698
+ handleMcpSleep,
13699
+ handleMcpUpdate,
13700
+ handleMcpWake,
13701
+ handleMemoryForget,
13702
+ handleMemoryList,
13703
+ handleMemoryRemember,
13704
+ handleModeRoute,
13705
+ handlePrefsRoute,
13706
+ handleProcessKill,
13707
+ handleProcessKillAll,
13708
+ handleProcessList,
13709
+ handleProjectRoute,
13710
+ handlePromptsContent,
13711
+ handlePromptsCreate,
13712
+ handlePromptsFavorite,
13713
+ handlePromptsList,
13714
+ handlePromptsRecent,
13715
+ handlePromptsSearch,
13716
+ handlePromptsUsed,
13717
+ handleProviderRoute,
13718
+ handleSddBoardRoute,
13719
+ handleSddWizardRoute,
13720
+ handleSessionRoute,
13721
+ handleShellGitRoute,
13722
+ handleShellOpen,
13723
+ handleSkillsContent,
13724
+ handleSkillsCreate,
13725
+ handleSkillsEdit,
13726
+ handleSkillsExport,
13727
+ handleSkillsInstall,
13728
+ handleSkillsList,
13729
+ handleSkillsUninstall,
13730
+ handleSkillsUpdate,
13731
+ handleSpecsRoute,
13732
+ handleWorklistMessage,
13733
+ hostForBrowserUrl,
13734
+ hostHeaderOk,
13735
+ injectWsConfig,
13736
+ injectWsPort,
13737
+ isHiddenEntry,
13738
+ isInsideDist,
13739
+ isLoopbackBind,
13740
+ isLoopbackHostname,
13741
+ isPathInside,
13742
+ isPidAlive,
13743
+ isPortFree,
13744
+ isWildcardBind,
13745
+ listInstances,
13746
+ loadManifest,
13747
+ loadSavedProviders,
13748
+ maskedKey,
13749
+ messagePreview,
13750
+ messageTokens,
13751
+ normalizeKeys,
13752
+ openBrowser,
13753
+ patchConfig,
13754
+ persistPrefsToConfig,
13755
+ projectSavedProviders,
13756
+ projectsJsonPath,
13757
+ rankFiles,
13758
+ readZipEntries,
13759
+ registerInstance,
13760
+ registerShutdownHandlers,
13761
+ registryPath,
13762
+ removeProvider,
13763
+ resolveAllPendingConfirms,
13764
+ resolveAuthToken,
13765
+ resolveProviderCatalogForModels,
13766
+ resolveProviderModelMetadata,
13767
+ resolveWorkingDirInsideProject,
13768
+ resolveYoloEligiblePendingConfirms,
13769
+ saveManifest,
13770
+ saveProviders,
13771
+ send,
13772
+ sendResult,
13773
+ setActiveKey,
13774
+ setupEvents,
13775
+ setupWebUICodebaseIndexing,
13776
+ startWebUI,
13777
+ statusProjectHashFromWatchFilename,
13778
+ stringifyContent,
13779
+ tokenMatches,
13780
+ unregisterInstance,
13781
+ upsertKey,
13782
+ validateAutonomySwitchPayload,
13783
+ validateBrainAskPayload,
13784
+ validateBrainRiskPayload,
13785
+ validateContextModeCreatePayload,
13786
+ validateContextModeDeletePayload,
13787
+ validateContextModeSwitchPayload,
13788
+ validateContextModeUpdatePayload,
13789
+ validateGitDiffPayload,
13790
+ validateMailboxAgentsPayload,
13791
+ validateMailboxMessagesPayload,
13792
+ validateMailboxPurgePayload,
13793
+ validateModeSwitchPayload,
13794
+ validateModelSwitchPayload,
13795
+ validatePlanTemplateUsePayload,
13796
+ validatePrefsUpdatePayload,
13797
+ validateProcessKillPayload,
13798
+ validateProjectsAddPayload,
13799
+ validateProjectsSelectPayload,
13800
+ validateShellOpenPayload,
13801
+ validateSkillsCreatePayload,
13802
+ validateSkillsEditPayload,
13803
+ validateWorkingDirSetPayload,
13804
+ verifyClient,
13805
+ writeKeysBack
13806
+ };
12538
13807
  //# sourceMappingURL=index.js.map
12539
- //# sourceMappingURL=index.js.map