@cleocode/runtime 2026.10.4 → 2026.10.5-rc.ci.672

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.
Files changed (36) hide show
  1. package/dist/{chunk-L5BLVQDA.js → chunk-4PUSKGDO.js} +2 -2
  2. package/dist/chunk-4PUSKGDO.js.map +1 -0
  3. package/dist/{chunk-KQBVXGOL.js → chunk-GULKX44R.js} +5 -3
  4. package/dist/chunk-GULKX44R.js.map +1 -0
  5. package/dist/{chunk-7Q7BEWM7.js → chunk-HXTRXEFI.js} +7 -9
  6. package/dist/chunk-HXTRXEFI.js.map +1 -0
  7. package/dist/{chunk-U2ESNIJ5.js → chunk-NOPLC5WS.js} +4 -2
  8. package/dist/chunk-NOPLC5WS.js.map +1 -0
  9. package/dist/chunk-PDZ5T4LO.js +597 -0
  10. package/dist/chunk-PDZ5T4LO.js.map +1 -0
  11. package/dist/{chunk-XJOMQIXL.js → chunk-WO6LJMDC.js} +4 -2
  12. package/dist/chunk-WO6LJMDC.js.map +1 -0
  13. package/dist/daemon/index.js +1 -1
  14. package/dist/dispatch-BWjVeeMg.d.ts +290 -0
  15. package/dist/gateway/dispatch.d.ts +5 -0
  16. package/dist/gateway/dispatch.js +53 -0
  17. package/dist/gateway/dispatch.js.map +1 -0
  18. package/dist/gateway/http/index.d.ts +897 -11
  19. package/dist/gateway/http/index.js +2 -2
  20. package/dist/gateway/index.d.ts +549 -4
  21. package/dist/gateway/index.js +26 -581
  22. package/dist/gateway/index.js.map +1 -1
  23. package/dist/gateway/mcp/index.d.ts +232 -10
  24. package/dist/gateway/mcp/index.js +2 -2
  25. package/dist/gateway/rpc/index.d.ts +5 -2036
  26. package/dist/gateway/rpc/index.js +1 -1
  27. package/dist/gateway-handler-DCU6E0a2.d.ts +80 -0
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +9 -6
  30. package/dist/index.js.map +1 -1
  31. package/package.json +7 -3
  32. package/dist/chunk-7Q7BEWM7.js.map +0 -1
  33. package/dist/chunk-KQBVXGOL.js.map +0 -1
  34. package/dist/chunk-L5BLVQDA.js.map +0 -1
  35. package/dist/chunk-U2ESNIJ5.js.map +0 -1
  36. package/dist/chunk-XJOMQIXL.js.map +0 -1
@@ -1,481 +1,7 @@
1
- import { DomainHandler, Middleware, DispatchRequest, DispatchResponse, Gateway, GatewayStreamEvent, Source, DispatchResponseMeta, CanonicalDomain, Tier } from '@cleocode/contracts/gateway';
2
- import { BackgroundJobManager } from '@cleocode/core/store/background-jobs.js';
3
- import { Subsystem, OperationDef, SuggestedNextOp, Resolution } from '@cleocode/contracts';
4
- import { IncomingMessage, Server, ServerResponse } from 'node:http';
5
- import '@cleocode/contracts/operations/orchestrate';
6
- import * as _cleocode_core_internal from '@cleocode/core/internal';
7
- import { EngineResult, ProblemDetails } from '@cleocode/core';
8
1
  import { GatewayRpcRequestFrame, GatewayRpcErrorFrame, GatewayRpcErrorCode, GatewayRpcResponseFrame } from '@cleocode/contracts/gateway/rpc';
