@wrongstack/webui-server 0.306.4 → 0.307.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/index.js +3567 -2746
  2. package/dist/protocol/client-integrations.d.ts +1 -1
  3. package/dist/protocol/client-operations.d.ts +1 -1
  4. package/dist/protocol/client-workspace.d.ts +1 -1
  5. package/dist/protocol/index.js +14 -0
  6. package/dist/protocol/registry.d.ts +2 -2
  7. package/dist/protocol/server-integrations.d.ts +1 -1
  8. package/dist/protocol/server-workspace.d.ts +1 -1
  9. package/dist/server/collaboration-ws-handler.d.ts +18 -0
  10. package/dist/server/connections/collector.d.ts +15 -0
  11. package/dist/server/connections/helpers.d.ts +18 -0
  12. package/dist/server/connections/index.d.ts +5 -0
  13. package/dist/server/connections/service-actions.d.ts +16 -0
  14. package/dist/server/connections/types.d.ts +55 -0
  15. package/dist/server/connections-health-route.d.ts +5 -66
  16. package/dist/server/entry.js +3525 -2716
  17. package/dist/server/file-handlers.d.ts +37 -0
  18. package/dist/server/goal-ws-handler.d.ts +16 -0
  19. package/dist/server/http-server/api-router.d.ts +22 -0
  20. package/dist/server/http-server/index.d.ts +6 -0
  21. package/dist/server/http-server/security-helpers.d.ts +24 -0
  22. package/dist/server/http-server/static-file-handler.d.ts +10 -0
  23. package/dist/server/http-server.d.ts +28 -87
  24. package/dist/server/index.d.ts +3 -2
  25. package/dist/server/network-info.d.ts +2 -3
  26. package/dist/server/project-watcher.d.ts +31 -0
  27. package/dist/server/prompts-handlers.d.ts +2 -0
  28. package/dist/server/sdd-board-ws-handler.d.ts +9 -0
  29. package/dist/server/sdd-wizard-ws-handler.d.ts +1 -0
  30. package/dist/server/setup-events-pattern-handlers.d.ts +10 -0
  31. package/dist/server/setup-events-status-watcher.d.ts +1 -1
  32. package/dist/server/setup-events-subagent-handlers.d.ts +14 -0
  33. package/dist/server/setup-events-tool-handlers.d.ts +18 -0
  34. package/dist/server/setup-events.d.ts +1 -2
  35. package/dist/server/shell-open.d.ts +9 -8
  36. package/dist/server/start-webui-companion.d.ts +16 -0
  37. package/dist/server/start-webui-credential-watcher.d.ts +13 -0
  38. package/dist/server/start-webui-remediation.d.ts +14 -0
  39. package/dist/server/start-webui-shutdown.d.ts +43 -0
  40. package/dist/server/start-webui-todos.d.ts +13 -0
  41. package/dist/server/start-webui.d.ts +2 -29
  42. package/dist/server/techstack-handlers.d.ts +2 -2
  43. package/dist/server/worktree-ws-handler.d.ts +7 -0
  44. package/package.json +13 -13
