@cleocode/runtime 2026.6.14 → 2026.6.15

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.
@@ -1,7 +1,7 @@
1
1
  import { DomainHandler, Middleware, DispatchRequest, DispatchResponse, Gateway, GatewayStreamEvent, Source, DispatchResponseMeta, CanonicalDomain, Tier } from '@cleocode/contracts/gateway';
2
2
  import { BackgroundJobManager } from '@cleocode/core/store/background-jobs.js';
3
3
  import { Subsystem, OperationDef, SuggestedNextOp, Resolution } from '@cleocode/contracts';
4
- import { IncomingMessage, Server } from 'node:http';
4
+ import { IncomingMessage, Server, ServerResponse } from 'node:http';
5
5
  import '@cleocode/contracts/operations/orchestrate';
6
6
  import * as _cleocode_core_internal from '@cleocode/core/internal';
7
7
  import { EngineResult, ProblemDetails } from '@cleocode/core';
@@ -349,6 +349,21 @@ interface HttpServerOptions {
349
349
  * unavailable" close rather than crashing.
350
350
  */
351
351
  wsPty?: WsPtyOptions;
352
+ /**
353
+ * Studio static asset directory for the bundled Web Studio (T11979).
354
+ *
355
+ * When provided, requests whose path starts with `/studio` are served as
356
+ * static files from this directory (the `build/client/` output from an
357
+ * `adapter-node` Studio build), with SPA fallback to `index.html` for
358
+ * sub-routes. When `undefined` or the path does not exist on disk, a clean
359
+ * JSON 503 envelope is returned instead of a stack trace — allowing the
360
+ * gateway to start without a Studio bundle present (dev checkouts).
361
+ *
362
+ * The value is resolved and injected by the caller (`cleo daemon serve`)
363
+ * using {@link resolveStudioStaticDir} from `@cleocode/cleo` so the runtime
364
+ * layer stays free of any path-resolution logic.
365
+ */
366
+ studioStaticDir?: string;
352
367
  }
353
368
  /**
354
369
  * A live HTTP server handle returned by {@link startHttpServer}.
@@ -1421,6 +1436,133 @@ declare function registerStreamSource(domain: string, operation: string, source:
1421
1436
  */
1422
1437
  declare function resolveStreamSource(domain: string, operation: string): GatewayStreamSource | undefined;
1423
1438
 
1439
+ /**
1440
+ * Static file serving for the bundled CLEO Web Studio assets.
1441
+ *
1442
+ * The gateway HTTP server calls {@link serveStudioStatic} for any request whose
1443
+ * path starts with the `/studio` prefix. The handler:
1444
+ *
1445
+ * 1. Strips the `/studio` prefix and resolves the remainder against the
1446
+ * `studioStaticDir` (the `build/client/` output of the Studio
1447
+ * `adapter-node` build bundled inside `@cleocode/cleo`).
1448
+ * 2. Serves the matched file with the correct MIME type (inferred from the
1449
+ * extension via the built-in {@link MIME_TYPES} map).
1450
+ * 3. Returns `index.html` for any path that does not resolve to a static file
1451
+ * — the SPA fallback that lets the SvelteKit client-side router handle
1452
+ * sub-routes.
1453
+ * 4. Returns a clean JSON error envelope (not a stack trace) when
1454
+ * `studioStaticDir` is absent (dev checkout without a Studio build, or an
1455
+ * npm install where the postbuild copy was skipped).
1456
+ *
1457
+ * The handler deliberately has NO `process.exit`, NO drizzle, NO
1458
+ * `@cleocode/cleo` dependency — it is a pure file-system responder that can be
1459
+ * embedded in any `node:http` server (the gateway daemon, a test harness).
1460
+ *
1461
+ * The `build/client/` static tree is served under `/studio/` so all of its
1462
+ * relative asset references (`/_app/immutable/...`) resolve correctly. The root
1463
+ * `/studio` (no trailing slash) redirects to `/studio/` to keep the base URL
1464
+ * consistent with the SvelteKit assets.
1465
+ *
1466
+ * @packageDocumentation
1467
+ * @module @cleocode/runtime/gateway/http
1468
+ *
1469
+ * @task T11979
1470
+ * @epic T11261
1471
+ */
1472
+
1473
+ /** The URL prefix the Studio static handler claims. */
1474
+ declare const STUDIO_PREFIX = "/studio";
1475
+ /**
1476
+ * Options for the Studio static handler, injected at server startup.
1477
+ *
1478
+ * The caller resolves the absolute path to the static asset directory and
1479
+ * passes it here — the handler never reads environment variables or resolves
1480
+ * paths itself, keeping concerns separated and the handler testable.
1481
+ */
1482
+ interface StudioStaticOptions {
1483
+ /**
1484
+ * Absolute path to the Studio static asset directory. This is the
1485
+ * `build/client/` subdirectory from an `adapter-node` Studio build, bundled
1486
+ * inside `@cleocode/cleo` at `studio-dist/client/`.
1487
+ *
1488
+ * When `undefined` or the path does not exist on disk, every request under
1489
+ * `/studio` returns a clean JSON 503 "bundle absent" envelope.
1490
+ */
1491
+ staticDir: string | undefined;
1492
+ }
1493
+ /**
1494
+ * Determine whether a request path falls under the Studio prefix and should be
1495
+ * handled by the static asset handler rather than the API gateway.
1496
+ *
1497
+ * @param pathname - The URL pathname (without query string).
1498
+ * @returns `true` when the path is `/studio`, `/studio/`, or any
1499
+ * `/studio/<subpath>`.
1500
+ */
1501
+ declare function isStudioPath(pathname: string): boolean;
1502
+ /**
1503
+ * Resolve the file-system path for a Studio request within `staticDir`.
1504
+ *
1505
+ * Strips the `/studio` prefix and normalises the remainder; guards against
1506
+ * path-traversal (a normalised path that escapes `staticDir` is rejected).
1507
+ *
1508
+ * @param staticDir - Absolute path to the Studio static asset directory.
1509
+ * @param pathname - The request path (already verified to start with `/studio`).
1510
+ * @returns The absolute candidate path, or `undefined` when path traversal is
1511
+ * detected.
1512
+ */
1513
+ declare function resolveStudioAssetPath(staticDir: string, pathname: string): string | undefined;
1514
+ /**
1515
+ * Infer the MIME type for a file from its extension.
1516
+ *
1517
+ * @param filePath - The file path (only the extension is used).
1518
+ * @returns The appropriate `Content-Type` header value.
1519
+ */
1520
+ declare function mimeType(filePath: string): string;
1521
+ /**
1522
+ * Write the "bundle absent" JSON error envelope and close the response.
1523
+ *
1524
+ * Used when `staticDir` is `undefined` or does not exist on disk — the Studio
1525
+ * build was not found. The response is a `503 Service Unavailable` with a clean
1526
+ * LAFS-flavoured JSON body so clients can detect the condition programmatically.
1527
+ *
1528
+ * @param res - The server response to write to.
1529
+ * @param hint - Optional developer hint (e.g. the expected bundle path).
1530
+ */
1531
+ declare function writeBundleAbsentError(res: ServerResponse, hint?: string): void;
1532
+ /**
1533
+ * Handle a single HTTP request whose path falls under `/studio`.
1534
+ *
1535
+ * Routing logic:
1536
+ * - `GET /studio` → 302 redirect to `/studio/` (keep asset refs consistent)
1537
+ * - `GET /studio/<static-asset>` → serve the file from `staticDir`
1538
+ * - `GET /studio/*` (no matching file) → serve `index.html` (SPA fallback)
1539
+ * - Any request when `staticDir` is absent → 503 JSON envelope
1540
+ * - Non-GET method → 405 JSON envelope (Studio is read-only from the gateway)
1541
+ *
1542
+ * @param req - The inbound HTTP request.
1543
+ * @param res - The outbound HTTP response.
1544
+ * @param opts - Static handler options (resolved at server startup).
1545
+ * @returns A promise that resolves once the response has been written.
1546
+ */
1547
+ declare function serveStudioStatic(req: IncomingMessage, res: ServerResponse, opts: StudioStaticOptions): Promise<void>;
1548
+ /**
1549
+ * Resolve the bundled Studio static directory from the `@cleocode/cleo` package
1550
+ * location.
1551
+ *
1552
+ * When running from an npm install, the Studio build is bundled at
1553
+ * `<cleo-package-root>/studio-dist/client/`. When running from a monorepo dev
1554
+ * checkout, the directory may not exist (developers must run
1555
+ * `pnpm --filter @cleocode/studio run build` manually) — `undefined` is
1556
+ * returned in that case, and the gateway handler will serve the bundle-absent
1557
+ * error instead of crashing.
1558
+ *
1559
+ * @param cleoPackageDir - Absolute path to the `@cleocode/cleo` package root.
1560
+ * Typically derived from `import.meta.url` at the call site.
1561
+ * @returns The absolute path to `studio-dist/client/`, or `undefined` if the
1562
+ * directory does not exist.
1563
+ */
1564
+ declare function resolveStudioStaticDir(cleoPackageDir: string): string | undefined;
1565
+
1424
1566
  /**
1425
1567
  * Singleton accessor for the gateway {@link BackgroundJobManager}.
1426
1568
  *
@@ -2013,6 +2155,17 @@ interface ServeGatewayOptions {
2013
2155
  host?: string;
2014
2156
  /** The scope this gateway process serves. Defaults to `'global'`. */
2015
2157
  scope?: GatewayScope;
2158
+ /**
2159
+ * Absolute path to the Studio static asset directory (T11979).
2160
+ *
2161
+ * When set, the HTTP server serves the bundled CLEO Web Studio at `/studio`
2162
+ * using the `build/client/` static assets from an `adapter-node` Studio build.
2163
+ * When `undefined`, `/studio` routes return a clean 503 "bundle absent" JSON
2164
+ * envelope. The caller resolves this path (typically via
2165
+ * `resolveStudioStaticDir` from `@cleocode/cleo`) so the runtime stays free of
2166
+ * any path-resolution policy.
2167
+ */
2168
+ studioStaticDir?: string;
2016
2169
  }
2017
2170
  /**
2018
2171
  * A live gateway server handle returned by {@link serveGateway}.
@@ -2101,4 +2254,4 @@ interface GatewayHandler {
2101
2254
  */
2102
2255
  declare function createGatewayHandler(config: DispatcherConfig): GatewayHandler;
2103
2256
 