9
- import { Server as Server$1 } from 'node:net';
10
- import { Duplex } from 'node:stream';
11
-
12
- /**
13
- * Central Dispatcher -- Routes requests through middleware to domain handlers.
14
- *
15
- * The dispatcher is the single entry point for the CLI adapter.
16
- * It resolves operations, validates parameters, runs the middleware pipeline,
17
- * and delegates to the appropriate domain handler.
18
- *
19
- * Flow: DispatchRequest → resolve → param validation → middleware → handler
20
- *
21
- * ## T1088 — Dialectic Evaluator Hook (PSYCHE Wave 3)
22
- *
23
- * After each successful mutate operation, the dispatcher fires a background
24
- * `evaluateDialectic` call via `setImmediate` if all of the following apply:
25
- *
26
- * 1. The response indicates `success: true`.
27
- * 2. The request gateway is `"mutate"` (state-modifying user-facing commands).
28
- * 3. A `sessionId` is present on the request (identifies conversation context).
29
- * 4. The in-memory rate limiter allows the evaluation (1 per 10s per session).
30
- *
31
- * Errors from the background evaluation are caught and logged at `warn` level
32
- * so they never affect the caller.
33
- *
34
- * ## T12024 — Bounded deadline for background LLM calls
35
- *
36
- * The dialectic evaluation makes an HTTP call to an LLM backend (Ollama,
37
- * Anthropic, etc.) which keeps the Node.js event loop alive. After the CLI
38
- * success-path `finally` tears down workers and databases via
39
- * `shutdownCliRuntime`, the pending HTTP connection is the sole remaining
40
- * reference — preventing the process from draining the loop and exiting rc:0.
41
- *
42
- * Each evaluation now runs against an unrefed deadline so the deadline
43
- * timer itself never holds the loop open, while the abort signal cancels the
44
- * underlying HTTP request when the deadline fires, releasing the connection
45
- * and allowing natural exit. The timeout is generous enough for normal
46
- * responses but prevents hung connections from blocking shutdown.
47
- *
48
- * @epic T4820
49
- * @task T1088
50
- * @task T12024
51
- */
52
-
53
- interface DispatcherConfig {
54
- handlers: Map<string, DomainHandler>;
55
- middlewares?: Middleware[];
56
- }
57
- declare class Dispatcher {
58
- private handlers;
59
- private pipeline;
60
- constructor(config: DispatcherConfig);
61
- dispatch(request: DispatchRequest): Promise<DispatchResponse>;
62
- }
63
-
64
- /**
65
- * WebSocket terminal/PTY endpoint on the `/v1` gateway HTTP listener (T11922 ·
66
- * M5 realtime transport, WS half).
67
- *
68
- * The SSE half (T11921) gives the gateway a SERVER→CLIENT push stream; this
69
- * module adds the BIDIRECTIONAL realtime transport an interactive TUI / terminal
70
- * session needs: a WebSocket upgrade on the SAME `node:http` listener
71
- * ({@link import('./listen.js').startHttpServer}) that bridges a PTY both
72
- * directions over WS frames.
73
- *
74
- * Design constraints (all four ACs of T11922):
75
- *
76
- * - **AC1 — upgrade on the existing listener.** The WS endpoint is NOT a second
77
- * server: it attaches to the daemon's existing HTTP server via its
78
- * `'upgrade'` event ({@link attachWsPtyEndpoint}), so it inherits the loopback
79
- * bind + the daemon's lifecycle. The endpoint path is `/v1/terminal/pty`.
80
- * - **AC2 — bidirectional PTY bridge with backpressure.** Inbound WS frames are
81
- * written to the PTY stdin; PTY output is framed back to the client. A control
82
- * frame (`{type:'resize',cols,rows}`) resizes the PTY. Backpressure is honored
83
- * on the socket write path (pause-on-`false`-write, resume on `'drain'`).
84
- * - **AC3 — leak-free, deterministic teardown.** Every open/close/error/abort
85
- * path routes through ONE idempotent `teardown()` that kills the PTY, removes
86
- * its listeners, and ends the socket exactly once. A server `close()` tears
87
- * down every live session.
88
- * - **AC4 — auth/origin enforced at the upgrade edge; no secrets on the wire.**
89
- * The upgrade is rejected with a `401`/`403` BEFORE any PTY is spawned unless
90
- * it comes from loopback AND (when a token is configured) presents the bearer
91
- * token AND (when an origin allowlist is configured) presents an allowed
92
- * `Origin`. The spawned PTY runs under a MINIMAL, explicitly-constructed env
93
- * ({@link scrubPtyEnv}) — the daemon's secret-bearing `process.env` is NEVER
94
- * inherited, so a secret cannot be exfiltrated through the terminal stream.
95
- *
96
- * To keep the **publish surface clean** (D11142 dependency-discipline):
97
- * - There is NO `ws` dependency. The minimal RFC 6455 server framing needed
98
- * (accept handshake, decode masked client frames, encode unmasked server
99
- * frames, close/ping/pong) is implemented over the raw `node:net` socket the
100
- * `'upgrade'` event hands us. This adds ZERO weight to `@cleocode/runtime`.
101
- * - `node-pty` is an OPTIONAL, dynamically-loaded dependency (same pattern as
102
- * {@link import('../../../../core/src/llm/pi/gondolin-loader.js')} and
103
- * `packages/core/src/tools/pty.ts`): the npm specifier is held in a variable,
104
- * its shape is declared LOCALLY (no `import type`), and absence is a graceful
105
- * close frame ("PTY backend unavailable") — NOT a crash. The endpoint always
106
- * upgrades; it bridges a PTY only when the backend is present.
107
- *
108
- * @packageDocumentation
109
- * @module @cleocode/runtime/gateway/http/ws-pty
110
- *
111
- * @task T11922
112
- * @epic T11769
113
- * @saga T10400
114
- * @see ./sse.ts — the SSE half (server→client push) on the same listener
115
- */
116
-
117
- /** The canonical WS terminal/PTY endpoint path on the `/v1` facade. */
118
- declare const WS_PTY_PATH = "/v1/terminal/pty";
119
- /**
120
- * The install hint surfaced to the client (as a WS close reason) when the PTY
121
- * backend is unavailable. `node-pty` is OPTIONAL — the endpoint still upgrades
122
- * and then closes cleanly with this reason rather than crashing the daemon.
123
- */
124
- declare const NODE_PTY_INSTALL_HINT: string;
125
- /** A disposable subscription handle returned by `node-pty`'s event registrars. */
126
- interface PtyDisposable {
127
- dispose(): void;
128
- }
129
- /** The minimal structural shape of a spawned `node-pty` process the bridge uses. */
130
- interface NodePtyProcess {
131
- /** Subscribe to interleaved stdout/stderr output. */
132
- onData(cb: (data: string) => void): PtyDisposable | undefined;
133
- /** Subscribe to process exit. */
134
- onExit(cb: (e: {
135
- exitCode: number;
136
- signal?: number;
137
- }) => void): PtyDisposable | undefined;
138
- /** Write to the PTY stdin. */
139
- write(data: string): void;
140
- /** Resize the PTY. */
141
- resize(cols: number, rows: number): void;
142
- /** Kill the underlying process (idempotent host-side; signal optional). */
143
- kill(signal?: string): void;
144
- }
145
- /** The minimal structural shape of the `node-pty` module the bridge uses. */
146
- interface NodePtyModule {
147
- spawn(file: string, args: readonly string[], options: {
148
- readonly name?: string;
149
- readonly cols?: number;
150
- readonly rows?: number;
151
- readonly cwd?: string;
152
- readonly env?: Record<string, string>;
153
- }): NodePtyProcess;
154
- }
155
- /**
156
- * Attempt to lazily load `node-pty`. Returns `null` when the optional dep is not
157
- * installed, fails to load, or does not expose the `spawn` surface — so the
158
- * caller closes the WS cleanly with {@link NODE_PTY_INSTALL_HINT} instead of
159
- * crashing. NEVER throws.
160
- *
161
- * The import specifier is held in a variable (passed to the {@link importer}
162
- * seam) so bundlers / TS do not treat the missing optional dep as a hard,
163
- * statically-resolved dependency (same technique as the gondolin loader).
164
- *
165
- * @returns The shape-checked module, or `null` when unavailable.
166
- */
167
- declare function loadNodePty(): Promise<NodePtyModule | null>;
168
- /**
169
- * Build the MINIMAL environment the bridged PTY runs under (AC4).
170
- *
171
- * Starts EMPTY, copies only the small {@link PASSTHROUGH_KEYS} allowlist from the
172
- * parent, pins `PATH` to {@link TRUSTED_PATH}, and forces `TERM=xterm-256color`
173
- * for a sane terminal. The daemon's secrets are NEVER forwarded.
174
- *
175
- * @param parentEnv - The parent process environment (defaults to `process.env`).
176
- * @returns The scrubbed, secret-free env for the PTY.
177
- */
178
- declare function scrubPtyEnv(parentEnv?: NodeJS.ProcessEnv): Record<string, string>;
179
- /**
180
- * Derive the `Sec-WebSocket-Accept` response header value from a client
181
- * `Sec-WebSocket-Key` per RFC 6455 §4.2.2 (SHA-1 of key + GUID, base64).
182
- *
183
- * @param key - The client's `Sec-WebSocket-Key` header value.
184
- * @returns The base64 accept hash.
185
- */
186
- declare function computeAcceptKey(key: string): string;
187
- /**
188
- * Encode a WS data frame (server→client, UNMASKED per RFC 6455 §5.1). Used for
189
- * `text` (PTY output) and `close` control frames.
190
- *
191
- * @param opcode - The WS opcode (e.g. {@link OPCODE_TEXT}).
192
- * @param payload - The frame payload bytes.
193
- * @returns The framed bytes ready to write to the socket.
194
- */
195
- declare function encodeWsFrame(opcode: number, payload: Buffer): Buffer;
196
- /** One decoded inbound WS frame. */
197
- interface DecodedFrame {
198
- /** The frame opcode. */
199
- opcode: number;
200
- /** The unmasked payload bytes. */
201
- payload: Buffer;
202
- /** Total bytes consumed from the input buffer (header + payload). */
203
- consumed: number;
204
- }
205
- /**
206
- * Decode a single inbound (client→server, MASKED) WS frame from a buffer.
207
- *
208
- * Returns `null` when the buffer does not yet hold a complete frame (the caller
209
- * waits for more bytes). Client frames MUST be masked (RFC 6455 §5.3); the mask
210
- * is applied to recover the payload. Fragmentation is not expected from a
211
- * terminal client, so a non-FIN frame is decoded as-is (its opcode handled by
212
- * the caller). NEVER throws on a partial buffer.
213
- *
214
- * @param buf - The accumulated inbound bytes.
215
- * @returns The decoded frame, or `null` when more bytes are needed.
216
- */
217
- declare function decodeWsFrame(buf: Buffer): DecodedFrame | null;
218
- /** Configuration for the WS terminal/PTY endpoint. */
219
- interface WsPtyOptions {
220
- /**
221
- * Optional bearer token an upgrade MUST present (as `Authorization: Bearer
222
- * <token>` or `?token=<token>`). When omitted, no token is required (loopback
223
- * is still enforced unconditionally).
224
- */
225
- token?: string;
226
- /**
227
- * Optional `Origin` allowlist. When non-empty, an upgrade MUST present an
228
- * `Origin` header whose value is in the set, else it is rejected `403`. When
229
- * omitted/empty, the `Origin` header is not checked (loopback already bounds
230
- * the surface).
231
- */
232
- allowedOrigins?: readonly string[];
233
- /** The shell launched for the PTY (default `process.env.SHELL` or `/bin/sh`). */
234
- shell?: string;
235
- /** Working directory for the PTY (default `process.cwd()`). */
236
- cwd?: string;
237
- }
238
- /** The decision of the upgrade-edge gate. */
239
- type GateDecision = {
240
- ok: true;
241
- } | {
242
- ok: false;
243
- status: number;
244
- reason: string;
245
- };
246
- /**
247
- * Authorize a WS upgrade at the edge (AC4) — BEFORE any PTY is spawned.
248
- *
249
- * Enforced unconditionally:
250
- * - the connection MUST originate from loopback (`127.0.0.1` / `::1`); a
251
- * non-loopback peer is rejected `403` (the gateway is local-process-facing).
252
- *
253
- * Enforced when configured:
254
- * - a `token` (matched against `Authorization: Bearer` or `?token=`); a missing
255
- * or mismatched token is rejected `401`.
256
- * - an `Origin` allowlist; a missing or disallowed `Origin` is rejected `403`.
257
- *
258
- * @param req - The inbound upgrade request.
259
- * @param remoteAddress - The socket's remote address.
260
- * @param opts - The endpoint auth configuration.
261
- * @returns A {@link GateDecision} — `{ok:true}` to proceed, else a typed reject.
262
- */
263
- declare function authorizeUpgrade(req: IncomingMessage, remoteAddress: string | undefined, opts: WsPtyOptions): GateDecision;
264
- /** Whether a request path is the WS terminal/PTY endpoint. */
265
- declare function isWsPtyPath(url: string | undefined): boolean;
266
- /**
267
- * A handle returned by {@link attachWsPtyEndpoint} so the caller can detach the
268
- * `'upgrade'` listener and tear down every live session on server close (AC3).
269
- */
270
- interface WsPtyEndpointHandle {
271
- /** The number of live WS-PTY sessions (for tests / health). */
272
- readonly sessionCount: number;
273
- /** Detach the listener and tear down every live session. Idempotent. */
274
- close(): void;
275
- }
276
- /**
277
- * The shape of the HTTP server's `'upgrade'` event surface this module needs.
278
- * Declared structurally so the module does not couple to a concrete `Server`.
279
- */
280
- interface UpgradableServer {
281
- on(event: 'upgrade', listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): void;
282
- removeListener(event: 'upgrade', listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): void;
283
- }
284
- /**
285
- * Attach the WS terminal/PTY endpoint (AC1) to an existing HTTP server's
286
- * `'upgrade'` event.
287
- *
288
- * Every upgrade to {@link WS_PTY_PATH} is gated ({@link authorizeUpgrade}, AC4)
289
- * BEFORE any PTY is spawned; a rejected upgrade gets a raw `401`/`403` and the
290
- * socket is destroyed. An accepted upgrade completes the RFC 6455 handshake and
291
- * bridges a PTY bidirectionally ({@link bridgePty}, AC2). An upgrade to any OTHER
292
- * path is left for other upgrade handlers (this listener only claims its own
293
- * path and never destroys a foreign socket).
294
- *
295
- * Each live session is tracked so {@link WsPtyEndpointHandle.close} tears every
296
- * one down deterministically on server close (AC3).
297
- *
298
- * @param server - The HTTP server whose `'upgrade'` event to attach to.
299
- * @param opts - The endpoint auth + PTY configuration.
300
- * @returns A handle to detach + tear down on server close.
301
- */
302
- declare function attachWsPtyEndpoint(server: UpgradableServer, opts?: WsPtyOptions): WsPtyEndpointHandle;
303
-
304
- /**
305
- * `node:http` server binding for the framework-agnostic HTTP gateway adapter.
306
- *
307
- * The HTTP adapter ({@link routeUnary} + the SSE primitives) is deliberately
308
- * framework-agnostic — it owns NO port binding and NO `Request`/`Response`
309
- * lifecycle, so an embedder (a SvelteKit route, a test harness) can drive it
310
- * directly. The **daemon**, however, is itself the embedder: it must expose the
311
- * gateway to external HTTP clients over a real listening socket. This module is
312
- * that thin embedder — a minimal `node:http` server that parses the request URL
313
- * into a `(gateway, domain, operation)` triple, reads the JSON body, and routes
314
- * it through {@link routeUnary}. It deliberately stays in the runtime (no
315
- * SvelteKit / no `@cleocode/cleo` / no drizzle dependency) so the daemon can
316
- * host the gateway without reaching across a package boundary.
317
- *
318
- * Routing contract (intentionally minimal — the wire vocabulary is the same as
319
- * every other transport, only the framing differs):
320
- *
321
- * `POST /<gateway>/<domain>/<operation>` with a JSON body → operation params
322
- *
323
- * where `<gateway>` is `query` | `mutate`. A non-POST method, an unparseable
324
- * path, or a body that is not a JSON object is rejected at the wire edge with a
325
- * LAFS-shaped error envelope — the injected {@link GatewayHandler} is never
326
- * invoked with a malformed request.
327
- *
328
- * @packageDocumentation
329
- * @module @cleocode/runtime/gateway/http
330
- *
331
- * @task T11451
332
- * @epic T11254
333
- * @saga T11243 SG-RUNTIME-UNIFICATION
334
- */
335
-
336
- /**
337
- * Options for {@link startHttpServer}.
338
- *
339
- * Mirrors the connection-oriented {@link RpcServerOptions} shape: the caller
340
- * resolves the bind coordinates so the runtime stays free of any
341
- * `@cleocode/paths` dependency.
342
- */
343
- interface HttpServerOptions {
344
- /**
345
- * TCP port to bind. `0` selects an ephemeral port (the bound port is then
346
- * readable from {@link HttpServerHandle.port}).
347
- */
348
- port: number;
349
- /**
350
- * Host/interface to bind. Defaults to `127.0.0.1` (loopback only) — the
351
- * gateway is local-process-facing; exposing it on a public interface is an
352
- * explicit caller decision.
353
- */
354
- host?: string;
355
- /**
356
- * WebSocket terminal/PTY endpoint configuration (T11922 · M5 WS half). When
357
- * provided, a WS upgrade handler for {@link WS_PTY_PATH} (`/v1/terminal/pty`)
358
- * is attached to this server's `'upgrade'` event — gated at the edge (loopback
359
- * + optional token/origin) and bridging a PTY bidirectionally over WS frames.
360
- * When OMITTED, no upgrade handler is attached and the server stays a pure
361
- * unary+SSE HTTP listener (the capability is opt-in — the daemon enables it
362
- * explicitly). The optional `node-pty` backend is loaded dynamically; if it is
363
- * absent, the endpoint still upgrades and reports a clean "PTY backend
364
- * unavailable" close rather than crashing.
365
- */
366
- wsPty?: WsPtyOptions;
367
- /**
368
- * Studio static asset directory for the bundled Web Studio (T11979).
369
- *
370
- * When provided, requests whose path starts with `/studio` are served as
371
- * static files from this directory (the `build/client/` output from an
372
- * `adapter-node` Studio build), with SPA fallback to `index.html` for
373
- * sub-routes. When `undefined` or the path does not exist on disk, a clean
374
- * JSON 503 envelope is returned instead of a stack trace — allowing the
375
- * gateway to start without a Studio bundle present (dev checkouts).
376
- *
377
- * The value is resolved and injected by the caller (`cleo daemon serve`)
378
- * using {@link resolveStudioStaticDir} from `@cleocode/cleo` so the runtime
379
- * layer stays free of any path-resolution logic.
380
- */
381
- studioStaticDir?: string;
382
- }
383
- /**
384
- * A live HTTP server handle returned by {@link startHttpServer}.
385
- *
386
- * Exposes the bound {@link Server}, the actually-bound `port` (resolved even
387
- * when `0` was requested), and an idempotent {@link close}. The adapter never
388
- * calls `process.exit` — lifecycle stays with the caller (R3 contract).
389
- */
390
- interface HttpServerHandle {
391
- /** The bound `node:http` server. */
392
- server: Server;
393
- /** The actually-bound TCP port (resolved when `0` was requested). */
394
- port: number;
395
- /** Host/interface the server is bound to. */
396
- host: string;
397
- /** Close the server + all live connections; resolves once fully closed. */
398
- close(): Promise<void>;
399
- }
400
- /**
401
- * The result of parsing a request line into routing coordinates: a resolved
402
- * unary triple, a recognized health probe, or a rejection for the wire edge.
403
- */
404
- type ParsedRoute = {
405
- ok: true;
406
- kind: 'unary';
407
- gateway: Gateway;
408
- domain: string;
409
- operation: string;
410
- } | {
411
- ok: true;
412
- kind: 'stream';
413
- gateway: Gateway;
414
- domain: string;
415
- operation: string;
416
- } | {
417
- ok: true;
418
- kind: 'health';
419
- } | {
420
- ok: false;
421
- status: number;
422
- code: string;
423
- message: string;
424
- };
425
- /**
426
- * Parse a gateway request line into a routing decision.
427
- *
428
- * Two POST routing vocabularies are accepted (the wire payload is identical to
429
- * every other transport — only the framing differs):
430
- *
431
- * - `POST /v1/<domain>/<operation>` — the **versioned REST facade** (T11919).
432
- * The gateway (`query` | `mutate`) is INFERRED from the registry, so the
433
- * client need not name it. An unregistered `(domain, operation)` pair is a
434
- * `404`.
435
- * - `POST /<gateway>/<domain>/<operation>` — the **legacy** form, where the
436
- * client names the gateway segment explicitly (validated against the two
437
- * CQRS gateways so a client cannot smuggle an arbitrary value).
438
- *
439
- * Two GET routing vocabularies are accepted:
440
- *
441
- * - `GET /v1/health` — the liveness probe.
442
- * - `GET /v1/<domain>/<operation>` — the **SSE streaming** endpoint (T11921),
443
- * recognized only when the `(domain, operation)` pair is registered AND
444
- * flagged `streaming: true` in {@link OPERATIONS} (resolved via
445
- * {@link resolveStreamRoute}). The SAME pair is dispatched UNARY over POST,
446
- * so the streaming GET co-exists with the unary POST route. A GET to a
447
- * unary-only op is a `405` (method-not-allowed), distinguishing "route
448
- * exists, this method is unsupported" from "no such route" (`404`).
449
- *
450
- * A non-POST/GET method is rejected at the edge (`405`); a path that resolves to
451
- * neither vocabulary is a `404`. The injected {@link GatewayHandler} is never
452
- * invoked with a malformed request.
453
- *
454
- * @param method - The HTTP request method.
455
- * @param url - The HTTP request URL (path + query).
456
- * @returns The parsed route, or a typed rejection for the wire edge.
457
- */
458
- declare function parseHttpRoute(method: string | undefined, url: string | undefined): ParsedRoute;
459
- /**
460
- * Start a `node:http` server that hosts the gateway over HTTP for external
461
- * clients.
462
- *
463
- * Binds {@link HttpServerOptions.port} on {@link HttpServerOptions.host}
464
- * (default loopback), routes every `POST /<gateway>/<domain>/<operation>`
465
- * through the injected {@link GatewayHandler} via {@link routeUnary}, and
466
- * resolves once listening. The returned {@link HttpServerHandle} exposes an
467
- * idempotent `close()` for deterministic teardown (daemon shutdown, tests). The
468
- * adapter never calls `process.exit`.
469
- *
470
- * The caller assembles the handler (via `createGatewayHandler` with its domain
471
- * handlers + middleware) and injects it here — this server never builds
472
- * handlers itself, keeping the runtime free of any `@cleocode/cleo` dependency.
473
- *
474
- * @param handler - The transport-neutral gateway handler to route through.
475
- * @param opts - Bind coordinates.
476
- * @returns A promise resolving to the live {@link HttpServerHandle}.
477
- */
478
- declare function startHttpServer(handler: GatewayHandler, opts: HttpServerOptions): Promise<HttpServerHandle>;
2
+ import { Server } from 'node:net';
3
+ import { G as GatewayHandler } from '../../gateway-handler-DCU6E0a2.js';
4
+ import '@cleocode/contracts/gateway';
479
5
 
