@wrongstack/webui-server 0.284.1 → 0.285.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.
- package/dist/index.d.ts +11 -2300
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1019 -277
- package/dist/index.js.map +7 -1
- package/dist/server/autophase-routes.d.ts +10 -0
- package/dist/server/autophase-routes.d.ts.map +1 -0
- package/dist/server/autophase-ws-handler.d.ts +87 -0
- package/dist/server/autophase-ws-handler.d.ts.map +1 -0
- package/dist/server/backend-services.d.ts +114 -0
- package/dist/server/backend-services.d.ts.map +1 -0
- package/dist/server/boot.d.ts +19 -0
- package/dist/server/boot.d.ts.map +1 -0
- package/dist/server/brain-routes.d.ts +9 -0
- package/dist/server/brain-routes.d.ts.map +1 -0
- package/dist/server/codebase-indexing.d.ts +15 -0
- package/dist/server/codebase-indexing.d.ts.map +1 -0
- package/dist/server/collaboration-ws-handler.d.ts +159 -0
- package/dist/server/collaboration-ws-handler.d.ts.map +1 -0
- package/dist/server/completion-handlers.d.ts +39 -0
- package/dist/server/completion-handlers.d.ts.map +1 -0
- package/dist/server/connection-handler.d.ts +72 -0
- package/dist/server/connection-handler.d.ts.map +1 -0
- package/dist/server/context-meta.d.ts +28 -0
- package/dist/server/context-meta.d.ts.map +1 -0
- package/dist/server/custom-context-modes.d.ts +43 -0
- package/dist/server/custom-context-modes.d.ts.map +1 -0
- package/dist/server/design-handlers.d.ts +40 -0
- package/dist/server/design-handlers.d.ts.map +1 -0
- package/dist/server/discover-mailbox-bridge.d.ts +26 -0
- package/dist/server/discover-mailbox-bridge.d.ts.map +1 -0
- package/dist/server/entry.d.ts +2 -2
- package/dist/server/entry.d.ts.map +1 -0
- package/dist/server/entry.js +811 -300
- package/dist/server/entry.js.map +7 -1
- package/dist/server/eternal-iteration-broadcast.d.ts +11 -0
- package/dist/server/eternal-iteration-broadcast.d.ts.map +1 -0
- package/dist/server/file-handlers.d.ts +45 -0
- package/dist/server/file-handlers.d.ts.map +1 -0
- package/dist/server/file-picker.d.ts +26 -0
- package/dist/server/file-picker.d.ts.map +1 -0
- package/dist/server/git-handlers.d.ts +55 -0
- package/dist/server/git-handlers.d.ts.map +1 -0
- package/dist/server/goal-handlers.d.ts +19 -0
- package/dist/server/goal-handlers.d.ts.map +1 -0
- package/dist/server/handlers/index.d.ts +12 -0
- package/dist/server/handlers/index.d.ts.map +1 -0
- package/dist/server/handlers/worklist-handlers.d.ts +49 -0
- package/dist/server/handlers/worklist-handlers.d.ts.map +1 -0
- package/dist/server/handlers.d.ts +1 -51
- package/dist/server/handlers.js +17 -8
- package/dist/server/handlers.js.map +7 -1
- package/dist/server/http-server/analytics-handler.d.ts +35 -0
- package/dist/server/http-server/analytics-handler.d.ts.map +1 -0
- package/dist/server/http-server/api-handlers.d.ts +44 -0
- package/dist/server/http-server/api-handlers.d.ts.map +1 -0
- package/dist/server/http-server.d.ts +108 -0
- package/dist/server/http-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +93 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/instance-registry.d.ts +63 -0
- package/dist/server/instance-registry.d.ts.map +1 -0
- package/dist/server/kanban-routes.d.ts +7 -0
- package/dist/server/kanban-routes.d.ts.map +1 -0
- package/dist/server/lifecycle.d.ts +51 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/mailbox-handlers.d.ts +51 -0
- package/dist/server/mailbox-handlers.d.ts.map +1 -0
- package/dist/server/mailbox-routes.d.ts +10 -0
- package/dist/server/mailbox-routes.d.ts.map +1 -0
- package/dist/server/mcp-handlers.d.ts +46 -0
- package/dist/server/mcp-handlers.d.ts.map +1 -0
- package/dist/server/mcp-routes.d.ts +37 -0
- package/dist/server/mcp-routes.d.ts.map +1 -0
- package/dist/server/memory-handlers.d.ts +28 -0
- package/dist/server/memory-handlers.d.ts.map +1 -0
- package/dist/server/message-dispatcher.d.ts +41 -0
- package/dist/server/message-dispatcher.d.ts.map +1 -0
- package/dist/server/mode-handlers.d.ts +50 -0
- package/dist/server/mode-handlers.d.ts.map +1 -0
- package/dist/server/mode-routes.d.ts +8 -0
- package/dist/server/mode-routes.d.ts.map +1 -0
- package/dist/server/model-auto-discovery.d.ts +9 -0
- package/dist/server/model-auto-discovery.d.ts.map +1 -0
- package/dist/server/model-catalog.d.ts +16 -0
- package/dist/server/model-catalog.d.ts.map +1 -0
- package/dist/server/open-browser.d.ts +16 -0
- package/dist/server/open-browser.d.ts.map +1 -0
- package/dist/server/path-containment.d.ts +3 -0
- package/dist/server/path-containment.d.ts.map +1 -0
- package/dist/server/pending-confirms.d.ts +14 -0
- package/dist/server/pending-confirms.d.ts.map +1 -0
- package/dist/server/port-utils.d.ts +28 -0
- package/dist/server/port-utils.d.ts.map +1 -0
- package/dist/server/pre-context-services.d.ts +58 -0
- package/dist/server/pre-context-services.d.ts.map +1 -0
- package/dist/server/pref-helpers.d.ts +43 -0
- package/dist/server/pref-helpers.d.ts.map +1 -0
- package/dist/server/prefs-routes.d.ts +27 -0
- package/dist/server/prefs-routes.d.ts.map +1 -0
- package/dist/server/process-handlers.d.ts +20 -0
- package/dist/server/process-handlers.d.ts.map +1 -0
- package/dist/server/project-handlers.d.ts +53 -0
- package/dist/server/project-handlers.d.ts.map +1 -0
- package/dist/server/project-routes.d.ts +10 -0
- package/dist/server/project-routes.d.ts.map +1 -0
- package/dist/server/projects-manifest.d.ts +18 -0
- package/dist/server/projects-manifest.d.ts.map +1 -0
- package/dist/server/prompts-handlers.d.ts +38 -0
- package/dist/server/prompts-handlers.d.ts.map +1 -0
- package/dist/server/provider-config-io.d.ts +15 -0
- package/dist/server/provider-config-io.d.ts.map +1 -0
- package/dist/server/provider-config-standalone.d.ts +13 -0
- package/dist/server/provider-config-standalone.d.ts.map +1 -0
- package/dist/server/provider-handlers.d.ts +74 -0
- package/dist/server/provider-handlers.d.ts.map +1 -0
- package/dist/server/provider-keys.d.ts +53 -0
- package/dist/server/provider-keys.d.ts.map +1 -0
- package/dist/server/provider-routes.d.ts +13 -0
- package/dist/server/provider-routes.d.ts.map +1 -0
- package/dist/server/provider-store.d.ts +26 -0
- package/dist/server/provider-store.d.ts.map +1 -0
- package/dist/server/routes.d.ts +182 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/sdd-board-routes.d.ts +11 -0
- package/dist/server/sdd-board-routes.d.ts.map +1 -0
- package/dist/server/sdd-board-ws-handler.d.ts +57 -0
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -0
- package/dist/server/sdd-wizard-routes.d.ts +16 -0
- package/dist/server/sdd-wizard-routes.d.ts.map +1 -0
- package/dist/server/sdd-wizard-wiring.d.ts +26 -0
- package/dist/server/sdd-wizard-wiring.d.ts.map +1 -0
- package/dist/server/sdd-wizard-ws-handler.d.ts +66 -0
- package/dist/server/sdd-wizard-ws-handler.d.ts.map +1 -0
- package/dist/server/server-runtime.d.ts +118 -0
- package/dist/server/server-runtime.d.ts.map +1 -0
- package/dist/server/session-handlers.d.ts +44 -0
- package/dist/server/session-handlers.d.ts.map +1 -0
- package/dist/server/session-routes.d.ts +23 -0
- package/dist/server/session-routes.d.ts.map +1 -0
- package/dist/server/setup-events.d.ts +67 -0
- package/dist/server/setup-events.d.ts.map +1 -0
- package/dist/server/setup-screen.d.ts +44 -0
- package/dist/server/setup-screen.d.ts.map +1 -0
- package/dist/server/shell-git-routes.d.ts +10 -0
- package/dist/server/shell-git-routes.d.ts.map +1 -0
- package/dist/server/shell-open.d.ts +12 -0
- package/dist/server/shell-open.d.ts.map +1 -0
- package/dist/server/skills-handlers.d.ts +77 -0
- package/dist/server/skills-handlers.d.ts.map +1 -0
- package/dist/server/specs-routes.d.ts +11 -0
- package/dist/server/specs-routes.d.ts.map +1 -0
- package/dist/server/specs-ws-handler.d.ts +34 -0
- package/dist/server/specs-ws-handler.d.ts.map +1 -0
- package/dist/server/start-webui.d.ts +29 -0
- package/dist/server/start-webui.d.ts.map +1 -0
- package/dist/server/terminal-ws-handler.d.ts +70 -0
- package/dist/server/terminal-ws-handler.d.ts.map +1 -0
- package/dist/server/token-estimator.d.ts +66 -0
- package/dist/server/token-estimator.d.ts.map +1 -0
- package/dist/server/types.d.ts +143 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/usage-cost.d.ts +39 -0
- package/dist/server/usage-cost.d.ts.map +1 -0
- package/dist/server/worktree-ws-handler.d.ts +67 -0
- package/dist/server/worktree-ws-handler.d.ts.map +1 -0
- package/dist/server/ws-auth.d.ts +83 -0
- package/dist/server/ws-auth.d.ts.map +1 -0
- package/dist/server/ws-payload-validation.d.ts +122 -0
- package/dist/server/ws-payload-validation.d.ts.map +1 -0
- package/dist/server/ws-utils.d.ts +38 -0
- package/dist/server/ws-utils.d.ts.map +1 -0
- package/dist/server/zip.d.ts +9 -0
- package/dist/server/zip.d.ts.map +1 -0
- 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
|
-
*
|
|
2
|
+
* @wrongstack/webui-server — public API entry.
|
|
18
3
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
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
|
-
|
|
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
|