2104
- export { type ErrorMeta as $, startHttpServer as A, statusForResponse as B, tickStreamSource as C, type GatewayOperationKey as D, type DecodeResult, type McpContent as E, type McpInputProperty as F, type GateDecision as G, type HttpServerHandle as H, type McpServerOptions as I, type McpTool as J, type McpToolResult as K, buildToolsList as L, LineBuffer, MCP_TOOL_PREFIX as M, NODE_PTY_INSTALL_HINT as N, callTool as O, exposedOperations as P, operationToMcpTool as Q, operationToToolName as R, type RpcServerHandle, type RpcServerOptions, SSE_HEADERS as S, startMcpServer as T, type UpgradableServer as U, toolNameToOperationKey as V, WS_PTY_PATH as W, BRAIN_DB_FILENAME as X, CLEO_DIR_NAME as Y, Dispatcher as Z, type DispatcherConfig as _, type GatewayStreamSource as a, type GatewayHandler as a0, type GatewayScope as a1, type GatewaySubsystemContext as a2, type GatewaySubsystemOptions as a3, OPERATIONS as a4, STRING_TO_EXIT as a5, type ServeGatewayHandle as a6, type ServeGatewayOptions as a7, type StreamResolution as a8, WORKFLOWS_SUBDIR as a9, setJobManager as aA, stampNexusMeta as aB, validateLabels as aC, validateRequiredParams as aD, validateSuggestedNext as aE, buildNexusMetaExtensions as aa, cleoErrorToEngineError as ab, compose as ac, createDispatchMeta as ad, createGatewayHandler as ae, defineGatewaySubsystem as af, deriveGatewayMatrix as ag, engineError as ah, formatSuggestedNext as ai, gatewaySubsystemName as aj, generateTemplateConfig as ak, getActiveDomains as al, getByDomain as am, getByGateway as an, getByTier as ao, getCounts as ap, getGatewayDomains as aq, getJobManager as ar, getTemplateForSubcommand as as, inferGateway as at, mapNumericExitCodeToString as au, parseIssueTemplates as av, pickDecoratorMetaExtensions as aw, resolve as ax, resolveStreamRoute as ay, serveGateway as az, type HttpServerOptions as b, buildErrorFrame, type HttpUnaryRequest as c, type HttpUnaryResult as d, decodeLine, type SseEmitter as e, encodeFrame, type SseFrame as f, type SseSource as g, type StreamSourceContext as h, type StreamSourceTeardown as i, type WsPtyEndpointHandle as j, type WsPtyOptions as k, attachWsPtyEndpoint as l, authorizeUpgrade as m, computeAcceptKey as n, createSseStream as o, decodeWsFrame as p, encodeSseFrame as q, encodeStreamEvent as r, routeFrame, encodeWsFrame as s, startRpcServer, isWsPtyPath as t, loadNodePty as u, parseHttpRoute as v, registerStreamSource as w, resolveStreamSource as x, routeUnary as y, scrubPtyEnv as z };
2257
+ export { operationToMcpTool as $, registerStreamSource as A, resolveStreamSource as B, resolveStudioAssetPath as C, resolveStudioStaticDir as D, type DecodeResult, routeUnary as E, scrubPtyEnv as F, type GateDecision as G, type HttpServerHandle as H, serveStudioStatic as I, startHttpServer as J, statusForResponse as K, tickStreamSource as L, LineBuffer, writeBundleAbsentError as M, NODE_PTY_INSTALL_HINT as N, type GatewayOperationKey as O, MCP_TOOL_PREFIX as P, type McpContent as Q, type McpInputProperty as R, type RpcServerHandle, type RpcServerOptions, SSE_HEADERS as S, type McpServerOptions as T, type UpgradableServer as U, type McpTool as V, WS_PTY_PATH as W, type McpToolResult as X, buildToolsList as Y, callTool as Z, exposedOperations as _, type GatewayStreamSource as a, operationToToolName as a0, startMcpServer as a1, toolNameToOperationKey as a2, BRAIN_DB_FILENAME as a3, CLEO_DIR_NAME as a4, Dispatcher as a5, type DispatcherConfig as a6, type ErrorMeta as a7, type GatewayHandler as a8, type GatewayScope as a9, getTemplateForSubcommand as aA, inferGateway as aB, mapNumericExitCodeToString as aC, parseIssueTemplates as aD, pickDecoratorMetaExtensions as aE, resolve as aF, resolveStreamRoute as aG, serveGateway as aH, setJobManager as aI, stampNexusMeta as aJ, validateLabels as aK, validateRequiredParams as aL, validateSuggestedNext as aM, type GatewaySubsystemContext as aa, type GatewaySubsystemOptions as ab, OPERATIONS as ac, STRING_TO_EXIT as ad, type ServeGatewayHandle as ae, type ServeGatewayOptions as af, type StreamResolution as ag, WORKFLOWS_SUBDIR as ah, buildNexusMetaExtensions as ai, cleoErrorToEngineError as aj, compose as ak, createDispatchMeta as al, createGatewayHandler as am, defineGatewaySubsystem as an, deriveGatewayMatrix as ao, engineError as ap, formatSuggestedNext as aq, gatewaySubsystemName as ar, generateTemplateConfig as as, getActiveDomains as at, getByDomain as au, getByGateway as av, getByTier as aw, getCounts as ax, getGatewayDomains as ay, getJobManager as az, type HttpServerOptions as b, buildErrorFrame, type HttpUnaryRequest as c, type HttpUnaryResult as d, decodeLine, STUDIO_PREFIX as e, encodeFrame, type SseEmitter as f, type SseFrame as g, type SseSource as h, type StreamSourceContext as i, type StreamSourceTeardown as j, type StudioStaticOptions as k, type WsPtyEndpointHandle as l, type WsPtyOptions as m, attachWsPtyEndpoint as n, authorizeUpgrade as o, computeAcceptKey as p, createSseStream as q, decodeWsFrame as r, routeFrame, encodeSseFrame as s, startRpcServer, encodeStreamEvent as t, encodeWsFrame as u, isStudioPath as v, isWsPtyPath as w, loadNodePty as x, mimeType as y, parseHttpRoute as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/runtime",
3
- "version": "2026.6.14",
3
+ "version": "2026.6.15",
4
4
  "description": "Long-running process layer for CLEO — agent polling, SSE connections, heartbeat, key rotation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "node-cron": "^4.2.1",
36
- "@cleocode/contracts": "2026.6.14",
37
- "@cleocode/core": "2026.6.14"
36
+ "@cleocode/contracts": "2026.6.15",
37
+ "@cleocode/core": "2026.6.15"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/gateway/http/listen.ts","../src/gateway/http/server.ts","../src/gateway/http/sse.ts","../src/gateway/http/stream-sources.ts","../src/gateway/http/ws-pty.ts"],"sourcesContent":["/**\n * `node:http` server binding for the framework-agnostic HTTP gateway adapter.\n *\n * The HTTP adapter ({@link routeUnary} + the SSE primitives) is deliberately\n * framework-agnostic — it owns NO port binding and NO `Request`/`Response`\n * lifecycle, so an embedder (a SvelteKit route, a test harness) can drive it\n * directly. The **daemon**, however, is itself the embedder: it must expose the\n * gateway to external HTTP clients over a real listening socket. This module is\n * that thin embedder — a minimal `node:http` server that parses the request URL\n * into a `(gateway, domain, operation)` triple, reads the JSON body, and routes\n * it through {@link routeUnary}. It deliberately stays in the runtime (no\n * SvelteKit / no `@cleocode/cleo` / no drizzle dependency) so the daemon can\n * host the gateway without reaching across a package boundary.\n *\n * Routing contract (intentionally minimal — the wire vocabulary is the same as\n * every other transport, only the framing differs):\n *\n * `POST /<gateway>/<domain>/<operation>` with a JSON body → operation params\n *\n * where `<gateway>` is `query` | `mutate`. A non-POST method, an unparseable\n * path, or a body that is not a JSON object is rejected at the wire edge with a\n * LAFS-shaped error envelope — the injected {@link GatewayHandler} is never\n * invoked with a malformed request.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/gateway/http\n *\n * @task T11451\n * @epic T11254\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { DispatchResponse, Gateway, GatewayStreamEvent } from '@cleocode/contracts/gateway';\nimport { getLogger } from '@cleocode/core';\nimport type { GatewayHandler } from '../index.js';\nimport { inferGateway, resolveStreamRoute } from '../registry.js';\nimport { routeUnary } from './server.js';\nimport { createSseStream, encodeStreamEvent, SSE_HEADERS } from './sse.js';\nimport { resolveStreamSource, type StreamSourceContext } from './stream-sources.js';\nimport type { HttpUnaryRequest } from './types.js';\nimport { attachWsPtyEndpoint, type WsPtyOptions } from './ws-pty.js';\n\n/** The two CQRS gateways a path segment may name. */\nconst GATEWAYS: ReadonlySet<string> = new Set<Gateway>(['query', 'mutate']);\n\n/** Maximum accepted request-body size (1 MiB) — a crude DoS guard at the edge. */\nconst MAX_BODY_BYTES = 1_048_576;\n\n/**\n * The versioned REST facade prefix (T11919). `/v1/<domain>/<operation>` infers\n * the gateway from the registry; `/v1/health` is the liveness probe. The legacy\n * `/<gateway>/<domain>/<operation>` form is retained for backward compatibility.\n */\nconst V1_PREFIX = 'v1';\n\n/**\n * Options for {@link startHttpServer}.\n *\n * Mirrors the connection-oriented {@link RpcServerOptions} shape: the caller\n * resolves the bind coordinates so the runtime stays free of any\n * `@cleocode/paths` dependency.\n */\nexport interface HttpServerOptions {\n /**\n * TCP port to bind. `0` selects an ephemeral port (the bound port is then\n * readable from {@link HttpServerHandle.port}).\n */\n port: number;\n /**\n * Host/interface to bind. Defaults to `127.0.0.1` (loopback only) — the\n * gateway is local-process-facing; exposing it on a public interface is an\n * explicit caller decision.\n */\n host?: string;\n /**\n * WebSocket terminal/PTY endpoint configuration (T11922 · M5 WS half). When\n * provided, a WS upgrade handler for {@link WS_PTY_PATH} (`/v1/terminal/pty`)\n * is attached to this server's `'upgrade'` event — gated at the edge (loopback\n * + optional token/origin) and bridging a PTY bidirectionally over WS frames.\n * When OMITTED, no upgrade handler is attached and the server stays a pure\n * unary+SSE HTTP listener (the capability is opt-in — the daemon enables it\n * explicitly). The optional `node-pty` backend is loaded dynamically; if it is\n * absent, the endpoint still upgrades and reports a clean \"PTY backend\n * unavailable\" close rather than crashing.\n */\n wsPty?: WsPtyOptions;\n}\n\n/**\n * A live HTTP server handle returned by {@link startHttpServer}.\n *\n * Exposes the bound {@link Server}, the actually-bound `port` (resolved even\n * when `0` was requested), and an idempotent {@link close}. The adapter never\n * calls `process.exit` — lifecycle stays with the caller (R3 contract).\n */\nexport interface HttpServerHandle {\n /** The bound `node:http` server. */\n server: Server;\n /** The actually-bound TCP port (resolved when `0` was requested). */\n port: number;\n /** Host/interface the server is bound to. */\n host: string;\n /** Close the server + all live connections; resolves once fully closed. */\n close(): Promise<void>;\n}\n\n/**\n * The result of parsing a request line into routing coordinates: a resolved\n * unary triple, a recognized health probe, or a rejection for the wire edge.\n */\ntype ParsedRoute =\n | { ok: true; kind: 'unary'; gateway: Gateway; domain: string; operation: string }\n | { ok: true; kind: 'stream'; gateway: Gateway; domain: string; operation: string }\n | { ok: true; kind: 'health' }\n | { ok: false; status: number; code: string; message: string };\n\n/**\n * Parse a gateway request line into a routing decision.\n *\n * Two POST routing vocabularies are accepted (the wire payload is identical to\n * every other transport — only the framing differs):\n *\n * - `POST /v1/<domain>/<operation>` — the **versioned REST facade** (T11919).\n * The gateway (`query` | `mutate`) is INFERRED from the registry, so the\n * client need not name it. An unregistered `(domain, operation)` pair is a\n * `404`.\n * - `POST /<gateway>/<domain>/<operation>` — the **legacy** form, where the\n * client names the gateway segment explicitly (validated against the two\n * CQRS gateways so a client cannot smuggle an arbitrary value).\n *\n * Two GET routing vocabularies are accepted:\n *\n * - `GET /v1/health` — the liveness probe.\n * - `GET /v1/<domain>/<operation>` — the **SSE streaming** endpoint (T11921),\n * recognized only when the `(domain, operation)` pair is registered AND\n * flagged `streaming: true` in {@link OPERATIONS} (resolved via\n * {@link resolveStreamRoute}). The SAME pair is dispatched UNARY over POST,\n * so the streaming GET co-exists with the unary POST route. A GET to a\n * unary-only op is a `405` (method-not-allowed), distinguishing \"route\n * exists, this method is unsupported\" from \"no such route\" (`404`).\n *\n * A non-POST/GET method is rejected at the edge (`405`); a path that resolves to\n * neither vocabulary is a `404`. The injected {@link GatewayHandler} is never\n * invoked with a malformed request.\n *\n * @param method - The HTTP request method.\n * @param url - The HTTP request URL (path + query).\n * @returns The parsed route, or a typed rejection for the wire edge.\n */\nexport function parseHttpRoute(method: string | undefined, url: string | undefined): ParsedRoute {\n // Parse against a dummy origin so only the path is consumed (query ignored).\n const pathname = new URL(url ?? '/', 'http://localhost').pathname;\n const segments = pathname.split('/').filter((s) => s.length > 0);\n\n // GET routes: the liveness probe and the SSE streaming endpoint.\n if (method === 'GET') {\n if (segments.length === 2 && segments[0] === V1_PREFIX && segments[1] === 'health') {\n return { ok: true, kind: 'health' };\n }\n // GET /v1/<domain>/<operation> — SSE streaming endpoint (T11921). Only a\n // `(domain, operation)` pair registered AND flagged `streaming: true` opens\n // a `text/event-stream`; this co-exists with the unary POST routes (the\n // SAME pair is dispatched unary over POST). A GET to a unary-only op falls\n // through to a 405 so the caller can tell \"route exists, method unsupported\"\n // from \"no such route\" (404).\n if (segments.length === 3 && segments[0] === V1_PREFIX) {\n const [, domain, operation] = segments;\n const stream = resolveStreamRoute(domain, operation);\n if (stream !== undefined) {\n return { ok: true, kind: 'stream', gateway: stream.gateway, domain, operation };\n }\n }\n return {\n ok: false,\n status: 405,\n code: 'E_HTTP_METHOD_NOT_ALLOWED',\n message: `method GET not allowed for '${pathname}'; only GET /v1/health and registered streaming ops are served`,\n };\n }\n\n if (method !== 'POST') {\n return {\n ok: false,\n status: 405,\n code: 'E_HTTP_METHOD_NOT_ALLOWED',\n message: `method ${method ?? '<none>'} not allowed; gateway accepts POST`,\n };\n }\n\n // POST /v1/<domain>/<operation> — versioned facade; gateway inferred.\n if (segments.length === 3 && segments[0] === V1_PREFIX) {\n const [, domain, operation] = segments;\n const gateway = inferGateway(domain, operation);\n if (gateway === undefined) {\n return {\n ok: false,\n status: 404,\n code: 'E_HTTP_NOT_FOUND',\n message: `unknown operation '${domain}/${operation}'; no registered query/mutate matches`,\n };\n }\n return { ok: true, kind: 'unary', gateway, domain, operation };\n }\n\n // POST /<gateway>/<domain>/<operation> — legacy explicit-gateway form.\n if (segments.length !== 3) {\n return {\n ok: false,\n status: 404,\n code: 'E_HTTP_NOT_FOUND',\n message: `expected POST /v1/<domain>/<operation> or /<gateway>/<domain>/<operation>, got '${pathname}'`,\n };\n }\n const [gateway, domain, operation] = segments;\n if (!GATEWAYS.has(gateway)) {\n return {\n ok: false,\n status: 404,\n code: 'E_HTTP_NOT_FOUND',\n message: `unknown gateway '${gateway}'; expected one of query, mutate`,\n };\n }\n return { ok: true, kind: 'unary', gateway: gateway as Gateway, domain, operation };\n}\n\n/**\n * Read the full request body as a string, enforcing {@link MAX_BODY_BYTES}.\n *\n * @param req - The inbound request stream.\n * @returns The decoded UTF-8 body.\n * @throws {Error} When the body exceeds the size cap (the connection is then\n * destroyed by the caller).\n */\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n reject(new Error(`request body exceeds ${MAX_BODY_BYTES} bytes`));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\n/**\n * Write a LAFS-shaped error envelope to the response at the wire edge.\n *\n * Used for rejections that never reach the {@link GatewayHandler} (bad method,\n * unroutable path, malformed body), so a client always sees a well-formed LAFS\n * envelope rather than a bare HTTP error.\n *\n * @param res - The server response.\n * @param status - The HTTP status to set.\n * @param code - The machine-readable LAFS error code.\n * @param message - A human-readable message.\n */\nfunction writeEdgeError(res: ServerResponse, status: number, code: string, message: string): void {\n const body: DispatchResponse = {\n meta: {\n gateway: 'query',\n domain: '',\n operation: '',\n timestamp: new Date().toISOString(),\n duration_ms: 0,\n source: 'http',\n requestId: '',\n },\n success: false,\n error: { code, message },\n };\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n}\n\n/**\n * Write a LAFS-shaped success envelope for the `GET /v1/health` liveness probe.\n *\n * The probe never touches the {@link GatewayHandler} (it is a pure wire-edge\n * liveness signal), so it returns a self-contained `200` envelope reporting the\n * server is up and the wire version it serves.\n *\n * @param res - The server response.\n */\nfunction writeHealth(res: ServerResponse): void {\n const body: DispatchResponse = {\n meta: {\n gateway: 'query',\n domain: 'gateway',\n operation: 'health',\n timestamp: new Date().toISOString(),\n duration_ms: 0,\n source: 'http',\n requestId: '',\n },\n success: true,\n data: { status: 'ok', version: V1_PREFIX },\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n}\n\n/**\n * Decode the query string of a streaming `GET` into a params object.\n *\n * Numeric-looking values (e.g. `?ticks=3`) are coerced to numbers so a declared\n * `number` param (`ticks`) arrives typed; everything else stays a string. This\n * mirrors how the unary path receives an already-decoded JSON body, but for the\n * GET-with-query-string streaming shape.\n *\n * @param url - The raw request URL (path + query).\n * @returns The decoded params object (empty when there is no query string).\n */\nfunction parseStreamParams(url: string | undefined): Record<string, unknown> {\n const parsed = new URL(url ?? '/', 'http://localhost');\n const params: Record<string, unknown> = {};\n for (const [key, value] of parsed.searchParams.entries()) {\n const asNumber = Number(value);\n params[key] = value !== '' && Number.isFinite(asNumber) ? asNumber : value;\n }\n return params;\n}\n\n/**\n * Write a single terminal SSE `error` frame and close the response.\n *\n * Used when a streaming route is recognized but has no registered source — the\n * client gets one well-formed `data: {kind:'error',…}` frame on the\n * `text/event-stream` body (not a JSON error envelope, which a streaming client\n * is not parsing for), then a clean close.\n *\n * @param res - The server response.\n * @param domain - The streaming route domain (for the error message).\n * @param operation - The streaming route operation (for the error message).\n * @param requestId - The request id stamped on the frame.\n */\nfunction writeStreamError(\n res: ServerResponse,\n domain: string,\n operation: string,\n requestId: string,\n): void {\n const frame: GatewayStreamEvent = {\n kind: 'error',\n seq: 0,\n error: {\n code: 'E_HTTP_NO_STREAM_SOURCE',\n message: `no streaming source registered for '${domain}/${operation}'`,\n },\n requestId,\n };\n res.writeHead(200, { ...SSE_HEADERS });\n res.end(encodeStreamEvent(frame));\n}\n\n/**\n * Handle a `GET /v1/<domain>/<operation>` streaming request: resolve the\n * registered {@link GatewayStreamSource}, open a `text/event-stream`, and pipe\n * its {@link GatewayStreamEvent} frames through the abort-safe\n * {@link createSseStream} builder until the source completes or the client\n * disconnects.\n *\n * The request's `close` is bridged to an {@link AbortController} so a client\n * disconnect closes the stream leak-free (the source's teardown runs exactly\n * once). The web {@link ReadableStream} the builder produces is adapted onto the\n * `node:http` response via {@link Readable.fromWeb}. Secrets never touch the\n * wire — the source resolves any credential server-side before emitting.\n *\n * @param req - The inbound request.\n * @param res - The outbound response.\n * @param domain - The resolved streaming-route domain.\n * @param operation - The resolved streaming-route operation.\n * @param log - The adapter's pino logger.\n */\nasync function handleStreamRequest(\n req: IncomingMessage,\n res: ServerResponse,\n domain: string,\n operation: string,\n log: ReturnType<typeof getLogger>,\n): Promise<void> {\n const requestId = randomUUID();\n const source = resolveStreamSource(domain, operation);\n if (source === undefined) {\n writeStreamError(res, domain, operation, requestId);\n log.warn({ domain, operation }, 'sse stream has no registered source');\n return;\n }\n\n // Bridge client disconnect → abort so createSseStream tears the source down.\n const controller = new AbortController();\n const onClose = (): void => controller.abort();\n req.once('close', onClose);\n\n const context: StreamSourceContext = {\n domain,\n operation,\n params: parseStreamParams(req.url),\n requestId,\n };\n\n res.writeHead(200, { ...SSE_HEADERS });\n\n const webStream = createSseStream((emitter) => source(emitter, context), controller.signal);\n\n // Drain the web ReadableStream's reader directly onto the node response. We do\n // NOT use `Readable.fromWeb` here: its DOM-typed `ReadableStream<any>` clashes\n // with the global web-streams `ReadableStream<Uint8Array>` over ArrayBuffer\n // variance under `tsup --dts`. Reading the reader manually keeps the types\n // exact and gives us a single place to honor backpressure + abort.\n const reader = webStream.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (res.writableEnded) break;\n // Honor backpressure: if the kernel buffer is full, wait for `drain`.\n if (!res.write(value)) {\n await new Promise<void>((resolve) => res.once('drain', resolve));\n }\n }\n } catch (err) {\n log.warn({ err, domain, operation }, 'sse stream pipe failed');\n } finally {\n reader.releaseLock();\n req.removeListener('close', onClose);\n if (!controller.signal.aborted) controller.abort();\n if (!res.writableEnded) res.end();\n log.debug({ domain, operation, requestId }, 'sse stream closed');\n }\n}\n\n/**\n * Handle a single inbound HTTP request: parse the route, read + JSON-parse the\n * body, route it through the gateway handler, and serialize the LAFS response.\n *\n * All failure modes are rendered as LAFS envelopes — a bad route or malformed\n * body is rejected at the edge (handler never invoked); a thrown handler error\n * is already trapped inside {@link routeUnary} as a `500`. The function never\n * throws and never calls `process.exit`.\n *\n * @param handler - The injected transport-neutral gateway handler.\n * @param req - The inbound request.\n * @param res - The outbound response.\n * @param log - The adapter's pino logger.\n */\nasync function handleHttpRequest(\n handler: GatewayHandler,\n req: IncomingMessage,\n res: ServerResponse,\n log: ReturnType<typeof getLogger>,\n): Promise<void> {\n const route = parseHttpRoute(req.method, req.url);\n if (!route.ok) {\n writeEdgeError(res, route.status, route.code, route.message);\n return;\n }\n if (route.kind === 'health') {\n writeHealth(res);\n log.debug({ route: 'health' }, 'http health probe served');\n return;\n }\n if (route.kind === 'stream') {\n await handleStreamRequest(req, res, route.domain, route.operation, log);\n return;\n }\n\n let params: Record<string, unknown> | undefined;\n try {\n const raw = await readBody(req);\n if (raw.trim().length > 0) {\n const parsed: unknown = JSON.parse(raw);\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n writeEdgeError(res, 400, 'E_HTTP_BAD_REQUEST', 'request body must be a JSON object');\n return;\n }\n params = parsed as Record<string, unknown>;\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n writeEdgeError(res, 400, 'E_HTTP_BAD_REQUEST', `invalid request body: ${message}`);\n return;\n }\n\n const unary: HttpUnaryRequest = {\n gateway: route.gateway,\n domain: route.domain,\n operation: route.operation,\n params,\n };\n const result = await routeUnary(handler, unary);\n res.writeHead(result.status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result.body));\n log.debug(\n {\n gateway: route.gateway,\n domain: route.domain,\n operation: route.operation,\n status: result.status,\n },\n 'http request routed',\n );\n}\n\n/**\n * Start a `node:http` server that hosts the gateway over HTTP for external\n * clients.\n *\n * Binds {@link HttpServerOptions.port} on {@link HttpServerOptions.host}\n * (default loopback), routes every `POST /<gateway>/<domain>/<operation>`\n * through the injected {@link GatewayHandler} via {@link routeUnary}, and\n * resolves once listening. The returned {@link HttpServerHandle} exposes an\n * idempotent `close()` for deterministic teardown (daemon shutdown, tests). The\n * adapter never calls `process.exit`.\n *\n * The caller assembles the handler (via `createGatewayHandler` with its domain\n * handlers + middleware) and injects it here — this server never builds\n * handlers itself, keeping the runtime free of any `@cleocode/cleo` dependency.\n *\n * @param handler - The transport-neutral gateway handler to route through.\n * @param opts - Bind coordinates.\n * @returns A promise resolving to the live {@link HttpServerHandle}.\n */\nexport function startHttpServer(\n handler: GatewayHandler,\n opts: HttpServerOptions,\n): Promise<HttpServerHandle> {\n const log = getLogger('gateway-http');\n const host = opts.host ?? '127.0.0.1';\n\n const server = createServer((req, res) => {\n handleHttpRequest(handler, req, res, log).catch((err: unknown) => {\n // handleHttpRequest traps its own failures; this guards the wire path.\n const message = err instanceof Error ? err.message : String(err);\n log.warn({ err }, 'http request handling failed');\n if (!res.headersSent) {\n writeEdgeError(res, 500, 'E_HTTP_INTERNAL', message);\n } else if (!res.writableEnded) {\n res.end();\n }\n });\n });\n\n // Attach the WS terminal/PTY endpoint (T11922) to the SAME server's `'upgrade'`\n // event when configured — the WS capability lives on the existing listener\n // (AC1), gated at the edge (AC4) and torn down with the server (AC3).\n const wsPty = opts.wsPty !== undefined ? attachWsPtyEndpoint(server, opts.wsPty) : undefined;\n\n return new Promise<HttpServerHandle>((resolve, reject) => {\n server.once('error', reject);\n server.listen(opts.port, host, () => {\n server.removeListener('error', reject);\n const address = server.address();\n const boundPort = typeof address === 'object' && address !== null ? address.port : opts.port;\n log.info(\n { host, port: boundPort, wsPty: wsPty !== undefined },\n 'http gateway server listening',\n );\n\n let closed = false;\n const handle: HttpServerHandle = {\n server,\n port: boundPort,\n host,\n close(): Promise<void> {\n if (closed) return Promise.resolve();\n closed = true;\n // Tear down every live WS-PTY session + detach the upgrade listener\n // BEFORE closing the HTTP server, so a hung terminal cannot block\n // shutdown (deterministic teardown — AC3).\n wsPty?.close();\n return new Promise<void>((res) => {\n server.close(() => res());\n // Drop keep-alive connections so close() resolves promptly.\n server.closeIdleConnections();\n });\n },\n };\n resolve(handle);\n });\n });\n}\n","/**\n * HTTP transport adapter — serves gateway unary + SSE over HTTP.\n *\n * The fourth and final gateway transport (after CLI, MCP, RPC). Unlike the\n * connection-oriented RPC/MCP adapters, the HTTP adapter is framework-agnostic\n * by design: it does NOT bind a port or own a `Request`/`Response` lifecycle.\n * Instead it exposes two pure routing primitives the embedder (a SvelteKit\n * route handler, the daemon's HTTP server, a test harness) calls directly:\n *\n * - {@link routeUnary} — one POST → `source: 'http'` {@link DispatchRequest}\n * → injected {@link GatewayHandler} → `{ status, body }` (LAFS envelope).\n * - the SSE primitives in `./sse.js` (`createSseStream`) for streaming ops.\n *\n * This keeps the runtime free of any HTTP-server / SvelteKit / `@cleocode/cleo`\n * dependency: the embedder owns wire concerns (parsing the URL into a\n * `(gateway, domain, operation)` triple, setting headers, `process.exit`,\n * error-render at the edge), exactly mirroring how the CLI adapter wraps the\n * same handler. There is NO `process.exit` and NO error-render in the handlers\n * (R3 contract); the adapter never calls `process.exit` at all.\n *\n * The request `source` is forced to `'http'` regardless of what the embedder\n * passes, so a client cannot impersonate another transport.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/gateway/http\n *\n * @task T11450\n * @epic T11254\n * @saga T11243\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { DispatchRequest, DispatchResponse } from '@cleocode/contracts/gateway';\nimport type { GatewayHandler } from '../index.js';\nimport type { HttpUnaryRequest, HttpUnaryResult } from './types.js';\n\n/**\n * Map a LAFS dispatch error code to an HTTP status code.\n *\n * The canonical machine-readable error code (`E_NOT_FOUND`, `E_VALIDATION_…`,\n * `E_FORBIDDEN`, …) is the source of truth; the numeric LAFS exit code is a\n * secondary hint. Unknown error codes fall back to `500`. A successful envelope\n * is always `200`.\n *\n * @param response - The dispatch response.\n * @returns The HTTP status the embedder should set.\n */\nexport function statusForResponse(response: DispatchResponse): number {\n if (response.success) return 200;\n const code = response.error?.code ?? '';\n if (code === 'E_NOT_FOUND' || code.endsWith('_NOT_FOUND')) return 404;\n if (code === 'E_UNAUTHORIZED' || code === 'E_AUTH_REQUIRED') return 401;\n if (code === 'E_FORBIDDEN') return 403;\n if (code === 'E_RATE_LIMITED' || code === 'E_RATE_LIMIT_EXCEEDED') return 429;\n if (\n code === 'E_VALIDATION' ||\n code.startsWith('E_VALIDATION') ||\n code === 'E_INVALID_INPUT' ||\n code === 'E_BAD_REQUEST'\n ) {\n return 400;\n }\n if (code === 'E_NOT_IMPLEMENTED' || code === 'E_UNSUPPORTED') return 501;\n return 500;\n}\n\n/**\n * Route a single HTTP unary request through the gateway handler.\n *\n * Builds a `source: 'http'` {@link DispatchRequest} from the embedder-resolved\n * `(gateway, domain, operation, params)` triple and delegates to the injected\n * {@link GatewayHandler}. A thrown handler error is trapped and rendered as a\n * `500` LAFS error envelope — never propagated as an unhandled rejection or\n * `process.exit`. The `source` is always forced to `'http'`.\n *\n * @param handler - The injected transport-neutral gateway handler.\n * @param req - The embedder-resolved unary request coordinates + body params.\n * @returns The HTTP status + LAFS response body for the embedder to serialize.\n */\nexport async function routeUnary(\n handler: GatewayHandler,\n req: HttpUnaryRequest,\n): Promise<HttpUnaryResult> {\n const requestId = req.requestId ?? randomUUID();\n const request: DispatchRequest = {\n gateway: req.gateway,\n domain: req.domain,\n operation: req.operation,\n params: req.params,\n // Force the transport of origin — a client cannot impersonate cli/mcp/rpc.\n source: 'http',\n requestId,\n sessionId: req.sessionId,\n };\n\n try {\n const body = await handler.handle(request);\n return { status: statusForResponse(body), body };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const body: DispatchResponse = {\n meta: {\n gateway: req.gateway,\n domain: req.domain,\n operation: req.operation,\n timestamp: new Date().toISOString(),\n duration_ms: 0,\n source: 'http',\n requestId,\n },\n success: false,\n error: { code: 'E_HTTP_INTERNAL', message },\n };\n return { status: 500, body };\n }\n}\n","/**\n * Server-Sent Events (SSE) wire primitives for the `@cleocode/runtime/gateway/http`\n * adapter.\n *\n * The HTTP transport is the only gateway transport that streams: long-running\n * and subscription operations emit a sequence of {@link GatewayStreamEvent}\n * frames terminated by a `done` or `error` frame. This module owns ONLY the SSE\n * wire encoding + the `ReadableStream` lifecycle plumbing; the actual frame\n * production (polling a DB, tailing a job, etc.) lives in the caller's async\n * source. Keeping the encode/stream concern here lets BOTH the gateway-routed\n * streaming ops AND the existing Studio SSE endpoints (which carry their own\n * domain event shapes) share one tested, leak-free stream builder.\n *\n * Two encoders are provided so an existing stream can adopt this plumbing\n * WITHOUT changing its observed bytes:\n * - {@link encodeStreamEvent} — the canonical `data: <json>\\n\\n` form for\n * {@link GatewayStreamEvent} frames (gateway-routed streaming ops).\n * - {@link encodeSseFrame} — a generic `event:`/`id:`/`data:` writer for any\n * payload, so endpoints with a bespoke wire (a named `event:` field, a\n * `data:`-only frame) keep an IDENTICAL byte stream while still routing their\n * lifecycle through the shared, abort-safe builder.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/gateway/http/sse\n *\n * @task T11450\n * @epic T11254\n * @saga T11243\n */\n\nimport type { GatewayStreamEvent } from '@cleocode/contracts/gateway';\n\n/** SSE field/record delimiter constants (the SSE wire is line-oriented). */\nconst LF = '\\n';\n/** A blank line terminates an SSE record (`\\n\\n`). */\nconst RECORD_END = '\\n\\n';\n\n/**\n * One generic SSE frame: an optional named `event`, an optional `id`, and a\n * `data` payload that is JSON-serialized.\n *\n * This is intentionally NOT {@link GatewayStreamEvent} — it is the raw wire\n * shape so a bespoke endpoint can reproduce its exact existing bytes (e.g. a\n * named `event: task-updated` line, or a `data:`-only frame with no `event`).\n */\nexport interface SseFrame {\n /** Optional SSE `event:` field name (omitted → a `data:`-only frame). */\n event?: string;\n /** Optional SSE `id:` field for client-side last-event-id resumption. */\n id?: string;\n /** The frame payload; JSON-serialized into the `data:` field. */\n data: unknown;\n}\n\n/**\n * Encode a generic {@link SseFrame} to its SSE wire bytes.\n *\n * Field order is `event:` (if present) → `id:` (if present) → `data:`, matching\n * the SSE spec record layout, then a blank line. When `event` is omitted the\n * frame is a `data:`-only record — byte-identical to a handwritten\n * `data: <json>\\n\\n`.\n *\n * @param frame - The frame to serialize.\n * @returns The SSE record as a string (UTF-8 caller-encoded).\n */\nexport function encodeSseFrame(frame: SseFrame): string {\n let out = '';\n if (frame.event !== undefined) out += `event: ${frame.event}${LF}`;\n if (frame.id !== undefined) out += `id: ${frame.id}${LF}`;\n out += `data: ${JSON.stringify(frame.data)}${RECORD_END}`;\n return out;\n}\n\n/**\n * Encode a canonical {@link GatewayStreamEvent} as a `data:`-only SSE record.\n *\n * Gateway-routed streaming ops carry the discriminator + sequence inside the\n * JSON payload (`{ kind, seq, data, error, requestId }`), so no named SSE\n * `event:` field is used — the client demultiplexes on `kind`/`seq`. The\n * monotonic `seq` is also surfaced as the SSE `id:` so a reconnecting client can\n * send `Last-Event-ID`.\n *\n * @param frame - The gateway stream event to serialize.\n * @returns The SSE record as a string.\n */\nexport function encodeStreamEvent(frame: GatewayStreamEvent): string {\n return encodeSseFrame({ id: String(frame.seq), data: frame });\n}\n\n/** The standard SSE response headers (no caching, no proxy buffering). */\nexport const SSE_HEADERS: Readonly<Record<string, string>> = Object.freeze({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n});\n\n/**\n * A push-style SSE controller handed to an {@link SseSource} so it can emit\n * frames and close the stream without touching the underlying\n * `ReadableStreamDefaultController`.\n */\nexport interface SseEmitter {\n /** Emit one generic SSE frame; a no-op once the stream is closed. */\n send(frame: SseFrame): void;\n /** Emit one canonical {@link GatewayStreamEvent}; a no-op once closed. */\n sendStreamEvent(frame: GatewayStreamEvent): void;\n /**\n * Emit a pre-encoded SSE record verbatim; a no-op once closed. For endpoints\n * that own their own wire encoder (e.g. a bespoke `data: <json>\\n\\n`) and want\n * to keep emitting byte-identical records while still routing lifecycle\n * through this builder.\n */\n sendRaw(record: string): void;\n /** Whether the stream has been closed (client disconnect or `close()`). */\n readonly closed: boolean;\n /** Close the stream; idempotent. */\n close(): void;\n}\n\n/**\n * A teardown callback returned by an {@link SseSource}, invoked exactly once when\n * the stream ends (client disconnect, `emitter.close()`, or producer completion).\n */\nexport type SseTeardown = () => void;\n\n/**\n * A frame source for {@link createSseStream}. Receives an {@link SseEmitter} and\n * MAY return an {@link SseTeardown} callback (return nothing to skip teardown).\n *\n * The source is started SYNCHRONOUSLY inside `ReadableStream.start`, mirroring\n * the existing Studio endpoints (which schedule timers inside `start`). A source\n * may push frames immediately and/or schedule async work.\n */\nexport type SseSource = (emitter: SseEmitter) => SseTeardown | undefined;\n\n/**\n * Build a `text/event-stream` {@link ReadableStream} driven by an\n * {@link SseSource}.\n *\n * This is the shared, abort-safe stream builder every SSE endpoint routes\n * through. It guarantees:\n * - frames are dropped (never throw) after the stream closes;\n * - the source's teardown callback runs exactly once;\n * - an optional {@link AbortSignal} (the request's) closes the stream on client\n * disconnect, with its listener removed on teardown.\n *\n * The byte output is fully controlled by what the source emits — so an endpoint\n * adopting this builder reproduces its prior wire exactly by emitting the same\n * frames in the same order.\n *\n * @param source - The frame producer.\n * @param signal - Optional request abort signal that closes the stream on\n * client disconnect.\n * @returns A ReadableStream of UTF-8 SSE bytes for a `Response` body.\n */\nexport function createSseStream(\n source: SseSource,\n signal?: AbortSignal,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n\n return new ReadableStream<Uint8Array>({\n start(controller): void {\n let closed = false;\n /** Set once the source returns; invoked exactly once when the stream ends. */\n let teardown: (() => void) | undefined;\n /** Guards against running the source teardown more than once. */\n let teardownDone = false;\n /** True while `source(emitter)` is still executing synchronously. */\n let sourceRunning = false;\n\n const onAbort = (): void => emitter.close();\n\n /** Invoke the source teardown at most once (no-op until the source returns). */\n const runTeardown = (): void => {\n if (teardownDone) return;\n teardownDone = true;\n if (typeof teardown === 'function') teardown();\n };\n\n const emitter: SseEmitter = {\n get closed(): boolean {\n return closed;\n },\n send(frame: SseFrame): void {\n if (closed) return;\n try {\n controller.enqueue(encoder.encode(encodeSseFrame(frame)));\n } catch {\n emitter.close();\n }\n },\n sendStreamEvent(frame: GatewayStreamEvent): void {\n if (closed) return;\n try {\n controller.enqueue(encoder.encode(encodeStreamEvent(frame)));\n } catch {\n emitter.close();\n }\n },\n sendRaw(record: string): void {\n if (closed) return;\n try {\n controller.enqueue(encoder.encode(record));\n } catch {\n emitter.close();\n }\n },\n close(): void {\n if (closed) return;\n closed = true;\n if (signal) signal.removeEventListener('abort', onAbort);\n // If the source closed synchronously mid-body, it has not yet returned\n // its teardown — defer it to the post-return reconciliation below.\n if (!sourceRunning) runTeardown();\n try {\n controller.close();\n } catch {\n // already closed by the runtime — nothing to do.\n }\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n // Client already gone before we started — close immediately. The\n // source is never invoked, so there is no teardown to run.\n closed = true;\n teardownDone = true;\n try {\n controller.close();\n } catch {\n // already closed.\n }\n return;\n }\n signal.addEventListener('abort', onAbort);\n }\n\n sourceRunning = true;\n const returned = source(emitter);\n teardown = typeof returned === 'function' ? returned : undefined;\n sourceRunning = false;\n\n // The source may have called `emitter.close()` synchronously before\n // returning its teardown; run it now that the callback is in hand.\n if (closed) runTeardown();\n },\n });\n}\n","/**\n * Streaming-operation source adapters for the gateway HTTP/SSE transport (T11921).\n *\n * A streaming operation (`OperationDef.streaming === true`, served as\n * `GET /v1/<domain>/<operation>`) needs a *producer*: an async source that tails\n * an event origin (the agent/worker/board lifecycle bus) and feeds\n * {@link GatewayStreamEvent} frames into the shared, abort-safe\n * {@link createSseStream} builder. This module owns ONLY that producer concern —\n * the wire encoding + stream lifecycle live in `./sse.ts`, and the route parsing\n * lives in `./listen.ts`.\n *\n * The runtime is deliberately DB-free (the runtime invariant — no drizzle, no\n * `@cleocode/cleo`), so a source here MUST NOT open a store directly. Instead the\n * listener resolves a source by `<domain>.<operation>` key from the\n * {@link STREAM_SOURCES} registry; an embedder that owns a richer origin (the\n * daemon, a SvelteKit route) can inject its own source into the listener at\n * boot. The default source shipped here is a bounded tick/heartbeat producer that\n * proves the pipe end-to-end without reaching across the package boundary — and\n * is exactly what the in-process test exercises.\n *\n * Secrets never touch the wire: a source emits only the public lifecycle payload\n * it is handed; credential resolution (if any) happens server-side before the\n * frame is built, exactly as it does for a unary dispatch.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/gateway/http/stream-sources\n *\n * @task T11921\n * @epic T11769\n * @saga T10400\n */\n\nimport type { GatewayStreamEvent } from '@cleocode/contracts/gateway';\nimport type { SseEmitter } from './sse.js';\n\n/** Default interval (ms) between emitted stream frames — the keepalive cadence. */\nconst DEFAULT_TICK_INTERVAL_MS = 1000;\n\n/** Hard upper bound on emitted frames so an unbounded `ticks` cannot run forever. */\nconst MAX_TICKS = 86_400;\n\n/**\n * The bind coordinates a {@link GatewayStreamSource} receives: the resolved\n * `(domain, operation)` of the streaming route, the already-validated request\n * params, and the request id (so emitted frames correlate back to the origin\n * request).\n */\nexport interface StreamSourceContext {\n /** The canonical domain segment of the streaming route. */\n domain: string;\n /** The operation segment of the streaming route. */\n operation: string;\n /** The decoded query params for the streaming request. */\n params: Record<string, unknown>;\n /** The request id stamped on every emitted {@link GatewayStreamEvent}. */\n requestId: string;\n}\n\n/**\n * A teardown callback a {@link GatewayStreamSource} MAY return; invoked exactly\n * once when the stream ends (client disconnect, terminal frame, completion).\n */\nexport type StreamSourceTeardown = () => void;\n\n/**\n * A producer for one streaming operation. Receives an {@link SseEmitter}\n * (`sendStreamEvent` emits one canonical {@link GatewayStreamEvent}) plus the\n * resolved {@link StreamSourceContext}, and MAY return a teardown callback.\n *\n * The source is started synchronously inside the stream builder, mirroring the\n * Studio SSE endpoints; it may push frames immediately and/or schedule async\n * work. It MUST drop frames once `emitter.closed` is true (the emitter already\n * guards this) and MUST clean up timers/listeners in its teardown so a client\n * disconnect closes the stream leak-free.\n */\nexport type GatewayStreamSource = (\n emitter: SseEmitter,\n context: StreamSourceContext,\n) => StreamSourceTeardown | undefined;\n\n/**\n * Coerce a `ticks` param into a bounded, non-negative integer frame budget.\n *\n * `undefined` / non-finite → open-ended (`Infinity`, capped at {@link MAX_TICKS}\n * worth of timer fires); a finite value is floored to `[0, MAX_TICKS]`.\n *\n * @param raw - The raw `ticks` param value.\n * @returns The resolved frame budget.\n */\nfunction resolveTickBudget(raw: unknown): number {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return MAX_TICKS;\n if (raw <= 0) return 0;\n return Math.min(Math.floor(raw), MAX_TICKS);\n}\n\n/**\n * The default `orchestrate.events` source: a bounded tick/heartbeat producer.\n *\n * Emits a monotonic sequence of `data` {@link GatewayStreamEvent} frames at\n * {@link DEFAULT_TICK_INTERVAL_MS}, then a terminal `done` frame once the\n * `ticks` budget is exhausted; a client disconnect tears the timer down before\n * then. It carries no DB handle and no secret — each frame's payload is a public\n * heartbeat (`{ tick, ts }`) standing in for an agent/worker/board lifecycle\n * event until the daemon injects its real origin-tailing source.\n *\n * A first frame is emitted SYNCHRONOUSLY so a client (and the test) observes a\n * well-formed `data:` frame immediately, without waiting a full interval.\n *\n * @param emitter - The SSE emitter for this stream.\n * @param context - The resolved streaming-route context.\n * @returns A teardown that clears the tick timer.\n */\nexport const tickStreamSource: GatewayStreamSource = (emitter, context) => {\n const budget = resolveTickBudget(context.params.ticks);\n let seq = 0;\n\n /** Emit one `data` frame; the emitter drops it after close (never throws). */\n const emitData = (): void => {\n const frame: GatewayStreamEvent = {\n kind: 'data',\n seq,\n data: { tick: seq, ts: new Date().toISOString() },\n requestId: context.requestId,\n };\n emitter.sendStreamEvent(frame);\n seq += 1;\n };\n\n /** Emit the terminal `done` frame and close the stream. */\n const emitDone = (): void => {\n const frame: GatewayStreamEvent = {\n kind: 'done',\n seq,\n data: { ticks: seq, ts: new Date().toISOString() },\n requestId: context.requestId,\n };\n emitter.sendStreamEvent(frame);\n emitter.close();\n };\n\n // Emit the first frame synchronously so a consumer sees a frame immediately.\n if (budget <= 0) {\n emitDone();\n return undefined;\n }\n emitData();\n\n const interval = setInterval(() => {\n if (emitter.closed) {\n clearInterval(interval);\n return;\n }\n if (seq >= budget) {\n clearInterval(interval);\n emitDone();\n return;\n }\n emitData();\n }, DEFAULT_TICK_INTERVAL_MS);\n\n // Teardown: stop the timer when the stream ends (disconnect, done, close).\n return () => clearInterval(interval);\n};\n\n/**\n * The default `tasks.subscribe` source: a bounded board-event heartbeat producer\n * (T11785 · epic T11556).\n *\n * Emits `data` {@link GatewayStreamEvent} frames standing in for task/board\n * lifecycle events (`created`/`updated`/`deleted`) until the `ticks` budget is\n * exhausted, then a terminal `done` frame; a client disconnect tears the timer\n * down before then. Each frame carries the optional `root` scope (a saga/parent\n * task ID) so a consumer can correlate the stream to the board slice it\n * subscribed to. Like {@link tickStreamSource} it carries NO DB handle and NO\n * secret — it proves the SSE pipe end-to-end without reaching across the runtime\n * package boundary, and the daemon injects its real store-tailing source via\n * {@link registerStreamSource} at boot.\n *\n * @param emitter - The SSE emitter for this stream.\n * @param context - The resolved streaming-route context (carries `params.root`).\n * @returns A teardown that clears the heartbeat timer.\n */\nexport const taskBoardStreamSource: GatewayStreamSource = (emitter, context) => {\n const budget = resolveTickBudget(context.params.ticks);\n const root = typeof context.params.root === 'string' ? context.params.root : null;\n let seq = 0;\n\n /** Emit one board-event `data` frame; dropped after close (never throws). */\n const emitData = (): void => {\n const frame: GatewayStreamEvent = {\n kind: 'data',\n seq,\n data: { scope: 'tasks.board', root, event: 'heartbeat', ts: new Date().toISOString() },\n requestId: context.requestId,\n };\n emitter.sendStreamEvent(frame);\n seq += 1;\n };\n\n /** Emit the terminal `done` frame and close the stream. */\n const emitDone = (): void => {\n const frame: GatewayStreamEvent = {\n kind: 'done',\n seq,\n data: { scope: 'tasks.board', root, frames: seq, ts: new Date().toISOString() },\n requestId: context.requestId,\n };\n emitter.sendStreamEvent(frame);\n emitter.close();\n };\n\n if (budget <= 0) {\n emitDone();\n return undefined;\n }\n // Emit the first frame synchronously so a consumer sees a frame immediately.\n emitData();\n\n const interval = setInterval(() => {\n if (emitter.closed) {\n clearInterval(interval);\n return;\n }\n if (seq >= budget) {\n clearInterval(interval);\n emitDone();\n return;\n }\n emitData();\n }, DEFAULT_TICK_INTERVAL_MS);\n\n return () => clearInterval(interval);\n};\n\n/**\n * The registry of streaming-operation sources, keyed by `<domain>.<operation>`.\n *\n * The listener resolves a source from this map when a `GET /v1/<domain>/<operation>`\n * matches a registered streaming op. An embedder MAY override an entry (or add a\n * domain-specific origin-tailing source) via {@link registerStreamSource} before\n * starting the server; until then `orchestrate.events` (and `tasks.subscribe`)\n * are served by their default tick sources so the pipe is exercisable with zero\n * injection.\n */\nconst STREAM_SOURCES = new Map<string, GatewayStreamSource>([\n ['orchestrate.events', tickStreamSource],\n // T11785 (epic T11556) — the FIRST streaming task op; default board heartbeat.\n ['tasks.subscribe', taskBoardStreamSource],\n]);\n\n/**\n * Build the `<domain>.<operation>` registry key for a streaming source.\n *\n * @param domain - The canonical domain.\n * @param operation - The operation name.\n * @returns The composite registry key.\n */\nfunction streamSourceKey(domain: string, operation: string): string {\n return `${domain}.${operation}`;\n}\n\n/**\n * Register (or override) the {@link GatewayStreamSource} for a streaming op.\n *\n * Lets an embedder that owns a richer event origin (the daemon tailing the\n * agent/worker/board lifecycle bus, a SvelteKit route reading a store) replace\n * the default tick source for a given `(domain, operation)` without changing the\n * listener. Idempotent — a later call wins.\n *\n * @param domain - The canonical domain of the streaming op.\n * @param operation - The operation name of the streaming op.\n * @param source - The producer to serve this streaming route with.\n */\nexport function registerStreamSource(\n domain: string,\n operation: string,\n source: GatewayStreamSource,\n): void {\n STREAM_SOURCES.set(streamSourceKey(domain, operation), source);\n}\n\n/**\n * Resolve the {@link GatewayStreamSource} registered for a streaming op.\n *\n * @param domain - The canonical domain of the streaming op.\n * @param operation - The operation name of the streaming op.\n * @returns The registered source, or `undefined` when none is registered (the\n * listener then closes the stream with a single terminal `error` frame).\n */\nexport function resolveStreamSource(\n domain: string,\n operation: string,\n): GatewayStreamSource | undefined {\n return STREAM_SOURCES.get(streamSourceKey(domain, operation));\n}\n","/**\n * WebSocket terminal/PTY endpoint on the `/v1` gateway HTTP listener (T11922 ·\n * M5 realtime transport, WS half).\n *\n * The SSE half (T11921) gives the gateway a SERVER→CLIENT push stream; this\n * module adds the BIDIRECTIONAL realtime transport an interactive TUI / terminal\n * session needs: a WebSocket upgrade on the SAME `node:http` listener\n * ({@link import('./listen.js').startHttpServer}) that bridges a PTY both\n * directions over WS frames.\n *\n * Design constraints (all four ACs of T11922):\n *\n * - **AC1 — upgrade on the existing listener.** The WS endpoint is NOT a second\n * server: it attaches to the daemon's existing HTTP server via its\n * `'upgrade'` event ({@link attachWsPtyEndpoint}), so it inherits the loopback\n * bind + the daemon's lifecycle. The endpoint path is `/v1/terminal/pty`.\n * - **AC2 — bidirectional PTY bridge with backpressure.** Inbound WS frames are\n * written to the PTY stdin; PTY output is framed back to the client. A control\n * frame (`{type:'resize',cols,rows}`) resizes the PTY. Backpressure is honored\n * on the socket write path (pause-on-`false`-write, resume on `'drain'`).\n * - **AC3 — leak-free, deterministic teardown.** Every open/close/error/abort\n * path routes through ONE idempotent `teardown()` that kills the PTY, removes\n * its listeners, and ends the socket exactly once. A server `close()` tears\n * down every live session.\n * - **AC4 — auth/origin enforced at the upgrade edge; no secrets on the wire.**\n * The upgrade is rejected with a `401`/`403` BEFORE any PTY is spawned unless\n * it comes from loopback AND (when a token is configured) presents the bearer\n * token AND (when an origin allowlist is configured) presents an allowed\n * `Origin`. The spawned PTY runs under a MINIMAL, explicitly-constructed env\n * ({@link scrubPtyEnv}) — the daemon's secret-bearing `process.env` is NEVER\n * inherited, so a secret cannot be exfiltrated through the terminal stream.\n *\n * To keep the **publish surface clean** (D11142 dependency-discipline):\n * - There is NO `ws` dependency. The minimal RFC 6455 server framing needed\n * (accept handshake, decode masked client frames, encode unmasked server\n * frames, close/ping/pong) is implemented over the raw `node:net` socket the\n * `'upgrade'` event hands us. This adds ZERO weight to `@cleocode/runtime`.\n * - `node-pty` is an OPTIONAL, dynamically-loaded dependency (same pattern as\n * {@link import('../../../../core/src/llm/pi/gondolin-loader.js')} and\n * `packages/core/src/tools/pty.ts`): the npm specifier is held in a variable,\n * its shape is declared LOCALLY (no `import type`), and absence is a graceful\n * close frame (\"PTY backend unavailable\") — NOT a crash. The endpoint always\n * upgrades; it bridges a PTY only when the backend is present.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/gateway/http/ws-pty\n *\n * @task T11922\n * @epic T11769\n * @saga T10400\n * @see ./sse.ts — the SSE half (server→client push) on the same listener\n */\n\nimport { createHash } from 'node:crypto';\nimport type { IncomingMessage } from 'node:http';\nimport { delimiter } from 'node:path';\nimport type { Duplex } from 'node:stream';\nimport { getLogger } from '@cleocode/core';\n\n/** The canonical WS terminal/PTY endpoint path on the `/v1` facade. */\nexport const WS_PTY_PATH = '/v1/terminal/pty';\n\n/** The RFC 6455 GUID concatenated with `Sec-WebSocket-Key` to derive the accept hash. */\nconst WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';\n\n/** Default PTY geometry when the client does not request one. */\nconst DEFAULT_COLS = 80;\nconst DEFAULT_ROWS = 24;\n\n/** WS opcodes (RFC 6455 §5.2) the bridge handles. */\nconst OPCODE_TEXT = 0x1;\nconst OPCODE_BINARY = 0x2;\nconst OPCODE_CLOSE = 0x8;\nconst OPCODE_PING = 0x9;\nconst OPCODE_PONG = 0xa;\n\n/** Loopback hosts an upgrade may originate from (AC4). */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);\n\n// ---------------------------------------------------------------------------\n// node-pty optional-dep loader — mirrors gondolin-loader.ts / tools/pty.ts.\n// The specifier is held in a variable; the consumed shape is declared LOCALLY\n// (no `import type` from the optional package) so the type-check + bundle pass\n// with node-pty uninstalled, and absence resolves `null` instead of throwing.\n// ---------------------------------------------------------------------------\n\n/**\n * The install hint surfaced to the client (as a WS close reason) when the PTY\n * backend is unavailable. `node-pty` is OPTIONAL — the endpoint still upgrades\n * and then closes cleanly with this reason rather than crashing the daemon.\n */\nexport const NODE_PTY_INSTALL_HINT =\n 'PTY backend unavailable: the optional \"node-pty\" package is not installed. ' +\n 'Install it with `pnpm add node-pty` (a native build) to enable interactive terminal sessions.';\n\n/** A disposable subscription handle returned by `node-pty`'s event registrars. */\ninterface PtyDisposable {\n dispose(): void;\n}\n\n/** The minimal structural shape of a spawned `node-pty` process the bridge uses. */\ninterface NodePtyProcess {\n /** Subscribe to interleaved stdout/stderr output. */\n onData(cb: (data: string) => void): PtyDisposable | undefined;\n /** Subscribe to process exit. */\n onExit(cb: (e: { exitCode: number; signal?: number }) => void): PtyDisposable | undefined;\n /** Write to the PTY stdin. */\n write(data: string): void;\n /** Resize the PTY. */\n resize(cols: number, rows: number): void;\n /** Kill the underlying process (idempotent host-side; signal optional). */\n kill(signal?: string): void;\n}\n\n/** The minimal structural shape of the `node-pty` module the bridge uses. */\ninterface NodePtyModule {\n spawn(\n file: string,\n args: readonly string[],\n options: {\n readonly name?: string;\n readonly cols?: number;\n readonly rows?: number;\n readonly cwd?: string;\n readonly env?: Record<string, string>;\n },\n ): NodePtyProcess;\n}\n\n/** A pluggable dynamic-importer (test seam). Defaults to the real `import()`. */\ntype DynamicImporter = (specifier: string) => Promise<unknown>;\n\n/** The npm specifier — held in a VARIABLE so it is never statically resolved. */\nconst NODE_PTY_SPECIFIER = 'node-pty';\n\n/** The real dynamic importer (specifier passed as an argument, never inlined). */\nconst realImporter: DynamicImporter = (specifier) => import(specifier);\n\n/** The active importer (swapped only by {@link __setWsPtyTestHooks}). */\nlet importer: DynamicImporter = realImporter;\n\n/**\n * Structurally validate a dynamically-imported candidate against the `spawn`\n * export the bridge consumes. Returns the typed module on a match, else `null`.\n */\nfunction shapeCheckPty(candidate: unknown): NodePtyModule | null {\n if (\n candidate !== null &&\n typeof candidate === 'object' &&\n typeof (candidate as { spawn?: unknown }).spawn === 'function'\n ) {\n return candidate as NodePtyModule;\n }\n return null;\n}\n\n/**\n * Attempt to lazily load `node-pty`. Returns `null` when the optional dep is not\n * installed, fails to load, or does not expose the `spawn` surface — so the\n * caller closes the WS cleanly with {@link NODE_PTY_INSTALL_HINT} instead of\n * crashing. NEVER throws.\n *\n * The import specifier is held in a variable (passed to the {@link importer}\n * seam) so bundlers / TS do not treat the missing optional dep as a hard,\n * statically-resolved dependency (same technique as the gondolin loader).\n *\n * @returns The shape-checked module, or `null` when unavailable.\n */\nexport async function loadNodePty(): Promise<NodePtyModule | null> {\n try {\n const mod: unknown = await importer(NODE_PTY_SPECIFIER);\n const candidate = (mod as { default?: unknown }).default ?? mod;\n return shapeCheckPty(candidate);\n } catch {\n return null;\n }\n}\n\n/**\n * Override the dynamic-importer (TESTS ONLY) so a unit test can deterministically\n * simulate \"node-pty absent\" (importer rejects) and \"node-pty present\" (importer\n * resolves a mock module with a fake `spawn`) WITHOUT touching the module graph\n * or building the native package. Call with no arguments to restore the real\n * importer.\n *\n * @param hooks - Partial override for the importer.\n * @internal\n */\nexport function __setWsPtyTestHooks(hooks?: { importer?: DynamicImporter }): void {\n importer = hooks?.importer ?? realImporter;\n}\n\n// ---------------------------------------------------------------------------\n// PTY environment scrub (AC4 — secrets never traverse the socket).\n//\n// The spawned PTY must NOT inherit the daemon's secret-bearing `process.env`\n// (`*_API_KEY`, `*_TOKEN`, `CLEO_VAULT_*`, OAuth headers, …) — a single `env`\n// line in an interactive terminal would otherwise exfiltrate them over the WS.\n// We build a MINIMAL, allowlist-only env with a pinned trusted PATH, mirroring\n// the doctrine of `@cleocode/core`'s `scrubSubprocessEnv` (kept inline so the\n// framework-agnostic runtime does not reach into core's tools subpath).\n// ---------------------------------------------------------------------------\n\n/** A fixed, trusted absolute `PATH` — never the (possibly poisoned) caller PATH. */\nconst TRUSTED_PATH = [\n '/usr/local/sbin',\n '/usr/local/bin',\n '/usr/sbin',\n '/usr/bin',\n '/sbin',\n '/bin',\n].join(delimiter);\n\n/**\n * Benign environment variables copied through from the parent when present.\n * These carry NO secrets and NO code-execution surface — locale, terminal type,\n * timezone, home/user identity, and the tmp dir. `PATH` is deliberately ABSENT\n * (pinned to {@link TRUSTED_PATH}); every secret / loader hook is ABSENT.\n */\nconst PASSTHROUGH_KEYS: readonly string[] = [\n 'HOME',\n 'USER',\n 'LOGNAME',\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TZ',\n 'TMPDIR',\n];\n\n/**\n * Build the MINIMAL environment the bridged PTY runs under (AC4).\n *\n * Starts EMPTY, copies only the small {@link PASSTHROUGH_KEYS} allowlist from the\n * parent, pins `PATH` to {@link TRUSTED_PATH}, and forces `TERM=xterm-256color`\n * for a sane terminal. The daemon's secrets are NEVER forwarded.\n *\n * @param parentEnv - The parent process environment (defaults to `process.env`).\n * @returns The scrubbed, secret-free env for the PTY.\n */\nexport function scrubPtyEnv(parentEnv: NodeJS.ProcessEnv = process.env): Record<string, string> {\n const env: Record<string, string> = { PATH: TRUSTED_PATH, TERM: 'xterm-256color' };\n for (const key of PASSTHROUGH_KEYS) {\n const value = parentEnv[key];\n if (typeof value === 'string' && value.length > 0) env[key] = value;\n }\n return env;\n}\n\n// ---------------------------------------------------------------------------\n// Minimal RFC 6455 server framing — no `ws` dependency.\n// ---------------------------------------------------------------------------\n\n/**\n * Derive the `Sec-WebSocket-Accept` response header value from a client\n * `Sec-WebSocket-Key` per RFC 6455 §4.2.2 (SHA-1 of key + GUID, base64).\n *\n * @param key - The client's `Sec-WebSocket-Key` header value.\n * @returns The base64 accept hash.\n */\nexport function computeAcceptKey(key: string): string {\n return createHash('sha1')\n .update(key + WS_GUID)\n .digest('base64');\n}\n\n/**\n * Encode a WS data frame (server→client, UNMASKED per RFC 6455 §5.1). Used for\n * `text` (PTY output) and `close` control frames.\n *\n * @param opcode - The WS opcode (e.g. {@link OPCODE_TEXT}).\n * @param payload - The frame payload bytes.\n * @returns The framed bytes ready to write to the socket.\n */\nexport function encodeWsFrame(opcode: number, payload: Buffer): Buffer {\n const len = payload.length;\n let header: Buffer;\n if (len < 126) {\n header = Buffer.alloc(2);\n header[1] = len;\n } else if (len < 65536) {\n header = Buffer.alloc(4);\n header[1] = 126;\n header.writeUInt16BE(len, 2);\n } else {\n header = Buffer.alloc(10);\n header[1] = 127;\n // High 32 bits are 0 for any realistic terminal frame.\n header.writeUInt32BE(Math.floor(len / 0x1_0000_0000), 2);\n header.writeUInt32BE(len >>> 0, 6);\n }\n header[0] = 0x80 | (opcode & 0x0f); // FIN + opcode\n return Buffer.concat([header, payload]);\n}\n\n/** Build an unmasked `close` control frame carrying a status code + UTF-8 reason. */\nfunction encodeCloseFrame(code: number, reason: string): Buffer {\n const reasonBytes = Buffer.from(reason, 'utf8').subarray(0, 123);\n const payload = Buffer.alloc(2 + reasonBytes.length);\n payload.writeUInt16BE(code, 0);\n reasonBytes.copy(payload, 2);\n return encodeWsFrame(OPCODE_CLOSE, payload);\n}\n\n/** One decoded inbound WS frame. */\ninterface DecodedFrame {\n /** The frame opcode. */\n opcode: number;\n /** The unmasked payload bytes. */\n payload: Buffer;\n /** Total bytes consumed from the input buffer (header + payload). */\n consumed: number;\n}\n\n/**\n * Decode a single inbound (client→server, MASKED) WS frame from a buffer.\n *\n * Returns `null` when the buffer does not yet hold a complete frame (the caller\n * waits for more bytes). Client frames MUST be masked (RFC 6455 §5.3); the mask\n * is applied to recover the payload. Fragmentation is not expected from a\n * terminal client, so a non-FIN frame is decoded as-is (its opcode handled by\n * the caller). NEVER throws on a partial buffer.\n *\n * @param buf - The accumulated inbound bytes.\n * @returns The decoded frame, or `null` when more bytes are needed.\n */\nexport function decodeWsFrame(buf: Buffer): DecodedFrame | null {\n if (buf.length < 2) return null;\n const opcode = buf[0] & 0x0f;\n const masked = (buf[1] & 0x80) !== 0;\n let len = buf[1] & 0x7f;\n let offset = 2;\n if (len === 126) {\n if (buf.length < offset + 2) return null;\n len = buf.readUInt16BE(offset);\n offset += 2;\n } else if (len === 127) {\n if (buf.length < offset + 8) return null;\n // Low 32 bits suffice for any realistic terminal frame.\n len = buf.readUInt32BE(offset + 4);\n offset += 8;\n }\n let mask: Buffer | undefined;\n if (masked) {\n if (buf.length < offset + 4) return null;\n mask = buf.subarray(offset, offset + 4);\n offset += 4;\n }\n if (buf.length < offset + len) return null;\n const raw = buf.subarray(offset, offset + len);\n const payload = Buffer.allocUnsafe(len);\n if (mask) {\n for (let i = 0; i < len; i++) payload[i] = raw[i] ^ mask[i & 3];\n } else {\n raw.copy(payload);\n }\n return { opcode, payload, consumed: offset + len };\n}\n\n// ---------------------------------------------------------------------------\n// Auth/origin gate (AC4) — enforced at the upgrade edge, before any PTY spawn.\n// ---------------------------------------------------------------------------\n\n/** Configuration for the WS terminal/PTY endpoint. */\nexport interface WsPtyOptions {\n /**\n * Optional bearer token an upgrade MUST present (as `Authorization: Bearer\n * <token>` or `?token=<token>`). When omitted, no token is required (loopback\n * is still enforced unconditionally).\n */\n token?: string;\n /**\n * Optional `Origin` allowlist. When non-empty, an upgrade MUST present an\n * `Origin` header whose value is in the set, else it is rejected `403`. When\n * omitted/empty, the `Origin` header is not checked (loopback already bounds\n * the surface).\n */\n allowedOrigins?: readonly string[];\n /** The shell launched for the PTY (default `process.env.SHELL` or `/bin/sh`). */\n shell?: string;\n /** Working directory for the PTY (default `process.cwd()`). */\n cwd?: string;\n}\n\n/** The decision of the upgrade-edge gate. */\nexport type GateDecision = { ok: true } | { ok: false; status: number; reason: string };\n\n/**\n * Authorize a WS upgrade at the edge (AC4) — BEFORE any PTY is spawned.\n *\n * Enforced unconditionally:\n * - the connection MUST originate from loopback (`127.0.0.1` / `::1`); a\n * non-loopback peer is rejected `403` (the gateway is local-process-facing).\n *\n * Enforced when configured:\n * - a `token` (matched against `Authorization: Bearer` or `?token=`); a missing\n * or mismatched token is rejected `401`.\n * - an `Origin` allowlist; a missing or disallowed `Origin` is rejected `403`.\n *\n * @param req - The inbound upgrade request.\n * @param remoteAddress - The socket's remote address.\n * @param opts - The endpoint auth configuration.\n * @returns A {@link GateDecision} — `{ok:true}` to proceed, else a typed reject.\n */\nexport function authorizeUpgrade(\n req: IncomingMessage,\n remoteAddress: string | undefined,\n opts: WsPtyOptions,\n): GateDecision {\n // 1. Loopback-only (unconditional).\n if (remoteAddress === undefined || !LOOPBACK_HOSTS.has(remoteAddress)) {\n return { ok: false, status: 403, reason: 'non-loopback connections are not permitted' };\n }\n\n // 2. Origin allowlist (when configured).\n if (opts.allowedOrigins !== undefined && opts.allowedOrigins.length > 0) {\n const origin = req.headers.origin;\n if (typeof origin !== 'string' || !opts.allowedOrigins.includes(origin)) {\n return { ok: false, status: 403, reason: 'origin not allowed' };\n }\n }\n\n // 3. Bearer token (when configured).\n if (opts.token !== undefined && opts.token.length > 0) {\n const presented = extractToken(req);\n if (presented === undefined || !timingSafeEqualStr(presented, opts.token)) {\n return { ok: false, status: 401, reason: 'missing or invalid token' };\n }\n }\n\n return { ok: true };\n}\n\n/** Extract a bearer token from `Authorization: Bearer <t>` or the `?token=<t>` query. */\nfunction extractToken(req: IncomingMessage): string | undefined {\n const auth = req.headers.authorization;\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n return auth.slice('Bearer '.length).trim();\n }\n const url = new URL(req.url ?? '/', 'http://localhost');\n const q = url.searchParams.get('token');\n return q !== null && q.length > 0 ? q : undefined;\n}\n\n/** Constant-time string compare (avoids leaking token length/contents via timing). */\nfunction timingSafeEqualStr(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n// ---------------------------------------------------------------------------\n// The upgrade handler + PTY bridge.\n// ---------------------------------------------------------------------------\n\n/** Whether a request path is the WS terminal/PTY endpoint. */\nexport function isWsPtyPath(url: string | undefined): boolean {\n const pathname = new URL(url ?? '/', 'http://localhost').pathname;\n return pathname === WS_PTY_PATH;\n}\n\n/** A live WS-PTY session, tracked so a server close tears every one down. */\ninterface WsPtySession {\n /** Tear down this session (idempotent) — kills the PTY + ends the socket. */\n teardown(code?: number, reason?: string): void;\n}\n\n/**\n * Write a raw HTTP rejection to the upgrade socket and destroy it. Used by the\n * auth/origin gate (AC4) so a rejected upgrade never reaches the PTY spawn.\n */\nfunction rejectUpgrade(socket: Duplex, status: number, reason: string): void {\n const statusText = status === 401 ? 'Unauthorized' : status === 403 ? 'Forbidden' : 'Bad Request';\n socket.write(\n `HTTP/1.1 ${status} ${statusText}\\r\\n` +\n 'Connection: close\\r\\n' +\n 'Content-Type: text/plain\\r\\n' +\n `Content-Length: ${Buffer.byteLength(reason)}\\r\\n` +\n '\\r\\n' +\n reason,\n );\n socket.destroy();\n}\n\n/**\n * Terminal `'error'` sink installed by a session's teardown.\n *\n * Teardown removes the live `onError` listener (it routes back into teardown)\n * but the socket still has a close frame + FIN in flight; an abrupt peer RST\n * surfaces that write failure asynchronously. Without a listener Node turns\n * the `ECONNRESET`/`EPIPE` into an uncaught exception — fatal for the daemon.\n * The session is already dead at this point, so the error is intentionally\n * dropped.\n *\n * @task T11961\n */\nfunction swallowLateSocketError(): void {\n // intentionally empty — late socket errors on a torn-down session are noise\n}\n\n/**\n * Complete the RFC 6455 handshake on a gated upgrade socket, then spawn + bridge\n * a PTY bidirectionally. Returns a {@link WsPtySession} whose `teardown()` is the\n * single idempotent close path (AC3). When `node-pty` is absent, the socket is\n * upgraded and then closed with {@link NODE_PTY_INSTALL_HINT} — never crashed.\n *\n * @param req - The inbound upgrade request.\n * @param socket - The raw duplex socket handed to us by the `'upgrade'` event.\n * @param opts - The endpoint configuration (shell, cwd).\n * @param log - The adapter logger.\n * @param onTeardown - Invoked exactly once when this session tears down (any\n * path: client close, error, abort, PTY exit, server close) — the registry\n * uses it to de-register the session leak-free (AC3).\n * @returns The tracked session.\n */\nasync function bridgePty(\n req: IncomingMessage,\n socket: Duplex,\n opts: WsPtyOptions,\n log: ReturnType<typeof getLogger>,\n onTeardown: () => void,\n): Promise<WsPtySession> {\n const key = req.headers['sec-websocket-key'];\n socket.write(\n 'HTTP/1.1 101 Switching Protocols\\r\\n' +\n 'Upgrade: websocket\\r\\n' +\n 'Connection: Upgrade\\r\\n' +\n `Sec-WebSocket-Accept: ${computeAcceptKey(typeof key === 'string' ? key : '')}\\r\\n` +\n '\\r\\n',\n );\n\n let tornDown = false;\n let pty: NodePtyProcess | undefined;\n const disposables: PtyDisposable[] = [];\n // Widened to `ArrayBufferLike` so concatenations of socket chunks (which are\n // `Buffer<ArrayBufferLike>`) assign cleanly under `@types/node`'s generic Buffer.\n let inbound: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n\n /** Send one server→client text frame, honoring socket backpressure. */\n const sendText = (data: string): void => {\n if (tornDown || socket.writableEnded) return;\n const frame = encodeWsFrame(OPCODE_TEXT, Buffer.from(data, 'utf8'));\n if (!socket.write(frame)) {\n // Kernel buffer full — pause PTY-driven writes until the socket drains so\n // a fast producer cannot blow the buffer (AC2 backpressure).\n socket.once('drain', () => undefined);\n }\n };\n\n /** The single idempotent teardown (AC3) — every close path routes here. */\n const teardown = (code = 1000, reason = ''): void => {\n if (tornDown) return;\n tornDown = true;\n for (const d of disposables.splice(0)) {\n try {\n d.dispose();\n } catch {\n // best-effort listener disposal\n }\n }\n if (pty !== undefined) {\n try {\n pty.kill();\n } catch {\n // best-effort PTY kill\n }\n pty = undefined;\n }\n socket.removeListener('data', onData);\n socket.removeListener('error', onError);\n socket.removeListener('close', onSocketClose);\n // The socket outlives teardown: the close frame + FIN below are still in\n // flight, and a peer that destroyed abruptly (RST) surfaces the write\n // failure ASYNCHRONOUSLY as a socket 'error' AFTER the listeners above are\n // removed. The try/catch only covers synchronous throws — without a sink\n // the late ECONNRESET/EPIPE becomes an uncaught exception that kills the\n // daemon (T11961; also the macOS CI shard killer in ws-pty.test.ts).\n socket.on('error', swallowLateSocketError);\n if (!socket.writableEnded) {\n try {\n socket.write(encodeCloseFrame(code, reason.slice(0, 120)));\n } catch {\n // socket may already be gone\n }\n socket.end();\n }\n try {\n onTeardown();\n } catch {\n // registry de-registration is best-effort\n }\n log.debug({ code, reason }, 'ws-pty session torn down');\n };\n\n /** Handle one fully-decoded inbound frame. */\n const onFrame = (frame: DecodedFrame): void => {\n switch (frame.opcode) {\n case OPCODE_TEXT:\n case OPCODE_BINARY: {\n const text = frame.payload.toString('utf8');\n // A JSON control frame resizes the PTY; everything else is stdin bytes.\n const control = tryParseControl(text);\n if (control !== undefined && control.type === 'resize') {\n pty?.resize(control.cols, control.rows);\n } else {\n pty?.write(text);\n }\n break;\n }\n case OPCODE_PING:\n if (!tornDown && !socket.writableEnded)\n socket.write(encodeWsFrame(OPCODE_PONG, frame.payload));\n break;\n case OPCODE_CLOSE:\n teardown(1000, 'client closed');\n break;\n // PONG and continuation frames are ignored.\n default:\n break;\n }\n };\n\n const onData = (chunk: Buffer): void => {\n inbound = inbound.length === 0 ? chunk : Buffer.concat([inbound, chunk]);\n for (;;) {\n const frame = decodeWsFrame(inbound);\n if (frame === null) break;\n inbound = inbound.subarray(frame.consumed);\n onFrame(frame);\n if (tornDown) break;\n }\n };\n const onError = (): void => teardown(1011, 'socket error');\n const onSocketClose = (): void => teardown();\n\n socket.on('data', onData);\n socket.on('error', onError);\n socket.on('close', onSocketClose);\n\n // Load the OPTIONAL PTY backend. Absent → clean close with the install hint\n // (the upgrade already succeeded; we never crash the daemon).\n const ptyModule = await loadNodePty();\n if (tornDown) return { teardown };\n if (ptyModule === null) {\n teardown(1011, NODE_PTY_INSTALL_HINT);\n return { teardown };\n }\n\n const shell = opts.shell ?? process.env.SHELL ?? '/bin/sh';\n try {\n pty = ptyModule.spawn(shell, [], {\n name: 'xterm-256color',\n cols: DEFAULT_COLS,\n rows: DEFAULT_ROWS,\n cwd: opts.cwd ?? process.cwd(),\n env: scrubPtyEnv(),\n });\n } catch (err) {\n log.warn({ err }, 'ws-pty spawn failed');\n teardown(1011, 'PTY spawn failed');\n return { teardown };\n }\n\n const dataSub = pty.onData((d) => sendText(d));\n if (dataSub && typeof dataSub.dispose === 'function') disposables.push(dataSub);\n const exitSub = pty.onExit(({ exitCode }) => teardown(1000, `pty exited (${exitCode})`));\n if (exitSub && typeof exitSub.dispose === 'function') disposables.push(exitSub);\n\n log.debug({ shell }, 'ws-pty session established');\n return { teardown };\n}\n\n/** A `{type:'resize',cols,rows}` control frame parsed from an inbound text frame. */\ninterface ResizeControl {\n type: 'resize';\n cols: number;\n rows: number;\n}\n\n/** Parse an inbound text frame as a JSON control message; `undefined` when it is plain stdin. */\nfunction tryParseControl(text: string): ResizeControl | undefined {\n if (text.length === 0 || text[0] !== '{') return undefined;\n try {\n const parsed: unknown = JSON.parse(text);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n (parsed as { type?: unknown }).type === 'resize' &&\n typeof (parsed as { cols?: unknown }).cols === 'number' &&\n typeof (parsed as { rows?: unknown }).rows === 'number'\n ) {\n const p = parsed as { cols: number; rows: number };\n return { type: 'resize', cols: p.cols, rows: p.rows };\n }\n } catch {\n // Not JSON — treat as plain stdin bytes.\n }\n return undefined;\n}\n\n/**\n * A handle returned by {@link attachWsPtyEndpoint} so the caller can detach the\n * `'upgrade'` listener and tear down every live session on server close (AC3).\n */\nexport interface WsPtyEndpointHandle {\n /** The number of live WS-PTY sessions (for tests / health). */\n readonly sessionCount: number;\n /** Detach the listener and tear down every live session. Idempotent. */\n close(): void;\n}\n\n/**\n * The shape of the HTTP server's `'upgrade'` event surface this module needs.\n * Declared structurally so the module does not couple to a concrete `Server`.\n */\nexport interface UpgradableServer {\n on(\n event: 'upgrade',\n listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void,\n ): void;\n removeListener(\n event: 'upgrade',\n listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void,\n ): void;\n}\n\n/**\n * Attach the WS terminal/PTY endpoint (AC1) to an existing HTTP server's\n * `'upgrade'` event.\n *\n * Every upgrade to {@link WS_PTY_PATH} is gated ({@link authorizeUpgrade}, AC4)\n * BEFORE any PTY is spawned; a rejected upgrade gets a raw `401`/`403` and the\n * socket is destroyed. An accepted upgrade completes the RFC 6455 handshake and\n * bridges a PTY bidirectionally ({@link bridgePty}, AC2). An upgrade to any OTHER\n * path is left for other upgrade handlers (this listener only claims its own\n * path and never destroys a foreign socket).\n *\n * Each live session is tracked so {@link WsPtyEndpointHandle.close} tears every\n * one down deterministically on server close (AC3).\n *\n * @param server - The HTTP server whose `'upgrade'` event to attach to.\n * @param opts - The endpoint auth + PTY configuration.\n * @returns A handle to detach + tear down on server close.\n */\nexport function attachWsPtyEndpoint(\n server: UpgradableServer,\n opts: WsPtyOptions = {},\n): WsPtyEndpointHandle {\n const log = getLogger('gateway-ws-pty');\n const sessions = new Set<WsPtySession>();\n\n const onUpgrade = (req: IncomingMessage, socket: Duplex, _head: Buffer): void => {\n // Only claim our own path — leave every other upgrade for other handlers.\n if (!isWsPtyPath(req.url)) return;\n\n const decision = authorizeUpgrade(req, req.socket.remoteAddress, opts);\n if (!decision.ok) {\n log.debug({ status: decision.status, reason: decision.reason }, 'ws-pty upgrade rejected');\n rejectUpgrade(socket, decision.status, decision.reason);\n return;\n }\n\n // A box the bridge's teardown callback closes over to de-register itself —\n // the session is added to the registry below, but teardown may fire (PTY\n // absent → immediate close) before the promise resolves, so the callback\n // removes whatever was registered for this socket once it is known.\n let session: WsPtySession | undefined;\n let deregistered = false;\n const deregister = (): void => {\n if (deregistered) return;\n deregistered = true;\n if (session !== undefined) sessions.delete(session);\n };\n\n bridgePty(req, socket, opts, log, deregister)\n .then((s) => {\n session = s;\n // If teardown already ran (e.g. PTY absent), do not re-register a dead\n // session; otherwise track it for the server-close sweep.\n if (!deregistered) sessions.add(s);\n })\n .catch((err: unknown) => {\n log.warn({ err }, 'ws-pty bridge failed');\n deregister();\n if (!socket.destroyed) socket.destroy();\n });\n };\n\n server.on('upgrade', onUpgrade);\n\n let closed = false;\n return {\n get sessionCount(): number {\n return sessions.size;\n },\n close(): void {\n if (closed) return;\n closed = true;\n server.removeListener('upgrade', onUpgrade);\n for (const session of [...sessions]) session.teardown(1001, 'server closing');\n sessions.clear();\n },\n };\n}\n"],"mappings":"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","names":["randomUUID","getLogger","domain","operation","gateway","randomUUID","getLogger"]}