480
6
  /**
481
7
  * NDJSON codec for the CLI-RPC transport.
@@ -611,7 +137,7 @@ interface RpcServerOptions {
611
137
  */
612
138
  interface RpcServerHandle {
613
139
  /** The bound `node:net` server. */
614
- server: Server$1;
140
+ server: Server;
615
141
  /** Absolute path of the bound unix socket. */
616
142
  socketPath: string;
617
143
  /** Close the server + all live connections; resolves once fully closed. */
@@ -713,1561 +239,4 @@ declare function routeFrame(handler: GatewayHandler, frame: GatewayRpcRequestFra
713
239
  */
714
240
  declare function startRpcServer(handler: GatewayHandler, opts: RpcServerOptions): Promise<RpcServerHandle>;
715
241
 
716
- /**
717
- * The gateway as a supervised **daemon subsystem**.
718
- *
719
- * R3-T7 (this task) closes the loop between the two R-track halves: the R3
720
- * gateway (dispatcher + the four transport adapters) and the R2 daemon
721
- * (`defineSubsystem` + the `SubsystemRegistry`). It expresses the
722
- * external-facing gateway as a uniform {@link Subsystem} so the R2 daemon
723
- * registry drives its `start → healthProbe → shutdown` lifecycle exactly like
724
- * every other long-running concern (Studio supervision, the GC cron, …). One
725
- * gateway subsystem is registered per scope (project / global) → one
726
- * external-facing gateway process per scope.
727
- *
728
- * Which transports the daemon hosts:
729
- * - the **RPC unix socket** (`@cleocode/runtime/gateway/rpc`) — the canonical
730
- * local-client wire: the `cleo` CLI, Studio's server process, and test
731
- * harnesses open it to dispatch without paying a cold `cleo` spawn.
732
- * - the **HTTP server** (`@cleocode/runtime/gateway/http` via the daemon-local
733
- * `startHttpServer` embedder) — for external clients that speak HTTP rather
734
- * than the unix-socket NDJSON wire.
735
- *
736
- * The CLI and MCP adapters are intentionally NOT hosted by the daemon: the CLI
737
- * adapter is in-process to the `cleo` binary, and the MCP adapter is a stdio
738
- * server an MCP client spawns and owns — neither is a long-lived,
739
- * daemon-supervised listener. The two external-facing listeners (RPC + HTTP)
740
- * are the ones the daemon owns.
741
- *
742
- * This subsystem adds NO behavior to the adapters themselves — it only wires
743
- * their existing `start*Server` / `close()` lifecycle (and their already-merged
744
- * routing) into the daemon registry's uniform lifecycle. It carries NO
745
- * `@cleocode/cleo` dependency and NO drizzle-orm (the runtime invariant): the
746
- * caller injects the assembled {@link GatewayHandler} and the resolved bind
747
- * coordinates; this module only orchestrates listeners.
748
- *
749
- * @packageDocumentation
750
- * @module @cleocode/runtime/gateway
751
- *
752
- * @task T11451 (R3-T7)
753
- * @epic T11254
754
- * @saga T11243 SG-RUNTIME-UNIFICATION
755
- */
756
-
757
- /** The scope a single gateway process serves — one external-facing process per scope. */
758
- type GatewayScope = 'project' | 'global';
759
- /**
760
- * Configuration for one scoped gateway subsystem.
761
- *
762
- * The caller injects the assembled, transport-neutral {@link GatewayHandler}
763
- * (built with `createGatewayHandler` from its domain handlers + middleware) and
764
- * the resolved bind coordinates for each hosted transport. The runtime never
765
- * resolves paths/ports itself — that keeps it free of `@cleocode/paths` and of
766
- * any policy decision about where a scope's socket/port lives.
767
- */
768
- interface GatewaySubsystemOptions {
769
- /** The scope this gateway process serves (`project` | `global`). */
770
- scope: GatewayScope;
771
- /** The transport-neutral gateway handler every hosted transport routes through. */
772
- handler: GatewayHandler;
773
- /**
774
- * RPC unix-socket transport options. When omitted, the RPC transport is not
775
- * hosted (a deployment that only wants HTTP). At least one transport MUST be
776
- * configured.
777
- */
778
- rpc?: RpcServerOptions;
779
- /**
780
- * HTTP transport options. When omitted, the HTTP transport is not hosted (a
781
- * deployment that only wants the local RPC socket). At least one transport
782
- * MUST be configured.
783
- */
784
- http?: HttpServerOptions;
785
- }
786
- /**
787
- * The live context a started gateway subsystem threads from `start` into
788
- * `healthProbe`/`shutdown`: the bound transport handles plus the owning pid.
789
- *
790
- * `void`-typed contexts are common for subsystems, but the gateway needs to
791
- * carry its bound handles so `shutdown` can close exactly what `start` opened
792
- * (and `healthProbe` can report the bound coordinates).
793
- */
794
- interface GatewaySubsystemContext {
795
- /** The OS pid hosting the listeners (this daemon process). */
796
- readonly pid: number;
797
- /** The bound RPC server handle, when the RPC transport is hosted. */
798
- readonly rpc?: RpcServerHandle;
799
- /** The bound HTTP server handle, when the HTTP transport is hosted. */
800
- readonly http?: HttpServerHandle;
801
- }
802
- /**
803
- * The stable subsystem name (and supervised `child_id`) of a scoped gateway.
804
- *
805
- * Suffixed with the scope so a single daemon process supervising both scopes
806
- * (project + global) registers two distinct, non-colliding subsystems. The
807
- * `child_id` matches the id the Rust supervisor tracks the listener under.
808
- *
809
- * @param scope - The gateway scope.
810
- * @returns The subsystem name, e.g. `gateway-project`.
811
- */
812
- declare function gatewaySubsystemName(scope: GatewayScope): string;
813
- /**
814
- * Declare the external-facing gateway as a supervised daemon {@link Subsystem}.
815
- *
816
- * The returned subsystem (frozen by {@link defineSubsystem}) is registered with
817
- * a `SubsystemRegistry`, which then drives its lifecycle uniformly:
818
- *
819
- * - `start()` — binds the configured transports (RPC unix socket and/or
820
- * HTTP server) over the injected handler, in a fixed order (RPC then HTTP).
821
- * If the second listener fails to bind, the first is rolled back so a failed
822
- * start leaves no half-open listener — then the error is re-thrown for the
823
- * registry's `onError` sink.
824
- * - `healthProbe()` — reports a single {@link SubsystemHealth} row keyed on the
825
- * scoped `child_id`, `running` while the configured listeners are live, with
826
- * a `detail` summarizing the bound coordinates. Projects losslessly onto the
827
- * supervisor's `ChildStatus`.
828
- * - `shutdown()` — closes every bound listener (best-effort, idempotent) in
829
- * reverse bind order (HTTP then RPC).
830
- *
831
- * One subsystem instance is declared per scope, so registering the project and
832
- * global gateways yields two supervised listeners → one external-facing gateway
833
- * process surface per scope (AC1).
834
- *
835
- * @param opts - The scoped gateway configuration (handler + per-transport bind
836
- * coordinates).
837
- * @returns A frozen {@link Subsystem} ready to `register()` with a
838
- * `SubsystemRegistry`.
839
- * @throws {TypeError} When neither the RPC nor the HTTP transport is configured
840
- * (a gateway with no hosted transport serves nothing).
841
- *
842
- * @example
843
- * ```ts
844
- * const registry = new SubsystemRegistry();
845
- * registry.register(
846
- * defineGatewaySubsystem({
847
- * scope: 'project',
848
- * handler: createGatewayHandler(dispatcherConfig),
849
- * rpc: { socketPath: '/run/cleo/cleo-gateway-rpc-project.sock' },
850
- * http: { port: 7777 },
851
- * }),
852
- * );
853
- * await registry.startAll(); // binds the RPC socket + HTTP server
854
- * const health = await registry.aggregateHealth();
855
- * await registry.shutdownAll(); // closes both listeners
856
- * ```
857
- */
858
- declare function defineGatewaySubsystem(opts: GatewaySubsystemOptions): Subsystem<GatewaySubsystemContext>;
859
-
860
- /**
861
- * Centralized engine error helper for the transport-agnostic gateway.
862
- *
863
- * Every domain handler (and the engine barrel) imports `engineError()` /
864
- * `engineSuccess()` from this module to produce consistently typed error
865
- * results with correct exit codes and pino logging.
866
- *
867
- * The STRING_TO_EXIT map is the canonical mapping from string error codes
868
- * to numeric exit codes. It mirrors ERROR_CODE_TO_EXIT in cli.ts.
869
- *
870
- * @remarks
871
- * Relocated from `packages/cleo/src/dispatch/engines/_error.ts` into
872
- * `@cleocode/runtime/gateway` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION) so the
873
- * runtime owns the shared handler dependencies and can assemble the handler map
874
- * without importing `@cleocode/cleo`. A thin re-export shim remains at the old
875
- * `engines/_error.ts` path so in-package import sites compile unchanged.
876
- *
877
- * Error codes are organized into numeric ranges:
878
- * - 1-9: General errors (invalid input, file, dependency, validation)
879
- * - 10-19: Hierarchy errors (parent, depth, siblings, circular deps)
880
- * - 20-29: Concurrency errors (checksum, modification, collision)
881
- * - 30-39: Session errors (scope, claims, active task)
882
- * - 40-47: Verification errors (gates, agents, rounds)
883
- * - 50-54: Context safeguard (warning through emergency)
884
- * - 60-67: Orchestrator / protocol errors
885
- * - 70-79: Nexus errors
886
- * - 80-94: Lifecycle, artifact, provenance errors
887
- * - 95-99: Adapter errors
888
- * - 100+: Special informational codes (not errors)
889
- */
890
-
891
- /**
892
- * Canonical mapping from string error codes to numeric exit codes.
893
- *
894
- * Source of truth: src/types/exit-codes.ts (ExitCode enum).
895
- * Must stay in sync with ERROR_CODE_TO_EXIT in src/dispatch/adapters/cli.ts.
896
- */
897
- declare const STRING_TO_EXIT: Record<string, number>;
898
- /**
899
- * Create a typed engine error result with pino logging and correct exit code.
900
- *
901
- * @remarks
902
- * The exit code is derived from the {@link STRING_TO_EXIT} mapping. If the
903
- * code is not found in the map, exit code 1 (general error) is used.
904
- * Pino logging is automatically triggered at the appropriate level
905
- * (error for internal issues, warn for user/domain errors, debug for
906
- * informational codes). Logging is suppressed under Vitest to keep
907
- * test output clean.
908
- *
909
- * @param code - String error code (e.g., 'E_NOT_FOUND')
910
- * @param message - Human-readable error message
911
- * @param options - Optional details, fix command, and alternatives
912
- * @returns EngineResult with success=false and properly structured error
913
- *
914
- * @example
915
- * ```typescript
916
- * import { engineError } from './_error.js';
917
- *
918
- * return engineError('E_NOT_FOUND', `Task ${id} not found`, {
919
- * fix: `cleo show ${id}`,
920
- * details: { taskId: id },
921
- * });
922
- * ```
923
- */
924
- declare function engineError<T>(code: string, message: string, options?: {
925
- exitCode?: number;
926
- details?: Record<string, unknown>;
927
- fix?: string;
928
- alternatives?: Array<{
929
- action: string;
930
- command: string;
931
- }>;
932
- problemDetails?: ProblemDetails;
933
- }): EngineResult<T>;
934
-
935
- /**
936
- * Minimal meta shape carrying the request ID for RFC 7807 `instance` population.
937
- *
938
- * Callers may pass the dispatch-layer `DispatchRequest.requestId` (or any
939
- * other correlation identifier) so that the resulting `problemDetails.instance`
940
- * URI uniquely identifies the specific error occurrence.
941
- */
942
- interface ErrorMeta {
943
- /** Unique request identifier (used as the RFC 7807 `instance` field). */
944
- requestId?: string;
945
- }
946
- /**
947
- * Convert any caught value into an {@link EngineResult}, forwarding the rich
948
- * `fix`, `details`, and `alternatives` fields when the caught value is a
949
- * `CleoError` (or structurally compatible object).
950
- *
951
- * @remarks
952
- * This is the canonical catch-block helper for all dispatch engines. It
953
- * replaces the previous pattern of:
954
- * ```typescript
955
- * const code = (err as { code?: number })?.code;
956
- * if (code === 4) return engineError('E_NOT_FOUND', …);
957
- * …
958
- * ```
959
- * with a single call that preserves every field the core layer attached.
960
- *
961
- * The `fallbackCode` is used when the numeric `err.code` is not present in
962
- * the {@link STRING_TO_EXIT} mapping (e.g. unknown exit codes, plain `Error`
963
- * instances, or values thrown by non-CLEO code).
964
- *
965
- * RFC 7807 `problemDetails` is always populated from the resolved code, message,
966
- * and exit code. The optional `meta.requestId` is forwarded to the `instance`
967
- * field so every error occurrence is uniquely identifiable.
968
- *
969
- * @param err - The caught value (unknown type)
970
- * @param fallbackCode - String error code to use when mapping fails
971
- * @param fallbackMessage - Human-readable message when `err.message` is absent
972
- * @param meta - Optional dispatch meta carrying `requestId` for RFC 7807 `instance`
973
- * @returns EngineResult with all available rich fields propagated
974
- *
975
- * @example
976
- * ```typescript
977
- * import { cleoErrorToEngineError } from './_error.js';
978
- *
979
- * } catch (err: unknown) {
980
- * return cleoErrorToEngineError(err, 'E_NOT_INITIALIZED', 'Task database not initialized');
981
- * }
982
- * ```
983
- *
984
- * @example With requestId for RFC 7807 instance field:
985
- * ```typescript
986
- * } catch (err: unknown) {
987
- * return cleoErrorToEngineError(err, 'E_NOT_FOUND', 'Not found', { requestId });
988
- * }
989
- * ```
990
- */
991
- declare function cleoErrorToEngineError<T>(err: unknown, fallbackCode: string, fallbackMessage: string, meta?: ErrorMeta): EngineResult<T>;
992
-
993
- /**
994
- * Parse all templates from the repo's .github/ISSUE_TEMPLATE/ directory.
995
- *
996
- * @remarks
997
- * Delegates to the core template parser and adapts the result to the
998
- * dispatch EngineResult type.
999
- *
1000
- * @param projectRoot - Absolute path to the project root
1001
- * @returns EngineResult containing the parsed template configuration
1002
- *
1003
- * @example
1004
- * ```typescript
1005
- * const result = parseIssueTemplates('/path/to/project');
1006
- * if (result.success) {
1007
- * console.log(result.data.templates);
1008
- * }
1009
- * ```
1010
- */
1011
- declare function parseIssueTemplates(projectRoot: string): EngineResult<_cleocode_core_internal.TemplateConfig>;
1012
- /**
1013
- * Get template config for a specific subcommand (bug/feature/help).
1014
- *
1015
- * @remarks
1016
- * Looks up a specific template by subcommand name from the project's
1017
- * issue template directory.
1018
- *
1019
- * @param projectRoot - Absolute path to the project root
1020
- * @param subcommand - Template subcommand identifier (e.g. "bug", "feature")
1021
- * @returns EngineResult containing the matched issue template
1022
- *
1023
- * @example
1024
- * ```typescript
1025
- * const result = getTemplateForSubcommand('/path/to/project', 'bug');
1026
- * ```
1027
- */
1028
- declare function getTemplateForSubcommand(projectRoot: string, subcommand: string): EngineResult<_cleocode_core_internal.IssueTemplate>;
1029
- /**
1030
- * Generate and cache the config as .cleo/issue-templates.json.
1031
- *
1032
- * @remarks
1033
- * Parses all templates and writes the resulting config to disk for caching.
1034
- *
1035
- * @param projectRoot - Absolute path to the project root
1036
- * @returns EngineResult containing the generated template configuration
1037
- *
1038
- * @example
1039
- * ```typescript
1040
- * const result = await generateTemplateConfig('/path/to/project');
1041
- * ```
1042
- */
1043
- declare function generateTemplateConfig(projectRoot: string): Promise<EngineResult<_cleocode_core_internal.TemplateConfig>>;
1044
- /**
1045
- * Validate that labels exist on a GitHub repo.
1046
- *
1047
- * @remarks
1048
- * Compares a set of required labels against the labels found on the repo.
1049
- *
1050
- * @param labels - Labels required by the template
1051
- * @param repoLabels - Labels that exist on the GitHub repo
1052
- * @returns EngineResult with existing and missing label lists
1053
- *
1054
- * @example
1055
- * ```typescript
1056
- * const result = validateLabels(['bug', 'enhancement'], repoLabels);
1057
- * ```
1058
- */
1059
- declare function validateLabels(labels: string[], repoLabels: string[]): EngineResult<{
1060
- existing: string[];
1061
- missing: string[];
1062
- }>;
1063
-
1064
- /**
1065
- * Shared exit-code mapping utilities for the transport-agnostic gateway.
1066
- *
1067
- * Provides a canonical reverse lookup from numeric exit code to string error code,
1068
- * shared between the gateway engine helpers and every transport adapter (CLI,
1069
- * MCP, RPC, HTTP).
1070
- *
1071
- * @remarks
1072
- * Relocated from `packages/cleo/src/dispatch/lib/exit-codes.ts` into
1073
- * `@cleocode/runtime/gateway` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION) so the
1074
- * runtime can host the shared handler dependencies without importing
1075
- * `@cleocode/cleo`. A thin re-export shim remains at the old path.
1076
- *
1077
- * The forward mapping (string → number) lives in `engine-error.ts` as
1078
- * `STRING_TO_EXIT`. This module derives the inverse on first call and memoises
1079
- * it so engines can resolve a caught `CleoError.code` (numeric) to the
1080
- * string code expected by `engineError()`.
1081
- *
1082
- * @task T374
1083
- * @task T11455
1084
- * @epic T335
1085
- */
1086
- /**
1087
- * Map a numeric CleoError exit code to the canonical string engine error code.
1088
- *
1089
- * @remarks
1090
- * Returns `undefined` when the numeric code is not present in the
1091
- * `STRING_TO_EXIT` map, allowing callers to fall back to their own default.
1092
- *
1093
- * @param numericCode - Numeric exit code from a caught `CleoError.code`
1094
- * @returns Canonical string error code (e.g. `'E_NOT_FOUND'`), or `undefined`
1095
- *
1096
- * @example
1097
- * ```typescript
1098
- * import { mapNumericExitCodeToString } from '../lib/exit-codes.js';
1099
- *
1100
- * const code = mapNumericExitCodeToString(4) ?? 'E_NOT_INITIALIZED';
1101
- * // → 'E_NOT_FOUND'
1102
- * ```
1103
- */
1104
- declare function mapNumericExitCodeToString(numericCode: number | undefined): string | undefined;
1105
-
1106
- /**
1107
- * Wire + lifecycle types for the `@cleocode/runtime/gateway/http` adapter.
1108
- *
1109
- * The HTTP transport carries the SAME gateway payloads as every other transport
1110
- * — request/response shapes are owned by `@cleocode/contracts/gateway` (R3-T2 ·
1111
- * T11446) and the streaming frame by `GatewayStreamEvent`. This module holds
1112
- * only the runtime-adapter-local option/handle shapes (mirroring the MCP and RPC
1113
- * adapters' `types.ts`), so the runtime stays framework-agnostic and free of any
1114
- * `@cleocode/cleo` / SvelteKit / drizzle dependency.
1115
- *
1116
- * @task T11450
1117
- * @epic T11254
1118
- * @saga T11243
1119
- */
1120
-
1121
- /**
1122
- * Where on the wire the `(gateway, domain, operation)` triple of an HTTP unary
1123
- * request is sourced from. The adapter is framework-agnostic, so the embedder
1124
- * (SvelteKit route, daemon HTTP server, test harness) supplies the resolved
1125
- * coordinates explicitly rather than the adapter parsing a URL itself.
1126
- */
1127
- interface HttpUnaryRequest {
1128
- /** CQRS gateway — `'mutate'` (write) or `'query'` (read). */
1129
- gateway: Gateway;
1130
- /** Target canonical domain (e.g. `'tasks'`). */
1131
- domain: string;
1132
- /** Domain operation name (e.g. `'show'`). */
1133
- operation: string;
1134
- /** Decoded JSON request body → operation params. */
1135
- params?: Record<string, unknown>;
1136
- /**
1137
- * Optional caller-supplied request id for tracing. When omitted the adapter
1138
- * mints a fresh UUID.
1139
- */
1140
- requestId?: string;
1141
- /** Bound session id, if any (forwarded into the dispatch request). */
1142
- sessionId?: string;
1143
- }
1144
- /**
1145
- * The result of routing one HTTP unary request through the gateway: the LAFS
1146
- * {@link DispatchResponse} JSON body plus the HTTP status the embedder should
1147
- * set (200 on success, 4xx/5xx mapped from the dispatch error).
1148
- *
1149
- * The adapter never writes the response itself (no framework coupling) — it
1150
- * returns this so the embedder serializes it onto its own `Response`.
1151
- */
1152
- interface HttpUnaryResult {
1153
- /** HTTP status code derived from the dispatch outcome. */
1154
- status: number;
1155
- /** The LAFS envelope to serialize as the JSON response body. */
1156
- body: DispatchResponse;
1157
- }
1158
-
1159
- /**
1160
- * HTTP transport adapter — serves gateway unary + SSE over HTTP.
1161
- *
1162
- * The fourth and final gateway transport (after CLI, MCP, RPC). Unlike the
1163
- * connection-oriented RPC/MCP adapters, the HTTP adapter is framework-agnostic
1164
- * by design: it does NOT bind a port or own a `Request`/`Response` lifecycle.
1165
- * Instead it exposes two pure routing primitives the embedder (a SvelteKit
1166
- * route handler, the daemon's HTTP server, a test harness) calls directly:
1167
- *
1168
- * - {@link routeUnary} — one POST → `source: 'http'` {@link DispatchRequest}
1169
- * → injected {@link GatewayHandler} → `{ status, body }` (LAFS envelope).
1170
- * - the SSE primitives in `./sse.js` (`createSseStream`) for streaming ops.
1171
- *
1172
- * This keeps the runtime free of any HTTP-server / SvelteKit / `@cleocode/cleo`
1173
- * dependency: the embedder owns wire concerns (parsing the URL into a
1174
- * `(gateway, domain, operation)` triple, setting headers, `process.exit`,
1175
- * error-render at the edge), exactly mirroring how the CLI adapter wraps the
1176
- * same handler. There is NO `process.exit` and NO error-render in the handlers
1177
- * (R3 contract); the adapter never calls `process.exit` at all.
1178
- *
1179
- * The request `source` is forced to `'http'` regardless of what the embedder
1180
- * passes, so a client cannot impersonate another transport.
1181
- *
1182
- * @packageDocumentation
1183
- * @module @cleocode/runtime/gateway/http
1184
- *
1185
- * @task T11450
1186
- * @epic T11254
1187
- * @saga T11243
1188
- */
1189
-
1190
- /**
1191
- * Map a LAFS dispatch error code to an HTTP status code.
1192
- *
1193
- * The canonical machine-readable error code (`E_NOT_FOUND`, `E_VALIDATION_…`,
1194
- * `E_FORBIDDEN`, …) is the source of truth; the numeric LAFS exit code is a
1195
- * secondary hint. Unknown error codes fall back to `500`. A successful envelope
1196
- * is always `200`.
1197
- *
1198
- * @param response - The dispatch response.
1199
- * @returns The HTTP status the embedder should set.
1200
- */
1201
- declare function statusForResponse(response: DispatchResponse): number;
1202
- /**
1203
- * Route a single HTTP unary request through the gateway handler.
1204
- *
1205
- * Builds a `source: 'http'` {@link DispatchRequest} from the embedder-resolved
1206
- * `(gateway, domain, operation, params)` triple and delegates to the injected
1207
- * {@link GatewayHandler}. A thrown handler error is trapped and rendered as a
1208
- * `500` LAFS error envelope — never propagated as an unhandled rejection or
1209
- * `process.exit`. The `source` is always forced to `'http'`.
1210
- *
1211
- * @param handler - The injected transport-neutral gateway handler.
1212
- * @param req - The embedder-resolved unary request coordinates + body params.
1213
- * @returns The HTTP status + LAFS response body for the embedder to serialize.
1214
- */
1215
- declare function routeUnary(handler: GatewayHandler, req: HttpUnaryRequest): Promise<HttpUnaryResult>;
1216
-
1217
- /**
1218
- * Server-Sent Events (SSE) wire primitives for the `@cleocode/runtime/gateway/http`
1219
- * adapter.
1220
- *
1221
- * The HTTP transport is the only gateway transport that streams: long-running
1222
- * and subscription operations emit a sequence of {@link GatewayStreamEvent}
1223
- * frames terminated by a `done` or `error` frame. This module owns ONLY the SSE
1224
- * wire encoding + the `ReadableStream` lifecycle plumbing; the actual frame
1225
- * production (polling a DB, tailing a job, etc.) lives in the caller's async
1226
- * source. Keeping the encode/stream concern here lets BOTH the gateway-routed
1227
- * streaming ops AND the existing Studio SSE endpoints (which carry their own
1228
- * domain event shapes) share one tested, leak-free stream builder.
1229
- *
1230
- * Two encoders are provided so an existing stream can adopt this plumbing
1231
- * WITHOUT changing its observed bytes:
1232
- * - {@link encodeStreamEvent} — the canonical `data: <json>\n\n` form for
1233
- * {@link GatewayStreamEvent} frames (gateway-routed streaming ops).
1234
- * - {@link encodeSseFrame} — a generic `event:`/`id:`/`data:` writer for any
1235
- * payload, so endpoints with a bespoke wire (a named `event:` field, a
1236
- * `data:`-only frame) keep an IDENTICAL byte stream while still routing their
1237
- * lifecycle through the shared, abort-safe builder.
1238
- *
1239
- * @packageDocumentation
1240
- * @module @cleocode/runtime/gateway/http/sse
1241
- *
1242
- * @task T11450
1243
- * @epic T11254
1244
- * @saga T11243
1245
- */
1246
-
1247
- /**
1248
- * One generic SSE frame: an optional named `event`, an optional `id`, and a
1249
- * `data` payload that is JSON-serialized.
1250
- *
1251
- * This is intentionally NOT {@link GatewayStreamEvent} — it is the raw wire
1252
- * shape so a bespoke endpoint can reproduce its exact existing bytes (e.g. a
1253
- * named `event: task-updated` line, or a `data:`-only frame with no `event`).
1254
- */
1255
- interface SseFrame {
1256
- /** Optional SSE `event:` field name (omitted → a `data:`-only frame). */
1257
- event?: string;
1258
- /** Optional SSE `id:` field for client-side last-event-id resumption. */
1259
- id?: string;
1260
- /** The frame payload; JSON-serialized into the `data:` field. */
1261
- data: unknown;
1262
- }
1263
- /**
1264
- * Encode a generic {@link SseFrame} to its SSE wire bytes.
1265
- *
1266
- * Field order is `event:` (if present) → `id:` (if present) → `data:`, matching
1267
- * the SSE spec record layout, then a blank line. When `event` is omitted the
1268
- * frame is a `data:`-only record — byte-identical to a handwritten
1269
- * `data: <json>\n\n`.
1270
- *
1271
- * @param frame - The frame to serialize.
1272
- * @returns The SSE record as a string (UTF-8 caller-encoded).
1273
- */
1274
- declare function encodeSseFrame(frame: SseFrame): string;
1275
- /**
1276
- * Encode a canonical {@link GatewayStreamEvent} as a `data:`-only SSE record.
1277
- *
1278
- * Gateway-routed streaming ops carry the discriminator + sequence inside the
1279
- * JSON payload (`{ kind, seq, data, error, requestId }`), so no named SSE
1280
- * `event:` field is used — the client demultiplexes on `kind`/`seq`. The
1281
- * monotonic `seq` is also surfaced as the SSE `id:` so a reconnecting client can
1282
- * send `Last-Event-ID`.
1283
- *
1284
- * @param frame - The gateway stream event to serialize.
1285
- * @returns The SSE record as a string.
1286
- */
1287
- declare function encodeStreamEvent(frame: GatewayStreamEvent): string;
1288
- /** The standard SSE response headers (no caching, no proxy buffering). */
1289
- declare const SSE_HEADERS: Readonly<Record<string, string>>;
1290
- /**
1291
- * A push-style SSE controller handed to an {@link SseSource} so it can emit
1292
- * frames and close the stream without touching the underlying
1293
- * `ReadableStreamDefaultController`.
1294
- */
1295
- interface SseEmitter {
1296
- /** Emit one generic SSE frame; a no-op once the stream is closed. */
1297
- send(frame: SseFrame): void;
1298
- /** Emit one canonical {@link GatewayStreamEvent}; a no-op once closed. */
1299
- sendStreamEvent(frame: GatewayStreamEvent): void;
1300
- /**
1301
- * Emit a pre-encoded SSE record verbatim; a no-op once closed. For endpoints
1302
- * that own their own wire encoder (e.g. a bespoke `data: <json>\n\n`) and want
1303
- * to keep emitting byte-identical records while still routing lifecycle
1304
- * through this builder.
1305
- */
1306
- sendRaw(record: string): void;
1307
- /** Whether the stream has been closed (client disconnect or `close()`). */
1308
- readonly closed: boolean;
1309
- /** Close the stream; idempotent. */
1310
- close(): void;
1311
- }
1312
- /**
1313
- * A teardown callback returned by an {@link SseSource}, invoked exactly once when
1314
- * the stream ends (client disconnect, `emitter.close()`, or producer completion).
1315
- */
1316
- type SseTeardown = () => void;
1317
- /**
1318
- * A frame source for {@link createSseStream}. Receives an {@link SseEmitter} and
1319
- * MAY return an {@link SseTeardown} callback (return nothing to skip teardown).
1320
- *
1321
- * The source is started SYNCHRONOUSLY inside `ReadableStream.start`, mirroring
1322
- * the existing Studio endpoints (which schedule timers inside `start`). A source
1323
- * may push frames immediately and/or schedule async work.
1324
- */
1325
- type SseSource = (emitter: SseEmitter) => SseTeardown | undefined;
1326
- /**
1327
- * Build a `text/event-stream` {@link ReadableStream} driven by an
1328
- * {@link SseSource}.
1329
- *
1330
- * This is the shared, abort-safe stream builder every SSE endpoint routes
1331
- * through. It guarantees:
1332
- * - frames are dropped (never throw) after the stream closes;
1333
- * - the source's teardown callback runs exactly once;
1334
- * - an optional {@link AbortSignal} (the request's) closes the stream on client
1335
- * disconnect, with its listener removed on teardown.
1336
- *
1337
- * The byte output is fully controlled by what the source emits — so an endpoint
1338
- * adopting this builder reproduces its prior wire exactly by emitting the same
1339
- * frames in the same order.
1340
- *
1341
- * @param source - The frame producer.
1342
- * @param signal - Optional request abort signal that closes the stream on
1343
- * client disconnect.
1344
- * @returns A ReadableStream of UTF-8 SSE bytes for a `Response` body.
1345
- */
1346
- declare function createSseStream(source: SseSource, signal?: AbortSignal): ReadableStream<Uint8Array>;
1347
-
1348
- /**
1349
- * Streaming-operation source adapters for the gateway HTTP/SSE transport (T11921).
1350
- *
1351
- * A streaming operation (`OperationDef.streaming === true`, served as
1352
- * `GET /v1/<domain>/<operation>`) needs a *producer*: an async source that tails
1353
- * an event origin (the agent/worker/board lifecycle bus) and feeds
1354
- * {@link GatewayStreamEvent} frames into the shared, abort-safe
1355
- * {@link createSseStream} builder. This module owns ONLY that producer concern —
1356
- * the wire encoding + stream lifecycle live in `./sse.ts`, and the route parsing
1357
- * lives in `./listen.ts`.
1358
- *
1359
- * The runtime is deliberately DB-free (the runtime invariant — no drizzle, no
1360
- * `@cleocode/cleo`), so a source here MUST NOT open a store directly. Instead the
1361
- * listener resolves a source by `<domain>.<operation>` key from the
1362
- * {@link STREAM_SOURCES} registry; an embedder that owns a richer origin (the
1363
- * daemon, a SvelteKit route) can inject its own source into the listener at
1364
- * boot. The default source shipped here is a bounded tick/heartbeat producer that
1365
- * proves the pipe end-to-end without reaching across the package boundary — and
1366
- * is exactly what the in-process test exercises.
1367
- *
1368
- * Secrets never touch the wire: a source emits only the public lifecycle payload
1369
- * it is handed; credential resolution (if any) happens server-side before the
1370
- * frame is built, exactly as it does for a unary dispatch.
1371
- *
1372
- * @packageDocumentation
1373
- * @module @cleocode/runtime/gateway/http/stream-sources
1374
- *
1375
- * @task T11921
1376
- * @epic T11769
1377
- * @saga T10400
1378
- */
1379
-
1380
- /**
1381
- * The bind coordinates a {@link GatewayStreamSource} receives: the resolved
1382
- * `(domain, operation)` of the streaming route, the already-validated request
1383
- * params, and the request id (so emitted frames correlate back to the origin
1384
- * request).
1385
- */
1386
- interface StreamSourceContext {
1387
- /** The canonical domain segment of the streaming route. */
1388
- domain: string;
1389
- /** The operation segment of the streaming route. */
1390
- operation: string;
1391
- /** The decoded query params for the streaming request. */
1392
- params: Record<string, unknown>;
1393
- /** The request id stamped on every emitted {@link GatewayStreamEvent}. */
1394
- requestId: string;
1395
- }
1396
- /**
1397
- * A teardown callback a {@link GatewayStreamSource} MAY return; invoked exactly
1398
- * once when the stream ends (client disconnect, terminal frame, completion).
1399
- */
1400
- type StreamSourceTeardown = () => void;
1401
- /**
1402
- * A producer for one streaming operation. Receives an {@link SseEmitter}
1403
- * (`sendStreamEvent` emits one canonical {@link GatewayStreamEvent}) plus the
1404
- * resolved {@link StreamSourceContext}, and MAY return a teardown callback.
1405
- *
1406
- * The source is started synchronously inside the stream builder, mirroring the
1407
- * Studio SSE endpoints; it may push frames immediately and/or schedule async
1408
- * work. It MUST drop frames once `emitter.closed` is true (the emitter already
1409
- * guards this) and MUST clean up timers/listeners in its teardown so a client
1410
- * disconnect closes the stream leak-free.
1411
- */
1412
- type GatewayStreamSource = (emitter: SseEmitter, context: StreamSourceContext) => StreamSourceTeardown | undefined;
1413
- /**
1414
- * The default `orchestrate.events` source: a bounded tick/heartbeat producer.
1415
- *
1416
- * Emits a monotonic sequence of `data` {@link GatewayStreamEvent} frames at
1417
- * {@link DEFAULT_TICK_INTERVAL_MS}, then a terminal `done` frame once the
1418
- * `ticks` budget is exhausted; a client disconnect tears the timer down before
1419
- * then. It carries no DB handle and no secret — each frame's payload is a public
1420
- * heartbeat (`{ tick, ts }`) standing in for an agent/worker/board lifecycle
1421
- * event until the daemon injects its real origin-tailing source.
1422
- *
1423
- * A first frame is emitted SYNCHRONOUSLY so a client (and the test) observes a
1424
- * well-formed `data:` frame immediately, without waiting a full interval.
1425
- *
1426
- * @param emitter - The SSE emitter for this stream.
1427
- * @param context - The resolved streaming-route context.
1428
- * @returns A teardown that clears the tick timer.
1429
- */
1430
- declare const tickStreamSource: GatewayStreamSource;
1431
- /**
1432
- * Register (or override) the {@link GatewayStreamSource} for a streaming op.
1433
- *
1434
- * Lets an embedder that owns a richer event origin (the daemon tailing the
1435
- * agent/worker/board lifecycle bus, a SvelteKit route reading a store) replace
1436
- * the default tick source for a given `(domain, operation)` without changing the
1437
- * listener. Idempotent — a later call wins.
1438
- *
1439
- * @param domain - The canonical domain of the streaming op.
1440
- * @param operation - The operation name of the streaming op.
1441
- * @param source - The producer to serve this streaming route with.
1442
- */
1443
- declare function registerStreamSource(domain: string, operation: string, source: GatewayStreamSource): void;
1444
- /**
1445
- * Resolve the {@link GatewayStreamSource} registered for a streaming op.
1446
- *
1447
- * @param domain - The canonical domain of the streaming op.
1448
- * @param operation - The operation name of the streaming op.
1449
- * @returns The registered source, or `undefined` when none is registered (the
1450
- * listener then closes the stream with a single terminal `error` frame).
1451
- */
1452
- declare function resolveStreamSource(domain: string, operation: string): GatewayStreamSource | undefined;
1453
-
1454
- /**
1455
- * Static file serving for the bundled CLEO Web Studio assets.
1456
- *
1457
- * The gateway HTTP server calls {@link serveStudioStatic} for any request whose
1458
- * path starts with the `/studio` prefix. The handler:
1459
- *
1460
- * 1. Strips the `/studio` prefix and resolves the remainder against the
1461
- * `studioStaticDir` (the `build/client/` output of the Studio
1462
- * `adapter-node` build bundled inside `@cleocode/cleo`).
1463
- * 2. Serves the matched file with the correct MIME type (inferred from the
1464
- * extension via the built-in {@link MIME_TYPES} map).
1465
- * 3. Returns `index.html` for any path that does not resolve to a static file
1466
- * — the SPA fallback that lets the SvelteKit client-side router handle
1467
- * sub-routes.
1468
- * 4. Returns a clean JSON error envelope (not a stack trace) when
1469
- * `studioStaticDir` is absent (dev checkout without a Studio build, or an
1470
- * npm install where the postbuild copy was skipped).
1471
- *
1472
- * The handler deliberately has NO `process.exit`, NO drizzle, NO
1473
- * `@cleocode/cleo` dependency — it is a pure file-system responder that can be
1474
- * embedded in any `node:http` server (the gateway daemon, a test harness).
1475
- *
1476
- * The `build/client/` static tree is served under `/studio/` so all of its
1477
- * relative asset references (`/_app/immutable/...`) resolve correctly. The root
1478
- * `/studio` (no trailing slash) redirects to `/studio/` to keep the base URL
1479
- * consistent with the SvelteKit assets.
1480
- *
1481
- * @packageDocumentation
1482
- * @module @cleocode/runtime/gateway/http
1483
- *
1484
- * @task T11979
1485
- * @epic T11261
1486
- */
1487
-
1488
- /** The URL prefix the Studio static handler claims. */
1489
- declare const STUDIO_PREFIX = "/studio";
1490
- /**
1491
- * Options for the Studio static handler, injected at server startup.
1492
- *
1493
- * The caller resolves the absolute path to the static asset directory and
1494
- * passes it here — the handler never reads environment variables or resolves
1495
- * paths itself, keeping concerns separated and the handler testable.
1496
- */
1497
- interface StudioStaticOptions {
1498
- /**
1499
- * Absolute path to the Studio static asset directory. This is the
1500
- * `build/client/` subdirectory from an `adapter-node` Studio build, bundled
1501
- * inside `@cleocode/cleo` at `studio-dist/client/`.
1502
- *
1503
- * When `undefined` or the path does not exist on disk, every request under
1504
- * `/studio` returns a clean JSON 503 "bundle absent" envelope.
1505
- */
1506
- staticDir: string | undefined;
1507
- }
1508
- /**
1509
- * Determine whether a request path falls under the Studio prefix and should be
1510
- * handled by the static asset handler rather than the API gateway.
1511
- *
1512
- * @param pathname - The URL pathname (without query string).
1513
- * @returns `true` when the path is `/studio`, `/studio/`, or any
1514
- * `/studio/<subpath>`.
1515
- */
1516
- declare function isStudioPath(pathname: string): boolean;
1517
- /**
1518
- * Resolve the file-system path for a Studio request within `staticDir`.
1519
- *
1520
- * Strips the `/studio` prefix and normalises the remainder; guards against
1521
- * path-traversal (a normalised path that escapes `staticDir` is rejected).
1522
- *
1523
- * @param staticDir - Absolute path to the Studio static asset directory.
1524
- * @param pathname - The request path (already verified to start with `/studio`).
1525
- * @returns The absolute candidate path, or `undefined` when path traversal is
1526
- * detected.
1527
- */
1528
- declare function resolveStudioAssetPath(staticDir: string, pathname: string): string | undefined;
1529
- /**
1530
- * Infer the MIME type for a file from its extension.
1531
- *
1532
- * @param filePath - The file path (only the extension is used).
1533
- * @returns The appropriate `Content-Type` header value.
1534
- */
1535
- declare function mimeType(filePath: string): string;
1536
- /**
1537
- * Write the "bundle absent" JSON error envelope and close the response.
1538
- *
1539
- * Used when `staticDir` is `undefined` or does not exist on disk — the Studio
1540
- * build was not found. The response is a `503 Service Unavailable` with a clean
1541
- * LAFS-flavoured JSON body so clients can detect the condition programmatically.
1542
- *
1543
- * @param res - The server response to write to.
1544
- * @param hint - Optional developer hint (e.g. the expected bundle path).
1545
- */
1546
- declare function writeBundleAbsentError(res: ServerResponse, hint?: string): void;
1547
- /**
1548
- * Handle a single HTTP request whose path falls under `/studio`.
1549
- *
1550
- * Routing logic:
1551
- * - `GET /studio` → 302 redirect to `/studio/` (keep asset refs consistent)
1552
- * - `GET /studio/<static-asset>` → serve the file from `staticDir`
1553
- * - `GET /studio/*` (no matching file) → serve `index.html` (SPA fallback)
1554
- * - Any request when `staticDir` is absent → 503 JSON envelope
1555
- * - Non-GET method → 405 JSON envelope (Studio is read-only from the gateway)
1556
- *
1557
- * @param req - The inbound HTTP request.
1558
- * @param res - The outbound HTTP response.
1559
- * @param opts - Static handler options (resolved at server startup).
1560
- * @returns A promise that resolves once the response has been written.
1561
- */
1562
- declare function serveStudioStatic(req: IncomingMessage, res: ServerResponse, opts: StudioStaticOptions): Promise<void>;
1563
- /**
1564
- * Resolve the bundled Studio static directory from the `@cleocode/cleo` package
1565
- * location.
1566
- *
1567
- * When running from an npm install, the Studio build is bundled at
1568
- * `<cleo-package-root>/studio-dist/client/`. When running from a monorepo dev
1569
- * checkout, the directory may not exist (developers must run
1570
- * `pnpm --filter @cleocode/studio run build` manually) — `undefined` is
1571
- * returned in that case, and the gateway handler will serve the bundle-absent
1572
- * error instead of crashing.
1573
- *
1574
- * @param cleoPackageDir - Absolute path to the `@cleocode/cleo` package root.
1575
- * Typically derived from `import.meta.url` at the call site.
1576
- * @returns The absolute path to `studio-dist/client/`, or `undefined` if the
1577
- * directory does not exist.
1578
- */
1579
- declare function resolveStudioStaticDir(cleoPackageDir: string): string | undefined;
1580
-
1581
- /**
1582
- * Singleton accessor for the gateway {@link BackgroundJobManager}.
1583
- *
1584
- * @remarks
1585
- * Relocated from `packages/cleo/src/dispatch/lib/job-manager-accessor.ts` into
1586
- * `@cleocode/runtime/gateway` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION) so the
1587
- * `admin` domain handler resolves the job-manager singleton from the runtime
1588
- * layer rather than a cleo-internal path. A thin re-export shim remains at the
1589
- * old path so any existing import site compiles unchanged.
1590
- *
1591
- * Long-lived hosts (e.g. the daemon) call {@link setJobManager} once at startup;
1592
- * short-lived CLI invocations leave the singleton `null`, and consumers handle
1593
- * the absence gracefully.
1594
- *
1595
- * @task T4820
1596
- * @task T11455
1597
- */
1598
-
1599
- /**
1600
- * Register the process-wide {@link BackgroundJobManager} singleton.
1601
- *
1602
- * @param manager - The manager to expose, or null to unregister a stopped host.
1603
- * @remarks Hosts unregister before closing their store; absence is reported explicitly.
1604
- */
1605
- declare function setJobManager(manager: BackgroundJobManager | null): void;
1606
- /**
1607
- * Retrieve the registered {@link BackgroundJobManager} singleton.
1608
- *
1609
- * @returns The registered manager, or `null` when running in a short-lived
1610
- * host (e.g. a one-shot CLI invocation) that never called {@link setJobManager}.
1611
- */
1612
- declare function getJobManager(): BackgroundJobManager | null;
1613
-
1614
- /**
1615
- * MCP wire types for the `@cleocode/runtime/gateway/mcp` transport adapter.
1616
- *
1617
- * A deliberately-minimal subset of the Model Context Protocol shapes the
1618
- * adapter needs — no external MCP SDK dependency. Mirrors the legacy
1619
- * `@cleocode/mcp-adapter` `types.ts` so the wire surface is unchanged.
1620
- *
1621
- * @task T11448
1622
- * @epic T11254
1623
- * @saga T11243
1624
- */
1625
- /** JSON Schema property descriptor surfaced for a single tool input field. */
1626
- interface McpInputProperty {
1627
- /** JSON Schema primitive type (`'string'`, `'number'`, …). */
1628
- type: string;
1629
- /** Human-readable description shown in MCP clients. */
1630
- description: string;
1631
- /** Allowed values (JSON Schema `enum`) for finite-domain string params. */
1632
- enum?: readonly string[];
1633
- }
1634
- /**
1635
- * MCP tool definition shape (minimal — no external MCP SDK dependency).
1636
- * Structurally compatible with the legacy `@cleocode/mcp-adapter` `McpTool`.
1637
- */
1638
- interface McpTool {
1639
- /** Stable tool name (snake_case, e.g. `cleo_sentient_status`). */
1640
- name: string;
1641
- /** Human-readable description shown in MCP clients. */
1642
- description: string;
1643
- /** JSON Schema for the tool's input parameters. */
1644
- inputSchema: {
1645
- type: 'object';
1646
- properties: Record<string, McpInputProperty>;
1647
- required?: string[];
1648
- };
1649
- }
1650
- /** A single content block returned by a tool handler. */
1651
- interface McpContent {
1652
- type: 'text';
1653
- text: string;
1654
- }
1655
- /** Result returned by a tool handler over the MCP wire. */
1656
- interface McpToolResult {
1657
- content: McpContent[];
1658
- isError?: boolean;
1659
- }
1660
-
1661
- /**
1662
- * MCP stdio transport adapter — routes `tools/call` through the gateway.
1663
- *
1664
- * Serves the Model Context Protocol over stdio (newline-delimited JSON-RPC 2.0)
1665
- * so any MCP-compatible client (Claude Code, LLM-agnostic tools) can discover
1666
- * and invoke CLEO operations. Unlike the legacy standalone `@cleocode/mcp-adapter`
1667
- * — which called the `@cleocode/core` SDK directly — this adapter is a thin
1668
- * TRANSPORT over the unified gateway:
1669
- *
1670
- * MCP `tools/call` { name, arguments }
1671
- * → {@link GatewayOperationKey} (via the `mcpExposed` registry subset)
1672
- * → DispatchRequest { source: 'mcp', gateway, domain, operation, params }
1673
- * → injected {@link GatewayHandler}.handle()
1674
- * → DispatchResponse (LAFS envelope) → MCP tool result (JSON text content)
1675
- *
1676
- * The adapter owns ONLY wire concerns: JSON-RPC framing, the stdio read loop,
1677
- * `tools/list` generation, error rendering, and `process.exit` on stream close.
1678
- * All domain logic (resolution, validation, middleware, handler execution) lives
1679
- * behind the {@link GatewayHandler} — exactly mirroring how the CLI adapter
1680
- * wraps the same handler. `process.exit` and error-render stay HERE, never in
1681
- * the handlers (R3-T4 contract).
1682
- *
1683
- * `tools/list` is generated from the OPERATIONS registry behind the default-deny
1684
- * `mcpExposed` flag, so the external tool surface is identical to the historical
1685
- * standalone adapter (no behavior change).
1686
- *
1687
- * @task T11448
1688
- * @epic T11254
1689
- * @saga T11243
1690
- */
1691
-
1692
- /**
1693
- * Route a single MCP `tools/call` through the gateway handler.
1694
- *
1695
- * Resolves the tool name to a `(domain, operation)` key against the exposed
1696
- * (`mcpExposed`) registry subset, builds a `source: 'mcp'` {@link DispatchRequest},
1697
- * and delegates to the injected {@link GatewayHandler}. Unknown tool names and
1698
- * thrown handler errors are rendered as MCP error results — never propagated as
1699
- * `process.exit` from this layer.
1700
- *
1701
- * @param handler - The injected transport-neutral gateway handler.
1702
- * @param toolName - The MCP `tools/call` tool name.
1703
- * @param args - The parsed tool arguments (already JSON-decoded by the client).
1704
- * @returns An MCP {@link McpToolResult}.
1705
- */
1706
- declare function callTool(handler: GatewayHandler, toolName: string, args: Record<string, unknown>): Promise<McpToolResult>;
1707
- /**
1708
- * Options for {@link startMcpServer}.
1709
- */
1710
- interface McpServerOptions {
1711
- /** Inbound stream (defaults to `process.stdin`). */
1712
- input?: NodeJS.ReadableStream;
1713
- /** Outbound stream (defaults to `process.stdout`). */
1714
- output?: NodeJS.WritableStream;
1715
- /**
1716
- * Whether to call `process.exit(0)` when the input stream closes.
1717
- * Defaults to `true` (production stdio). Tests pass `false`.
1718
- */
1719
- exitOnClose?: boolean;
1720
- }
1721
- /**
1722
- * Start the MCP stdio server over the gateway.
1723
- *
1724
- * Reads newline-delimited JSON-RPC 2.0 requests from the input stream, routes
1725
- * `tools/call` through the injected {@link GatewayHandler}, and writes responses
1726
- * to the output stream. Each inbound line must be one complete JSON-RPC request.
1727
- *
1728
- * The caller assembles the {@link GatewayHandler} (via `createGatewayHandler`
1729
- * with its domain handlers + middleware) and injects it here — this adapter
1730
- * never builds handlers itself, keeping the runtime free of any
1731
- * `@cleocode/cleo` dependency.
1732
- *
1733
- * @param handler - The transport-neutral gateway handler to route through.
1734
- * @param opts - Optional stream + lifecycle overrides (defaults to stdio).
1735
- */
1736
- declare function startMcpServer(handler: GatewayHandler, opts?: McpServerOptions): void;
1737
-
1738
- /**
1739
- * MCP tool-name <-> gateway-operation mapping.
1740
- *
1741
- * The MCP wire protocol identifies a tool by a single flat `name` string
1742
- * (snake_case, e.g. `cleo_sentient_propose_list`), whereas the gateway routes
1743
- * on a `(gateway, domain, operation)` triple (e.g.
1744
- * `query / sentient / propose.list`). This module is the bijective bridge
1745
- * between the two surfaces:
1746
- *
1747
- * gateway op → MCP tool name {@link operationToToolName}
1748
- * MCP tool name → domain+operation {@link toolNameToOperationKey}
1749
- *
1750
- * ## Naming convention (frozen — matches the legacy `@cleocode/mcp-adapter`)
1751
- *
1752
- * `cleo_<domain>_<operation>` where every `.` and `-` in the dotted operation
1753
- * name becomes `_`. The three historically-exposed tools therefore map as:
1754
- *
1755
- * | gateway op | MCP tool name |
1756
- * |-----------------------------|--------------------------------|
1757
- * | `sentient` · `status` | `cleo_sentient_status` |
1758
- * | `sentient` · `propose.list` | `cleo_sentient_propose_list` |
1759
- * | `sentient` · `propose.enable` | `cleo_sentient_propose_enable` |
1760
- *
1761
- * Preserving this exact mapping is what guarantees R3-T4's "no behavior change
1762
- * to the MCP tool surface" acceptance criterion.
1763
- *
1764
- * @task T11448
1765
- * @epic T11254
1766
- * @saga T11243
1767
- */
1768
-
1769
- /** Prefix every CLEO MCP tool name carries. */
1770
- declare const MCP_TOOL_PREFIX = "cleo_";
1771
- /**
1772
- * Identifies a gateway operation by its `(domain, operation)` pair. The CQRS
1773
- * gateway resolves which `Gateway` ('query' | 'mutate') the pair belongs to via
1774
- * the operation registry, so the MCP adapter does not need to carry it.
1775
- */
1776
- interface GatewayOperationKey {
1777
- /** Canonical domain (e.g. `'sentient'`). */
1778
- readonly domain: string;
1779
- /** Dotted operation name (e.g. `'propose.list'`). */
1780
- readonly operation: string;
1781
- }
1782
- /**
1783
- * Derive the flat MCP tool name for a gateway operation.
1784
- *
1785
- * @param op - An {@link OperationDef} (or any object carrying `domain` +
1786
- * `operation`) from the registry.
1787
- * @returns The MCP tool name, e.g. `'cleo_sentient_propose_list'`.
1788
- */
1789
- declare function operationToToolName(op: Pick<OperationDef, 'domain' | 'operation'>): string;
1790
- /**
1791
- * Resolve an MCP tool name back to its `(domain, operation)` gateway key.
1792
- *
1793
- * The reverse of {@link operationToToolName} cannot recover dots vs dashes from
1794
- * the snake_case wire form, so resolution is performed against the provided
1795
- * `exposed` set (the same `mcpExposed: true` subset used to build `tools/list`).
1796
- * This keeps the mapping exact and rejects unknown tool names.
1797
- *
1798
- * @param toolName - The MCP `tools/call` tool name.
1799
- * @param exposed - The exposed operation defs (the `mcpExposed` subset).
1800
- * @returns The matching {@link GatewayOperationKey}, or `undefined` when the
1801
- * tool name does not correspond to any exposed operation.
1802
- */
1803
- declare function toolNameToOperationKey(toolName: string, exposed: readonly OperationDef[]): GatewayOperationKey | undefined;
1804
-
1805
- /**
1806
- * MCP `tools/list` generation from the OPERATIONS registry.
1807
- *
1808
- * The MCP tool surface is **default-deny**: only {@link OperationDef} entries
1809
- * that explicitly set `mcpExposed: true` are surfaced. This replaces the legacy
1810
- * `@cleocode/mcp-adapter`'s hand-maintained `ALL_TOOLS` array with a single
1811
- * generation point driven by the canonical registry — promoting a new tool is
1812
- * now one `mcpExposed: true` edit in `@cleocode/contracts`, not a fork-and-copy
1813
- * into a standalone adapter.
1814
- *
1815
- * @task T11448
1816
- * @epic T11254
1817
- * @saga T11243
1818
- */
1819
-
1820
- /**
1821
- * The subset of OPERATIONS surfaced over MCP (default-deny `mcpExposed` filter).
1822
- *
1823
- * @returns Every {@link OperationDef} with `mcpExposed === true`, in registry
1824
- * order. Empty when no operation opts in.
1825
- */
1826
- declare function exposedOperations(): OperationDef[];
1827
- /**
1828
- * Convert a single exposed {@link OperationDef} into its MCP tool definition.
1829
- *
1830
- * @param op - An operation with `mcpExposed === true`.
1831
- * @returns The MCP {@link McpTool} (name + description + inputSchema).
1832
- */
1833
- declare function operationToMcpTool(op: OperationDef): McpTool;
1834
- /**
1835
- * Generate the full MCP `tools/list` payload from the registry.
1836
- *
1837
- * @returns Every exposed operation rendered as an {@link McpTool}, in registry
1838
- * order (stable across calls).
1839
- */
1840
- declare function buildToolsList(): McpTool[];
1841
-
1842
- /**
1843
- * Dispatch-local metadata factory.
1844
- *
1845
- * Generates LAFS-conformant metadata for dispatch layer responses.
1846
- * Generates LAFS-conformant metadata for dispatch responses.
1847
- *
1848
- * @task T4772
1849
- */
1850
-
1851
- /**
1852
- * Create metadata for a dispatch response.
1853
- *
1854
- * @param gateway - Gateway name (e.g., 'query', 'mutate')
1855
- * @param domain - Domain name (e.g., 'tasks', 'session')
1856
- * @param operation - Operation name (e.g., 'show', 'list')
1857
- * @param startTime - Timestamp from Date.now() at start of request
1858
- * @param source - Where the request originated
1859
- * @param requestId - Optional pre-generated request ID
1860
- * @param sessionId - Optional session ID to include in metadata
1861
- * @param originSessionId - Optional root workflow/saga session ID
1862
- * @param executionSessionId - Optional execution-attempt session ID
1863
- * @returns Metadata conforming to DispatchResponse['meta']
1864
- *
1865
- * @task T4772
1866
- * @task T4959
1867
- */
1868
- declare function createDispatchMeta(gateway: string, domain: string, operation: string, startTime: number, source?: Source, requestId?: string, sessionId?: string | null, originSessionId?: string | null, executionSessionId?: string | null): DispatchResponse['meta'];
1869
-
1870
- /**
1871
- * nexus-decorator — stamps `meta._nexus` on every nexus-domain dispatch response.
1872
- *
1873
- * Wave 2 of the Nexus Restructure (T9146). Reads `getNexusDescriptor(opId)` from
1874
- * the W1 SSoT (`NEXUS_SCOPE_MAP`) and injects the namespaced `_nexus` block into
1875
- * `DispatchResponse['meta']`.
1876
- *
1877
- * Usage:
1878
- * ```ts
1879
- * const decorated = stampNexusMeta(response, operation, params);
1880
- * ```
1881
- *
1882
- * The `suggestedNext` field uses a structured form (not literal display strings).
1883
- * Display strings are derived by `formatSuggestedNext()` for human/markdown renderers.
1884
- *
1885
- * Typed-registry gate: `validateSuggestedNext()` fails the build (throws at import-time
1886
- * in test mode) if any `suggestedNext.op` is absent from `NEXUS_SCOPE_MAP`.
1887
- *
1888
- * @remarks
1889
- * Relocated from `packages/cleo/src/dispatch/nexus-decorator.ts` into
1890
- * `@cleocode/runtime/gateway` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION) so the
1891
- * nexus domain handler can import it from the runtime layer rather than a
1892
- * cleo-internal path. The module is pure (`@cleocode/contracts` only). A thin
1893
- * re-export shim remains at the old path for the renderer / CLI import sites.
1894
- *
1895
- * @task T9146
1896
- * @task T11455
1897
- * @module runtime/gateway/nexus-decorator
1898
- */
1899
-
1900
- /**
1901
- * Assert that every `suggestedNext.op` in a list resolves to a known
1902
- * NEXUS_SCOPE_MAP entry.
1903
- *
1904
- * Throws `TypeError` at runtime (caught by tests and CI) if an unknown op
1905
- * is referenced — acting as a build-time gate when imported in test suites.
1906
- *
1907
- * @throws {TypeError} if any `op` is absent from `NEXUS_SCOPE_MAP`
1908
- * @task T9146
1909
- */
1910
- declare function validateSuggestedNext(suggestions: ReadonlyArray<SuggestedNextOp>): void;
1911
- /**
1912
- * Derive a human-readable display string from a {@link SuggestedNextOp}.
1913
- *
1914
- * Example: `"cleo nexus context --project-id myproj (read-only, no confirmation needed)"`
1915
- */
1916
- declare function formatSuggestedNext(s: SuggestedNextOp): string;
1917
- /**
1918
- * Stamp `meta._nexus` onto a nexus-domain {@link DispatchResponse}.
1919
- *
1920
- * Call this after every nexus handler produces its `DispatchResponse`. The
1921
- * function is a pure transformer — it returns a new object with `meta._nexus`
1922
- * merged in.
1923
- *
1924
- * @param response - The original dispatch response from the nexus handler.
1925
- * @param operation - The nexus operation key (must be a `keyof NexusOps`).
1926
- * @param params - Raw params passed to the handler (used for binding-source resolution).
1927
- * @param isTopLevel - Whether this is a top-level CLI invocation (not a sub-call).
1928
- * @returns A new `DispatchResponse` with `meta._nexus` populated.
1929
- *
1930
- * @task T9146
1931
- */
1932
- declare function stampNexusMeta(response: DispatchResponse, operation: string, params: Record<string, unknown>, isTopLevel?: boolean): DispatchResponse;
1933
- /**
1934
- * Pick the decorator-stamped fields (`_nexus`, `deprecated`) from a dispatch
1935
- * response's `meta` so they can be forwarded via `cliOutput`'s `extensions`
1936
- * option into the emitted LAFS envelope.
1937
- *
1938
- * Without this, `cliOutput(response.data, ...)` discards `response.meta._nexus`
1939
- * + `response.meta.deprecated` (T9393 defect). Canonical CLI meta fields
1940
- * (`operation`, `requestId`, `timestamp`) are intentionally NOT forwarded —
1941
- * those are produced by {@link createCliMeta} on the CLI side.
1942
- *
1943
- * @task T9393
1944
- */
1945
- declare function pickDecoratorMetaExtensions(responseMeta: DispatchResponseMeta | undefined): Record<string, unknown>;
1946
- /**
1947
- * Build CLI envelope extensions for nexus commands that bypass the dispatcher
1948
- * (e.g. `cleo nexus status` SSoT-EXEMPT path). Runs the same {@link stampNexusMeta}
1949
- * logic against a synthetic response so the resulting envelope still carries
1950
- * `meta._nexus` (and `meta.deprecated` for alias shims).
1951
- *
1952
- * Use this only where the command does NOT call `dispatchRaw`. When you have a
1953
- * real dispatch response, use {@link pickDecoratorMetaExtensions} on
1954
- * `response.meta` instead — that path already ran the decorator.
1955
- *
1956
- * @task T9393
1957
- */
1958
- declare function buildNexusMetaExtensions(operation: string, params?: Record<string, unknown>): Record<string, unknown>;
1959
-
1960
- /**
1961
- * Gateway-level path constants shared by domain handlers.
1962
- *
1963
- * @remarks
1964
- * The `orchestrate` and `admin` (smoke-provider) domain handlers join a handful
1965
- * of well-known `.cleo/` path segments when locating workflow definitions and
1966
- * the BRAIN database. Those constants were previously defined in
1967
- * `packages/cleo/src/cli/paths.ts` (a CLI-internal leaf), which coupled the
1968
- * handler subgraph to `@cleocode/cleo`.
1969
- *
1970
- * To let `@cleocode/runtime` assemble the handler map WITHOUT importing
1971
- * `@cleocode/cleo` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION), the constants the
1972
- * handlers actually consume are hosted here as the canonical source. The
1973
- * CLI-side `cli/paths.ts` re-exports them so the rest of the CLI (backup /
1974
- * restore / doctor) keeps importing from its established surface — there is a
1975
- * single definition, no drift.
1976
- *
1977
- * @task T090
1978
- * @task T11455
1979
- */
1980
- /** Name of the project-local CLEO data directory (`.cleo`). */
1981
- declare const CLEO_DIR_NAME: ".cleo";
1982
- /** Subdirectory under `.cleo/` that stores CANT workflow definitions. */
1983
- declare const WORKFLOWS_SUBDIR: "workflows";
1984
- /** Project-local SQLite database filename for cognitive memory (BRAIN). */
1985
- declare const BRAIN_DB_FILENAME: "brain.db";
1986
-
1987
- /**
1988
- * Unified CQRS Dispatch Layer -- Middleware Pipeline
1989
- *
1990
- * Provides compose() to chain multiple Middleware functions together
1991
- * into a single executable pipeline.
1992
- *
1993
- * @epic T4820
1994
- * @task T4815
1995
- */
1996
-
1997
- /**
1998
- * Composes an array of Middleware functions into a single Middleware function.
1999
- * Execution flows through the array from first to last, and returns bubble
2000
- * back up from last to first.
2001
- *
2002
- * @remarks
2003
- * Uses an index-tracked dispatch function to prevent `next()` from being
2004
- * called multiple times within a single middleware. If the array is empty,
2005
- * a passthrough middleware that immediately calls `next()` is returned.
2006
- *
2007
- * @param middlewares - Array of middleware functions to chain
2008
- * @returns A single composed Middleware function
2009
- *
2010
- * @example
2011
- * ```typescript
2012
- * import { compose } from './pipeline.js';
2013
- *
2014
- * const pipeline = compose([sanitizer, rateLimiter, protocolEnforcement]);
2015
- * const response = await pipeline(request, handler);
2016
- * ```
2017
- */
2018
- declare function compose(middlewares: Middleware[]): Middleware;
2019
-
2020
- /**
2021
- * Unified CQRS Dispatch Layer -- Operation Registry (shim)
2022
- *
2023
- * The `OPERATIONS` data array was relocated to
2024
- * `@cleocode/contracts/dispatch/operations-registry` in T10061 / T9833b
2025
- * (E-CLI-BOUNDARY · Saga T9831 SG-ARCH-SOLID). This file is now a thin
2026
- * re-export shim that:
2027
- *
2028
- * 1. Re-exports `OPERATIONS` from contracts so every downstream consumer
2029
- * that already imports `{ OPERATIONS }` from this path continues to
2030
- * compile unchanged.
2031
- * 2. Keeps the pure utility / derivation functions (`resolve`,
2032
- * `validateRequiredParams`, `getByDomain`, etc.) that operate on
2033
- * the data and are rightly colocated with the CLI package.
2034
- *
2035
- * @epic T4820
2036
- * @task T4814, T5241, T5615, T10061
2037
- */
2038
-
2039
- /** The single source of truth for all operations in CLEO (data lives in @cleocode/contracts). */
2040
- declare const OPERATIONS: OperationDef[];
2041
- /**
2042
- * Derive a gateway operation matrix from the registry.
2043
- *
2044
- * Returns `Record<string, string[]>` containing:
2045
- * - All canonical domains with their operations
2046
- *
2047
- * This is the SINGLE derivation point — gateways use this instead of
2048
- * maintaining independent operation lists.
2049
- */
2050
- declare function deriveGatewayMatrix(gateway: Gateway): Record<string, string[]>;
2051
- /**
2052
- * Get all accepted domain names for a gateway (canonical only).
2053
- */
2054
- declare function getGatewayDomains(gateway: Gateway): string[];
2055
- /**
2056
- * Resolves a domain + operation to its registered definition.
2057
- */
2058
- declare function resolve(gateway: Gateway, domain: string, operation: string): Resolution | undefined;
2059
- /**
2060
- * Infer the CQRS {@link Gateway} for a `(domain, operation)` pair from the
2061
- * registry, without the caller naming `query`/`mutate` up front.
2062
- *
2063
- * This backs the versioned `/v1/<domain>/<operation>` REST facade (T11919),
2064
- * where — unlike the legacy `/<gateway>/<domain>/<operation>` form — the client
2065
- * does not spell the gateway segment: the registry is the single source of truth
2066
- * for whether an operation is a read (`query`) or a write (`mutate`). Each
2067
- * `(domain, operation)` pair maps to exactly one gateway in {@link OPERATIONS},
2068
- * so the inference is unambiguous.
2069
- *
2070
- * @param domain - The canonical domain segment (e.g. `'tasks'`).
2071
- * @param operation - The operation segment (e.g. `'show'`).
2072
- * @returns The owning gateway, or `undefined` when no registered operation
2073
- * matches the pair (the wire edge then renders a `404`).
2074
- */
2075
- declare function inferGateway(domain: string, operation: string): Gateway | undefined;
2076
- /**
2077
- * The result of resolving a streaming route: the owning gateway plus the matched
2078
- * {@link OperationDef} (so the caller has the op's tier/params on hand without a
2079
- * second lookup).
2080
- */
2081
- interface StreamResolution {
2082
- /** The CQRS gateway the streaming op is registered under. */
2083
- gateway: Gateway;
2084
- /** The matched streaming operation definition. */
2085
- def: OperationDef;
2086
- }
2087
- /**
2088
- * Resolve a `(domain, operation)` pair to a registered **streaming** operation.
2089
- *
2090
- * Backs the `GET /v1/<domain>/<operation>` SSE route (T11921): the listener only
2091
- * opens a `text/event-stream` for a `(domain, operation)` pair that is BOTH
2092
- * registered AND explicitly flagged `streaming: true` in {@link OPERATIONS}. A
2093
- * unary-only op (or an unknown pair) resolves to `undefined`, so the listener
2094
- * never streams an operation that was not declared streamable — a GET to a
2095
- * unary op stays a `405` (method-not-allowed) rather than silently opening a
2096
- * never-ending stream.
2097
- *
2098
- * @param domain - The canonical domain segment (e.g. `'orchestrate'`).
2099
- * @param operation - The operation segment (e.g. `'events'`).
2100
- * @returns The streaming resolution, or `undefined` when no registered op
2101
- * matches the pair OR the matched op is not flagged `streaming: true`.
2102
- */
2103
- declare function resolveStreamRoute(domain: string, operation: string): StreamResolution | undefined;
2104
- /**
2105
- * Validates that all required parameters are present in the request.
2106
- * Returns an array of missing parameter keys.
2107
- */
2108
- declare function validateRequiredParams(def: OperationDef, params?: Record<string, unknown>): string[];
2109
- /** Get all operations for a specific canonical domain. */
2110
- declare function getByDomain(domain: CanonicalDomain): OperationDef[];
2111
- /** Get all operations for a specific gateway. */
2112
- declare function getByGateway(gateway: Gateway): OperationDef[];
2113
- /** Get all operations available at or below a specific tier. */
2114
- declare function getByTier(tier: Tier): OperationDef[];
2115
- /** Get a list of canonical domains that actually have operations registered. */
2116
- declare function getActiveDomains(): CanonicalDomain[];
2117
- /**
2118
- * Returns summary counts of operations for module validation.
2119
- */
2120
- declare function getCounts(): {
2121
- query: number;
2122
- mutate: number;
2123
- total: number;
2124
- };
2125
-
2126
- /**
2127
- * Daemon-side gateway server bootstrap — the call-site `cleo daemon serve`
2128
- * drives (T11919).
2129
- *
2130
- * The HTTP gateway adapter (`./http/listen.ts`, T11254) and the supervised
2131
- * `defineGatewaySubsystem` unit (`./daemon-subsystem.ts`, T11451) both already
2132
- * exist, but nothing started them: the daemon had no `serve` call-site. This
2133
- * module is that thin bootstrap — it assembles a {@link SubsystemRegistry} with
2134
- * one scoped gateway subsystem hosting the HTTP transport (loopback by default)
2135
- * over an INJECTED {@link GatewayHandler}, starts it, and returns a live handle
2136
- * with the bound port + an idempotent `close()`.
2137
- *
2138
- * It carries NO `@cleocode/cleo` dependency and NO drizzle (the runtime
2139
- * invariant): the caller (`cleo daemon serve`) injects the assembled CLI gateway
2140
- * handler and the resolved bind coordinates. Secrets never touch the wire —
2141
- * sealed-handle resolution happens server-side inside the dispatched handler,
2142
- * exactly as it does for an in-process CLI call. The HTTP listener binds
2143
- * `127.0.0.1` by default (loopback only); exposing it on a public interface is
2144
- * an explicit caller decision.
2145
- *
2146
- * The streaming transports (SSE — T11921, WS — T11922) extend the SAME
2147
- * subsystem: when their listeners land they bind alongside the unary HTTP server
2148
- * here, so this bootstrap deliberately owns only transport assembly + lifecycle,
2149
- * leaving the per-route seam to the adapter (`parseHttpRoute`).
2150
- *
2151
- * @packageDocumentation
2152
- * @module @cleocode/runtime/gateway
2153
- *
2154
- * @task T11919
2155
- * @epic T11769
2156
- * @saga T10400
2157
- */
2158
-
2159
- /**
2160
- * Options for {@link serveGateway}.
2161
- *
2162
- * The caller injects the assembled {@link GatewayHandler} and resolves the bind
2163
- * coordinates so the runtime stays free of `@cleocode/paths`/`@cleocode/cleo`.
2164
- */
2165
- interface ServeGatewayOptions {
2166
- /** The transport-neutral gateway handler every hosted transport routes through. */
2167
- handler: GatewayHandler;
2168
- /** TCP port to bind. `0` selects an ephemeral port (readable from the handle). */
2169
- port: number;
2170
- /** Host/interface to bind. Defaults to `127.0.0.1` (loopback only). */
2171
- host?: string;
2172
- /** The scope this gateway process serves. Defaults to `'global'`. */
2173
- scope?: GatewayScope;
2174
- /**
2175
- * Absolute path to the Studio static asset directory (T11979).
2176
- *
2177
- * When set, the HTTP server serves the bundled CLEO Web Studio at `/studio`
2178
- * using the `build/client/` static assets from an `adapter-node` Studio build.
2179
- * When `undefined`, `/studio` routes return a clean 503 "bundle absent" JSON
2180
- * envelope. The caller resolves this path (typically via
2181
- * `resolveStudioStaticDir` from `@cleocode/cleo`) so the runtime stays free of
2182
- * any path-resolution policy.
2183
- */
2184
- studioStaticDir?: string;
2185
- }
2186
- /**
2187
- * A live gateway server handle returned by {@link serveGateway}.
2188
- *
2189
- * Exposes the actually-bound `port`/`host` (resolved even when `0` was
2190
- * requested) and an idempotent {@link close} for deterministic teardown
2191
- * (daemon shutdown, tests). Mirrors the lower-level
2192
- * {@link import('./http/listen.js').HttpServerHandle} so a test can boot, probe,
2193
- * and tear down on an ephemeral port.
2194
- */
2195
- interface ServeGatewayHandle {
2196
- /** The actually-bound HTTP port (resolved when `0` was requested). */
2197
- port: number;
2198
- /** Host/interface the HTTP listener is bound to. */
2199
- host: string;
2200
- /** The scope the gateway process serves. */
2201
- scope: GatewayScope;
2202
- /** Stop every bound listener (best-effort, idempotent); resolves once closed. */
2203
- close(): Promise<void>;
2204
- }
2205
- /**
2206
- * Boot the HTTP gateway as a supervised subsystem and return a live handle.
2207
- *
2208
- * Declares one scoped gateway subsystem hosting the HTTP transport (loopback
2209
- * default) over the injected handler via {@link defineGatewaySubsystem}, starts
2210
- * it, and reads the actually-bound port from the start context (so the caller —
2211
- * and tests — learn the ephemeral port when `0` was requested). The returned
2212
- * `close()` drives the subsystem's `shutdown(context)` so teardown is the same
2213
- * lifecycle the daemon registry uses for every other supervised concern.
2214
- *
2215
- * Using `defineGatewaySubsystem` directly (rather than a full
2216
- * `SubsystemRegistry`) keeps the bound HTTP handle in reach for the port
2217
- * resolution while still routing through the canonical start→shutdown contract.
2218
- *
2219
- * @param opts - The injected handler + resolved bind coordinates.
2220
- * @returns A promise resolving to the live {@link ServeGatewayHandle}.
2221
- *
2222
- * @example
2223
- * ```ts
2224
- * const handle = await serveGateway({
2225
- * handler: createCliGatewayHandler(),
2226
- * port: 0, // ephemeral
2227
- * });
2228
- * // POST http://127.0.0.1:${handle.port}/v1/tasks/show
2229
- * await handle.close();
2230
- * ```
2231
- */
2232
- declare function serveGateway(opts: ServeGatewayOptions): Promise<ServeGatewayHandle>;
2233
-
2234
- /**
2235
- * `@cleocode/runtime/gateway` — the transport-agnostic CQRS dispatch core.
2236
- *
2237
- * Relocated from `packages/cleo/src/dispatch` (R3-T3 · T11447 ·
2238
- * SG-RUNTIME-UNIFICATION) so the dispatcher is owned by the runtime layer and
2239
- * can be driven by ANY transport adapter — CLI, MCP, RPC, HTTP — not just the
2240
- * CLI. Mirrors the `@cleocode/runtime/daemon` subpath (R2).
2241
- *
2242
- * The CLI adapter (`packages/cleo/src/dispatch/adapters/cli.ts`) and the
2243
- * domain handlers + transport-specific middleware stay in `packages/cleo`; they
2244
- * are INJECTED into the {@link Dispatcher} via {@link DispatcherConfig}. The
2245
- * gateway contract itself lives in `@cleocode/contracts/gateway` (R3-T2).
2246
- *
2247
- * `packages/cleo/src/dispatch/{dispatcher,registry,lib/meta,middleware/pipeline}.ts`
2248
- * remain as thin re-export shims pointing here, so the in-package import sites
2249
- * (cli adapter, ~18 registry consumers, getCliDispatcher) compile unchanged.
2250
- *
2251
- * @epic T4820
2252
- * @task T11447
2253
- * @saga T11243
2254
- */
2255
-
2256
- /**
2257
- * Transport-agnostic gateway entrypoint. Wraps a configured {@link Dispatcher}
2258
- * (`compose(middlewares) → DomainHandler`) behind a single `handle()` call that
2259
- * every transport adapter (CLI/MCP/RPC/HTTP) invokes uniformly. The adapter
2260
- * owns wire concerns (error-render, `process.exit`, serialization); the handler
2261
- * only resolves, validates, runs middleware, and returns a {@link DispatchResponse}.
2262
- */
2263
- interface GatewayHandler {
2264
- /** Route one gateway request through the dispatch pipeline. */
2265
- handle(req: DispatchRequest): Promise<DispatchResponse>;
2266
- }
2267
- /**
2268
- * Build a {@link GatewayHandler} from a {@link DispatcherConfig} (injected
2269
- * domain handlers + middleware). The returned handler is transport-neutral.
2270
- */
2271
- declare function createGatewayHandler(config: DispatcherConfig): GatewayHandler;
2272
-
2273
- 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 };
242
+ export { type DecodeResult, LineBuffer, type RpcServerHandle, type RpcServerOptions, buildErrorFrame, decodeLine, encodeFrame, routeFrame, startRpcServer };