@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.
@@ -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
  *
@@ -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<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;
@@ -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.299.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/providers": "0.299.0",
44
- "@wrongstack/core": "0.299.0",
45
- "@wrongstack/techstack": "0.299.0",
46
- "@wrongstack/mcp": "0.299.0",
47
- "@wrongstack/requirement-intake": "0.299.0",
48
- "@wrongstack/sdd": "0.299.0",
49
- "@wrongstack/kanban": "0.299.0",
50
- "@wrongstack/tools": "0.299.0",
51
- "@wrongstack/sage": "0.299.0",
52
- "@wrongstack/runtime": "0.299.0"
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",