@wrongstack/webui-server 0.298.3 → 0.300.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.
@@ -0,0 +1,19 @@
1
+ import type { EventBus } from '@wrongstack/core/kernel';
2
+ import type { WSClientMessage } from './types.js';
3
+ /**
4
+ * Bridge a `model.fallback_choice` client message to the
5
+ * `provider.fallback_choice` EventBus emission consumed by the fallback gate
6
+ * (`packages/cli/src/wiring/fallback-gate.ts`). Shared by the standalone route
7
+ * table (`routes.ts`) and the embedded message router so wire validation and
8
+ * the event contract live in exactly one place.
9
+ *
10
+ * Returns `true` when the payload was valid and forwarded, `false` when the
11
+ * payload failed validation (the caller may send an error frame).
12
+ */
13
+ export declare function emitFallbackChoice(events: EventBus | undefined, msg: WSClientMessage): {
14
+ ok: true;
15
+ } | {
16
+ ok: false;
17
+ message: string;
18
+ };
19
+ //# sourceMappingURL=fallback-choice.d.ts.map
@@ -75,6 +75,15 @@ export interface StaticServeOptions {
75
75
  deferListen?: boolean | undefined;
76
76
  /** Package-resolution/build seams supplied by the owning host. */
77
77
  ensureDistDeps?: EnsureDistDeps | undefined;
78
+ /**
79
+ * Requirements Intake service backing `/api/requirement-intakes*`. Omitted by
80
+ * every real host — a per-project service is constructed from `projectRoot` +
81
+ * `globalRoot` below, so the CLI-hosted WebUI serves the same intake records
82
+ * as the standalone server. Pass one only to override (tests/embeds); when
83
+ * `projectRoot` is absent there is no project to scope a store to and the
84
+ * routes correctly answer 503.
85
+ */
86
+ intakeService?: CreateHttpServerOptions['intakeService'];
78
87
  }
79
88
  /**
80
89
  * Resolve the webui package's built `dist` directory.
@@ -42,6 +42,19 @@ export interface CreateHttpServerOptions {
42
42
  * URL-token-only flow (e.g. in tests that don't want cookie state).
43
43
  */
44
44
  enableWsCookie?: boolean | undefined;
45
+ /**
46
+ * Mark the `ws_token` cookie `Secure` and give it the `__Host-` prefix.
47
+ *
48
+ * Inferred from a `wss://` {@link CreateHttpServerOptions.publicWsUrl} when
49
+ * omitted, which covers the tunnel deployments this server documents. Set it
50
+ * explicitly when TLS is terminated in front of the server by something that
51
+ * does not surface through `publicWsUrl` (e.g. an nginx `proxy_pass` with a
52
+ * separately configured WS origin).
53
+ *
54
+ * Must stay false on a plain-HTTP loopback bind: browsers do not send a
55
+ * `Secure` cookie over HTTP, so enabling it there breaks the WS handshake.
56
+ */
57
+ secureCookies?: boolean | undefined;
45
58
  /**
46
59
  * Optional file watcher metrics object. When provided, the
47
60
  * /debug/watcher-metrics endpoint will be enabled to expose these metrics.
@@ -74,10 +87,53 @@ export interface CreateHttpServerOptions {
74
87
  } | undefined) | undefined;
75
88
  /** Permission-governed language_package bridge for approved remediation. */
76
89
  executePackageOperation?: import('./techstack-handlers.js').TechStackHandlerDeps['executePackageOperation'];
90
+ /**
91
+ * Optional Requirements Intake service. When provided, the
92
+ * /api/projects/:projectId/requirement-intakes and
93
+ * /api/requirement-intakes/:intakeId endpoints are enabled; otherwise they
94
+ * respond 503.
95
+ */
96
+ intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
77
97
  }
