@wrongstack/webui-server 0.300.0 → 0.302.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.
@@ -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,7 +42,7 @@ export { type HostRouteHandlers, handleHostRoute } from './host-routes.js';
42
42
  export { clearAnalyticsBuffer, getAnalyticsBuffer, handleApiAnalyticsGet, handleApiAnalyticsPost, handleApiAnalyticsSummary, } from './http-server/analytics-handler.js';
43
43
  export type { CreateHttpServerOptions } from './http-server.js';
44
44
  export { buildCspHeader, createHttpServer, decodeSessionId, injectWsConfig, isInsideDist, } from './http-server.js';
45
- export { defaultBaseDir, formatInstances, isPidAlive, listInstances, registerInstance, registryPath, unregisterInstance, type WebUIInstanceRecord, } from './instance-registry.js';
45
+ export { defaultBaseDir, formatInstances, isPidAlive, joinSessionRegistryWithWebUIInstances, listInstances, registerInstance, registryPath, unregisterInstance, type WebUIInstanceAuthInfo, type WebUIInstanceRecord, type WebUIInstanceRole, type WebUISessionAttachCandidate, type WebUISessionAttachDegradedReason, type WebUISessionAttachEndpoint, } from './instance-registry.js';
46
46
  export { handleIntrospectionRoute, type IntrospectionRouteContext, } from './introspection-routes.js';
47
47
  export { handleKanbanTaskDispatch, type KanbanDispatchContext, type KanbanDispatchResult, type KanbanTaskDispatcher, parseResolvedDispatchRoute, type ResolvedDispatchRoute, } from './kanban-dispatch.js';
48
48
  export { handleKanbanHostRoute, type KanbanHostRouteHandlers } from './kanban-host-routes.js';
@@ -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
@@ -18,12 +18,20 @@
18
18
  * - **Best-effort**: a failure to read/write the registry must NEVER take the
19
19
  * server down. Callers wrap these in `.catch()`.
20
20
  */
21
+ import type { SessionRegistryEntry, SessionLiveStatus } from '@wrongstack/core/storage';
22
+ export type WebUIInstanceRole = 'standalone' | 'parent-shell' | 'session-child';
23
+ export interface WebUIInstanceAuthInfo {
24
+ /** How a same-user parent/sibling process can authenticate to this endpoint. */
25
+ scheme: 'registry-token' | 'cookie-bootstrap' | 'none';
26
+ /** Whether the record has a usable token in `authToken`. */
27
+ tokenPresent: boolean;
28
+ }
21
29
  /** One running WebUI / SimpleUI process. */
22
30
  export interface WebUIInstanceRecord {
23
31
  /** OS process id — also the liveness key. */
24
32
  pid: number;
25
- /** Surface kind — 'webui' or 'simpleui'. */
26
- surface: 'webui' | 'simpleui';
33
+ /** Surface kind — 'webui' or 'simpleui'. Additional strings are tolerated for new surfaces. */
34
+ surface: 'webui' | 'simpleui' | string;
27
35
  /** Port serving both HTTP and WebSocket. */
28
36
  httpPort: number;
29
37
  /** Bind host (e.g. 127.0.0.1 or 0.0.0.0). */
@@ -57,7 +65,48 @@ export interface WebUIInstanceRecord {
57
65
  * Optional so an older record (or a surface that has no token) still parses.
58
66
  */
59
67
  authToken?: string | undefined;
68
+ /** Runtime role. Missing means the legacy standalone WebUI/SimpleUI role. */
69
+ role?: WebUIInstanceRole | undefined;
70
+ /** Live session owned by this endpoint when `role === 'session-child'`. */
71
+ sessionId?: string | undefined;
72
+ /** Parent shell process identity, when this endpoint was spawned by a parent. */
73
+ parentPid?: number | undefined;
74
+ parentShellId?: string | undefined;
75
+ /** Stable child runtime id, distinct from the session id. */
76
+ runtimeId?: string | undefined;
77
+ /** Whether a parent shell should treat this endpoint as attachable. */
78
+ attachable?: boolean | undefined;
79
+ /** Descriptive auth metadata; the same-user token remains in `authToken`. */
80
+ auth?: WebUIInstanceAuthInfo | undefined;
81
+ /** Health/protocol hints for future parent shells. */
82
+ lastReadyAt?: string | undefined;
83
+ protocolVersion?: number | undefined;
84
+ capabilities?: string[] | undefined;
85
+ }
86
+ export interface WebUISessionAttachEndpoint {
87
+ host: string;
88
+ httpPort: number;
89
+ url: string;
90
+ authToken?: string | undefined;
91
+ }
92
+ export type WebUISessionAttachDegradedReason = 'live-session-no-webui-endpoint' | 'endpoint-owner-mismatch' | 'endpoint-missing-session-id' | 'endpoint-session-mismatch' | 'endpoint-not-session-child' | 'endpoint-not-attachable' | 'session-not-live';
93
+ export interface WebUISessionAttachCandidate {
94
+ sessionId: string;
95
+ projectRoot: string;
96
+ workingDir: string;
97
+ sessionPid: number;
98
+ status: SessionLiveStatus;
99
+ instance?: WebUIInstanceRecord | undefined;
100
+ endpoint?: WebUISessionAttachEndpoint | undefined;
101
+ attachable: boolean;
102
+ degradedReason?: WebUISessionAttachDegradedReason | undefined;
60
103
  }
104
+ export declare function joinSessionRegistryWithWebUIInstances(input: {
105
+ sessions: SessionRegistryEntry[];
106
+ instances: WebUIInstanceRecord[];
107
+ projectRoot?: string | undefined;
108
+ projectSlug?: string | undefined;
109
+ }): WebUISessionAttachCandidate[];
61
110
  /** Default wstack home dir (`~/.wrongstack`). Callers may override the base. */
62
111
  export declare function defaultBaseDir(): string;
63
112
  /** Resolve the registry file path for a given base dir. */
@@ -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<{
@@ -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', 'autoCollapseInput', '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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/webui-server",
3
- "version": "0.300.0",
3
+ "version": "0.302.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/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"
43
+ "@wrongstack/core": "0.302.0",
44
+ "@wrongstack/mcp": "0.302.0",
45
+ "@wrongstack/techstack": "0.302.0",
46
+ "@wrongstack/kanban": "0.302.0",
47
+ "@wrongstack/runtime": "0.302.0",
48
+ "@wrongstack/sage": "0.302.0",
49
+ "@wrongstack/providers": "0.302.0",
50
+ "@wrongstack/requirement-intake": "0.302.0",
51
+ "@wrongstack/tools": "0.302.0",
52
+ "@wrongstack/sdd": "0.302.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^26.1.2",