@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.d.ts CHANGED
@@ -1,2304 +1,15 @@
1
- import { WebSocket } from 'ws';
2
- import * as _wrongstack_core from '@wrongstack/core';
3
- import { Agent, Context, Logger, EventBus, Provider, Tool, SessionStore, ToolRegistry, ModelsRegistry, ConfigStore, SecretVault, JournalEntry, SessionEventBridge, WstackPaths, MemoryStore, PromptLoader, PromptUsageStore, ProviderConfig, ProviderApiKey, AgentFactory, BrainArbiter, SkillLoader, Config, DefaultSecretVault, DefaultLogger, IndexingConfig, CollaborationBus, ResolvedProvider, ResolvedModel } from '@wrongstack/core';
4
- import * as http from 'node:http';
5
- import { MCPRegistry } from '@wrongstack/mcp';
6
- import { SddInterviewDriver } from '@wrongstack/sdd';
7
- import { SkillInstaller } from '@wrongstack/core/skills';
8
- import { SessionReader, AnnotationsStore } from '@wrongstack/core/storage';
9
- export { WorklistContext, handleWorklistMessage } from './server/handlers.js';
10
- import { OAuthKind } from '@wrongstack/providers/oauth';
11
-
12
- interface AutoPhaseWSMessage {
13
- type: string;
14
- payload?: Record<string, unknown>;
15
- }
16
1
  /**
17
- * AutoPhaseWebSocketHandlerWebSocket-based AutoPhase control.
2
+ * @wrongstack/webui-serverpublic API entry.
18
3
  *
19
- * Message types:
20
- * autophase.start → { title, phases?, autonomous? }
21
- * autophase.pause → {}
22
- * autophase.resume → {}
23
- * autophase.stop → {}
24
- * autophase.status → {}
25
- * autophase.selectPhase → { phaseId }
26
- * autophase.taskStatus → { taskId, status }
27
- */
28
- declare class AutoPhaseWebSocketHandler {
29
- private agent;
30
- private context;
31
- private logger;
32
- private events?;
33
- private projectRoot?;
34
- private orchestrator;
35
- private graph;
36
- private store;
37
- private clients;
38
- private broadcastInterval;
39
- /** Aborts in-flight task agents AND the planning turn when the run is stopped. */
40
- private abort;
41
- /** Set the instant a stop/clear/revert is requested, so a planning turn that
42
- * resolves afterwards never launches the orchestrator (the abort alone can't
43
- * cover the window between the LLM call resolving and the orchestrator start). */
44
- private stopping;
45
- /** Optional per-phase git-worktree isolation (lazily created at start). */
46
- private worktrees;
47
- /** Base branch + tip SHA captured at run start so a revert can git-revert the
48
- * run's squash commits (history-preserving) instead of a destructive reset. */
49
- private runBase;
50
- /** Per-run worker identities so the board can show "who is on what". */
51
- private usedNicknames;
52
- constructor(agent: Agent, context: Context, logger: Logger, storeDir: string, events?: EventBus | undefined, projectRoot?: string | undefined);
53
- addClient(ws: WebSocket): void;
54
- handleMessage(msg: AutoPhaseWSMessage): Promise<void>;
55
- private handleStart;
56
- /**
57
- * Halt the run NOW — at any phase. Sets `stopping` (so a planning turn that
58
- * resolves afterwards bails), aborts in-flight agents, stops the orchestrator
59
- * tick, and ends the live broadcast. The board is kept for review; use
60
- * `autophase.clear` to reset or `autophase.revert` to undo the changes.
61
- */
62
- private handleStop;
63
- /**
64
- * Stop + wipe: tear down phase worktrees and reset to an empty board so the UI
65
- * returns to the start screen ("new one"). Does NOT touch already-merged commits
66
- * on the base branch — that is `autophase.revert`.
67
- */
68
- private handleClear;
69
- /**
70
- * Stop + undo: remove phase worktrees, then history-preservingly `git revert`
71
- * every commit this run landed on the base branch (captured `runBase`..HEAD),
72
- * then reset to an empty board. Refuses (reports a reason) on a dirty tree or a
73
- * conflicting revert rather than leaving the tree half-reverted.
74
- */
75
- private handleRevert;
76
- /** Generic fallback phases when the LLM planner produces nothing usable. */
77
- private defaultPhases;
78
- /** Plan phases+todos for the goal via the LLM; fall back to defaults on failure.
79
- * The caller passes the run's abort signal so a stop during planning cancels
80
- * the LLM turn (the previous fresh, never-aborted controller made planning
81
- * uninterruptible). */
82
- private planPhases;
83
- private executeTaskWithAgent;
84
- /** Persist + broadcast after an interactive board mutation. */
85
- private afterBoardMutation;
86
- private handleTaskStatusChange;
87
- private startBroadcast;
88
- private stopBroadcast;
89
- private broadcastState;
90
- private buildState;
91
- private sendState;
92
- private broadcast;
93
- private send;
94
- }
95
-
96
- /**
97
- * Context-aware editor completion for the WebUI Monaco surface.
98
- *
99
- * The handler combines fast symbol-index hits with a short, JSON-only LLM call.
100
- * It is intentionally side-effect free: it never writes files and only reads the
101
- * existing codebase index when available.
102
- */
103
-
104
- type CompletionItemKind = 'text' | 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'file' | 'reference';
105
- interface CompletionSuggestion {
106
- label: string;
107
- insertText: string;
108
- kind?: CompletionItemKind | undefined;
109
- detail?: string | undefined;
110
- documentation?: string | undefined;
111
- sortText?: string | undefined;
112
- source?: 'llm' | 'index' | 'lsp' | undefined;
113
- }
114
- interface CompletionHandlerOptions {
115
- projectRoot: string;
116
- provider?: Provider | undefined;
117
- model?: string | undefined;
118
- indexDir?: string | undefined;
119
- lspCompletion?: LspCompletionSource | undefined;
120
- timeoutMs?: number | undefined;
121
- }
122
- interface LspCompletionSourceRequest {
123
- filePath: string;
124
- lineNumber: number;
125
- column: number;
126
- content?: string | undefined;
127
- triggerCharacter?: string | undefined;
128
- signal: AbortSignal;
129
- }
130
- type LspCompletionSource = (request: LspCompletionSourceRequest) => Promise<CompletionSuggestion[]>;
131
- declare function handleCompletionRequest(ws: WebSocket, msg: unknown, opts: CompletionHandlerOptions): Promise<void>;
132
- declare function createToolLspCompletionSource(tool: Tool | undefined, ctx: Context): LspCompletionSource | undefined;
133
-
134
- /**
135
- * Custom context modes — user-defined presets that are loaded from disk,
136
- * merged with the built-in modes, and managed via WebSocket CRUD handlers.
137
- *
138
- * Stored in: ~/.wrongstack/custom-context-modes.json
139
- * Format: { "modes": ContextWindowMode[] }
140
- */
141
- interface CustomContextMode {
142
- id: string;
143
- name: string;
144
- description: string;
145
- thresholds: {
146
- warn: number;
147
- soft: number;
148
- hard: number;
149
- };
150
- aggressiveOn: string;
151
- preserveK: number;
152
- eliseThreshold: number;
153
- targetLoad: number;
154
- /** Whether this is a user-defined (custom) or built-in mode. */
155
- custom: boolean;
156
- }
157
- interface CustomModeStore {
158
- modes: Map<string, CustomContextMode>;
159
- load: () => Promise<void>;
160
- save: () => Promise<void>;
161
- create: (mode: CustomContextMode) => {
162
- ok: boolean;
163
- error?: string | undefined;
164
- };
165
- update: (id: string, patch: Partial<CustomContextMode>) => {
166
- ok: boolean;
167
- error?: string | undefined;
168
- };
169
- remove: (id: string) => {
170
- ok: boolean;
171
- error?: string | undefined;
172
- };
173
- list: () => CustomContextMode[];
174
- }
175
- declare function createCustomModeStore(wrongstackDir: string): CustomModeStore;
176
-
177
- /**
178
- * Shared Design Studio WebSocket handlers for both the standalone WebUI server
179
- * (`packages/webui/src/server/index.ts`) and the CLI's `--webui` embedded
180
- * server (`packages/cli/src/webui-server.ts`). One source of truth keeps the two
181
- * servers at parity (enforced by ws-handler-parity.test.ts).
182
- *
183
- * case 'design.list': return handleDesignList(ws, designCtx);
184
- * case 'design.use': return handleDesignUse(ws, designCtx, msg);
185
- * case 'design.state': return handleDesignState(ws, designCtx);
186
- * case 'design.set': return handleDesignSet(ws, designCtx, msg);
187
- * case 'design.materialize': return handleDesignMaterialize(ws, designCtx, msg);
188
- *
189
- * Browsing + customization of curated UI design kits; `design.use` pins the
190
- * active kit, `design.set` records color/token overrides, `design.materialize`
191
- * writes the (override-applied) tokens to a real theme file on disk.
192
- */
193
-
194
- interface DesignContext {
195
- projectRoot: string;
196
- /** Live agent context whose `meta.designStudio` we read/pin. Optional. */
197
- agentMeta?: {
198
- meta: Record<string, unknown>;
199
- } | undefined;
200
- }
201
- declare function handleDesignList(ws: WebSocket, ctx: DesignContext): Promise<void>;
202
- declare function handleDesignState(ws: WebSocket, ctx: DesignContext): Promise<void>;
203
- declare function handleDesignUse(ws: WebSocket, ctx: DesignContext, msg: {
204
- payload?: unknown;
205
- }): Promise<void>;
206
- /** Record structured color/token overrides without changing the pinned kit. */
207
- declare function handleDesignSet(ws: WebSocket, ctx: DesignContext, msg: {
208
- payload?: unknown;
209
- }): Promise<void>;
210
- /** Write the active kit's (override-applied) tokens to a real theme file. */
211
- declare function handleDesignMaterialize(ws: WebSocket, ctx: DesignContext, msg: {
212
- payload?: unknown;
213
- }): Promise<void>;
214
- /** Scan project UI files for off-palette colors against the active kit. */
215
- declare function handleDesignVerify(ws: WebSocket, ctx: DesignContext): Promise<void>;
216
-
217
- interface WSServerMessage {
218
- type: string;
219
- payload: unknown;
220
- }
221
- interface WSClientMessage {
222
- type: string;
223
- payload?: unknown | undefined;
224
- }
225
- interface WebUIOptions {
226
- /** HTTP frontend port. Prefer `httpPort`; `port` is kept for compatibility. */
227
- port?: number | undefined;
228
- /** HTTP frontend port. */
229
- httpPort?: number | undefined;
230
- webuiPort?: number | undefined;
231
- /** WebSocket backend port. */
232
- wsPort?: number | undefined;
233
- /** Host/interface to bind. */
234
- wsHost?: string | undefined;
235
- /** Fixed access token/password. Defaults to WEBUI_TOKEN or random per process. */
236
- accessToken?: string | undefined;
237
- /** Browser-facing HTTP URL, used in startup output and instance registry. */
238
- publicUrl?: string | undefined;
239
- /** Browser-facing WebSocket URL, injected into the frontend for tunnels/proxies. */
240
- publicWsUrl?: string | undefined;
241
- /** Force token/password protection even on loopback binds. */
242
- requireToken?: boolean | undefined;
243
- /**
244
- * Path to the directory containing the built WebUI frontend assets
245
- * (the `dist` directory of `@wrongstack/webui`). When omitted, the
246
- * server tries to resolve the frontend package from its own module
247
- * location; callers that embed the server (e.g. the desktop app) should
248
- * pass an explicit path for deterministic resolution.
249
- */
250
- distDir?: string | undefined;
251
- /**
252
- * Pre-built backend services. When provided, `startWebUI` skips its
253
- * default agent/event-bus/session/store construction and wires the
254
- * supplied instances into the WS message router and HTTP API
255
- * handlers instead.
256
- *
257
- * Intended for callers (most notably `cli/webui-server.ts`) that
258
- * already own the agent lifecycle — the CLI's `runWebUI` constructs
259
- * the Agent, EventBus, SessionStore, and friends so it can run an
260
- * eternal iteration against them, then hands the lot to the webui
261
- * for the human-facing surface.
262
- *
263
- * `session` is typed as `SessionStore` (read + write) rather than
264
- * the narrower `SessionWriter` because `startWebUI` needs to
265
- * `load()` existing session history to project the chat view, and
266
- * `list()` past sessions to populate the sessions dashboard. A
267
- * `SessionWriter`-only field would force `startWebUI` to take a
268
- * separate `sessionStore` for reads, which is a worse API for the
269
- * CLI caller (`runWebUI` already has one store, not two).
270
- *
271
- * When `services` is omitted, `startWebUI` retains its existing
272
- * behavior (builds the defaults in-place). This keeps the standalone
273
- * `node dist/index.js webui` flow fully back-compatible.
274
- */
275
- services?: BackendServices | undefined;
276
- /**
277
- * Subscribe to live per-iteration events from the eternal-autonomy
278
- * engine. When provided, `startWebUI` wires a WS broadcast that
279
- * pushes each `JournalEntry` to every connected client. Observability
280
- * only — starting the loop still goes through REPL/TUI or `--eternal`,
281
- * since the webui has no slash-command dispatch surface yet.
282
- *
283
- * The argument is a *function* the caller supplies that performs the
284
- * actual subscription; the returned disposer is invoked on
285
- * `shutdown()`. This indirection lets the caller (most commonly
286
- * `cli/webui-server.ts`) own the engine lifecycle and merely hand the
287
- * webui an observer slot.
288
- */
289
- subscribeEternalIteration?: ((fn: (entry: JournalEntry) => void) => () => void) | undefined;
290
- }
291
- interface BackendServices {
292
- agent: Agent;
293
- events: EventBus;
294
- session: SessionStore;
295
- toolRegistry: ToolRegistry;
296
- modelsRegistry: ModelsRegistry;
297
- configStore: ConfigStore;
298
- vault: SecretVault;
299
- globalConfigPath: string;
300
- projectRoot: string;
301
- }
302
- interface ConnectedClient {
303
- ws: WebSocket;
304
- sessionId: string | null;
305
- connectedAt: number;
306
- /** Unique per-connection id — used to key per-connection state (e.g. the
307
- * rate-limit bucket) so distinct browser tabs that share the same
308
- * `sessionId` do not collide, and so the entry is reliably removable on
309
- * close (`String(ws)` is `"[object Object]"` for every socket). */
310
- connId: string;
311
- }
312
-
313
- type EternalSubscribe = (fn: (entry: JournalEntry) => void) => () => void;
314
- type EternalBroadcast<C> = (clients: Map<WebSocket, C>, msg: WSServerMessage) => void;
315
- interface EternalSubscription {
316
- /** Tear down the underlying engine subscription. Idempotent. */
317
- dispose: () => void;
318
- }
319
- declare function createEternalSubscription<C>(subscribe: EternalSubscribe, broadcast: EternalBroadcast<C>, clientsRef: () => Map<WebSocket, C>): EternalSubscription;
320
-
321
- /**
322
- * Shared file-operation WebSocket handlers for both the standalone WebUI
323
- * server and the CLI's `--webui` embedded server. Extracted from the
324
- * duplicated switch cases in `index.ts` and `cli/src/webui-server.ts`.
325
- *
326
- * Each function handles the full request→response cycle for one message
327
- * type. Callers drop them into their switch statement:
328
- *
329
- * case 'files.tree': return handleFilesTree(ws, msg, projectRoot);
330
- */
331
-
332
- interface FilesWriteOptions {
333
- onWritten?: ((filePath: string) => void | Promise<void>) | undefined;
334
- }
335
- /**
336
- * Build and send a nested directory tree for the File Explorer.
337
- *
338
- * Walks `projectRoot` to depth 10 max, skipping heavyweight dirs
339
- * (node_modules, .git, dist, …) and dot-entries. Responds with
340
- * `{ type: 'files.tree', payload: { root, tree } }`.
341
- */
342
- declare function handleFilesTree(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
343
- /**
344
- * Read a file's content for the Monaco editor.
345
- *
346
- * Guards against path traversal (`../` escapes). Responds with
347
- * `{ type: 'files.read', payload: { filePath, content } }`.
348
- */
349
- declare function handleFilesRead(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
350
- /**
351
- * Write file content back to disk (atomic write via tmp + rename).
352
- *
353
- * Guards against path traversal. Responds with
354
- * `{ type: 'files.written', payload: { filePath, success } }`.
355
- */
356
- declare function handleFilesWrite(ws: WebSocket, msg: unknown, projectRoot: string, opts?: FilesWriteOptions): Promise<void>;
357
- /**
358
- * Lightweight project file picker for the chat `@` mention popup.
359
- *
360
- * Walks `projectRoot` (max depth 8), skipping hidden and heavyweight
361
- * dirs, then fuzzy-ranks results against `query`. Responds with
362
- * `{ type: 'files.list', payload: { files } }`.
363
- */
364
- declare function handleFilesList(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
365
-
366
- /**
367
- * Shared `git.info` WebSocket handler for both the standalone WebUI server and
368
- * the CLI's `--webui` embedded server. Extracted from the duplicated switch
369
- * cases in `index.ts` and `cli/src/webui-server.ts`, which had drifted (the
370
- * standalone copy transposed ahead/behind and never matched deletions). One
371
- * implementation here keeps both surfaces in lockstep.
372
- *
373
- * case 'git.info': return handleGitInfo(ws, projectRoot);
374
- */
375
-
376
- /**
377
- * Read git branch, change stats, and upstream sync status from `projectRoot`
378
- * and broadcast a `git.info` message. Never throws — a non-repo / missing-git
379
- * directory yields an empty-but-valid payload.
380
- */
381
- declare function handleGitInfo(ws: WebSocket, projectRoot: string): Promise<void>;
382
- /**
383
- * Read the working-tree change set (everything that differs from HEAD:
384
- * staged, unstaged, and untracked) and broadcast a `git.changes` message.
385
- *
386
- * The file list comes from `git status --porcelain -z` (NUL-delimited so
387
- * paths with spaces/unicode survive intact, and renames are unambiguous).
388
- * Per-file line counts come from `--numstat` of both the unstaged and the
389
- * staged diff, summed. Untracked files intentionally report 0/0 here so the
390
- * list view does not read every untracked file; `git.diff` loads a selected
391
- * file lazily on demand.
392
- * Never throws — a non-repo yields an empty list.
393
- */
394
- declare function handleGitChanges(ws: WebSocket, projectRoot: string): Promise<void>;
395
- /**
396
- * Resolve the before/after text for a single file and broadcast a `git.diff`
397
- * message. `oldText` is the file at HEAD (`git show HEAD:<path>`), `newText`
398
- * is the current working-tree content. New/untracked files have empty
399
- * `oldText`; deleted files have empty `newText`. Binary or oversized files
400
- * are reported with a flag instead of content so the client can show a notice.
401
- */
402
- declare function handleGitDiff(ws: WebSocket, projectRoot: string, path: string): Promise<void>;
403
-
404
- type ConfirmDecision = 'yes' | 'no' | 'always' | 'deny';
405
- interface PendingConfirm {
406
- resolve: (decision: ConfirmDecision) => void;
407
- decisionSource?: string | undefined;
408
- riskTier?: 'safe' | 'standard' | 'destructive' | undefined;
409
- /** The exact `tool.confirm_needed` broadcast payload, kept so the prompt
410
- * can be replayed to clients that connect while the confirm is pending
411
- * (e.g. a browser refresh mid-prompt). */
412
- payload?: Record<string, unknown> | undefined;
413
- }
414
- declare function resolveYoloEligiblePendingConfirms(pendingConfirms: Map<string, PendingConfirm>): void;
415
- declare function resolveAllPendingConfirms(pendingConfirms: Map<string, PendingConfirm>, decision: ConfirmDecision): void;
416
-
417
- /** Metrics for the file watcher that watches status.json files. */
418
- interface FileWatcherMetrics {
419
- /** Number of status.json filesystem events detected after filename filtering. */
420
- fileChangesDetected: number;
421
- filesProcessed: number;
422
- broadcastsSent: number;
423
- debounceResets: number;
424
- totalDebounceDelayMs: number;
425
- activeProjects: number;
426
- /** Average debounce delay in ms across all broadcasts. */
427
- averageDebounceDelayMs: number;
428
- /** Whether the file watcher is currently active. */
429
- watcherActive: boolean;
430
- }
431
- interface SetupEventsDeps {
432
- events: EventBus;
433
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
434
- clients: Map<WebSocket, ConnectedClient>;
435
- config: {
436
- tools?: {
437
- maxIterations?: number | undefined;
438
- };
439
- };
440
- context: Context;
441
- pendingConfirms: Map<string, PendingConfirm>;
442
- /** Optional global config dir (~/.wrongstack) — enables SessionRegistry poll for fleet view. */
443
- globalConfigPath?: string | undefined;
444
- /**
445
- * Audit-level-aware session log bridge. When provided, tool/error/provider
446
- * events are persisted to the session JSONL (same contract as the CLI) —
447
- * without it, standalone-WebUI sessions carry no audit events and resume
448
- * with no tool history.
449
- */
450
- sessionBridge?: SessionEventBridge | undefined;
451
- /** Optional wpaths for writing status.json file. */
452
- wpaths?: WstackPaths | undefined;
453
- /**
454
- * Optional object to populate with file watcher metrics.
455
- * When provided, the setupEvents function will populate this object
456
- * with real-time metrics from the file watcher.
457
- */
458
- watcherMetrics?: FileWatcherMetrics | undefined;
459
- /**
460
- * Receives the internal `broadcastSessions` fn so the HTTP layer can trigger
461
- * an immediate fleet re-broadcast on `POST /api/fleet/ping` (push-on-write
462
- * from a TUI/REPL), instead of waiting on the registry file-watch/poll.
463
- */
464
- onFleetBroadcaster?: ((fn: () => Promise<void>) => void) | undefined;
465
- }
466
- declare function statusProjectHashFromWatchFilename(projectsDir: string, filename: string | Buffer): string | null;
467
- /**
468
- * Wire kernel events to WS broadcasts and (when wpaths/globalConfigPath are
469
- * given) start the status-file watcher and session-poll interval.
470
- *
471
- * Returns a disposer that stops the watcher, clears the metrics/poll
472
- * intervals, and flushes pending debounce timers. Callers MUST invoke it on
473
- * shutdown — the watcher is `persistent: true` and the metrics interval is not
474
- * `unref`'d, so without disposal they keep the process alive and leak across
475
- * server restarts. (Previously this was hung off a non-existent
476
- * `process.on('cleanup')` event that never fired.)
477
- */
478
- declare function setupEvents(deps: SetupEventsDeps): () => void;
479
-
480
- interface CreateHttpServerOptions {
481
- /** Port to listen on. Defaults to 3456 (or the `PORT` env var). */
482
- port?: number | undefined;
483
- /** Host/interface to bind. Typically the loopback for the WebUI. */
484
- host: string;
485
- /** Resolved path to the directory containing the built React assets. */
486
- distDir: string;
487
- /**
488
- * WS port — appears in the CSP `connect-src` directive so the browser
489
- * is allowed to open a WebSocket back to the local server.
490
- */
491
- wsPort: number;
492
- /**
493
- * Public WebSocket URL injected into the frontend. Use this behind tunnels or
494
- * reverse proxies where the browser-facing WS URL differs from host:wsPort.
495
- */
496
- publicWsUrl?: string | undefined;
497
- /**
498
- * Path to the global WrongStack root (~/.wrongstack). Used by the
499
- * /api/sessions and /api/sessions/:id/agents endpoints to read the
500
- * cross-process SessionRegistry.
501
- */
502
- globalRoot?: string | undefined;
503
- /**
504
- * Shared auth token for HTTP and WS access. Required for non-loopback
505
- * binds (LAN exposure). Loopback binds accept local browser access without
506
- * a token (the WS path's loopback-bootstrap policy — see ws-auth.ts).
507
- */
508
- apiToken?: string | undefined;
509
- /** Force HTTP token auth even on loopback binds, useful behind public tunnels. */
510
- requireToken?: boolean | undefined;
511
- /**
512
- * If true, the `/ws-auth` endpoint exchanges a `?token=` query param (or
513
- * `X-WS-Token` header) for an `HttpOnly` auth cookie. The cookie is then
514
- * sent automatically on the WS upgrade, closing the C-598 query-string
515
- * token exposure class. Default: true. Set to false to keep the legacy
516
- * URL-token-only flow (e.g. in tests that don't want cookie state).
517
- */
518
- enableWsCookie?: boolean | undefined;
519
- /**
520
- * Optional file watcher metrics object. When provided, the
521
- * /debug/watcher-metrics endpoint will be enabled to expose these metrics.
522
- */
523
- watcherMetrics?: FileWatcherMetrics | undefined;
524
- /**
525
- * Push-on-write hook. `POST /api/fleet/ping` (loopback only) invokes this to
526
- * trigger an immediate fleet re-broadcast, so a TUI/REPL's registry write
527
- * reaches the map without waiting on the file-watch/poll. Best-effort.
528
- */
529
- onFleetPing?: (() => void) | undefined;
530
- }
531
- /**
532
- * Inject the live WS port into the served HTML so the frontend connects to
533
- * THIS instance's backend instead of a hardcoded default. Enables running
534
- * several WebUI instances simultaneously on different PORT/WS_PORT pairs
535
- * (e.g. one per project) — each instance serves HTML stamped with its own
536
- * WS port.
537
- *
538
- * A `<meta>` tag is used deliberately rather than an inline `<script>`: the
539
- * CSP sets `script-src 'self'`, which would block an inline script, but meta
540
- * tags are not subject to script-src. The frontend reads
541
- * `meta[name="wrongstack-ws-port"]` (see ws-client.ts `defaultWsUrl`).
542
- */
543
- declare function injectWsPort(html: string, wsPort: number): string;
544
- declare function injectWsConfig(html: string, opts: {
545
- wsPort: number;
546
- publicWsUrl?: string | undefined;
547
- }): string;
548
- /** Build the Content-Security-Policy value for the given WS port. */
549
- declare function buildCspHeader(wsPort: number, requestHost?: string | undefined, publicWsUrl?: string | undefined): string;
550
- /**
551
- * Returns true when `candidate` (a fully-resolved absolute path) lies
552
- * strictly inside `distDir` (or equals it). Used to reject path-traversal
553
- * attempts after `path.resolve` has normalised any `..` segments.
554
- *
555
- * Exported so tests can assert the guard's contract without having to
556
- * also defeat the WHATWG URL normaliser (which strips `..` from the
557
- * path string *before* the request even reaches the server, making a
558
- * black-box test via fetch impossible).
559
- */
560
- declare function isInsideDist(candidate: string, distDir: string): boolean;
561
- /**
562
- * Decode a `:id` path segment captured by the `/api/sessions/:id/*` routes.
563
- *
564
- * Session ids are `YYYY-MM-DD/sess_<ULID>` — they contain a literal
565
- * `/`. The frontend builds the URL with `encodeURIComponent(sessionId)`, so
566
- * that slash arrives as `%2F`. The route regex `([^/]+)` correctly captures
567
- * the whole percent-encoded segment (there is no real `/` in `%2F`), but the
568
- * SessionRegistry is keyed by the *decoded* id — so the capture must be
569
- * `decodeURIComponent`d before lookup. Without this, every
570
- * `/api/sessions/:id/{events,message,agents}` request 404s (the registry has
571
- * `2026-…/…` but we looked up `2026-…%2F…`), which broke the Fleet HQ
572
- * watch-stream and the steer-message composer.
573
- *
574
- * Malformed percent-encoding (a lone `%`) makes `decodeURIComponent` throw;
575
- * fall back to the raw segment so the caller still gets a clean 404 rather
576
- * than a 500.
577
- */
578
- declare function decodeSessionId(segment: string): string;
579
- /**
580
- * Create the static-file HTTP server. Returns the `http.Server` (not
581
- * listening yet) so the caller can attach to a `shutdown()` hook and
582
- * coordinate the listen() with the WebSocket bootstrap.
583
- */
584
- declare function createHttpServer(opts: CreateHttpServerOptions): http.Server;
585
-
586
- /**
587
- * Running-instance registry for the standalone WebUI server.
588
- *
589
- * Every live `wstackui` process records itself in a single JSON file under the
590
- * wstack home dir (`~/.wrongstack/webui-instances.json`) so a user running
591
- * several instances (one per project, or several per project on different
592
- * ports) can see at a glance which ports are open for which path.
593
- *
594
- * Design notes:
595
- * - **Self-healing**: every register/unregister/list prunes entries whose PID
596
- * is no longer alive (`process.kill(pid, 0)`), so a crashed instance that
597
- * never got to unregister doesn't leave a ghost behind.
598
- * - **Atomic writes**: the file is rewritten via `atomicWrite` (tmp + rename),
599
- * so a concurrent reader never sees a half-written file. Two instances
600
- * starting at the *exact* same millisecond could still race the
601
- * read-modify-write — acceptable for a best-effort tracking file, and the
602
- * next register() heals any dropped entry.
603
- * - **Best-effort**: a failure to read/write the registry must NEVER take the
604
- * server down. Callers wrap these in `.catch()`.
605
- */
606
- /** One running WebUI process. */
607
- interface WebUIInstanceRecord {
608
- /** OS process id — also the liveness key. */
609
- pid: number;
610
- /** HTTP port serving the React frontend. */
611
- httpPort: number;
612
- /** WebSocket port for the agent backend. */
613
- wsPort: number;
614
- /** Bind host (e.g. 127.0.0.1 or 0.0.0.0). */
615
- host: string;
616
- /** Absolute project root the instance booted against. */
617
- projectRoot: string;
618
- /** Display name (basename of projectRoot). */
619
- projectName: string;
620
- /** ISO timestamp when the instance registered. */
621
- startedAt: string;
622
- /** Convenience open-in-browser URL. */
623
- url: string;
624
- }
625
- /** Default wstack home dir (`~/.wrongstack`). Callers may override the base. */
626
- declare function defaultBaseDir(): string;
627
- /** Resolve the registry file path for a given base dir. */
628
- declare function registryPath(baseDir?: string): string;
629
- /**
630
- * Liveness probe. `process.kill(pid, 0)` sends no signal — it only checks the
631
- * process exists. ESRCH ⇒ dead; EPERM ⇒ alive but owned by another user (still
632
- * counts as alive). Any other error is treated conservatively as "alive" so we
633
- * never prune an instance we simply failed to probe.
634
- */
635
- declare function isPidAlive(pid: number): boolean;
636
- /**
637
- * Register (or refresh) this instance. Prunes dead entries and any stale entry
638
- * for our own PID before adding the current record. Best-effort — rejects only
639
- * on a hard fs error, which callers swallow.
640
- */
641
- declare function registerInstance(record: WebUIInstanceRecord, baseDir?: string): Promise<void>;
642
- /** Remove this instance (called on graceful shutdown). Also prunes dead pids. */
643
- declare function unregisterInstance(pid: number, baseDir?: string): Promise<void>;
644
- /** List live instances, pruning any dead entries encountered. */
645
- declare function listInstances(baseDir?: string): Promise<WebUIInstanceRecord[]>;
646
- /** Human-readable table of running instances for `wstackui --list`. */
647
- declare function formatInstances(instances: WebUIInstanceRecord[]): string;
648
-
649
- /**
650
- * MCP management handlers for the WebUI server (both the standalone
651
- * `wstackui` server and the CLI's embedded `--webui` server).
652
- *
653
- * These are thin WebSocket translators over the shared, surface-agnostic
654
- * management core in `@wrongstack/mcp` (`manage.ts`) — the SAME core the REPL
655
- * `/mcp` command writes against (same config.json, same MCPRegistry). All the
656
- * config IO, url/header persistence, and live registry start/stop logic lives
657
- * there; here we only map structured results to WS events the browser expects.
658
- */
659
-
660
- /** mcp.list — configured servers merged with live registry status + tools. */
661
- declare function handleMcpList(ws: WebSocket, _msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
662
- /** mcp.add — persist a new server (incl. url/headers) and start it if enabled. */
663
- declare function handleMcpAdd(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
664
- /** mcp.update — re-persist config (incl. url/headers) and re-apply to registry. */
665
- declare function handleMcpUpdate(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
666
- /** mcp.remove — stop the server and delete it from config. */
667
- declare function handleMcpRemove(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
668
- /** mcp.enable — flip enabled:true in config and start the server. */
669
- declare function handleMcpEnable(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
670
- /** mcp.disable — stop the server and flip enabled:false in config. */
671
- declare function handleMcpDisable(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
672
- /** mcp.sleep — stop a running server (config stays enabled). */
673
- declare function handleMcpSleep(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
674
- /** mcp.wake — restart a sleeping/stopped server from config. */
675
- declare function handleMcpWake(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
676
- /** mcp.restart — stop + start a server. */
677
- declare function handleMcpRestart(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
678
- /** mcp.discover — ensure the server is running and report its live tools. */
679
- declare function handleMcpDiscover(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
680
-
681
- /**
682
- * Shared memory-operation WebSocket handlers for both the standalone WebUI
683
- * server and the CLI's `--webui` embedded server. Extracted from the
684
- * duplicated switch cases in `index.ts` and `cli/src/webui-server.ts`.
685
- *
686
- * Each function handles the full request→response cycle for one message
687
- * type. Callers drop them into their switch statement:
688
- *
689
- * case 'memory.list': return handleMemoryList(ws, memoryStore);
690
- */
691
-
692
- /**
693
- * List all memory entries across all scopes.
694
- * Responds with `{ type: 'memory.list', payload: { text } }`.
695
- */
696
- declare function handleMemoryList(ws: WebSocket, memoryStore: MemoryStore): Promise<void>;
697
- /**
698
- * Persist a new memory entry.
699
- * Responds with `{ type: 'key.operation_result', payload: { success, message } }`.
700
- */
701
- declare function handleMemoryRemember(ws: WebSocket, msg: unknown, memoryStore: MemoryStore): Promise<void>;
702
- /**
703
- * Remove memory entries matching the given text.
704
- * Responds with `{ type: 'key.operation_result', payload: { success, message } }`.
705
- */
706
- declare function handleMemoryForget(ws: WebSocket, msg: unknown, memoryStore: MemoryStore): Promise<void>;
707
-
708
- /**
709
- * Best-effort "open this URL in the default browser" for `--webui --open`.
710
- *
711
- * Cross-platform via the OS opener (`start` / `open` / `xdg-open`). Fully
712
- * fire-and-forget: a missing opener, a headless box, or a spawn failure must
713
- * NEVER take the server down — the URL is always also printed to the console.
714
- */
715
- /** Resolve the platform's URL-opener command + args. */
716
- declare function browserOpenCommand(url: string, platform?: NodeJS.Platform): {
717
- command: string;
718
- args: string[];
719
- };
720
- /** Spawn the OS browser-opener for `url` and register it as a protected
721
- * process so it survives kill/killAll. Never throws. */
722
- declare function openBrowser(url: string, platform?: NodeJS.Platform): void;
723
-
724
- /**
725
- * Free-port discovery for the standalone WebUI server.
726
- *
727
- * When a user runs several instances, the default ports (HTTP 3456 / WS 3457)
728
- * are taken by the first one. Rather than make the user hand-pick `PORT` /
729
- * `WS_PORT` for every extra instance, the server probes upward from the
730
- * requested port and binds the first free one — then stamps that real port into
731
- * the served HTML and the instance registry so everything stays consistent.
732
- *
733
- * The probe binds a throwaway `net.Server`, then closes it, so there is a tiny
734
- * TOCTOU window between "found free" and "the real server binds it". For local
735
- * single-user multi-instance use that race is negligible; if it ever loses, the
736
- * real bind fails loudly with EADDRINUSE exactly as before.
737
- */
738
- /** Resolve true when `port` can be bound on `host`, false on EADDRINUSE/EACCES. */
739
- declare function isPortFree(host: string, port: number): Promise<boolean>;
740
- interface FindFreePortOptions {
741
- /** Ports to skip even if free (e.g. one already chosen for the sibling server). */
742
- exclude?: Set<number> | undefined;
743
- /** How many consecutive ports to try before giving up. Default 200. */
744
- maxTries?: number | undefined;
745
- }
746
- /**
747
- * Find the first free port at or above `startPort` on `host`, skipping any in
748
- * `exclude`. Throws if nothing is free within `maxTries` steps.
749
- */
750
- declare function findFreePort(host: string, startPort: number, opts?: FindFreePortOptions): Promise<number>;
751
-
752
- /**
753
- * Send a JSON message to a single WebSocket client.
754
- * No-op when the socket is not in OPEN state (disconnected / closing).
755
- */
756
- declare function send(ws: WebSocket, msg: object): void;
757
- /**
758
- * Broadcast a JSON message to every connected client.
759
- * Swallows per-socket send errors — a client that disconnected between the
760
- * readyState check and `ws.send()` is cleaned up by its own `close` handler.
761
- */
762
- declare function broadcast(clients: Map<WebSocket, ConnectedClient>, msg: object): void;
763
- /**
764
- * Send a success/failure result message (used by key.* and provider.* handlers).
765
- * The frontend expects `key.operation_result` with `{ success, message }`.
766
- */
767
- declare function sendResult(ws: WebSocket, success: boolean, message: string): void;
768
- /**
769
- * Extract a human-readable message from an unknown thrown value.
770
- */
771
- declare function errMessage(err: unknown): string;
772
- /**
773
- * Generate a cryptographically random WebSocket auth token (hex string).
774
- * Shared between standalone and CLI-embedded WebUI servers.
775
- */
776
- declare function generateAuthToken(): string;
777
- declare function resolveAuthToken(explicit?: string | undefined): string;
778
- declare function hostForBrowserUrl(bindHost: string): string;
779
- declare function buildWebUIAccessUrl(opts: {
780
- host: string;
781
- port: number;
782
- token?: string | undefined;
783
- protocol?: 'http' | 'https' | undefined;
784
- publicUrl?: string | undefined;
785
- }): string;
786
- declare function envFlag(name: string): boolean;
787
-
788
- /**
789
- * Shared prompt-library WebSocket handlers for BOTH the standalone WebUI server
790
- * (`packages/webui/src/server/index.ts`) and the CLI's `--webui` embedded server
791
- * (`packages/cli/src/webui-server.ts`). One source of truth so the two servers
792
- * never drift (the lesson from skills-handlers).
793
- *
794
- * Each function handles one request→response cycle; callers drop them into their
795
- * switch:
796
- *
797
- * case 'prompts.search': return handlePromptsSearch(ws, promptsCtx, msg);
798
- *
799
- * The prompt library is read across three layers (builtin + user + project) by
800
- * the injected `PromptLoader`; writes (create/favorite) go to the user layer
801
- * with copy-on-write for builtins. Treat synced/builtin content as DATA — these
802
- * handlers never execute it; the client inserts a chosen prompt into the chat
803
- * input as an ordinary user turn.
804
- */
805
-
806
- interface PromptsContext {
807
- /** Backs all prompt ops. Absent ⇒ feature unavailable. */
808
- promptLoader: PromptLoader | undefined;
809
- /** Records per-slug insert counts (shared with CLI `/prompt recent`). */
810
- promptUsage?: PromptUsageStore | undefined;
811
- }
812
- declare function handlePromptsList(ws: WSLike, ctx: PromptsContext): Promise<void>;
813
- declare function handlePromptsSearch(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
814
- declare function handlePromptsContent(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
815
- declare function handlePromptsFavorite(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
816
- declare function handlePromptsCreate(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
817
- /** Record that a prompt was inserted (best-effort; feeds CLI `/prompt recent`). */
818
- declare function handlePromptsUsed(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
819
- /** Return recently-inserted prompt slugs (most-recent first) for the modal's Recent view. */
820
- declare function handlePromptsRecent(ws: WSLike, ctx: PromptsContext): Promise<void>;
821
- /** Minimal structural type for the ws.send sink (matches `ws` WebSocket). */
822
- type WSLike = Parameters<typeof send>[0];
823
-
824
- /**
825
- * Read the `providers` section from the global config, decrypting
826
- * secret-bearing fields. Returns an empty record when the config file
827
- * doesn't exist or has no `providers` key.
828
- */
829
- declare function loadSavedProviders(configPath: string, vault: SecretVault): Promise<Record<string, ProviderConfig>>;
830
- /**
831
- * Write `providers` back into the global config, encrypting secrets first.
832
- * Refuses to overwrite a corrupt-but-existing config file (the operator
833
- * should fix it manually). When the config file is missing (ENOENT), starts
834
- * from an empty object.
835
- */
836
- declare function saveProviders(configPath: string, vault: SecretVault, providers: Record<string, ProviderConfig>): Promise<void>;
837
- /**
838
- * Small helper for the standalone WebUI entry point: create a
839
- * `{ load, save }` pair from a config path alone (uses the
840
- * config-directory-relative `.key` file for the vault). The `--webui`
841
- * CLI mode and the standalone server both need to read/write the
842
- * `providers` map identically.
843
- */
844
- declare function createProviderConfigIO(configPath: string): {
845
- load: () => Promise<Record<string, ProviderConfig>>;
846
- save: (providers: Record<string, ProviderConfig>) => Promise<void>;
847
- };
848
-
849
- /**
850
- * Pure provider/API-key record transforms for the WebUI server's `key.*` and
851
- * `provider.*` WebSocket handlers.
852
- *
853
- * These operate on an in-memory `providers` record (the decrypted
854
- * `config.providers` map) and return a `{ ok, message }` result mirroring the
855
- * status string the handler sends back to the client. All persistence
856
- * (load/decrypt, encrypt/atomic-write) and WS messaging stays in `index.ts` —
857
- * keeping this layer pure means the security-sensitive key bookkeeping (which
858
- * key is active, when a provider is dropped, how legacy single-key configs are
859
- * normalized) is unit-testable without a vault or a socket.
860
- *
861
- * Extracted from `index.ts`; transforms mutate the passed record in place, the
862
- * same way the original handlers did before calling `saveProviders`.
863
- */
864
-
865
- type ProvidersRecord = Record<string, ProviderConfig>;
866
- interface KeyOpResult {
867
- ok: boolean;
868
- message: string;
869
- }
870
- /**
871
- * Normalize a provider's keys to the array form, upgrading a legacy single
872
- * `apiKey` string to a one-element `[{ label: 'default', ... }]` list. Returns
873
- * fresh copies so callers can mutate without aliasing the stored config.
874
- */
875
- declare function normalizeKeys(cfg: ProviderConfig): ProviderApiKey[];
876
- /**
877
- * Write a normalized key list back onto a provider config: drop all key fields
878
- * when empty, otherwise sync `apiKeys` and re-point `activeKey` if it no longer
879
- * names a present key. Does NOT mirror the plaintext key to the legacy `apiKey`
880
- * field — that would leak the secret on accidental serialization. Consumers
881
- * that need the real key should read from `apiKeys[]` directly.
882
- */
883
- declare function writeKeysBack(cfg: ProviderConfig, keys: ProviderApiKey[]): void;
884
- /** Mask a secret for display: `••••` for short keys, `abcd…wxyz` otherwise. */
885
- declare function maskedKey(key: string | undefined): string;
886
- /** Add or replace a labeled key for a provider, creating the provider if new. */
887
- declare function upsertKey(providers: ProvidersRecord, providerId: string, label: string, apiKey: string, nowIso: string): KeyOpResult;
888
- /** Remove a labeled key; drops the provider entirely when its last key goes. */
889
- declare function deleteKey(providers: ProvidersRecord, providerId: string, label: string): KeyOpResult;
890
- /** Point a provider's active key at the given label. */
891
- declare function setActiveKey(providers: ProvidersRecord, providerId: string, label: string): KeyOpResult;
892
- /** Register a brand-new provider (optionally with an initial `default` key). */
893
- declare function addProvider(providers: ProvidersRecord, payload: {
894
- id: string;
895
- family: string;
896
- baseUrl?: string | undefined;
897
- apiKey?: string | undefined;
898
- }, nowIso: string): KeyOpResult;
899
- /** Remove an entire provider and all its keys. */
900
- declare function removeProvider(providers: ProvidersRecord, providerId: string): KeyOpResult;
901
-
902
- interface SddBoardWSMessage {
903
- type: string;
904
- payload?: Record<string, unknown>;
905
- }
906
- /** Project paths the handler needs to apply lifecycle ops directly. */
907
- interface SddBoardLifecycleDeps {
908
- projectRoot: string;
909
- paths: {
910
- projectSpecs: string;
911
- projectTaskGraphs: string;
912
- projectSddSession: string;
913
- projectSddBoards: string;
914
- };
915
- }
916
- /**
917
- * SddBoardWebSocketHandler — streams the live SDD multi-agent board to clients
918
- * and relays control commands back to the CLI-owned run.
919
- *
920
- * Two observe modes (one class, shared by both webui servers):
921
- * • in-process (CLI-hosted): subscribe the shared EventBus `sdd.board.snapshot`
922
- * for instant updates;
923
- * • standalone (separate process): poll the on-disk snapshot store (the CLI
924
- * run persists JSON every change).
925
- *
926
- * Control is uniform + cross-process: every command is appended to the run's
927
- * `<runId>.control.jsonl`, which the CLI run drains and applies — so the run
928
- * stays the single driver and nothing races on shared state.
929
- */
930
- declare class SddBoardWebSocketHandler {
931
- private readonly store;
932
- private readonly clients;
933
- private readonly lifecycle?;
934
- private latest;
935
- private poll;
936
- private unsub;
937
- constructor(boardsDir: string, events?: EventBus, lifecycle?: SddBoardLifecycleDeps);
938
- addClient(ws: WebSocket): void;
939
- handleMessage(msg: SddBoardWSMessage): Promise<void>;
940
- /**
941
- * Apply a cleanup/rollback/destroy from disk and broadcast a structured
942
- * `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
943
- * the user must stop it first; the UI gates the buttons on `!active` and the
944
- * Destroy flow auto-stops then waits before sending `destroy`.
945
- */
946
- private applyLifecycle;
947
- dispose(): void;
948
- private pollLatest;
949
- private sendCurrent;
950
- private broadcastCurrent;
951
- private loadLatestFromDisk;
952
- private broadcast;
953
- private send;
954
- }
955
-
956
- interface WizardMessage {
957
- type: string;
958
- payload?: Record<string, unknown>;
959
- }
960
- /**
961
- * Dependencies each webui server supplies. The handler is deliberately
962
- * agent-agnostic: every surface decides how to build a driver, how to run an
963
- * interview turn (on an isolated agent, off the main chat bus), and how to
964
- * start the real multi-agent run (CLI's director-backed factory vs the runtime
965
- * light factory). This keeps the wizard protocol identical across both servers.
966
- */
967
- interface SddWizardDeps {
968
- /** Build a fresh interview driver (disk spec/graph stores + session path). */
969
- makeDriver: () => SddInterviewDriver;
970
- /**
971
- * Run one interview turn: feed the AI prompt to an isolated agent and return
972
- * its final text. MUST NOT run on the main chat agent's bus — the wizard owns
973
- * this conversation, separate from the user's chat.
974
- */
975
- runInterviewTurn: (prompt: string) => Promise<string>;
976
- /**
977
- * Start the real multi-agent SDD run for the driver's task graph. Returns the
978
- * runId; the live board flows through the existing board handler.
979
- */
980
- startRun: (driver: SddInterviewDriver, opts: {
981
- parallelSlots?: number | undefined;
982
- defaultModel?: string | undefined;
983
- defaultProvider?: string | undefined;
984
- fallbackModels?: string[] | undefined;
985
- /** Per-run worktree-isolation override; undefined → env default. */
986
- worktrees?: boolean | undefined;
987
- }) => Promise<{
988
- runId: string;
989
- }>;
990
- }
991
- /**
992
- * SddWizardWebSocketHandler — drives the interactive "New SDD Project" wizard
993
- * (goal → Q&A → spec → task graph → start run) over WebSocket. Shared by both
994
- * webui servers; server-specific construction (agent, factory) is injected via
995
- * {@link SddWizardDeps}.
996
- */
997
- declare class SddWizardWebSocketHandler {
998
- private readonly deps;
999
- private readonly clients;
1000
- private driver;
1001
- /** The agent's most recent question — paired with the next user answer. */
1002
- private lastAgentText;
1003
- /** Guards against overlapping interview turns (one in flight at a time). */
1004
- private busy;
1005
- constructor(deps: SddWizardDeps);
1006
- addClient(ws: WebSocket): void;
1007
- handleMessage(msg: WizardMessage): Promise<void>;
1008
- private onStart;
1009
- private onMessage;
1010
- private onApprove;
1011
- private onRunStart;
1012
- /** Run one interview turn against the isolated agent, then ingest + broadcast. */
1013
- private runTurn;
1014
- private snapshotMsg;
1015
- private broadcast;
1016
- private send;
1017
- }
1018
-
1019
- interface SddWizardWiringOptions {
1020
- /** Leader agent — seeds the run's default factory + project context. */
1021
- agent: Agent;
1022
- /** Shared EventBus — the board projector emits sdd.board.snapshot on it. */
1023
- events: EventBus;
1024
- projectRoot: string;
1025
- /** Per-task agent factory: CLI's director-backed one, or the runtime light one. */
1026
- subagentFactory: AgentFactory;
1027
- /**
1028
- * Decision authority for the failure supervisor (the server's bound
1029
- * TOKENS.BrainArbiter). Omit to run without a supervisor (plain terminal-fail,
1030
- * matching a bare run) — but parity with the CLI wants it wired.
1031
- */
1032
- brain?: BrainArbiter | undefined;
1033
- /** Persisted-store directories (from resolveWstackPaths). */
1034
- paths: {
1035
- projectSpecs: string;
1036
- projectTaskGraphs: string;
1037
- projectSddBoards: string;
1038
- projectDir: string;
1039
- };
1040
- }
1041
- declare function buildSddWizardDeps(opts: SddWizardWiringOptions): SddWizardDeps;
1042
-
1043
- type ShellOpenTarget = 'terminal' | 'file-manager';
1044
- interface ShellOpenRequest {
1045
- path: string;
1046
- target: ShellOpenTarget;
1047
- }
1048
- interface ShellOpenResult {
1049
- success: boolean;
1050
- message: string;
1051
- }
1052
- declare function handleShellOpen(req: ShellOpenRequest, logger: Logger): Promise<ShellOpenResult>;
1053
-
1054
- /**
1055
- * Shared skills WebSocket handlers for both the standalone WebUI server
1056
- * (`packages/webui/src/server/index.ts`) and the CLI's `--webui` embedded
1057
- * server (`packages/cli/src/webui-server.ts`).
1058
- *
1059
- * These were previously inlined in BOTH servers, and the CLI copy had
1060
- * drifted — it only wired `skills.list`, so `skills.content` /
1061
- * `skills.export` / `skills.update` (and install/uninstall/create/edit)
1062
- * fell through to the "Unhandled message type" warning even though the
1063
- * SkillsPanel sends them. Extracting the full set here gives both servers
1064
- * one source of truth. Each function handles the full request→response
1065
- * cycle for one message type; callers drop them into their switch:
1066
- *
1067
- * case 'skills.content': return handleSkillsContent(ws, skillsCtx, msg);
1068
- *
1069
- * The logic is a verbatim lift of the standalone's inline cases — only the
1070
- * dependency references changed (`skillLoader`/`skillInstaller`/
1071
- * `projectRoot` → `ctx.*`, local `send`/`errMessage` → imported helpers).
1072
- */
1073
-
1074
- interface SkillsContext {
1075
- /** Backs skills.list/content/edit/export. Absent ⇒ feature disabled. */
1076
- skillLoader: SkillLoader | undefined;
1077
- /** Backs skills.install/uninstall/update. Absent ⇒ those ops disabled. */
1078
- skillInstaller: SkillInstaller | undefined;
1079
- /** Project root — used by skills.create to write `.wrongstack/skills/…`. */
1080
- projectRoot: string;
1081
- /** Project skills directory, normally `<project>/.wrongstack/skills`. */
1082
- projectSkillsDir?: string | undefined;
1083
- /** User-global skills directory, normally `~/.wrongstack/skills`. */
1084
- globalSkillsDir?: string | undefined;
1085
- }
1086
- /**
1087
- * List installed skills. Enriches each manifest with the source URL + git
1088
- * ref recorded by the installer (when present), so the panel can show
1089
- * provenance and offer update/uninstall.
1090
- */
1091
- declare function handleSkillsList(ws: WebSocket, ctx: SkillsContext): Promise<void>;
1092
- /**
1093
- * Read a single skill's body + its directory's related files + which other
1094
- * skills reference it by name. Powers the skill detail/preview view.
1095
- */
1096
- declare function handleSkillsContent(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1097
- /**
1098
- * Install a skill from a git ref (`owner/repo` or URL). Optional `global`
1099
- * installs into the user-wide skills dir instead of the project's.
1100
- */
1101
- declare function handleSkillsInstall(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1102
- /**
1103
- * Uninstall a skill by name. Optional `global` restricts/Targets the
1104
- * user-wide install.
1105
- */
1106
- declare function handleSkillsUninstall(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1107
- /**
1108
- * Update one skill (`name`) or all installed skills (when `name` is
1109
- * omitted). Reports per-skill updated/unchanged/error tallies.
1110
- */
1111
- declare function handleSkillsUpdate(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1112
- /**
1113
- * Scaffold a new project- or global-scoped skill from a name + description.
1114
- * Writes a templated `SKILL.md` under `.wrongstack/skills/<name>/` (project)
1115
- * or the user-wide skills dir (global).
1116
- */
1117
- declare function handleSkillsCreate(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1118
- /**
1119
- * Overwrite a skill's body. Refuses bundled skills (read-only) and unknown
1120
- * names.
1121
- */
1122
- declare function handleSkillsEdit(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
1123
- /**
1124
- * Export every readable skill as a base64-encoded zip (one folder per skill,
1125
- * each with its `SKILL.md`). Powers the panel's "Export all" button.
1126
- */
1127
- declare function handleSkillsExport(ws: WebSocket, ctx: SkillsContext): Promise<void>;
1128
-
1129
- interface SpecsWSMessage {
1130
- type: string;
1131
- payload?: Record<string, unknown>;
1132
- }
1133
- /**
1134
- * SpecsWebSocketHandler — read-only-ish browser of persisted SDD specs and their
1135
- * task graphs, rendered as a FORGE-style dependency board (topological phase
1136
- * columns + dependency refs). Shared by both webui servers via specs-routes.
1137
- *
1138
- * Message types:
1139
- * specs.list → all specs + progress
1140
- * specs.get { specId } → one spec's dependency board
1141
- * specs.taskStatus { graphId, taskId, status } → update + rebroadcast
1142
- */
1143
- declare class SpecsWebSocketHandler {
1144
- private specStore;
1145
- private graphStore;
1146
- private clients;
1147
- constructor(specsDir: string, taskGraphsDir: string);
1148
- addClient(ws: WebSocket): void;
1149
- handleMessage(msg: SpecsWSMessage): Promise<void>;
1150
- private buildList;
1151
- private broadcastList;
1152
- private sendList;
1153
- private broadcastDetail;
1154
- private findGraphForSpec;
1155
- private buildDetail;
1156
- private updateTaskStatus;
1157
- private broadcast;
1158
- private send;
1159
- }
1160
-
1161
- /**
1162
- * Per-section context-window token estimate for the `context.debug` command.
1163
- *
1164
- * Uses the simple 4-chars-per-token heuristic — not exact, but close enough to
1165
- * spot which section (system prompt, tool schemas, or message history) is
1166
- * eating the context window. Tool schemas in particular are easy to overlook:
1167
- * each tool ships its full JSON schema to the model every turn, so 20+ builtins
1168
- * can cost 10-20k tokens on their own.
1169
- *
1170
- * Extracted from `index.ts` as a pure function so the breakdown maths can be
1171
- * unit tested without standing up a Context/ToolRegistry.
1172
- */
1173
- /** 4-chars-per-token heuristic estimate for a string. */
1174
- declare function estimateTokens(s: string): number;
1175
- /** Stringify arbitrary content for length estimation (JSON, with fallbacks). */
1176
- declare function stringifyContent(c: unknown): string;
1177
- interface PromptBlock {
1178
- text?: string | undefined;
1179
- }
1180
- interface ToolLike {
1181
- name: string;
1182
- inputSchema?: unknown | undefined;
1183
- description?: string | undefined;
1184
- }
1185
- interface MessageLike {
1186
- role: string;
1187
- content: unknown;
1188
- }
1189
- interface ToolTokenEntry {
1190
- name: string;
1191
- tokens: number;
1192
- }
1193
- interface MessageTokenEntry {
1194
- index: number;
1195
- role: string;
1196
- tokens: number;
1197
- preview: string;
1198
- }
1199
- interface ContextBreakdown {
1200
- total: number;
1201
- systemPrompt: number;
1202
- tools: {
1203
- total: number;
1204
- count: number;
1205
- breakdown: ToolTokenEntry[];
1206
- };
1207
- messages: {
1208
- total: number;
1209
- count: number;
1210
- breakdown: MessageTokenEntry[];
1211
- };
1212
- }
1213
- declare function messageTokens(content: unknown): number;
1214
- declare function messagePreview(content: unknown): string;
1215
- /**
1216
- * Compute the per-section token breakdown for the active context. Mirrors the
1217
- * shape the `context.debug` WS reply expects (minus the `mode`/`policy` fields,
1218
- * which the caller layers on from `context.meta`).
1219
- */
1220
- declare function estimateContextBreakdown(input: {
1221
- systemPrompt: ReadonlyArray<PromptBlock>;
1222
- tools: ReadonlyArray<ToolLike>;
1223
- messages: ReadonlyArray<MessageLike>;
1224
- }): ContextBreakdown;
1225
-
1226
- interface WorktreeManagementDeps {
1227
- projectRoot: string;
1228
- /** Board snapshot dir — powers the cross-process liveness guard on cleanup. */
1229
- boardsDir: string;
1230
- }
1231
- /**
1232
- * WorktreeWebSocketHandler — mirrors AutoPhaseWebSocketHandler. Subscribes to
1233
- * the shared EventBus `worktree.*` lifecycle events, keeps a live snapshot of
1234
- * every worktree, and broadcasts:
1235
- * - `worktree.event` incrementally (drives the flowing activity strip)
1236
- * - `worktree.state` on connect + on a 2s timer (drives swim-lanes/DAG)
1237
- */
1238
- declare class WorktreeWebSocketHandler {
1239
- private readonly events;
1240
- private readonly logger;
1241
- private readonly management?;
1242
- private readonly clients;
1243
- private readonly handles;
1244
- private baseBranch;
1245
- private broadcastInterval;
1246
- private readonly offs;
1247
- constructor(events: EventBus, logger: Logger, management?: WorktreeManagementDeps | undefined);
1248
- addClient(ws: WebSocket): void;
1249
- /** Handle worktree-panel control messages (scan / clean / per-row ops). */
1250
- handleMessage(msg: {
1251
- type: string;
1252
- payload?: Record<string, unknown>;
1253
- }): Promise<boolean>;
1254
- dispose(): void;
1255
- /** Absolute managed-worktrees root for this project. */
1256
- private worktreesRoot;
1257
- /** True iff `dir` resolves strictly inside the managed worktrees root. */
1258
- private underRoot;
1259
- /** Branches of worktrees a live in-session run currently owns. */
1260
- private liveActiveBranches;
1261
- /**
1262
- * Scan the disk for managed worktrees/branches NOT owned by a live in-session
1263
- * run and broadcast them as orphans, with whether it is safe to clean now.
1264
- * No-op (empty inventory) when management deps were not wired.
1265
- */
1266
- private scanAndBroadcast;
1267
- /**
1268
- * Force-remove every orphaned worktree + branch. Refused while a run is live —
1269
- * in this session (active handles) OR another process (the SDD board liveness
1270
- * guard inside cleanupStaleSddWorktrees). Best-effort; reports the outcome.
1271
- */
1272
- private cleanupOrphans;
1273
- /** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
1274
- private removeOne;
1275
- /** Squash-merge ONE branch into base. Refused while a live run owns it. */
1276
- private mergeBranch;
1277
- /** Compact change summary for one worktree checkout. */
1278
- private diffOne;
1279
- private subscribe;
1280
- private upsert;
1281
- private patch;
1282
- private activity;
1283
- private stateMessage;
1284
- private broadcastState;
1285
- private ensureBroadcast;
1286
- private stopBroadcast;
1287
- private broadcast;
1288
- private send;
1289
- }
1290
-
1291
- /** A hostname that refers to the local machine. */
1292
- declare function isLoopbackHostname(hostname: string): boolean;
1293
- /** True when the server is bound to a loopback interface (vs. LAN/0.0.0.0). */
1294
- declare function isLoopbackBind(wsHost: string): boolean;
1295
- /**
1296
- * True when the server is bound to a wildcard address that exposes it on every
1297
- * interface — IPv4 `0.0.0.0` OR IPv6 `::` (and its bracketed form). The
1298
- * "LAN exposure = deny" guards below must treat both families identically; a
1299
- * `::` bind is exactly as exposed as `0.0.0.0` and previously slipped past the
1300
- * `wsHost === '0.0.0.0'` string check.
1301
- */
1302
- declare function isWildcardBind(wsHost: string): boolean;
1303
- /**
1304
- * Constant-time comparison of a provided token against the expected one.
1305
- * A length mismatch short-circuits (lengths aren't secret); equal-length
1306
- * inputs are compared with `timingSafeEqual` so the token can't be recovered
1307
- * byte-by-byte via response timing.
1308
- */
1309
- declare function tokenMatches(provided: string | undefined, expected: string): boolean;
1310
- /** Pull the `token` query param out of a request URL (`/?token=…`). */
1311
- declare function extractToken(url: string): string | undefined;
1312
- /**
1313
- * Pull the `ws_token` value out of a Cookie header (`Cookie: ws_token=…`).
1314
- * The WebUI's auth-token cookie is set via `Set-Cookie: ws_token=<token>;
1315
- * HttpOnly; SameSite=Strict; Path=/` from the `/ws-auth` HTTP endpoint. The
1316
- * browser then sends it back automatically on the WS upgrade request —
1317
- * closing the C-598 (Information Exposure Through Query String) class
1318
- * because the token never appears in the URL, browser history, or
1319
- * reverse-proxy access logs.
1320
- *
1321
- * Returns `undefined` if the cookie header is absent or malformed.
1322
- */
1323
- declare function extractTokenFromCookie(cookieHeader: string | string[] | undefined): string | undefined;
1324
- /**
1325
- * DNS-rebinding defense. On a loopback bind, the `Host` header must resolve to
1326
- * a loopback name. When the operator deliberately exposes the socket (wsHost is
1327
- * a LAN/0.0.0.0 address) the Host is legitimately non-loopback, so the guard is
1328
- * skipped and connection auth falls to the token check.
1329
- */
1330
- declare function hostHeaderOk(input: {
1331
- hostHeader: string | undefined;
1332
- wsHost: string;
1333
- allowedHostnames?: readonly string[] | undefined;
1334
- }): boolean;
1335
- interface VerifyClientInput {
1336
- /** Browser `Origin` header, or undefined for non-browser clients. */
1337
- origin?: string | undefined;
1338
- /** Request URL (`req.url`) — carries the `?token=…` query param. */
1339
- url: string;
1340
- /** `Host` header (`req.headers.host`). */
1341
- hostHeader?: string | undefined;
1342
- /** Peer address (`req.socket.remoteAddress`). */
1343
- remoteAddress?: string | undefined;
1344
- /** `Cookie` header (`req.headers.cookie`). Carries `ws_token=…` when the
1345
- * browser went through `/ws-auth` to set the HttpOnly auth cookie. */
1346
- cookieHeader?: string | string[] | undefined;
1347
- /** Host/interface the WS server is bound to. */
1348
- wsHost: string;
1349
- /** The server's generated auth token. */
1350
- expectedToken: string;
1351
- /** Force token auth even for loopback binds, useful behind public tunnels. */
1352
- requireToken?: boolean | undefined;
1353
- /** Extra Host header names allowed on loopback binds, e.g. a tunnel hostname. */
1354
- allowedHostnames?: readonly string[] | undefined;
1355
- /** Allow browser WS URL tokens for explicit public WS URLs where cookies cannot cross hostnames. */
1356
- allowBrowserUrlToken?: boolean | undefined;
1357
- }
1358
- /**
1359
- * Decide whether to accept an incoming WebSocket handshake. Pure mirror of the
1360
- * closure previously inlined in `index.ts`; see the module doc for the layered
1361
- * policy. Returns `true` to accept, `false` to reject.
1362
- *
1363
- * Token sources, in priority order:
1364
- * 1. `Cookie: ws_token=…` (browser clients that went through `/ws-auth`)
1365
- * 2. `?token=…` URL query param (non-browser clients: curl, scripts)
1366
- *
1367
- * Browser clients (with an `Origin` header) are restricted to the cookie path —
1368
- * URL token is rejected for them, closing the C-598 query-string token
1369
- * exposure class. Non-browser clients keep the URL-token fallback so curl
1370
- * and tests continue to work.
1371
- */
1372
- declare function verifyClient(input: VerifyClientInput): boolean;
1373
-
1374
- declare function startWebUI(opts?: WebUIOptions & {
1375
- wsPort?: number | undefined;
1376
- wsHost?: string | undefined;
1377
- httpPort?: number | undefined;
1378
- accessToken?: string | undefined;
1379
- publicUrl?: string | undefined;
1380
- publicWsUrl?: string | undefined;
1381
- requireToken?: boolean | undefined;
1382
- open?: boolean | undefined;
1383
- }): Promise<void>;
1384
-
1385
- interface AutoPhaseRouteHandlers {
1386
- handleMessage: (msg: {
1387
- type: string;
1388
- payload?: Record<string, unknown>;
1389
- }) => Promise<void>;
1390
- }
1391
- declare function handleAutoPhaseRoute(_ws: WebSocket, msg: WSClientMessage, handlers: AutoPhaseRouteHandlers): Promise<boolean>;
1392
-
1393
- interface BootResult {
1394
- config: Config;
1395
- vault: DefaultSecretVault;
1396
- globalConfigPath: string;
1397
- projectRoot: string;
1398
- wpaths: WstackPaths;
1399
- logger: InstanceType<typeof DefaultLogger>;
1400
- }
1401
- /**
1402
- * Thin WebUI wrapper over the canonical `bootConfig` in `@wrongstack/core`
1403
- * (mirrors packages/cli/src/boot-config.ts). All real boot behavior — wstack
1404
- * path resolution, the AES-GCM `DefaultSecretVault`, plaintext-secret
1405
- * migration, and config load/merge — lives in core so the WebUI server and the
1406
- * CLI can't drift. Only the secret-migration notice label (`WebUI`) differs.
1407
- */
1408
- declare function bootConfig(): Promise<BootResult>;
1409
- declare function patchConfig(config: Config, updates: Partial<Config>): Config;
1410
-
1411
- interface WebUICodebaseIndexingDeps {
1412
- config: {
1413
- indexing?: IndexingConfig | undefined;
1414
- };
1415
- context: Context;
1416
- projectRoot: string;
1417
- logger: Logger;
1418
- }
1419
- interface WebUICodebaseIndexing {
1420
- onFileWritten(filePath: string): void;
1421
- dispose(): void;
1422
- }
1423
- declare function setupWebUICodebaseIndexing(deps: WebUICodebaseIndexingDeps): WebUICodebaseIndexing;
1424
-
1425
- /**
1426
- * Goal-state WebSocket handler for the WebUI server, extracted from the
1427
- * `handleMessage` switch in `index.ts` as part of splitting that file (#31).
1428
- *
1429
- * case 'goal.get': return handleGoalGet(projectRoot, (m) => broadcast(clients, m));
1430
- *
1431
- * Reads the canonical goal.json and broadcasts it to every connected client so
1432
- * all browser tabs share one goal snapshot. Never throws — a missing or
1433
- * unparseable file broadcasts `null` so clients clear stale goal state.
1434
- */
1435
- /**
1436
- * Read `goal.json` for `projectRoot` and broadcast a `goal.updated` message.
1437
- * The path must match /goal, the autonomy engines, and TUI F9, which all
1438
- * resolve via `resolveWstackPaths().projectGoal`
1439
- * (`~/.wrongstack/projects/<slug>/goal.json`) — NOT the repo-local
1440
- * `.wrongstack/goal.json`.
1441
- */
1442
- declare function handleGoalGet(projectRoot: string, broadcast: (msg: object) => void): Promise<void>;
1443
-
1444
- /**
1445
- * Process-registry WebSocket handlers for the WebUI server, extracted from the
1446
- * `handleMessage` switch in `index.ts` as part of splitting that file (#31).
1447
- *
1448
- * case 'process.list': return handleProcessList(ws);
1449
- * case 'process.kill': return handleProcessKill(ws, msg.payload);
1450
- * case 'process.killAll': return handleProcessKillAll(ws);
1451
- *
1452
- * All three reach the registry via a dynamic `@wrongstack/tools` import so the
1453
- * server starts even when that package is unavailable, and never throw — a
1454
- * failure is reported back over the socket instead.
1455
- */
1456
-
1457
- /** Broadcast the tracked-process list; an empty list on any registry failure. */
1458
- declare function handleProcessList(ws: WebSocket): Promise<void>;
1459
- /** Kill one tracked PID. Rejects invalid payloads and protected processes. */
1460
- declare function handleProcessKill(ws: WebSocket, payload: unknown): Promise<void>;
1461
- /** Kill every tracked process. */
1462
- declare function handleProcessKillAll(ws: WebSocket): Promise<void>;
1463
-
1464
- interface BrainRouteHandlers {
1465
- status: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1466
- risk: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1467
- ask: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1468
- }
1469
- declare function handleBrainRoute(ws: WebSocket, msg: WSClientMessage, handlers: BrainRouteHandlers): Promise<boolean>;
1470
-
1471
- /**
1472
- * CollaborationWebSocketHandler — passive read-only session observer (Phase 1
1473
- * of idea #13 from IDEAS.md). Mirrors `WorktreeWebSocketHandler` and
1474
- * `AutoPhaseWebSocketHandler`.
1475
- *
1476
- * Capabilities in this phase:
1477
- * - A second human (or any client) joins an active agent run as an
1478
- * `observer` and receives a live mirror of the kernel's iteration /
1479
- * tool / subagent events.
1480
- * - The observer declares a `sessionId` on join (used for state scoping
1481
- * and future replay-on-join). Live event routing is session-agnostic
1482
- * for now — see the limitation note below.
1483
- * - The observer can leave at any time; cleanup runs on WS close/error.
1484
- * - The observer CANNOT modify the agent's state, pause it, or inject
1485
- * tool calls. Those capabilities land in Phase 2/3.
1486
- *
1487
- * Limitation (documented, acceptable for Phase 1):
1488
- * The webui server multiplexes every active session onto a single
1489
- * EventBus, and most event payloads (`tool.started`, `iteration.*`,
1490
- * `subagent.*`) do NOT carry a `sessionId` field. The webui's primary
1491
- * WS path works because it is the only consumer and assumes one
1492
- * active session at a time. We mirror that assumption here. When a
1493
- * future multi-session "session router" lands, this handler will be
1494
- * upgraded to filter by sessionId.
4
+ * The WebUI HTTP/WebSocket server module, extracted from @wrongstack/webui in
5
+ * PR #018b (see docs/backlog/2026-07-architecture-review/018-modularity-audit-and-plan.md
6
+ * §3.1.1). This re-export surface exists so consumers can `import { ... }
7
+ * from '@wrongstack/webui-server'` without reaching into ./server/...
8
+ * internals.
1495
9
  *
1496
- * Protocol additions (see `packages/webui/src/types.ts`):
1497
- * client server: collab.join { sessionId, role: 'observer' }
1498
- * collab.leave { sessionId }
1499
- * server → client: collab.state (initial + 2s periodic)
1500
- * collab.participant.joined
1501
- * collab.participant.left
1502
- * collab.event (live kernel event mirror)
1503
- */
1504
- declare class CollaborationWebSocketHandler {
1505
- private readonly events;
1506
- private readonly logger;
1507
- /**
1508
- * Optional reader over the on-disk session log. When provided, late
1509
- * joiners receive the last `REPLAY_LIMIT` events of the joined
1510
- * session before live mirroring begins. Without a reader, joining
1511
- * is still allowed — the observer simply starts from "now" with no
1512
- * historical context.
1513
- */
1514
- private readonly reader?;
1515
- /**
1516
- * Optional sidecar store for collaboration annotations. Required
1517
- * for the `annotator` role — without it, `collab.annotate` messages
1518
- * are rejected with an error.
1519
- */
1520
- private readonly annotations?;
1521
- /**
1522
- * Optional kernel-level pause/resume bus. Required for the
1523
- * `controller` role — without it, `collab.request_pause` is rejected
1524
- * with an error. Wired to the agent's `toolCall` pipeline via
1525
- * `collabPauseMiddleware` in the webui server boot.
1526
- */
1527
- private readonly bus?;
1528
- private readonly clients;
1529
- /** sessionId → participants currently watching it. */
1530
- private readonly bySession;
1531
- private broadcastInterval;
1532
- private readonly offs;
1533
- constructor(events: EventBus, logger: Logger,
1534
- /**
1535
- * Optional reader over the on-disk session log. When provided, late
1536
- * joiners receive the last `REPLAY_LIMIT` events of the joined
1537
- * session before live mirroring begins. Without a reader, joining
1538
- * is still allowed — the observer simply starts from "now" with no
1539
- * historical context.
1540
- */
1541
- reader?: SessionReader | undefined,
1542
- /**
1543
- * Optional sidecar store for collaboration annotations. Required
1544
- * for the `annotator` role — without it, `collab.annotate` messages
1545
- * are rejected with an error.
1546
- */
1547
- annotations?: AnnotationsStore | undefined,
1548
- /**
1549
- * Optional kernel-level pause/resume bus. Required for the
1550
- * `controller` role — without it, `collab.request_pause` is rejected
1551
- * with an error. Wired to the agent's `toolCall` pipeline via
1552
- * `collabPauseMiddleware` in the webui server boot.
1553
- */
1554
- bus?: CollaborationBus | undefined);
1555
- addClient(ws: WebSocket): void;
1556
- dispose(): void;
1557
- /**
1558
- * Dispatch a parsed client message. Returns true when the message was
1559
- * recognized and handled; false when the caller should ignore / log.
1560
- * Phase 1 only knows `collab.join` and `collab.leave`; unknown types
1561
- * return false so the upstream router can decide.
1562
- */
1563
- handleMessage(ws: WebSocket, msg: {
1564
- type: string;
1565
- payload?: unknown | undefined;
1566
- }): boolean;
1567
- private join;
1568
- private leave;
1569
- private handleDisconnect;
1570
- /**
1571
- * Look up the participant record for a given WS across all sessions.
1572
- * Returns null when the WS hasn't joined (e.g. the client sent a
1573
- * `collab.annotate` before `collab.join`).
1574
- */
1575
- private findParticipant;
1576
- private findParticipantById;
1577
- private handleAnnotate;
1578
- private handleResolve;
1579
- private subscribe;
1580
- private broadcastEvent;
1581
- /**
1582
- * Replay the last `REPLAY_LIMIT` events from the on-disk session log
1583
- * to a single observer (the late joiner). Each event is forwarded as
1584
- * a `collab.event` with `replay: true` so the client can distinguish
1585
- * history from the live stream.
1586
- *
1587
- * The session log stores typed `SessionEvent`s (`user_input`,
1588
- * `llm_response`, `tool_result`, etc.) — different from the kernel's
1589
- * bus events. We translate the most useful subset (`tool.*` and
1590
- * `iteration.*`-shaped ones) into the same `kind` namespace the live
1591
- * mirror uses, so the client can render a single activity strip.
1592
- */
1593
- private replayHistory;
1594
- /**
1595
- * Map a stored `SessionEvent` to a `collab.event.kind` so the live
1596
- * strip and the history strip can share a single rendering path.
1597
- * Returns null for events that don't have a meaningful live analog
1598
- * (e.g. `session_start`, file-snapshot bookkeeping, rewind markers).
1599
- */
1600
- private historyEventToKind;
1601
- private stateMessage;
1602
- private ensureBroadcast;
1603
- private stopBroadcast;
1604
- private broadcast;
1605
- private send;
1606
- private errorMessage;
1607
- private handleRequestPause;
1608
- private handleResume;
1609
- private handleGrantControl;
1610
- /**
1611
- * Phase 4 — handle a controller's manual tool-call injection.
1612
- * Validates the payload, queues it on the bus, and broadcasts
1613
- * the grant so observers see what just happened. The actual
1614
- * splice into the agent's pipeline is performed by the
1615
- * `collabInjectMiddleware` on the next tool call.
1616
- */
1617
- private handleInjectTool;
1618
- /**
1619
- * Bus callback: a queued injection was spliced into a real tool call. Re-emit
1620
- * `collab.injection.granted` with phase `'consumed'` and the now-known tool
1621
- * name. The injection carries no sessionId, so resolve it from the author's
1622
- * current session; if they've already left, fall back to every live session.
1623
- */
1624
- private broadcastInjectionConsumed;
1625
- }
1626
-
1627
- /**
1628
- * Pure filtering + ranking for the `files.list` project file picker (the chat
1629
- * `@`-mention popup). The directory *walk* stays in index.ts (it's I/O), but
1630
- * the two decisions that shape the result — which entries to hide and how to
1631
- * rank matches — are pure and live here so the scoring weights, depth penalty,
1632
- * and tie-break order can be unit tested. A silently-flipped weight would make
1633
- * the picker feel subtly wrong with nothing to catch it.
1634
- */
1635
- /** Heavyweight build/vcs/dependency dirs the picker never descends into. */
1636
- declare const SKIP_DIRS: ReadonlySet<string>;
1637
- /**
1638
- * Whether a directory entry should be hidden from the picker by its name.
1639
- * Dotfiles are hidden by default, except a few commonly-wanted ones.
1640
- */
1641
- declare function isHiddenEntry(name: string): boolean;
1642
- /**
1643
- * Rank `paths` against `query` and return up to `limit` paths, best first.
1644
- *
1645
- * Scoring (cheap heuristic, good enough for a picker): exact basename match
1646
- * (100) > basename prefix (60) > path substring (20); non-matches are dropped.
1647
- * Each match is penalized by its path depth so root files sort first. Ties
1648
- * break by lexicographic path. An empty query keeps every path (score 0), so
1649
- * the result is the paths sorted lexicographically, capped to `limit`.
1650
- */
1651
- declare function rankFiles(paths: readonly string[], query: string, limit: number): string[];
1652
-
1653
- /**
1654
- * HTTP /api/analytics handler for the WebUI server.
1655
- *
1656
- * Accepts a batch of analytics events from the frontend, validates them,
1657
- * and stores them for later aggregation. Events are kept in a small
1658
- * in-memory ring buffer (last 1000) and can be retrieved via GET for
1659
- * debugging or exported to an external system.
1660
- *
1661
- * The endpoint is intentionally simple: no persistent storage, no external
1662
- * dependencies. If you need durable analytics, poll the GET endpoint
1663
- * and ship the events to your own aggregation pipeline (PostHog,
1664
- * Segment, etc.).
1665
- */
1666
-
1667
- interface AnalyticsEvent {
1668
- event: string;
1669
- category: string;
1670
- label?: string | undefined;
1671
- value?: number | undefined;
1672
- metadata?: Record<string, unknown> | undefined;
1673
- timestamp: string;
1674
- sessionId?: string | undefined;
1675
- userAgent?: string | undefined;
1676
- }
1677
- /** POST /api/analytics — ingest a batch of events from the frontend. */
1678
- declare function handleApiAnalyticsPost(res: http.ServerResponse, req: http.IncomingMessage): Promise<void>;
1679
- /** GET /api/analytics — retrieve the last N events (debug/export). */
1680
- declare function handleApiAnalyticsGet(res: http.ServerResponse, url: URL): Promise<void>;
1681
- /** GET /api/analytics/summary — aggregated stats for quick inspection. */
1682
- declare function handleApiAnalyticsSummary(res: http.ServerResponse): Promise<void>;
1683
- /** Clear the in-memory buffer (useful for testing). */
1684
- declare function clearAnalyticsBuffer(): void;
1685
- /** Get a snapshot of the buffer (useful for testing). */
1686
- declare function getAnalyticsBuffer(): readonly AnalyticsEvent[];
1687
-
1688
- /**
1689
- * Process lifecycle for the WebUI server: graceful shutdown and the
1690
- * SIGINT/SIGTERM wiring that triggers it.
1691
- *
1692
- * On a termination signal we (best-effort) flush + close the active session,
1693
- * close every connected WebSocket, stop the HTTP and WS servers, then exit.
1694
- * A re-entrancy guard makes a second signal during shutdown a no-op (rapid
1695
- * double Ctrl+C no longer runs the teardown twice).
1696
- *
1697
- * Extracted from `index.ts` as a parameterized factory so the teardown
1698
- * sequence can be unit tested without a real process signal, server, or
1699
- * `process.exit` — `log` and `exit` are injectable seams.
1700
- */
1701
- interface LifecycleResources {
1702
- /** Persist + close the active session (best-effort; errors are logged). */
1703
- flushSession: () => Promise<void>;
1704
- /**
1705
- * Returns the currently-connected client sockets to close. A thunk (not a
1706
- * snapshot) so shutdown closes whoever is connected *at signal time*, not
1707
- * whoever was connected when the handler was registered.
1708
- */
1709
- clients: () => Iterable<{
1710
- close: () => void;
1711
- }>;
1712
- /** Servers to stop (HTTP + WS). `null`/`undefined` entries are skipped. */
1713
- servers: Array<{
1714
- close: () => void;
1715
- } | null | undefined>;
1716
- /**
1717
- * Optional best-effort cleanup run after the session flush and before exit
1718
- * (e.g. removing this process from the running-instance registry). Errors are
1719
- * logged, never thrown — cleanup must not block a clean shutdown.
1720
- */
1721
- onShutdown?: (() => Promise<void> | void) | undefined;
1722
- /** Output sink. Defaults to `console.log`. */
1723
- log?: ((msg: string) => void) | undefined;
1724
- /** Process exit. Defaults to `process.exit`. Injectable for tests. */
1725
- exit?: ((code: number) => void) | undefined;
1726
- }
1727
- /**
1728
- * Build the graceful-shutdown handler. Returns an idempotent async function:
1729
- * the first call runs the teardown, subsequent calls (e.g. a second SIGINT)
1730
- * return immediately.
1731
- */
1732
- declare function createShutdown(res: LifecycleResources): () => Promise<void>;
1733
- /**
1734
- * Register the shutdown handler on SIGINT and SIGTERM. Returns an unregister
1735
- * function that detaches both listeners (useful for tests and clean restarts).
1736
- */
1737
- declare function registerShutdownHandlers(res: LifecycleResources): () => void;
1738
-
1739
- /**
1740
- * Mailbox WebSocket handlers for the WebUI.
1741
- *
1742
- * Handles `mailbox.messages` and `mailbox.agents` message types.
1743
- * The frontend sends these to populate the mailbox panel; the server
1744
- * reads from the project-level GlobalMailbox and responds.
1745
- */
1746
-
1747
- interface MailboxHandlerDeps {
1748
- /** Absolute project root. */
1749
- projectRoot: string;
1750
- /** Global WrongStack root (~/.wrongstack). */
1751
- globalRoot: string;
1752
- }
1753
- /**
1754
- * List recent mailbox messages. Frontend sends:
1755
- * { type: 'mailbox.messages', limit?: number, incompleteOnly?: boolean }
1756
- *
1757
- * Uses `incompleteOnly` so the server filters to active/unread messages,
1758
- * making readByCount === 0 a reliable "unread to all agents" signal for
1759
- * the ActivityBar badge count.
1760
- */
1761
- declare function handleMailboxMessages(ws: WebSocket, deps: MailboxHandlerDeps, payload: {
1762
- limit?: number;
1763
- agentId?: string;
1764
- unreadOnly?: boolean;
1765
- incompleteOnly?: boolean;
1766
- } | undefined): Promise<void>;
1767
-
1768
- interface MailboxRouteHandlers {
1769
- messages: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1770
- agents: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1771
- clear: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1772
- purge: (ws: WebSocket, msg: WSClientMessage) => Promise<void> | void;
1773
- }
1774
- declare function handleMailboxRoute(ws: WebSocket, msg: WSClientMessage, handlers: MailboxRouteHandlers): Promise<boolean>;
1775
-
1776
- interface McpRouteHandlers {
1777
- list: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1778
- add: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1779
- update: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1780
- remove: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1781
- enable: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1782
- disable: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1783
- sleep: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1784
- wake: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1785
- restart: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1786
- discover: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1787
- }
1788
- /**
1789
- * Chain-of-responsibility dispatcher for the `mcp.*` WS message family.
1790
- * Returns `true` if the message was handled by this layer (so the caller's
1791
- * chain short-circuits), `false` if it should fall through to the next layer
1792
- * or the residual switch.
1793
- *
1794
- * Owned prefixes (10 message types — full coverage of the MCP management
1795
- * surface; the WebUI MCP panel only ever talks to the server through these):
1796
- * - mcp.list
1797
- * - mcp.add
1798
- * - mcp.update
1799
- * - mcp.remove
1800
- * - mcp.enable
1801
- * - mcp.disable
1802
- * - mcp.sleep
1803
- * - mcp.wake
1804
- * - mcp.restart
1805
- * - mcp.discover
1806
- *
1807
- * Regression-tested by packages/webui/tests/server/dispatcher-routing.test.ts.
1808
- */
1809
- declare function handleMcpRoute(ws: WebSocket, msg: WSClientMessage, handlers: McpRouteHandlers): Promise<boolean>;
1810
-
1811
- interface ModeRouteHandlers {
1812
- listModes: (ws: WebSocket) => Promise<void>;
1813
- switchMode: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1814
- }
1815
- declare function handleModeRoute(ws: WebSocket, msg: WSClientMessage, handlers: ModeRouteHandlers): Promise<boolean>;
1816
-
1817
- /**
1818
- * Resolve the catalog entry to use for a saved provider's WebUI model list.
1819
- * Runtime discovery injects provider-specific catalogs under the saved provider
1820
- * id (for example `omniroute`), while older aliases may still need to fall
1821
- * back to their generic wire/catalog `type`.
1822
- */
1823
- declare function resolveProviderCatalogForModels(modelsRegistry: Pick<ModelsRegistry, 'getProvider'>, providerId: string, cfg: Pick<ProviderConfig, 'type'> | undefined): Promise<ResolvedProvider | undefined>;
1824
- /**
1825
- * Resolve model metadata for a selected config provider. Prefer the saved
1826
- * provider id so provider-specific runtime discovery (for example Omniroute)
1827
- * supplies context/cost/capability metadata; fall back to cfg.type for aliases.
1828
- */
1829
- declare function resolveProviderModelMetadata(modelsRegistry: Pick<ModelsRegistry, 'getModel' | 'getProvider'> & Partial<Pick<ModelsRegistry, 'listProviders'>>, providerId: string, modelId: string, cfg: Pick<ProviderConfig, 'type' | 'family' | 'models' | 'customModels' | 'capabilities'> | undefined): Promise<ResolvedModel | undefined>;
1830
-
1831
- declare function isPathInside(root: string, target: string): boolean;
1832
- declare function resolveWorkingDirInsideProject(projectRoot: string, inputPath: string): Promise<string>;
1833
-
1834
- interface PrefHelperDeps {
1835
- globalConfigPath: string;
1836
- vault: SecretVault;
1837
- logger: {
1838
- warn(msg: string): void;
1839
- };
1840
- }
1841
- /** Mutable holder for the serialized-config-write lock. The helpers update
1842
- * `lock` in place so callers keep a stable reference across writes (the
1843
- * lock is non-poisoning: a failed write resolves the chain but logs).
1844
- *
1845
- * We use a holder object rather than returning the new lock because
1846
- * TypeScript flattens `Promise<Promise<void>>` into `Promise<void>`,
1847
- * which would make `await helper(...)` yield `void` instead of the new
1848
- * lock value. */
1849
- interface ConfigWriteLockHolder {
1850
- lock: Promise<void>;
1851
- }
1852
- /**
1853
- * Persist pref changes into the global config.json — the SAME keys the TUI
1854
- * settings picker writes — so a toggle made in the browser survives restarts
1855
- * and is visible to the CLI/TUI (and vice versa on next boot). Best-effort
1856
- * and serialized behind the holder's `lock`; failures log but never break
1857
- * the WS reply.
1858
- */
1859
- declare function persistPrefsToConfig(deps: PrefHelperDeps, holder: ConfigWriteLockHolder, payload: Record<string, unknown>): Promise<void>;
1860
-
1861
- interface PrefsRouteHandlers {
1862
- /** Respond to the WS client with the current pref snapshot. */
1863
- getPrefs: (ws: WebSocket) => Promise<void>;
1864
- /**
1865
- * Merge the supplied pref payload into context.meta, persist the durable
1866
- * keys to config.json, apply any runtime effects (YOLO toggle, feature-flag
1867
- * mutation, fallback chain update, AutoCompaction pipeline add/remove,
1868
- * logger.level), then broadcast the full current snapshot to all clients.
1869
- */
1870
- updatePrefs: (ws: WebSocket, payload: Record<string, unknown>) => Promise<void>;
1871
- }
1872
- /**
1873
- * Chain-of-responsibility dispatcher for the `prefs.*` WS message family.
1874
- * Returns `true` if the message was handled by this layer (so the caller's
1875
- * chain short-circuits), `false` if it should fall through to the next layer
1876
- * or the residual switch.
1877
- *
1878
- * Owned prefixes:
1879
- * - `prefs.get`
1880
- * - `prefs.update`
1881
- *
1882
- * Regression-tested by packages/webui/tests/server/dispatcher-routing.test.ts.
1883
- */
1884
- declare function handlePrefsRoute(ws: WebSocket, msg: WSClientMessage, handlers: PrefsRouteHandlers): Promise<boolean>;
1885
-
1886
- interface ProjectRouteHandlers {
1887
- listProjects: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1888
- addProject: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1889
- selectProject: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1890
- setWorkingDir: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1891
- }
1892
- declare function handleProjectRoute(ws: WebSocket, msg: WSClientMessage, handlers: ProjectRouteHandlers): Promise<boolean>;
1893
-
1894
- interface ProjectEntry {
1895
- name: string;
1896
- root: string;
1897
- slug: string;
1898
- lastSeen?: string | undefined;
1899
- createdAt?: string | undefined;
1900
- /** Working directory of the most recent session (may differ from root). */
1901
- lastWorkingDir?: string | undefined;
1902
- }
1903
- interface ProjectsManifest {
1904
- projects: ProjectEntry[];
1905
- }
1906
- declare function projectsJsonPath(globalConfigPath: string): string;
1907
- declare function loadManifest(globalConfigPath: string): Promise<ProjectsManifest>;
1908
- declare function saveManifest(manifest: ProjectsManifest, globalConfigPath: string): Promise<void>;
1909
- declare function ensureProjectDataDir(slug: string, globalConfigPath: string): Promise<string>;
1910
-
1911
- /**
1912
- * Wire shape of one saved provider as broadcast over `providers.saved`.
1913
- * The WebUI's `<ProviderModelsPanel>` consumes this — when
1914
- * `pickedModelId` / `models` is missing, the panel renders the empty
1915
- * state.
1916
- */
1917
- interface SavedProviderView {
1918
- id: string;
1919
- family?: string | undefined;
1920
- baseUrl?: string | undefined;
1921
- /** Saved model allowlist, verbatim (undefined / [] both possible). */
1922
- models?: string[] | undefined;
1923
- /** First entry of `models`, or undefined when the list is empty/unset. */
1924
- pickedModelId?: string | undefined;
1925
- apiKeys: Array<{
1926
- label: string;
1927
- maskedKey: string;
1928
- isActive: boolean;
1929
- createdAt: string;
1930
- }>;
1931
- }
1932
- /**
1933
- * Canonical projection from in-memory `ProviderConfig` to the
1934
- * `providers.saved` wire shape. Pure (no I/O) so it's unit-tested in
1935
- * isolation — see `tests/server/provider-handlers-projection.test.ts`.
1936
- *
1937
- * Secrets never leave: every key is run through `maskedKey` before it
1938
- * reaches the wire.
1939
- */
1940
- declare function projectSavedProviders(providers: Record<string, ProviderConfig>): SavedProviderView[];
1941
- interface ProviderHandlerDeps {
1942
- globalConfigPath: string;
1943
- vault: _wrongstack_core.SecretVault;
1944
- /** Shared config write lock — serialized via chained promises */
1945
- setConfigWriteLock: (lock: Promise<void>) => void;
1946
- getConfigWriteLock: () => Promise<void>;
1947
- /** Broadcast a message to all connected WebUI clients */
1948
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
1949
- /** Connected WebUI clients map */
1950
- clients: Map<WebSocket, ConnectedClient>;
1951
- /** Used by the ChatGPT OAuth flow's tier-2 model lookup (best-effort). */
1952
- modelsRegistry?: ModelsRegistry | undefined;
1953
- }
1954
- declare function createProviderHandlers(deps: ProviderHandlerDeps): {
1955
- handleKeyUpsert: (ws: WebSocket, providerId: string, label: string, apiKey: string) => Promise<void>;
1956
- handleKeyDelete: (ws: WebSocket, providerId: string, label: string) => Promise<void>;
1957
- handleKeySetActive: (ws: WebSocket, providerId: string, label: string) => Promise<void>;
1958
- handleProviderAdd: (ws: WebSocket, payload: {
1959
- id: string;
1960
- family: string;
1961
- baseUrl?: string | undefined;
1962
- apiKey?: string | undefined;
1963
- }) => Promise<void>;
1964
- handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
1965
- handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
1966
- handleProviderUndoClear: (ws: WebSocket, providerId: string, previousModels: string[]) => Promise<void>;
1967
- handleProviderUpdate: (ws: WebSocket, payload: {
1968
- id: string;
1969
- family?: string | undefined;
1970
- baseUrl?: string | undefined;
1971
- envVars?: string[] | undefined;
1972
- models?: string[] | undefined;
1973
- }) => Promise<void>;
1974
- handleProviderProbe: (ws: WebSocket, providerId: string, timeoutMs?: number) => Promise<void>;
1975
- handleOAuthStart: (ws: WebSocket, kind: OAuthKind) => Promise<void>;
1976
- handleOAuthCode: (ws: WebSocket, kind: OAuthKind, input: string) => Promise<void>;
1977
- handleOAuthCancel: (ws: WebSocket, kind: OAuthKind) => void;
1978
- loadConfigProviders: () => Promise<Record<string, ProviderConfig>>;
1979
- };
1980
-
1981
- interface ProviderRouteHandlers {
1982
- listProviders: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1983
- listSavedProviders: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1984
- listProviderModels: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1985
- switchModel: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1986
- refineModel: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
1987
- providerHandlers: ReturnType<typeof createProviderHandlers>;
1988
- }
1989
- declare function handleProviderRoute(ws: WebSocket, msg: WSClientMessage, routes: ProviderRouteHandlers): Promise<boolean>;
1990
-
1991
- interface ProviderStoreDeps {
1992
- globalConfigPath: string;
1993
- vault: DefaultSecretVault;
1994
- }
1995
- /**
1996
- * Serializes concurrent config writes to prevent races between model.switch
1997
- * and key.add/key.update handlers that both read-modify-write globalConfigPath.
1998
- */
1999
- declare function createConfigWriteLock(): {
2000
- readonly current: Promise<void>;
2001
- acquire(): {
2002
- prev: Promise<void>;
2003
- release: () => void;
2004
- };
2005
- };
2006
- interface ProviderStore {
2007
- load(): Promise<Record<string, ProviderConfig>>;
2008
- save(providers: Record<string, ProviderConfig>): Promise<void>;
2009
- normalizeKeys(cfg: ProviderConfig): ProviderApiKey[];
2010
- writeKeysBack(cfg: ProviderConfig, keys: ProviderApiKey[]): void;
2011
- maskedKey(key: string | undefined): string;
2012
- }
2013
- declare function createProviderStore(deps: ProviderStoreDeps): ProviderStore;
2014
-
2015
- interface SddBoardRouteHandlers {
2016
- handleMessage: (msg: {
2017
- type: string;
2018
- payload?: Record<string, unknown>;
2019
- }) => Promise<void>;
2020
- }
2021
- /** Forward any `sdd.board.*` message to the SddBoardWebSocketHandler. */
2022
- declare function handleSddBoardRoute(_ws: WebSocket, msg: WSClientMessage, handlers: SddBoardRouteHandlers): Promise<boolean>;
2023
-
2024
- interface SddWizardRouteHandlers {
2025
- handleMessage: (msg: {
2026
- type: string;
2027
- payload?: Record<string, unknown>;
2028
- }) => Promise<void>;
2029
- }
2030
- /**
2031
- * Forward the SDD wizard messages (`sdd.spec.*` and `sdd.run.start`) to the
2032
- * SddWizardWebSocketHandler. Note `sdd.board.*` is handled separately by the
2033
- * board route — the wizard owns spec-building + run kickoff, the board owns
2034
- * live observation/control.
2035
- */
2036
- declare function handleSddWizardRoute(_ws: WebSocket, msg: WSClientMessage, handlers: SddWizardRouteHandlers): Promise<boolean>;
2037
-
2038
- interface SessionRouteHandlers {
2039
- newSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2040
- clearContext: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2041
- debugContext: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2042
- compactContext: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2043
- repairContext: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2044
- listContextModes: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2045
- switchContextMode: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2046
- createContextMode: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2047
- updateContextMode: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2048
- deleteContextMode: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2049
- listSessions: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2050
- renameSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2051
- deleteSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2052
- resumeSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2053
- saveSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2054
- listCheckpoints: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2055
- rewindSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2056
- }
2057
- declare function handleSessionRoute(ws: WebSocket, msg: WSClientMessage, handlers: SessionRouteHandlers): Promise<boolean>;
2058
-
2059
- interface ShellGitRouteHandlers {
2060
- gitInfo: (ws: WebSocket) => Promise<void>;
2061
- gitChanges: (ws: WebSocket) => Promise<void>;
2062
- gitDiff: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2063
- shellOpen: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
2064
- }
2065
- declare function handleShellGitRoute(ws: WebSocket, msg: WSClientMessage, handlers: ShellGitRouteHandlers): Promise<boolean>;
2066
-
2067
- interface SpecsRouteHandlers {
2068
- handleMessage: (msg: {
2069
- type: string;
2070
- payload?: Record<string, unknown>;
2071
- }) => Promise<void>;
2072
- }
2073
- /** Forward any `specs.*` message to the SpecsWebSocketHandler. */
2074
- declare function handleSpecsRoute(_ws: WebSocket, msg: WSClientMessage, handlers: SpecsRouteHandlers): Promise<boolean>;
2075
-
2076
- /** Loose inbound shape — matches the server's internal WSClientMessage. */
2077
- type IncomingMessage = {
2078
- type: string;
2079
- payload?: unknown;
2080
- };
2081
- type PtyExit = {
2082
- exitCode: number;
2083
- signal?: number | undefined;
2084
- };
2085
- interface PtyProcess {
2086
- readonly pid?: number | undefined;
2087
- write(data: string): void;
2088
- resize(cols: number, rows: number): void;
2089
- kill(): void;
2090
- onData(cb: (data: string) => void): unknown;
2091
- onExit(cb: (event: PtyExit) => void): unknown;
2092
- }
2093
- interface NodePtyApi {
2094
- spawn(file: string, args: string[], opts: {
2095
- name: string;
2096
- cols: number;
2097
- rows: number;
2098
- cwd: string;
2099
- env: Record<string, string | undefined>;
2100
- useConptyDll?: boolean | undefined;
2101
- }): PtyProcess;
2102
- }
2103
- type LoadNodePty = () => NodePtyApi | null;
2104
- type KillProcessTree = (pid: number) => void;
2105
- /**
2106
- * TerminalWebSocketHandler — backs the WebUI's integrated terminal panel.
2107
- *
2108
- * Mirrors the lifecycle shape of WorktreeWebSocketHandler but is *per-client*
2109
- * and *interactive*: each connected WebSocket owns a map of real node-pty
2110
- * sessions keyed by a client-chosen id. Browser xterm.js ⇄ pty wiring:
2111
- * - `terminal.create` → spawn a shell pty, stream its output back
2112
- * - `terminal.input` → write keystrokes to the pty
2113
- * - `terminal.resize` → propagate xterm's fit dimensions
2114
- * - `terminal.close` → kill the pty
2115
- * When a client disconnects, every pty it owns is killed (no orphan shells).
2116
- */
2117
- declare class TerminalWebSocketHandler {
2118
- /** Resolves the cwd new terminals open in — tracks the live working dir. */
2119
- private readonly getCwd;
2120
- private readonly logger;
2121
- private readonly loadNodePty;
2122
- private readonly killProcessTree;
2123
- /** ws → (terminalId → pty). */
2124
- private readonly sessions;
2125
- constructor(
2126
- /** Resolves the cwd new terminals open in — tracks the live working dir. */
2127
- getCwd: () => string, logger: Logger, loadNodePty?: LoadNodePty, killProcessTree?: KillProcessTree);
2128
- addClient(ws: WebSocket): void;
2129
- /** Kill every pty owned by every client (server shutdown). */
2130
- dispose(): void;
2131
- /** True if this message was a terminal.* message (handled here). */
2132
- handleMessage(ws: WebSocket, msg: IncomingMessage): boolean;
2133
- private create;
2134
- private input;
2135
- private resize;
2136
- private close;
2137
- private disposeClient;
2138
- private killPty;
2139
- private killWindowsProcessTree;
2140
- private send;
2141
- }
2142
-
2143
- /**
2144
- * Token-usage cost math for the WebUI server.
2145
- *
2146
- * models.dev pricing is expressed in **dollars per 1,000,000 tokens**, and
2147
- * providers omit the field entirely for free/unmetered plans. Both the
2148
- * `session.start` payload (which ships the per-token rates to the client) and
2149
- * `stats.get` (which reports an actual dollar figure) repeated the same
2150
- * "read `model.cost.*` with a `?? 0` fallback, then divide by 1e6" logic
2151
- * inline. Pulling it here keeps the rate normalization and the cost formula in
2152
- * one tested place — a wrong field name or a missing `/ 1e6` silently produces
2153
- * a plausible-but-wrong number, which is exactly what a unit test should pin.
2154
- */
2155
- /** Per-1,000,000-token pricing, normalized to numbers (0 when unpriced). */
2156
- interface CostRates {
2157
- /** $ per 1M input tokens. */
2158
- input: number;
2159
- /** $ per 1M output tokens. */
2160
- output: number;
2161
- /** $ per 1M cache-read tokens. */
2162
- cacheRead: number;
2163
- }
2164
- /** Token counts for a turn/session. `cacheRead` is optional (older counters). */
2165
- interface TokenUsage {
2166
- input: number;
2167
- output: number;
2168
- cacheRead?: number | undefined;
2169
- }
2170
- /**
2171
- * Normalize a models.dev model object's pricing into {@link CostRates}.
2172
- * Missing model, missing `cost`, or missing individual fields all yield 0 —
2173
- * free/unmetered plans report `$0` rather than crashing.
2174
- */
2175
- declare function getCostRates(model: unknown): CostRates;
2176
- /**
2177
- * Dollar cost of `usage` at the given per-1M-token `rates`. Returns 0 when all
2178
- * rates are 0 (unpriced plan).
10
+ * The implementation lives in `./server/` — `./server/index.ts` is the
11
+ * canonical barrel. Consumers (CLI, future cross-package code) should
12
+ * import from this file (the package root) for forward-compat.
2179
13
  */
2180
- declare function computeUsageCost(usage: TokenUsage, rates: CostRates): number;
2181
-
2182
- type PayloadValidationResult<T> = {
2183
- ok: true;
2184
- value: T;
2185
- } | {
2186
- ok: false;
2187
- message: string;
2188
- };
2189
- interface ModelSwitchPayload {
2190
- provider: string;
2191
- model: string;
2192
- }
2193
- declare function validateModelSwitchPayload(payload: unknown): PayloadValidationResult<ModelSwitchPayload>;
2194
- interface PrefsUpdatePayload {
2195
- prefs: Record<string, unknown>;
2196
- }
2197
- interface MailboxMessagesPayload {
2198
- limit?: number;
2199
- agentId?: string;
2200
- unreadOnly?: boolean;
2201
- incompleteOnly?: boolean;
2202
- }
2203
- declare function validateMailboxMessagesPayload(payload: unknown): PayloadValidationResult<MailboxMessagesPayload | undefined>;
2204
- interface MailboxAgentsPayload {
2205
- onlineOnly?: boolean;
2206
- }
2207
- declare function validateMailboxAgentsPayload(payload: unknown): PayloadValidationResult<MailboxAgentsPayload | undefined>;
2208
- interface MailboxPurgePayload {
2209
- completedMaxAgeMs?: number;
2210
- incompleteMaxAgeMs?: number;
2211
- }
2212
- declare function validateMailboxPurgePayload(payload: unknown): PayloadValidationResult<MailboxPurgePayload | undefined>;
2213
- interface BrainRiskPayload {
2214
- level: string;
2215
- }
2216
- declare function validateBrainRiskPayload(payload: unknown): PayloadValidationResult<BrainRiskPayload>;
2217
- interface BrainAskPayload {
2218
- question: string;
2219
- }
2220
- declare function validateBrainAskPayload(payload: unknown): PayloadValidationResult<BrainAskPayload>;
2221
- interface AutonomySwitchPayload {
2222
- mode: string;
2223
- }
2224
- declare function validateAutonomySwitchPayload(payload: unknown): PayloadValidationResult<AutonomySwitchPayload>;
2225
- interface PlanTemplateUsePayload {
2226
- template: string;
2227
- }
2228
- declare function validatePlanTemplateUsePayload(payload: unknown): PayloadValidationResult<PlanTemplateUsePayload>;
2229
- declare function validatePrefsUpdatePayload(payload: unknown): PayloadValidationResult<PrefsUpdatePayload>;
2230
- interface SkillsCreatePayload {
2231
- name: string;
2232
- description: string;
2233
- scope: 'project' | 'global';
2234
- }
2235
- declare function validateSkillsCreatePayload(payload: unknown): PayloadValidationResult<SkillsCreatePayload>;
2236
- interface SkillsEditPayload {
2237
- name: string;
2238
- body: string;
2239
- }
2240
- declare function validateSkillsEditPayload(payload: unknown): PayloadValidationResult<SkillsEditPayload>;
2241
- interface ProcessKillPayload {
2242
- pid: number;
2243
- }
2244
- declare function validateProcessKillPayload(payload: unknown): PayloadValidationResult<ProcessKillPayload>;
2245
- interface WorkingDirSetPayload {
2246
- path: string;
2247
- }
2248
- declare function validateWorkingDirSetPayload(payload: unknown): PayloadValidationResult<WorkingDirSetPayload>;
2249
- interface ModeSwitchPayload {
2250
- id: string;
2251
- }
2252
- declare function validateModeSwitchPayload(payload: unknown): PayloadValidationResult<ModeSwitchPayload>;
2253
- interface ContextModeIdPayload {
2254
- id: string;
2255
- }
2256
- declare function validateContextModeSwitchPayload(payload: unknown): PayloadValidationResult<ContextModeIdPayload>;
2257
- declare function validateContextModeDeletePayload(payload: unknown): PayloadValidationResult<ContextModeIdPayload>;
2258
- interface ContextModeCreatePayload {
2259
- id: string;
2260
- name: string;
2261
- description: string;
2262
- thresholds: {
2263
- warn: number;
2264
- soft: number;
2265
- hard: number;
2266
- };
2267
- preserveK: number;
2268
- eliseThreshold: number;
2269
- }
2270
- declare function validateContextModeCreatePayload(payload: unknown): PayloadValidationResult<ContextModeCreatePayload>;
2271
- interface ContextModeUpdatePayload {
2272
- id: string;
2273
- name?: string;
2274
- description?: string;
2275
- thresholds?: {
2276
- warn?: number;
2277
- soft?: number;
2278
- hard?: number;
2279
- };
2280
- preserveK?: number;
2281
- eliseThreshold?: number;
2282
- }
2283
- declare function validateContextModeUpdatePayload(payload: unknown): PayloadValidationResult<ContextModeUpdatePayload>;
2284
- interface ShellOpenPayload {
2285
- path: string;
2286
- target?: 'file' | 'terminal';
2287
- }
2288
- declare function validateShellOpenPayload(payload: unknown): PayloadValidationResult<ShellOpenPayload>;
2289
- interface GitDiffPayload {
2290
- path: string;
2291
- }
2292
- declare function validateGitDiffPayload(payload: unknown): PayloadValidationResult<GitDiffPayload>;
2293
- interface ProjectsAddPayload {
2294
- root: string;
2295
- name?: string;
2296
- }
2297
- declare function validateProjectsAddPayload(payload: unknown): PayloadValidationResult<ProjectsAddPayload>;
2298
- interface ProjectsSelectPayload {
2299
- root: string;
2300
- name?: string;
2301
- }
2302
- declare function validateProjectsSelectPayload(payload: unknown): PayloadValidationResult<ProjectsSelectPayload>;
2303
-
2304
- export { type AutoPhaseRouteHandlers, AutoPhaseWebSocketHandler, type BackendServices, type BrainRouteHandlers, CollaborationWebSocketHandler, type CompletionHandlerOptions, type CompletionItemKind, type CompletionSuggestion, type ConnectedClient, type ContextBreakdown, type CreateHttpServerOptions, type CustomContextMode, type CustomModeStore, type DesignContext, type EternalBroadcast, type EternalSubscribe, type EternalSubscription, type KeyOpResult, type LspCompletionSource, type LspCompletionSourceRequest, type MailboxRouteHandlers, type MessageTokenEntry, type ModeRouteHandlers, type PendingConfirm, type ProjectRouteHandlers, type PromptsContext, type ProviderRouteHandlers, type ProviderStore, type ProvidersRecord, SKIP_DIRS, type SavedProviderView, SddBoardWebSocketHandler, type SddWizardDeps, SddWizardWebSocketHandler, type SddWizardWiringOptions, type SessionRouteHandlers, type ShellGitRouteHandlers, type ShellOpenRequest, type ShellOpenResult, type ShellOpenTarget, type SkillsContext, SpecsWebSocketHandler, TerminalWebSocketHandler, type ToolTokenEntry, type VerifyClientInput, type WSClientMessage, type WSServerMessage, type WebUIInstanceRecord, type WebUIOptions, 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, 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 };
14
+ export * from './server/index.js';
15
+ //# sourceMappingURL=index.d.ts.map