78
98
  export declare function injectWsConfig(html: string, opts: {
79
99
  publicWsUrl?: string | undefined;
80
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;
81
137
  /**
82
138
  * Build the Content-Security-Policy value for the WebUI.
83
139
  *
@@ -36,6 +36,27 @@ export interface WebUIInstanceRecord {
36
36
  startedAt: string;
37
37
  /** Convenience open-in-browser URL. */
38
38
  url: string;
39
+ /**
40
+ * Access token for this instance's HTTP API, so a same-project sibling
41
+ * process (TUI/REPL) can authenticate the `POST /api/fleet/ping`
42
+ * push-on-write nudge.
43
+ *
44
+ * Security scan 2026-08-04, finding H3: the API used to require no
45
+ * credential at all on a loopback bind. Closing that meant the one
46
+ * legitimate non-browser caller needed a way to obtain the token, and this
47
+ * registry — already `0o600`, already read by exactly those siblings — is
48
+ * where it belongs.
49
+ *
50
+ * Be precise about what this is worth. Storing the token here raises the bar
51
+ * against a *different-user* or sandboxed local process, which cannot read
52
+ * the file. It does nothing against a process running as the same user: that
53
+ * process can read this file, and could read `~/.wrongstack/projects/*` —
54
+ * the very transcripts the API exposes — without going near the API at all.
55
+ * The same reasoning already applies to HQ's `auth.json`.
56
+ *
57
+ * Optional so an older record (or a surface that has no token) still parses.
58
+ */
59
+ authToken?: string | undefined;
39
60
  }
40
61
  /** Default wstack home dir (`~/.wrongstack`). Callers may override the base. */
41
62
  export declare function defaultBaseDir(): string;
@@ -0,0 +1,13 @@
1
+ import { RequirementIntakeService } from '@wrongstack/requirement-intake';
2
+ /**
3
+ * Build the intake service backed by this project's
4
+ * `~/.wrongstack/projects/<slug>/requirement-intakes` directory.
5
+ *
6
+ * The HTTP token gate is the authorization boundary, so the service itself
7
+ * runs with a permissive authorizer inside it (see requirement-intake-handlers).
8
+ */
9
+ export declare function createProjectIntakeService(opts: {
10
+ projectRoot: string;
11
+ globalRoot: string;
12
+ }): RequirementIntakeService;
13
+ //# sourceMappingURL=intake-service.d.ts.map
@@ -9,6 +9,7 @@
9
9
  * there; here we only map structured results to WS events the browser expects.
10
10
  */
11
11
  import { type MCPRegistry, type MCPServerOperationalHealth, type McpServerInfo } from '@wrongstack/mcp';
12
+ import type { TrustBoundary } from '@wrongstack/core/security';
12
13
  import type { WebSocket } from 'ws';
13
14
  import type { WSClientMessage } from './types.js';
14
15
  /** Wire view of a server as the browser MCP panel consumes it. */
@@ -33,9 +34,9 @@ export declare function toView(info: McpServerInfo, health?: MCPServerOperationa
33
34
  /** mcp.list — configured servers merged with live registry status + tools. */
34
35
  export declare function handleMcpList(ws: WebSocket, _msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
35
36
  /** mcp.add — persist a new server (incl. url/headers) and start it if enabled. */
36
- export declare function handleMcpAdd(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
37
+ export declare function handleMcpAdd(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry, trustBoundary?: TrustBoundary): Promise<void>;
37
38
  /** mcp.update — re-persist config (incl. url/headers) and re-apply to registry. */
38
- export declare function handleMcpUpdate(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
39
+ export declare function handleMcpUpdate(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry, trustBoundary?: TrustBoundary): Promise<void>;
39
40
  /** mcp.remove — stop the server and delete it from config. */
40
41
  export declare function handleMcpRemove(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
41
42
  /** mcp.enable — flip enabled:true in config and start the server. */
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import type { SecretVault } from '@wrongstack/core/types';
19
19
  /** Pref keys exposed to the settings panel via prefs.get / prefs.updated. */
20
- export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqToken', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'pluginsEnabled', 'fleetChatVerbosity'];
20
+ export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqToken', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'autoCollapseInput', 'pluginsEnabled', 'fleetChatVerbosity'];
21
21
  export interface PrefHelperDeps {
22
22
  /** Path to the active profile config; the sole settings mutation target. */
23
23
  profileConfigPath: string;
@@ -95,7 +95,7 @@ export declare function createProviderOperations(deps: ProviderOperationsDeps):
95
95
  apiKey?: string | undefined;
96
96
  models?: string[] | undefined;
97
97
  customModels?: ProviderConfig['customModels'] | undefined;
98
- }) => Promise<void>;
98
+ }) => Promise<boolean>;
99
99
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
100
100
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
101
101
  handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
@@ -137,7 +137,7 @@ export declare function createProviderHandlers(deps: ProviderHandlerDeps): {
137
137
  apiKey?: string | undefined;
138
138
  models?: string[] | undefined;
139
139
  customModels?: ProviderConfig['customModels'] | undefined;
140
- }) => Promise<void>;
140
+ }) => Promise<boolean>;
141
141
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
142
142
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
143
143
  handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
