@wrongstack/webui-server 0.299.0 → 0.301.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.js +854 -268
- package/dist/protocol/client-conversation.d.ts +1 -1
- package/dist/protocol/index.js +23 -1
- package/dist/protocol/projections.d.ts +8 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-conversation.d.ts +2 -2
- package/dist/protocol/version.d.ts +2 -0
- package/dist/server/connections-health-route.d.ts +10 -0
- package/dist/server/context-editor.d.ts +9 -0
- package/dist/server/conversation-routes.d.ts +1 -0
- package/dist/server/discover-mailbox-bridge.d.ts +5 -0
- package/dist/server/embedded-host-adapters.d.ts +1 -1
- package/dist/server/embedded-lifecycle.d.ts +23 -5
- package/dist/server/entry.js +1296 -262
- package/dist/server/fallback-choice.d.ts +19 -0
- package/dist/server/goal-ws-handler.d.ts +12 -0
- package/dist/server/http-server.d.ts +49 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/instance-registry.d.ts +21 -0
- package/dist/server/kanban-broadcast.d.ts +37 -0
- package/dist/server/lifecycle.d.ts +6 -0
- package/dist/server/mcp-handlers.d.ts +3 -2
- package/dist/server/pref-helpers.d.ts +1 -1
- package/dist/server/provider-handlers.d.ts +2 -2
- package/dist/server/provider-routes.d.ts +3 -1
- package/dist/server/ws-auth.d.ts +18 -0
- package/dist/server/ws-payload-validation.d.ts +7 -0
- package/package.json +11 -11
|
@@ -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
|
|
@@ -111,6 +111,18 @@ export declare class GoalWebSocketHandler {
|
|
|
111
111
|
* Fire-and-forget: runs in the background and logs the review summary.
|
|
112
112
|
*/
|
|
113
113
|
private runChimeraReview;
|
|
114
|
+
/**
|
|
115
|
+
* Fire-and-forget persist.
|
|
116
|
+
*
|
|
117
|
+
* Every detached `store.save()` used to be a bare `void`, so a rejection
|
|
118
|
+
* became an unhandled rejection and — under Node 22's default
|
|
119
|
+
* `--unhandled-rejections=throw` — killed the process mid-run. On Windows an
|
|
120
|
+
* AV scanner or indexer holding the `.wrongstack/phases/<id>.json` rename
|
|
121
|
+
* target for a few hundred ms is enough (EPERM from `atomicWrite`), and in
|
|
122
|
+
* `--webui` mode that takes the CLI session down with it. `handleStop` at
|
|
123
|
+
* `:549` already had the `.catch`; these call sites did not.
|
|
124
|
+
*/
|
|
125
|
+
private persistDetached;
|
|
114
126
|
/** Persist + broadcast after an interactive board mutation. */
|
|
115
127
|
private afterBoardMutation;
|
|
116
128
|
private handleTaskStatusChange;
|
|
@@ -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.
|
|
@@ -85,6 +98,42 @@ export interface CreateHttpServerOptions {
|
|
|
85
98
|
export declare function injectWsConfig(html: string, opts: {
|
|
86
99
|
publicWsUrl?: string | undefined;
|
|
87
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;
|
|
88
137
|
/**
|
|
89
138
|
* Build the Content-Security-Policy value for the WebUI.
|
|
90
139
|
*
|
package/dist/server/index.d.ts
CHANGED
|
@@ -123,7 +123,7 @@ export { handleWorktreeRoute, type WorktreeRouteHandlers } from './worktree-rout
|
|
|
123
123
|
export { WorktreeWebSocketHandler } from './worktree-ws-handler.js';
|
|
124
124
|
export { extractToken, extractTokenFromCookie, hostHeaderOk, isLoopbackBind, isLoopbackHostname, isWildcardBind, tokenMatches, type VerifyClientInput, verifyClient, } from './ws-auth.js';
|
|
125
125
|
export { validateAutonomySwitchPayload, validateBrainAskPayload, validateBrainRiskPayload, validateContextModeCreatePayload, validateContextModeDeletePayload, validateContextModeSwitchPayload, validateContextModeUpdatePayload, validateGitDiffPayload, validateMailboxAgentsPayload, validateMailboxMessagesPayload, validateMailboxPurgePayload, validateModelSwitchPayload, validateModeSwitchPayload, validatePlanTemplateUsePayload, validatePrefsUpdatePayload, validateProcessKillPayload, validateProjectsAddPayload, validateProjectsSelectPayload, validateShellOpenPayload, validateSkillsCreatePayload, validateSkillsEditPayload, validateWorkingDirSetPayload, } from './ws-payload-validation.js';
|
|
126
|
-
export { broadcast, buildWebUIAccessUrl, envFlag, errMessage, generateAuthToken, hostForBrowserUrl, resolveAuthToken, send, sendResult, } from './ws-utils.js';
|
|
126
|
+
export { broadcast, buildWebUIAccessUrl, envFlag, errMessage, generateAuthToken, hostForBrowserUrl, resolveAuthToken, send, sendResult, sendSerialized, WEBUI_WS_MAX_BUFFERED_BYTES, } from './ws-utils.js';
|
|
127
127
|
export { formatExternalAccessUrls, getExternalAddresses, type NetworkAddress } from './network-info.js';
|
|
128
128
|
export { createZipBuffer, readZipEntries, type ZipEntryInput } from './zip.js';
|
|
129
129
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a Kanban board becomes a WebSocket message.
|
|
3
|
+
*
|
|
4
|
+
* Six call sites built these envelopes by hand — the daemon subscriber,
|
|
5
|
+
* decomposition routes (twice), the task dispatcher (twice), the run mirror and
|
|
6
|
+
* the supervisor. `WSServerMessage` is `{ type: string; payload: unknown }`, so
|
|
7
|
+
* the type system checks none of it: a board sent bare instead of wrapped in
|
|
8
|
+
* `{ board }` compiles, ships, and leaves the client's `isBoard(data)` branch
|
|
9
|
+
* silently unmatched. That has already happened once
|
|
10
|
+
* (`hq-kanban-sync-best-effort`), and hand-copied envelopes are how it happens
|
|
11
|
+
* again.
|
|
12
|
+
*
|
|
13
|
+
* These builders are the shape. Prefer them over an inline literal even for a
|
|
14
|
+
* one-off broadcast — an inline literal is how the seventh site starts.
|
|
15
|
+
*/
|
|
16
|
+
import type { KanbanBoard, KanbanBoardSummary, KanbanTask } from '@wrongstack/kanban';
|
|
17
|
+
import type { WSServerMessage } from './types.js';
|
|
18
|
+
/** A full board, for `kanban.get`. The client keys on `data.board`. */
|
|
19
|
+
export declare function kanbanBoardMessage(board: KanbanBoard): WSServerMessage;
|
|
20
|
+
/** The board list, for `kanban.list`. `data` is the array itself, not `{ boards }`. */
|
|
21
|
+
export declare function kanbanListMessage(boards: readonly KanbanBoardSummary[]): WSServerMessage;
|
|
22
|
+
/** A single task, for `kanban.task.update`. */
|
|
23
|
+
export declare function kanbanTaskMessage(boardId: string, task: KanbanTask): WSServerMessage;
|
|
24
|
+
/** A removed board, for `kanban.delete`. */
|
|
25
|
+
export declare function kanbanDeletedMessage(boardId: string): WSServerMessage;
|
|
26
|
+
/**
|
|
27
|
+
* Broadcast a board, and optionally the refreshed list behind it.
|
|
28
|
+
*
|
|
29
|
+
* The list is opt-in because the client already derives a summary from the
|
|
30
|
+
* board it receives (`kanban-store.ts` calls `upsertSummary` on `kanban.get`),
|
|
31
|
+
* so a second round trip is only needed when the SET of boards changed —
|
|
32
|
+
* created, deleted, renamed. Sites that skipped it were not leaving the sidebar
|
|
33
|
+
* stale; sites that sent it unconditionally were paying for a list read on
|
|
34
|
+
* every task move.
|
|
35
|
+
*/
|
|
36
|
+
export declare function publishKanbanBoard(broadcast: (message: WSServerMessage) => void, board: KanbanBoard, listBoards?: () => Promise<readonly KanbanBoardSummary[]>): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=kanban-broadcast.d.ts.map
|
|
@@ -19,8 +19,14 @@ export interface LifecycleResources {
|
|
|
19
19
|
* snapshot) so shutdown closes whoever is connected *at signal time*, not
|
|
20
20
|
* whoever was connected when the handler was registered.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Live client sockets. `terminate` is optional so a test double can supply
|
|
24
|
+
* `close` alone, but a real `ws` socket has it — and the shutdown path needs
|
|
25
|
+
* it: `close()` only starts a handshake the peer may never answer.
|
|
26
|
+
*/
|
|
22
27
|
clients: () => Iterable<{
|
|
23
28
|
close: () => void;
|
|
29
|
+
terminate?: (() => void) | undefined;
|
|
24
30
|
}>;
|
|
25
31
|
/** Servers to stop (HTTP + WS). `null`/`undefined` entries are skipped. */
|
|
26
32
|
servers: Array<{
|
|
@@ -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', 'nextStepsTool', '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', 'autoReviewModelSelection', '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<
|
|
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<
|
|
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<
|
|
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;
|
package/dist/server/ws-auth.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.301.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,16 +40,16 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@wrongstack/
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/
|
|
49
|
-
"@wrongstack/
|
|
50
|
-
"@wrongstack/
|
|
51
|
-
"@wrongstack/
|
|
52
|
-
"@wrongstack/
|
|
43
|
+
"@wrongstack/core": "0.301.0",
|
|
44
|
+
"@wrongstack/kanban": "0.301.0",
|
|
45
|
+
"@wrongstack/mcp": "0.301.0",
|
|
46
|
+
"@wrongstack/requirement-intake": "0.301.0",
|
|
47
|
+
"@wrongstack/techstack": "0.301.0",
|
|
48
|
+
"@wrongstack/sage": "0.301.0",
|
|
49
|
+
"@wrongstack/providers": "0.301.0",
|
|
50
|
+
"@wrongstack/runtime": "0.301.0",
|
|
51
|
+
"@wrongstack/sdd": "0.301.0",
|
|
52
|
+
"@wrongstack/tools": "0.301.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^26.1.2",
|