@@ -27,6 +27,11 @@ export declare function handleFilesTree(ws: WebSocket, msg: unknown, projectRoot
27
27
  * `{ type: 'files.read', payload: { filePath, content } }`.
28
28
  */
29
29
  export declare function handleFilesRead(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
30
+ /**
31
+ * Extract an AST-based skeleton for a file.
32
+ * Responds with `{ type: 'files.skeleton_result', payload: { filePath, lang, skeleton, stats } }`.
33
+ */
34
+ export declare function handleFilesSkeleton(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
30
35
  /**
31
36
  * Write file content back to disk (atomic write via tmp + rename).
32
37
  *
@@ -42,4 +47,36 @@ export declare function handleFilesWrite(ws: WebSocket, msg: unknown, projectRoo
42
47
  * `{ type: 'files.list', payload: { files } }`.
43
48
  */
44
49
  export declare function handleFilesList(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
50
+ /**
51
+ * Create a new file or directory inside the project root.
52
+ *
53
+ * Guards against path traversal via `resolveFileInsideProject`. Rejects
54
+ * if the target already exists. Responds with
55
+ * `{ type: 'files.created', payload: { filePath, success } }`.
56
+ */
57
+ export declare function handleFilesCreate(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
58
+ /**
59
+ * Delete a file or directory inside the project root.
60
+ *
61
+ * Guards against path traversal via `resolveFileInsideProject`. Directories
62
+ * require `recursive: true` to delete non-empty contents. Responds with
63
+ * `{ type: 'files.deleted', payload: { filePath, success } }`.
64
+ */
65
+ export declare function handleFilesDelete(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
66
+ /**
67
+ * Rename or move a file/directory within the project root.
68
+ *
69
+ * Guards both source and destination via `resolveFileInsideProject`.
70
+ * Rejects if the destination already exists or the source doesn't.
71
+ * Responds with `{ type: 'files.renamed', payload: { oldPath, newPath, success } }`.
72
+ */
73
+ export declare function handleFilesRename(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
74
+ /**
75
+ * Move a file/directory into a destination directory within the project root.
76
+ *
77
+ * Guards both source and destination via `resolveFileInsideProject`.
78
+ * The file keeps its basename — it is placed inside `destDir`.
79
+ * Responds with `{ type: 'files.moved', payload: { srcPath, destPath, success } }`.
80
+ */
81
+ export declare function handleFilesMove(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
45
82
  //# sourceMappingURL=file-handlers.d.ts.map
@@ -37,6 +37,14 @@ export declare class GoalWebSocketHandler {
37
37
  private store;
38
38
  private clients;
39
39
  private broadcastInterval;
40
+ /**
41
+ * Change-detection state for the 2s broadcast tick: a cheap content
42
+ * fingerprint of the graph and the last serialized progress payload, so an
43
+ * idle run costs one small string build per tick instead of a full
44
+ * buildState + serialize + fan-out every 2 seconds.
45
+ */
46
+ private lastGraphFingerprint;
47
+ private lastProgressJson;
40
48
  /** Aborts in-flight task agents AND the planning turn when the run is stopped. */
41
49
  private abort;
42
50
  /** Per-assessment AbortController so a newer assessment can abort the prior
@@ -129,6 +137,14 @@ export declare class GoalWebSocketHandler {
129
137
  private startBroadcast;
130
138
  private stopBroadcast;
131
139
  private broadcastState;
140
+ /**
141
+ * Cheap content fingerprint covering exactly what `buildState` renders:
142
+ * graph identity/flags plus per-phase status, timestamps, assignees, and
143
+ * task status counts with the newest task update. Any mutation that would
144
+ * change the projection changes this string, so the 2s tick can skip the
145
+ * full buildState + serialize + fan-out while the graph is idle.
146
+ */
147
+ private graphFingerprint;
132
148
  private buildState;
133
149
  private sendState;
134
150
  private broadcast;
@@ -0,0 +1,22 @@
1
+ import type * as http from 'node:http';
2
+ import { type TechStackEvent } from '../techstack-handlers.js';
3
+ export interface ApiRouterDeps {
4
+ globalRoot?: string | undefined;
5
+ projectRoot?: string | undefined;
6
+ indexDir?: string | undefined;
7
+ intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
8
+ onFleetPing?: (() => void) | undefined;
9
+ watcherMetrics?: import('../setup-events.js').FileWatcherMetrics | undefined;
10
+ onTechStackEvent?: ((event: TechStackEvent) => void) | undefined;
11
+ getLlm?: (() => {
12
+ provider: import('@wrongstack/core/types').Provider;
13
+ model: string;
14
+ } | undefined) | undefined;
15
+ executePackageOperation?: import('../techstack-handlers.js').TechStackHandlerDeps['executePackageOperation'];
16
+ }
17
+ export declare function handleApiRoutes(req: http.IncomingMessage, res: http.ServerResponse, url: URL, deps: ApiRouterDeps, requireAccessToken: boolean, accessTokenOk: boolean, getTechStackRuntime: () => Promise<{
18
+ store: import('@wrongstack/techstack').TechStackStore;
19
+ engine: import('@wrongstack/techstack').TechStackEngine;
20
+ runningJobs: Map<string, AbortController>;
21
+ }>): Promise<boolean>;
22
+ //# sourceMappingURL=api-router.d.ts.map
@@ -0,0 +1,6 @@
1
+ export * from './security-helpers.js';
2
+ export * from './static-file-handler.js';
3
+ export * from './analytics-handler.js';
4
+ export * from './api-handlers.js';
5
+ export * from './api-router.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,24 @@
1
+ import type * as http from 'node:http';
2
+ export declare const MIME_TYPES: Record<string, string>;
3
+ export declare function escapeHtmlAttr(value: string): string;
4
+ export declare function injectWsConfig(html: string, opts: {
5
+ publicWsUrl?: string | undefined;
6
+ }): string;
7
+ export declare function firstHeader(value: string | string[] | undefined): string | undefined;
8
+ export declare const WS_TOKEN_COOKIE = "ws_token";
9
+ export declare const WS_TOKEN_COOKIE_SECURE = "__Host-ws_token";
10
+ export declare function wsTokenCookie(token: string, secure: boolean): string;
11
+ export declare function setAuthCookieHeaders(res: http.ServerResponse, token: string, secure: boolean): void;
12
+ export declare function setStaticSecurityHeaders(res: http.ServerResponse): void;
13
+ export declare function requestToken(req: http.IncomingMessage, url: URL, opts?: {
14
+ allowQuery?: boolean;
15
+ }): string | undefined;
16
+ export declare function isLoopbackPeer(req: http.IncomingMessage): boolean;
17
+ export declare function formatCspHostname(hostname: string): string;
18
+ export declare function cspSourceFromUrl(rawUrl: string): string | undefined;
19
+ export declare const EXTRA_SCRIPT_SOURCES: readonly string[];
20
+ export declare function buildCspHeader(publicWsUrl?: string | undefined, host?: string, port?: number): string;
21
+ export declare function isInsideDist(candidate: string, distDir: string): boolean;
22
+ export declare function decodeSessionId(segment: string): string;
23
+ export declare function strictDecodeParam(segment: string, res: http.ServerResponse): string | null;
24
+ //# sourceMappingURL=security-helpers.d.ts.map
@@ -0,0 +1,10 @@
1
+ import type * as http from 'node:http';
2
+ export declare function handleStaticFileRequest(_req: http.IncomingMessage, res: http.ServerResponse, distDir: string, url: URL, opts: {
3
+ publicWsUrl?: string | undefined;
4
+ host: string;
5
+ }, port: number, shouldSetAuthCookie: boolean): Promise<void>;
6
+ export declare function handleSpaFallback(res: http.ServerResponse, distDir: string, opts: {
7
+ publicWsUrl?: string | undefined;
8
+ host: string;
9
+ }, port: number): Promise<void>;
10
+ //# sourceMappingURL=static-file-handler.d.ts.map
@@ -1,6 +1,33 @@
1
+ /**
2
+ * Static-file HTTP server for the WebUI / SimpleUI React frontends.
3
+ *
4
+ * Design:
5
+ * - **Single port — shared HTTP + WebSocket.** A single port serves both the
6
+ * static frontend and the WebSocket protocol. See the port-utils module.
7
+ * - **CSP**: `connect-src 'self'` covers the core same-origin case. For loopback
8
+ * binds (127.0.0.1 / localhost) we also add explicit `ws://`/`wss://` entries
9
+ * so browsers that strictly separate `ws:` from `http:` in CSP `connect-src`
10
+ * matching still allow WS upgrades. These loopback entries are safe because
11
+ * they only open connections to the local machine. Tunnel/proxy setups use
12
+ * `publicWsUrl` for the single external origin.
13
+ * - **Path-traversal guard**: `path.join` alone does NOT prevent
14
+ * `%2e%2e%2f` escapes (the `URL` constructor decodes percent-encoding
15
+ * before we see the path). We re-`resolve` the candidate and verify it
16
+ * stays under `distDir`.
17
+ * - **Access auth**: on non-loopback binds, all HTTP routes require the same
18
+ * shared token as the WS upgrade, accepted via `?token=...`, `X-WS-Token`,
19
+ * or the `ws_token` HttpOnly cookie. This protects the React UI and the
20
+ * `/api/*` control/read endpoints when `WS_HOST=0.0.0.0`.
21
+ *
22
+ * Extracted from `index.ts` so the static-serve concern can be tested
23
+ * with a tiny fake `distDir` and asserted on path-traversal, MIME
24
+ * matching, and CSP header presence.
25
+ */
1
26
  import * as http from 'node:http';
2
27
  import type { FileWatcherMetrics } from './setup-events.js';
3
- import { type TechStackEvent } from './techstack-handlers.js';
28
+ import type { TechStackEvent } from './techstack-handlers.js';
29
+ import { buildCspHeader, decodeSessionId, injectWsConfig, isInsideDist, requestToken, WS_TOKEN_COOKIE, WS_TOKEN_COOKIE_SECURE } from './http-server/security-helpers.js';
30
+ export { buildCspHeader, decodeSessionId, injectWsConfig, isInsideDist, requestToken, WS_TOKEN_COOKIE, WS_TOKEN_COOKIE_SECURE, };
4
31
  export interface CreateHttpServerOptions {
5
32
  /** Port to listen on. Defaults to 3456 (or the `PORT` env var). */
6
33
  port?: number | undefined;
@@ -95,92 +122,6 @@ export interface CreateHttpServerOptions {
95
122
  */
96
123
  intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
97
124
  }
98
- export declare function injectWsConfig(html: string, opts: {
99
- publicWsUrl?: string | undefined;
100
- }): string;
101
- /**
102
- * WS-107: the auth cookie never carried `Secure`, on the stated grounds that
103
- * "the dev server is plain HTTP on loopback". True for the default bind — and
104
- * wrong for the deployment this server explicitly supports, where a tunnel
105
- * terminates TLS in front of it (`publicWsUrl: wss://…`). There the browser is
106
- * on HTTPS, so a cookie set without `Secure` is one the browser will also
107
- * attach to a plaintext request to the same host: a downgrade or a stray
108
- * `http://` link leaks the token that authenticates the whole control plane.
109
- *
110
- * So the flag follows the deployment. On an HTTPS-fronted server the cookie is
111
- * additionally `__Host-`-prefixed, which browsers only accept when it is
112
- * Secure, Path=/, and Domain-less — the same hardening the HQ server adopted.
113
- * On loopback HTTP neither is usable (a `Secure` cookie would simply not be
114
- * sent), so the plain name is kept there.
115
- */
116
- export declare const WS_TOKEN_COOKIE = "ws_token";
117
- export declare const WS_TOKEN_COOKIE_SECURE = "__Host-ws_token";
118
- /**
119
- * Resolve the access token presented on an HTTP request.
120
- *
121
- * WS-108: the `?token=` query source was accepted on EVERY route regardless of
122
- * bind. On a tunnel that is the C-598 exposure the WS path already closed —
123
- * the token reaches reverse-proxy access logs, browser history, and any
124
- * `Referer` the page emits to a third-party origin. The header and cookie
125
- * sources have none of those channels.
126
- *
127
- * `allowQuery` is therefore opt-in and reserved for the two places the token
128
- * legitimately rides the URL: `/ws-auth`, whose entire job is to exchange it
129
- * for the cookie, and the HTML page load the operator opens from the printed
130
- * access URL. Everywhere else it is refused off-loopback — and when refused,
131
- * resolution falls through to the header and cookie rather than returning
132
- * early, so a client that sent both still authenticates.
133
- */
134
- export declare function requestToken(req: http.IncomingMessage, url: URL, opts?: {
135
- allowQuery?: boolean;
136
- }): string | undefined;
137
- /**
138
- * Build the Content-Security-Policy value for the WebUI.
139
- *
140
- * Adds explicit `ws://`/`wss://` entries for loopback addresses (`127.0.0.1`,
141
- * `localhost`) so `connect-src` covers WebSocket even in strict CSP
142
- * implementations that distinguish `ws:` from `http:` origins.
143
- *
144
- * @param publicWsUrl - Optional public-facing WS URL (tunnel/reverse-proxy).
145
- * When set, this origin is added to `connect-src` as a `ws://`/`wss://` entry.
146
- * @param host - The server bind host. When it matches a known loopback address
147
- * (`127.0.0.1`, `::1`, `[::1]`, or `localhost`), explicit `ws://`/`wss://`
148
- * entries for `127.0.0.1` and `localhost` are added to `connect-src`.
149
- * IPv6 loopback (`[::1]`) is excluded — it produces an invalid CSP source
150
- * and is covered by `'self'` (CSP maps ws:→http:, wss:→https:).
151
- * @param port - The server listen port. Defaults to `3456`. Unnecessary when
152
- * only publicWsUrl is used (no loopback branch).
153
- */
154
- export declare function buildCspHeader(publicWsUrl?: string | undefined, host?: string, port?: number): string;
155
- /**
156
- * Returns true when `candidate` (a fully-resolved absolute path) lies
157
- * strictly inside `distDir` (or equals it). Used to reject path-traversal
158
- * attempts after `path.resolve` has normalised any `..` segments.
159
- *
160
- * Exported so tests can assert the guard's contract without having to
161
- * also defeat the WHATWG URL normaliser (which strips `..` from the
162
- * path string *before* the request even reaches the server, making a
163
- * black-box test via fetch impossible).
164
- */
165
- export declare function isInsideDist(candidate: string, distDir: string): boolean;
166
- /**
167
- * Decode a `:id` path segment captured by the `/api/sessions/:id/*` routes.
168
- *
169
- * Session ids are `YYYY-MM-DD/sess_<ULID>` — they contain a literal
170
- * `/`. The frontend builds the URL with `encodeURIComponent(sessionId)`, so
171
- * that slash arrives as `%2F`. The route regex `([^/]+)` correctly captures
172
- * the whole percent-encoded segment (there is no real `/` in `%2F`), but the
173
- * SessionRegistry is keyed by the *decoded* id — so the capture must be
174
- * `decodeURIComponent`d before lookup. Without this, every
175
- * `/api/sessions/:id/{events,message,agents}` request 404s (the registry has
176
- * `2026-…/…` but we looked up `2026-…%2F…`), which broke the Fleet HQ
177
- * watch-stream and the steer-message composer.
178
- *
179
- * Malformed percent-encoding (a lone `%`) makes `decodeURIComponent` throw;
180
- * fall back to the raw segment so the caller still gets a clean 404 rather
181
- * than a 500.
182
- */
183
- export declare function decodeSessionId(segment: string): string;
184
125
  /**
185
126
  * Create the static-file HTTP server. Returns the `http.Server` (not
186
127
  * listening yet) so the caller can attach to a `shutdown()` hook and
@@ -30,7 +30,7 @@ export { collectConnectionsHealth, type ConnectionHealthService, type Connection
30
30
  export { type CustomContextMode, type CustomModeStore, createCustomModeStore, } from './custom-context-modes.js';
31
31
  export { type DesignContext, handleDesignList, handleDesignMaterialize, handleDesignSet, handleDesignState, handleDesignSwap, handleDesignTune, handleDesignUse, handleDesignVerify, } from './design-handlers.js';
32
32
  export { createEternalSubscription, type EternalBroadcast, type EternalSubscribe, type EternalSubscription, } from './eternal-iteration-broadcast.js';
33
- export { handleFilesList, handleFilesRead, handleFilesTree, handleFilesWrite, } from './file-handlers.js';
33
+ export { handleFilesCreate, handleFilesDelete, handleFilesList, handleFilesMove, handleFilesRead, handleFilesRename, handleFilesSkeleton, handleFilesTree, handleFilesWrite, } from './file-handlers.js';
34
34
  export { isHiddenEntry, rankFiles, SKIP_DIRS } from './file-picker.js';
35
35
  export { handleGitChanges, handleGitDiff, handleGitInfo } from './git-handlers.js';
36
36
  export { handleGoalGet } from './goal-handlers.js';
@@ -90,7 +90,7 @@ export type { ProjectRouteHandlers } from './project-routes.js';
90
90
  export { handleProjectRoute } from './project-routes.js';
91
91
  export { createProjectHandlers, type ProjectHandlersContext } from './project-handlers.js';
92
92
  export { ensureProjectDataDir, loadManifest, projectsJsonPath, saveManifest, touchProjectInManifest, } from './projects-manifest.js';
93
- export { handlePromptsContent, handlePromptsCreate, handlePromptsFavorite, handlePromptsList, handlePromptsRecent, handlePromptsSearch, handlePromptsUsed, type PromptsContext, } from './prompts-handlers.js';
93
+ export { handlePromptsContent, handlePromptsCreate, handlePromptsFavorite, handlePromptsJournal, handlePromptsList, handlePromptsRecent, handlePromptsSearch, handlePromptsUsed, type PromptsContext, } from './prompts-handlers.js';
94
94
  export { loadSavedProviders, saveProviders } from './provider-config-io.js';
95
95
  export { createProviderConfigIO } from './provider-config-standalone.js';
96
96
  export { createProviderHandlers, createProviderOperations, type ProviderOperationsDeps, type ProviderPersistence, probeModelDescriptors, projectSavedProviders, type SavedProviderView, } from './provider-handlers.js';
@@ -115,6 +115,7 @@ export { handleShellOpen, type ShellOpenOptions, type ShellOpenRequest, type She
115
115
  export { handleSkillsContent, handleSkillsCreate, handleSkillsEdit, handleSkillsExport, handleSkillsInstall, handleSkillsList, handleSkillsUninstall, handleSkillsUpdate, type SkillsContext, } from './skills-handlers.js';
116
116
  export { handleSpecsRoute, type SpecsRouteHandlers } from './specs-routes.js';
117
117
  export { SpecsWebSocketHandler } from './specs-ws-handler.js';
118
+ export { startProjectWatcher } from './project-watcher.js';
118
119
  export { startWebUI } from './start-webui.js';
119
120
  export { TerminalWebSocketHandler } from './terminal-ws-handler.js';
120
121
  export { type ContextBreakdown, estimateContextBreakdown, estimateTokens, type MessageTokenEntry, messagePreview, messageTokens, stringifyContent, type ToolTokenEntry, } from './token-estimator.js';
@@ -37,13 +37,12 @@ export declare function getExternalAddresses(getInterfaces?: () => NodeJS.Dict<o
37
37
  * Format the extra access-URL lines for a wildcard bind.
38
38
  *
39
39
  * Returns a list of indented, ready-to-print lines showing the external
40
- * IPs and their full access URLs (including token). Returns an empty
41
- * array for non-wildcard binds or when no external addresses are found.
40
+ * IPs and their access URLs. Authentication tokens are deliberately excluded
41
+ * because these lines are written to terminal and session logs.
42
42
  */
43
43
  export declare function formatExternalAccessUrls(opts: {
44
44
  bindHost: string;
45
45
  port: number;
46
- token?: string | undefined;
47
46
  publicUrl?: string | undefined;
48
47
  getInterfaces?: () => NodeJS.Dict<os.NetworkInterfaceInfo[]>;
49
48
  }): string[];
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Project source-tree watcher — broadcasts `files.tree.changed` when
3
+ * files are created, modified, or deleted inside the project root, so
4
+ * the WebUI file explorer refreshes its tree without manual navigation.
5
+ *
6
+ * Uses Node's native `fs.watch` with `recursive: true` (supported on
7
+ * Windows and macOS; Linux support landed in Node 22). Events are
8
+ * debounced (400ms) to coalesce bursts from build tools, git
9
+ * checkouts, and agent writes. Heavyweight directories
10
+ * (`node_modules`, `.git`, `dist`, …) are ignored to avoid event
11
+ * storms — the same `SKIP_DIRS` set the tree builder uses.
12
+ *
13
+ * The watcher is `persistent: false` and `unref`'d on each handle so
14
+ * it does not keep the process alive. The returned disposer closes
15
+ * all native handles.
16
+ */
17
+ import type { ConnectedClient, WSServerMessage } from './types.js';
18
+ export interface ProjectWatcherDeps {
19
+ /** Absolute project root to watch. */
20
+ projectRoot: string;
21
+ /** Broadcast a message to all connected WS clients. */
22
+ broadcast: (clients: Map<import('ws').WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
23
+ /** Live client map (same reference the rest of setupEvents uses). */
24
+ clients: Map<import('ws').WebSocket, ConnectedClient>;
25
+ }
26
+ /**
27
+ * Start watching `projectRoot` for filesystem changes. Returns a
28
+ * disposer that closes all native watch handles.
29
+ */
30
+ export declare function startProjectWatcher(deps: ProjectWatcherDeps): () => void;
31
+ //# sourceMappingURL=project-watcher.d.ts.map
@@ -33,6 +33,8 @@ export declare function handlePromptsCreate(ws: WSLike, ctx: PromptsContext, msg
33
33
  export declare function handlePromptsUsed(ws: WSLike, ctx: PromptsContext, msg: unknown): Promise<void>;
34
34
  /** Return recently-inserted prompt slugs (most-recent first) for the modal's Recent view. */
35
35
  export declare function handlePromptsRecent(ws: WSLike, ctx: PromptsContext): Promise<void>;
36
+ /** Read the project's hierarchical prompt journal (`<projectRoot>/.wrongstack/prompts/`). */
37
+ export declare function handlePromptsJournal(ws: WSLike, msg: unknown, projectRoot: string): Promise<void>;
36
38
  /** Minimal structural type for the ws.send sink (matches `ws` WebSocket). */
37
39
  type WSLike = Parameters<typeof send>[0];
38
40
  export {};
@@ -61,6 +61,15 @@ export declare class SddBoardWebSocketHandler {
61
61
  private applyLifecycle;
62
62
  dispose(): void;
63
63
  private pollLatest;
64
+ /**
65
+ * Cheap freshness probe used before the full snapshot load. In kanban mode
66
+ * the workflow-state listing IS the snapshot payload (no cheaper probe
67
+ * exists), so fall through to the load — the post-load `updatedAt` guard
68
+ * still dedups the broadcast. In legacy-file mode the store index carries
69
+ * per-run `updatedAt`, letting an unchanged board skip the snapshot file
70
+ * entirely. Probe failures fall through to the load path.
71
+ */
72
+ private hasNewerSnapshot;
64
73
  private startPolling;
65
74
  private stopPolling;
66
75
  private sendCurrent;
@@ -98,6 +98,7 @@ export declare class SddWizardWebSocketHandler {
98
98
  private onDiscard;
99
99
  private onMessage;
100
100
  private onApprove;
101
+ private onRewind;
101
102
  private onRunStart;
102
103
  private onRunFromGraph;
103
104
  private onRunFromSpec;
@@ -0,0 +1,10 @@
1
+ import type { EventBus } from '@wrongstack/core/kernel';
2
+ import type { WebSocket } from 'ws';
3
+ import type { ConnectedClient, WSServerMessage } from './types.js';
4
+ export declare function registerSetupEventsPatternHandlers(options: {
5
+ events: EventBus;
6
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
7
+ clients: Map<WebSocket, ConnectedClient>;
8
+ sessionPayload: <T extends Record<string, unknown>>(payload: T) => T;
9
+ }): Array<() => void>;
10
+ //# sourceMappingURL=setup-events-pattern-handlers.d.ts.map
@@ -8,7 +8,7 @@ export interface SetupEventsStatusWatcherDeps {
8
8
  watcherMetrics?: FileWatcherMetrics | undefined;
9
9
  clients: Map<WebSocket, ConnectedClient>;
10
10
  broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
11
- on: <E extends EventName>(event: E, listener: Listener<E>) => void;
11
+ on: <E extends EventName>(event: E, listener: Listener<E>) => (() => void) | void;
12
12
  isDisposed: () => boolean;
13
13
  }
14
14
  export declare function registerSetupEventsStatusWatcher(deps: SetupEventsStatusWatcherDeps): (() => void) | undefined;
@@ -0,0 +1,14 @@
1
+ import type { Context } from '@wrongstack/core/agent';
2
+ import type { EventName, Listener } from '@wrongstack/core/kernel';
3
+ import type { WebSocket } from 'ws';
4
+ import type { SetupEventProjection } from './setup-event-projection.js';
5
+ import type { ConnectedClient, WSServerMessage } from './types.js';
6
+ export declare function registerSetupEventsSubagentHandlers(options: {
7
+ on: <E extends EventName>(event: E, listener: Listener<E>) => void;
8
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
9
+ clients: Map<WebSocket, ConnectedClient>;
10
+ context: Context;
11
+ projection?: SetupEventProjection | undefined;
12
+ sessionPayload: <T extends Record<string, unknown>>(payload: T) => T;
13
+ }): void;
14
+ //# sourceMappingURL=setup-events-subagent-handlers.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { Context } from '@wrongstack/core/agent';
2
+ import type { EventName, Listener } from '@wrongstack/core/kernel';
3
+ import type { SessionEventBridge } from '@wrongstack/core/storage';
4
+ import type { WebSocket } from 'ws';
5
+ import type { PendingConfirm } from './pending-confirms.js';
6
+ import type { SetupEventProjection } from './setup-event-projection.js';
7
+ import type { ConnectedClient, WSServerMessage } from './types.js';
8
+ export declare function registerSetupEventsToolHandlers(options: {
9
+ on: <E extends EventName>(event: E, listener: Listener<E>) => void;
10
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
11
+ clients: Map<WebSocket, ConnectedClient>;
12
+ context: Context;
13
+ pendingConfirms: Map<string, PendingConfirm>;
14
+ projection?: SetupEventProjection | undefined;
15
+ sessionPayload: <T extends Record<string, unknown>>(payload: T) => T;
16
+ appendForCurrentSession: (sessionId: string | undefined, event: Parameters<SessionEventBridge['append']>[0]) => void;
17
+ }): void;
18
+ //# sourceMappingURL=setup-events-tool-handlers.d.ts.map
@@ -50,8 +50,7 @@ export { statusProjectHashFromWatchFilename } from './setup-events-watcher.js';
50
50
  * intervals, and flushes pending debounce timers. Callers MUST invoke it on
51
51
  * shutdown — the watcher is `persistent: true` and the metrics interval is not
52
52
  * `unref`'d, so without disposal they keep the process alive and leak across
53
- * server restarts. (Previously this was hung off a non-existent
54
- * `process.on('cleanup')` event that never fired.)
53
+ * server restarts.
55
54
  */
56
55
  export declare function setupEvents(deps: SetupEventsDeps): () => void;
57
56
  //# sourceMappingURL=setup-events.d.ts.map
@@ -17,20 +17,21 @@ export interface ShellOpenRequest {
17
17
  */
18
18
  export declare function normalizeShellOpenTarget(target: string | undefined): ShellOpenTarget;
19
19
  /**
20
- * Optional security options for `handleShellOpen`.
20
+ * Security options for `handleShellOpen`.
21
21
  *
22
- * `projectRoot`, when provided, confines the resolved path to the project
23
- * directory the OS file manager / terminal will never be launched at a
24
- * location outside the project. This is defense-in-depth: the TrustBoundary
25
- * authorization may use a permissive compatibility policy, so the path
26
- * containment must be enforced at the executor as well.
22
+ * `projectRoot` is **required** — it confines the resolved path to the
23
+ * project directory. Without it a WebSocket client could open the OS
24
+ * file manager or terminal at ANY existing path on the system
25
+ * (/etc, ~/.ssh, C:\Windows). The TrustBoundary authorization may use
26
+ * a permissive compatibility policy, so path containment is enforced
27
+ * here as defense-in-depth.
27
28
  */
28
29
  export interface ShellOpenOptions {
29
- projectRoot?: string;
30
+ projectRoot: string;
30
31
  }
31
32
  export interface ShellOpenResult {
32
33
  success: boolean;
33
34
  message: string;
34
35
  }
35
- export declare function handleShellOpen(req: ShellOpenRequest, logger: Logger, options?: ShellOpenOptions): Promise<ShellOpenResult>;
36
+ export declare function handleShellOpen(req: ShellOpenRequest, logger: Logger, options: ShellOpenOptions): Promise<ShellOpenResult>;
36
37
  //# sourceMappingURL=shell-open.d.ts.map
@@ -0,0 +1,16 @@
1
+ import * as http from 'node:http';
2
+ /**
3
+ * Dual-stack / IPv6 companion listener.
4
+ *
5
+ * When the primary bind is IPv4-only, also try the IPv6 equivalent so
6
+ * peers using IPv6 can connect. Tailscale assigns both v4 (100.x.x.x)
7
+ * and v6 (fd7a:…) addresses; Chrome/Edge on Windows resolve `localhost`
8
+ * to [::1] before 127.0.0.1. Without the companion listener, a v4-only
9
+ * bind causes ECONNREFUSED for all IPv6 peers.
10
+ *
11
+ * When the primary bind is IPv6-only (::), try the IPv4 companion as a
12
+ * fallback for systems where `::` does not accept IPv4-mapped
13
+ * connections (net.ipv6.bindv6only=1, some Windows configs).
14
+ */
15
+ export declare function setupCompanionServer(httpServer: http.Server, wsHost: string | undefined, httpPort: number): http.Server | null;
16
+ //# sourceMappingURL=start-webui-companion.d.ts.map
@@ -0,0 +1,13 @@
1
+ import type { WebSocket } from 'ws';
2
+ import type { WebuiDeps, WebuiMutableState } from './routes.js';
3
+ import type { ConnectedClient } from './types.js';
4
+ export declare function setupWebuiCredentialWatcher(options: {
5
+ watchConfigPath: string;
6
+ vault: WebuiDeps['vault'];
7
+ logger: WebuiDeps['logger'];
8
+ state: WebuiMutableState;
9
+ deps: WebuiDeps;
10
+ clients: Map<WebSocket, ConnectedClient>;
11
+ updateAutoCompactionMaxContext: (provider: any) => Promise<void>;
12
+ }): (() => void) | undefined;
13
+ //# sourceMappingURL=start-webui-credential-watcher.d.ts.map
@@ -0,0 +1,14 @@
1
+ import type { Context } from '@wrongstack/core/agent';
2
+ import type { ToolExecutor } from '@wrongstack/core/execution';
3
+ import type { EventBus } from '@wrongstack/core/kernel';
4
+ import type { PermissionPolicy } from '@wrongstack/core/types';
5
+ import { type PackageOperation } from '@wrongstack/techstack';
6
+ export declare function createPackageOperationExecutor(options: {
7
+ toolExecutor: ToolExecutor;
8
+ context: Context;
9
+ events: EventBus;
10
+ permissionPolicy: PermissionPolicy;
11
+ }): (operation: PackageOperation, workspace?: string) => Promise<{
12
+ detail: string;
13
+ }>;
14
+ //# sourceMappingURL=start-webui-remediation.d.ts.map
@@ -0,0 +1,43 @@
1
+ import type * as http from 'node:http';
2
+ import type { WebSocket, WebSocketServer } from 'ws';
3
+ import type { ConnectedClient } from './types.js';
4
+ export declare function setupWebuiShutdown(options: {
5
+ session: any;
6
+ tokenCounter: any;
7
+ clients: Map<WebSocket, ConnectedClient>;
8
+ httpServer: http.Server;
9
+ companionServer: http.Server | null;
10
+ wssPrimary: WebSocketServer;
11
+ wssSecondary?: WebSocketServer | null | undefined;
12
+ stopEmptySessionCleanup: {
13
+ dispose: () => Promise<void>;
14
+ };
15
+ getKanbanSupervisorDispose: () => (() => void) | null;
16
+ todosCheckpoint: {
17
+ detach: () => Promise<void>;
18
+ };
19
+ stopHeapWatchdog: () => Promise<void>;
20
+ getCredentialWatcherClose: () => (() => void) | undefined;
21
+ disposeRealtimeHandlers: () => void;
22
+ governanceHandle?: {
23
+ close: () => Promise<{
24
+ ok: boolean;
25
+ action?: string;
26
+ message?: string;
27
+ } | undefined>;
28
+ } | undefined;
29
+ logger: any;
30
+ brainMonitor: any;
31
+ agentServices: any;
32
+ mcpRegistry: any;
33
+ sessionIdentity: any;
34
+ eventArming: any;
35
+ getEternalSubscription: () => {
36
+ dispose: () => void;
37
+ } | null;
38
+ clearEternalSubscription: () => void;
39
+ codebaseIndexing: any;
40
+ memoryStore: any;
41
+ globalConfigPath: string;
42
+ }): () => void;
43
+ //# sourceMappingURL=start-webui-shutdown.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { attachTodosCheckpoint } from '@wrongstack/core/storage';
2
+ export declare function createStandaloneTodosCheckpointLifecycle(input: {
3
+ state: Parameters<typeof attachTodosCheckpoint>[0];
4
+ sessionsDir: string;
5
+ sessionId: string;
6
+ events?: Parameters<typeof attachTodosCheckpoint>[3];
7
+ traceId?: string | undefined;
8
+ warn?: ((message: string) => void) | undefined;
9
+ }): {
10
+ rebind: (sessionId: string, sessionsDir: string) => Promise<void>;
11
+ detach: () => Promise<void>;
12
+ };
13
+ //# sourceMappingURL=start-webui-todos.d.ts.map
@@ -7,19 +7,9 @@
7
7
  * service construction (Phase 1c), route/dispatcher/connection wiring
8
8
  * (Phase 1b/1a), WS + HTTP server creation, and graceful shutdown.
9
9
  */
10
- import { attachTodosCheckpoint } from '@wrongstack/core/storage';
10
+ import { createStandaloneTodosCheckpointLifecycle } from './start-webui-todos.js';
11
11
  import type { WebUIOptions } from './types.js';
12
- export declare function createStandaloneTodosCheckpointLifecycle(input: {
13
- state: Parameters<typeof attachTodosCheckpoint>[0];
14
- sessionsDir: string;
15
- sessionId: string;
16
- events?: Parameters<typeof attachTodosCheckpoint>[3];
17
- traceId?: string | undefined;
18
- warn?: ((message: string) => void) | undefined;
19
- }): {
20
- rebind: (sessionId: string, sessionsDir: string) => Promise<void>;
21
- detach: () => Promise<void>;
22
- };
12
+ export { createStandaloneTodosCheckpointLifecycle };
23
13
  export declare function startWebUI(opts?: WebUIOptions & {
24
14
  wsHost?: string | undefined;
25
15
  httpPort?: number | undefined;
@@ -29,21 +19,4 @@ export declare function startWebUI(opts?: WebUIOptions & {
29
19
  requireToken?: boolean | undefined;
30
20
  open?: boolean | undefined;
31
21
  }): Promise<void>;
32
- /**
33
- * Webui-side mailbox bridge discovery.
34
- *
35
- * The webui doesn't spawn a bridge — the bridge (`wstack mailbox serve`)
36
- * is spawned by any CLI surface via the auto-bootstrap wiring. We just
37
- * probe the per-project lock for an already-running instance and stash
38
- * the discovered handle on `ctx.meta['mailboxBridge']` so any later
39
- * code (the `/mailbox` HTTP surface, agent-status broadcasters,
40
- * external-agent proxy) can find it without re-running discovery.
41
- *
42
- * If no bridge is running, we log a breadcrumb so the user knows
43
- * to start one (`wstack --repl`, `wstack --webui`, or
44
- * `wstack mailbox serve` standalone).
45
- *
46
- * Best-effort: never throws. A failure (missing lock dir, ENOENT,
47
- * etc.) logs at warn level and returns — the webui keeps running.
48
- */
49
22
  //# sourceMappingURL=start-webui.d.ts.map