@@ -14,7 +14,7 @@ export interface ProviderMutationHandlers {
14
14
  apiKey?: string | undefined;
15
15
  models?: string[] | undefined;
16
16
  customModels?: ProviderConfig['customModels'] | undefined;
17
- }) => Promise<void>;
17
+ }) => Promise<boolean>;
18
18
  handleProviderRemove: (ws: WebSocket, providerId: string) => Promise<void>;
19
19
  handleProviderClearModels: (ws: WebSocket, providerId: string) => Promise<void>;
20
20
  handleCustomModelSet: (ws: WebSocket, providerId: string, modelId: string, definition: NonNullable<ProviderConfig['customModels']>[string]) => Promise<void>;
@@ -40,6 +40,8 @@ export interface ProviderRouteHandlers {
40
40
  searchProviderModels: (ws: WebSocket, query: string, limit?: number | undefined) => Promise<void>;
41
41
  switchModel: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
42
42
  refineModel: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
43
+ /** Forward a model.fallback_choice client message to the EventBus. */
44
+ fallbackChoice: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
43
45
  /** Adopt a just-added provider as the live default when no model is active. */
44
46
  adoptDefaultProviderIfUnset: (providerId: string) => Promise<void>;
45
47
  providerHandlers: ProviderMutationHandlers;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * HTTP handlers for the Requirements Intake REST API.
3
+ *
4
+ * Routes (registered in http-server.ts):
5
+ * POST /api/projects/:projectId/requirement-intakes
6
+ * GET /api/projects/:projectId/requirement-intakes
7
+ * GET /api/requirement-intakes/:intakeId
8
+ * PATCH /api/requirement-intakes/:intakeId
9
+ * POST /api/requirement-intakes/:intakeId/answers
10
+ * POST /api/requirement-intakes/:intakeId/suggestions
11
+ * POST /api/requirement-intakes/:intakeId/submit
12
+ * POST /api/requirement-intakes/:intakeId/cancel
13
+ * POST /api/requirement-intakes/:intakeId/archive
14
+ *
15
+ * The HTTP token gate (see http-server.ts) is the authorization boundary;
16
+ * the service runs with a permissive authorizer inside that boundary. Every
17
+ * handler maps `IntakeError` codes to conventional HTTP statuses and never
18
+ * echoes request content into logs.
19
+ */
20
+ import type * as http from 'node:http';
21
+ import { type RequirementIntakeService } from '@wrongstack/requirement-intake';
22
+ /** Map a domain error to an HTTP status + JSON error body. */
23
+ export declare function sendIntakeError(res: http.ServerResponse, error: unknown): void;
24
+ /**
25
+ * List intake records for the server's own project. The webui-server serves
26
+ * one project; the canonical `proj_<ulid>` id lives in `.wrongstack/project.json`,
27
+ * which the frontend cannot read — so this route resolves it server-side and
28
+ * returns it alongside the records (the frontend uses it for the project-scoped
29
+ * create endpoint).
30
+ */
31
+ export declare function handleRequirementIntakeListForServer(res: http.ServerResponse, service: RequirementIntakeService | undefined, projectRoot: string | undefined): Promise<void>;
32
+ export declare function handleRequirementIntakeCreate(res: http.ServerResponse, req: http.IncomingMessage, service: RequirementIntakeService | undefined, projectId: string): Promise<void>;
33
+ export declare function handleRequirementIntakeList(res: http.ServerResponse, service: RequirementIntakeService | undefined, projectId: string): Promise<void>;
34
+ export declare function handleRequirementIntakeGet(res: http.ServerResponse, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
35
+ export declare function handleRequirementIntakeUpdate(res: http.ServerResponse, req: http.IncomingMessage, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
36
+ export declare function handleRequirementIntakeAnswers(res: http.ServerResponse, req: http.IncomingMessage, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
37
+ export declare function handleRequirementIntakeSuggestions(res: http.ServerResponse, req: http.IncomingMessage, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
38
+ export declare function handleRequirementIntakeSubmit(res: http.ServerResponse, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
39
+ export declare function handleRequirementIntakeCancel(res: http.ServerResponse, req: http.IncomingMessage, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
40
+ export declare function handleRequirementIntakeArchive(res: http.ServerResponse, service: RequirementIntakeService | undefined, intakeId: string): Promise<void>;
41
+ //# sourceMappingURL=requirement-intake-handlers.d.ts.map
@@ -102,6 +102,9 @@ export declare function startHttpServer(opts: {
102
102
  } | undefined) | undefined;
103
103
  executePackageOperation?: import('./techstack-handlers.js').TechStackHandlerDeps['executePackageOperation'];
104
104
  distDir?: string | undefined;
105
+ /** Optional pre-built intake service (tests/embeds). Defaults to a fresh
106
+ * per-project service backed by `projectRequirementIntakes`. */
107
+ intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
105
108
  }): import('node:http').Server;
106
109
  interface ShutdownDeps {
107
110
  flushSession: () => Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { SessionSummary } from '@wrongstack/core/types';
1
+ import type { SessionEvent, SessionSummary } from '@wrongstack/core/types';
2
2
  /**
3
3
  * Stable WebSocket projection for the WebUI history surfaces.
4
4
  *
@@ -27,6 +27,47 @@ export interface SessionHistoryWireEntry {
27
27
  outcome?: SessionSummary['outcome'];
28
28
  isCurrent: boolean;
29
29
  }
30
+ export interface SessionInspectEvent {
31
+ ts: string;
32
+ type: SessionEvent['type'];
33
+ label: string;
34
+ detail: string;
35
+ }
36
+ export interface SessionInspectFileEntry {
37
+ operation: string;
38
+ filePath: string;
39
+ toolName: string;
40
+ ts: string;
41
+ }
42
+ export interface SessionInspectPayload {
43
+ id: string;
44
+ title: string;
45
+ name?: string | undefined;
46
+ model: string;
47
+ provider: string;
48
+ startedAt: string;
49
+ endedAt?: string | undefined;
50
+ tokenTotal: number;
51
+ outcome?: SessionSummary['outcome'];
52
+ messageCount: number;
53
+ iterationCount: number;
54
+ toolCallCount: number;
55
+ toolErrorCount: number;
56
+ fileChangeCount: number;
57
+ compactionCount: number;
58
+ toolBreakdown: Record<string, number>;
59
+ events: SessionInspectEvent[];
60
+ fileEvents: SessionInspectFileEntry[];
61
+ lastUserMessage?: string | undefined;
62
+ }
63
+ export declare function buildInspectPayload(summary: SessionSummary | undefined, events: SessionEvent[], fallback: {
64
+ id: string;
65
+ title: string;
66
+ model: string;
67
+ provider: string;
68
+ startedAt: string;
69
+ endedAt?: string | undefined;
70
+ }): SessionInspectPayload;
30
71
  export declare function toSessionHistoryEntry(summary: SessionSummary, currentSessionId: string): SessionHistoryWireEntry;
31
72
  export declare function toSessionHistoryEntries(summaries: SessionSummary[], currentSessionId: string): SessionHistoryWireEntry[];
32
73
  //# sourceMappingURL=session-history.d.ts.map
@@ -19,6 +19,7 @@ export interface SessionRouteHandlers {
19
19
  deleteSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
20
20
  resumeSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
21
21
  saveSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
22
+ inspectSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
22
23
  listCheckpoints: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
23
24
  rewindSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
24
25
  }
@@ -88,6 +88,24 @@ export interface VerifyClientInput {
88
88
  allowedHostnames?: readonly string[] | undefined;
89
89
  /** Allow browser WS URL tokens for explicit public WS URLs where cookies cannot cross hostnames. */
90
90
  allowBrowserUrlToken?: boolean | undefined;
91
+ /**
92
+ * Let a valid `ws_token` cookie authenticate a loopback origin whose **port**
93
+ * differs from this server's (WS-003 opt-out). **Development only.**
94
+ *
95
+ * The Vite dev server and the WS server necessarily occupy different ports
96
+ * (`packages/webui/vite.config.ts` asks for 3456 and auto-advances when the
97
+ * WS server already holds it), so the dev loop cannot satisfy the port check.
98
+ *
99
+ * It must stay opt-in because the cookie proves nothing across ports: cookies
100
+ * are keyed by host, not port, and SameSite computes *site* without the port,
101
+ * so the browser attaches `ws_token` to a socket opened from **any** other
102
+ * localhost origin automatically. Enabling this in a shipped build re-opens
103
+ * exactly the hole WS-003 closed — a second dev server, a local app's UI, or
104
+ * an XSS in either can drive the agent.
105
+ *
106
+ * Wired from `WRONGSTACK_WEBUI_DEV_CROSS_PORT_WS=1`; never defaulted on.
107
+ */
108
+ allowCrossPortLoopbackCookie?: boolean | undefined;
91
109
  }
92
110
  /**
93
111
  * Decide whether to accept an incoming WebSocket handshake. Pure mirror of the
@@ -12,6 +12,13 @@ interface ModelSwitchPayload {
12
12
  requestId?: string | undefined;
13
13
  }
14
14
  export declare function validateModelSwitchPayload(payload: unknown): PayloadValidationResult<ModelSwitchPayload>;
15
+ export interface ModelFallbackChoicePayload {
16
+ requestId: string;
17
+ providerId?: string | undefined;
18
+ model?: string | undefined;
19
+ autoSwitch?: boolean | undefined;
20
+ }
21
+ export declare function validateModelFallbackChoicePayload(payload: unknown): PayloadValidationResult<ModelFallbackChoicePayload>;
15
22
  interface MailboxMessagesPayload {
16
23
  limit?: number;
17
24
  agentId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/webui-server",
3
- "version": "0.298.3",
3
+ "version": "0.300.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
6
6
  "keywords": [
@@ -40,15 +40,16 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "ws": "^8.21.1",
43
- "@wrongstack/core": "0.298.3",
44
- "@wrongstack/providers": "0.298.3",
45
- "@wrongstack/runtime": "0.298.3",
46
- "@wrongstack/kanban": "0.298.3",
47
- "@wrongstack/sdd": "0.298.3",
48
- "@wrongstack/sage": "0.298.3",
49
- "@wrongstack/techstack": "0.298.3",
50
- "@wrongstack/tools": "0.298.3",
51
- "@wrongstack/mcp": "0.298.3"
43
+ "@wrongstack/core": "0.300.0",
44
+ "@wrongstack/providers": "0.300.0",
45
+ "@wrongstack/kanban": "0.300.0",
46
+ "@wrongstack/requirement-intake": "0.300.0",
47
+ "@wrongstack/runtime": "0.300.0",
48
+ "@wrongstack/techstack": "0.300.0",
49
+ "@wrongstack/sage": "0.300.0",
50
+ "@wrongstack/mcp": "0.300.0",
51
+ "@wrongstack/tools": "0.300.0",
52
+ "@wrongstack/sdd": "0.300.0"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/node": "^26.1.2",