@cleocode/runtime 2026.6.13 → 2026.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GOWVGQ5Q.js → chunk-3K3LFSDF.js} +24 -1
- package/dist/chunk-3K3LFSDF.js.map +1 -0
- package/dist/chunk-L5BLVQDA.js +91 -0
- package/dist/chunk-L5BLVQDA.js.map +1 -0
- package/dist/chunk-U2ESNIJ5.js +1107 -0
- package/dist/chunk-U2ESNIJ5.js.map +1 -0
- package/dist/{chunk-R4XBXUQN.js → chunk-XJOMQIXL.js} +5 -76
- package/dist/chunk-XJOMQIXL.js.map +1 -0
- package/dist/daemon/index.d.ts +83 -1
- package/dist/daemon/index.js +3 -1
- package/dist/gateway/http/index.d.ts +2 -1
- package/dist/gateway/http/index.js +44 -3
- package/dist/gateway/index.d.ts +3 -2
- package/dist/gateway/index.js +74 -16
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mcp/index.d.ts +2 -1
- package/dist/gateway/mcp/index.js +2 -1
- package/dist/gateway/rpc/index.d.ts +694 -9
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-GOWVGQ5Q.js.map +0 -1
- package/dist/chunk-R4XBXUQN.js.map +0 -1
- package/dist/chunk-YZBIPGMH.js +0 -305
- package/dist/chunk-YZBIPGMH.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DomainHandler, Middleware, DispatchRequest, DispatchResponse, Gateway, GatewayStreamEvent, Source, DispatchResponseMeta, CanonicalDomain, Tier } from '@cleocode/contracts/gateway';
|
|
2
2
|
import { BackgroundJobManager } from '@cleocode/core/store/background-jobs.js';
|
|
3
3
|
import { Subsystem, OperationDef, SuggestedNextOp, Resolution } from '@cleocode/contracts';
|
|
4
|
-
import { Server } from 'node:http';
|
|
4
|
+
import { IncomingMessage, Server, ServerResponse } from 'node:http';
|
|
5
5
|
import '@cleocode/contracts/operations/orchestrate';
|
|
6
6
|
import * as _cleocode_core_internal from '@cleocode/core/internal';
|
|
7
7
|
import { EngineResult, ProblemDetails } from '@cleocode/core';
|
|
8
8
|
import { GatewayRpcRequestFrame, GatewayRpcErrorFrame, GatewayRpcErrorCode, GatewayRpcResponseFrame } from '@cleocode/contracts/gateway/rpc';
|
|
9
9
|
import { Server as Server$1 } from 'node:net';
|
|
10
|
+
import { Duplex } from 'node:stream';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Central Dispatcher -- Routes requests through middleware to domain handlers.
|
|
@@ -45,6 +46,246 @@ declare class Dispatcher {
|
|
|
45
46
|
dispatch(request: DispatchRequest): Promise<DispatchResponse>;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* WebSocket terminal/PTY endpoint on the `/v1` gateway HTTP listener (T11922 ·
|
|
51
|
+
* M5 realtime transport, WS half).
|
|
52
|
+
*
|
|
53
|
+
* The SSE half (T11921) gives the gateway a SERVER→CLIENT push stream; this
|
|
54
|
+
* module adds the BIDIRECTIONAL realtime transport an interactive TUI / terminal
|
|
55
|
+
* session needs: a WebSocket upgrade on the SAME `node:http` listener
|
|
56
|
+
* ({@link import('./listen.js').startHttpServer}) that bridges a PTY both
|
|
57
|
+
* directions over WS frames.
|
|
58
|
+
*
|
|
59
|
+
* Design constraints (all four ACs of T11922):
|
|
60
|
+
*
|
|
61
|
+
* - **AC1 — upgrade on the existing listener.** The WS endpoint is NOT a second
|
|
62
|
+
* server: it attaches to the daemon's existing HTTP server via its
|
|
63
|
+
* `'upgrade'` event ({@link attachWsPtyEndpoint}), so it inherits the loopback
|
|
64
|
+
* bind + the daemon's lifecycle. The endpoint path is `/v1/terminal/pty`.
|
|
65
|
+
* - **AC2 — bidirectional PTY bridge with backpressure.** Inbound WS frames are
|
|
66
|
+
* written to the PTY stdin; PTY output is framed back to the client. A control
|
|
67
|
+
* frame (`{type:'resize',cols,rows}`) resizes the PTY. Backpressure is honored
|
|
68
|
+
* on the socket write path (pause-on-`false`-write, resume on `'drain'`).
|
|
69
|
+
* - **AC3 — leak-free, deterministic teardown.** Every open/close/error/abort
|
|
70
|
+
* path routes through ONE idempotent `teardown()` that kills the PTY, removes
|
|
71
|
+
* its listeners, and ends the socket exactly once. A server `close()` tears
|
|
72
|
+
* down every live session.
|
|
73
|
+
* - **AC4 — auth/origin enforced at the upgrade edge; no secrets on the wire.**
|
|
74
|
+
* The upgrade is rejected with a `401`/`403` BEFORE any PTY is spawned unless
|
|
75
|
+
* it comes from loopback AND (when a token is configured) presents the bearer
|
|
76
|
+
* token AND (when an origin allowlist is configured) presents an allowed
|
|
77
|
+
* `Origin`. The spawned PTY runs under a MINIMAL, explicitly-constructed env
|
|
78
|
+
* ({@link scrubPtyEnv}) — the daemon's secret-bearing `process.env` is NEVER
|
|
79
|
+
* inherited, so a secret cannot be exfiltrated through the terminal stream.
|
|
80
|
+
*
|
|
81
|
+
* To keep the **publish surface clean** (D11142 dependency-discipline):
|
|
82
|
+
* - There is NO `ws` dependency. The minimal RFC 6455 server framing needed
|
|
83
|
+
* (accept handshake, decode masked client frames, encode unmasked server
|
|
84
|
+
* frames, close/ping/pong) is implemented over the raw `node:net` socket the
|
|
85
|
+
* `'upgrade'` event hands us. This adds ZERO weight to `@cleocode/runtime`.
|
|
86
|
+
* - `node-pty` is an OPTIONAL, dynamically-loaded dependency (same pattern as
|
|
87
|
+
* {@link import('../../../../core/src/llm/pi/gondolin-loader.js')} and
|
|
88
|
+
* `packages/core/src/tools/pty.ts`): the npm specifier is held in a variable,
|
|
89
|
+
* its shape is declared LOCALLY (no `import type`), and absence is a graceful
|
|
90
|
+
* close frame ("PTY backend unavailable") — NOT a crash. The endpoint always
|
|
91
|
+
* upgrades; it bridges a PTY only when the backend is present.
|
|
92
|
+
*
|
|
93
|
+
* @packageDocumentation
|
|
94
|
+
* @module @cleocode/runtime/gateway/http/ws-pty
|
|
95
|
+
*
|
|
96
|
+
* @task T11922
|
|
97
|
+
* @epic T11769
|
|
98
|
+
* @saga T10400
|
|
99
|
+
* @see ./sse.ts — the SSE half (server→client push) on the same listener
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
/** The canonical WS terminal/PTY endpoint path on the `/v1` facade. */
|
|
103
|
+
declare const WS_PTY_PATH = "/v1/terminal/pty";
|
|
104
|
+
/**
|
|
105
|
+
* The install hint surfaced to the client (as a WS close reason) when the PTY
|
|
106
|
+
* backend is unavailable. `node-pty` is OPTIONAL — the endpoint still upgrades
|
|
107
|
+
* and then closes cleanly with this reason rather than crashing the daemon.
|
|
108
|
+
*/
|
|
109
|
+
declare const NODE_PTY_INSTALL_HINT: string;
|
|
110
|
+
/** A disposable subscription handle returned by `node-pty`'s event registrars. */
|
|
111
|
+
interface PtyDisposable {
|
|
112
|
+
dispose(): void;
|
|
113
|
+
}
|
|
114
|
+
/** The minimal structural shape of a spawned `node-pty` process the bridge uses. */
|
|
115
|
+
interface NodePtyProcess {
|
|
116
|
+
/** Subscribe to interleaved stdout/stderr output. */
|
|
117
|
+
onData(cb: (data: string) => void): PtyDisposable | undefined;
|
|
118
|
+
/** Subscribe to process exit. */
|
|
119
|
+
onExit(cb: (e: {
|
|
120
|
+
exitCode: number;
|
|
121
|
+
signal?: number;
|
|
122
|
+
}) => void): PtyDisposable | undefined;
|
|
123
|
+
/** Write to the PTY stdin. */
|
|
124
|
+
write(data: string): void;
|
|
125
|
+
/** Resize the PTY. */
|
|
126
|
+
resize(cols: number, rows: number): void;
|
|
127
|
+
/** Kill the underlying process (idempotent host-side; signal optional). */
|
|
128
|
+
kill(signal?: string): void;
|
|
129
|
+
}
|
|
130
|
+
/** The minimal structural shape of the `node-pty` module the bridge uses. */
|
|
131
|
+
interface NodePtyModule {
|
|
132
|
+
spawn(file: string, args: readonly string[], options: {
|
|
133
|
+
readonly name?: string;
|
|
134
|
+
readonly cols?: number;
|
|
135
|
+
readonly rows?: number;
|
|
136
|
+
readonly cwd?: string;
|
|
137
|
+
readonly env?: Record<string, string>;
|
|
138
|
+
}): NodePtyProcess;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Attempt to lazily load `node-pty`. Returns `null` when the optional dep is not
|
|
142
|
+
* installed, fails to load, or does not expose the `spawn` surface — so the
|
|
143
|
+
* caller closes the WS cleanly with {@link NODE_PTY_INSTALL_HINT} instead of
|
|
144
|
+
* crashing. NEVER throws.
|
|
145
|
+
*
|
|
146
|
+
* The import specifier is held in a variable (passed to the {@link importer}
|
|
147
|
+
* seam) so bundlers / TS do not treat the missing optional dep as a hard,
|
|
148
|
+
* statically-resolved dependency (same technique as the gondolin loader).
|
|
149
|
+
*
|
|
150
|
+
* @returns The shape-checked module, or `null` when unavailable.
|
|
151
|
+
*/
|
|
152
|
+
declare function loadNodePty(): Promise<NodePtyModule | null>;
|
|
153
|
+
/**
|
|
154
|
+
* Build the MINIMAL environment the bridged PTY runs under (AC4).
|
|
155
|
+
*
|
|
156
|
+
* Starts EMPTY, copies only the small {@link PASSTHROUGH_KEYS} allowlist from the
|
|
157
|
+
* parent, pins `PATH` to {@link TRUSTED_PATH}, and forces `TERM=xterm-256color`
|
|
158
|
+
* for a sane terminal. The daemon's secrets are NEVER forwarded.
|
|
159
|
+
*
|
|
160
|
+
* @param parentEnv - The parent process environment (defaults to `process.env`).
|
|
161
|
+
* @returns The scrubbed, secret-free env for the PTY.
|
|
162
|
+
*/
|
|
163
|
+
declare function scrubPtyEnv(parentEnv?: NodeJS.ProcessEnv): Record<string, string>;
|
|
164
|
+
/**
|
|
165
|
+
* Derive the `Sec-WebSocket-Accept` response header value from a client
|
|
166
|
+
* `Sec-WebSocket-Key` per RFC 6455 §4.2.2 (SHA-1 of key + GUID, base64).
|
|
167
|
+
*
|
|
168
|
+
* @param key - The client's `Sec-WebSocket-Key` header value.
|
|
169
|
+
* @returns The base64 accept hash.
|
|
170
|
+
*/
|
|
171
|
+
declare function computeAcceptKey(key: string): string;
|
|
172
|
+
/**
|
|
173
|
+
* Encode a WS data frame (server→client, UNMASKED per RFC 6455 §5.1). Used for
|
|
174
|
+
* `text` (PTY output) and `close` control frames.
|
|
175
|
+
*
|
|
176
|
+
* @param opcode - The WS opcode (e.g. {@link OPCODE_TEXT}).
|
|
177
|
+
* @param payload - The frame payload bytes.
|
|
178
|
+
* @returns The framed bytes ready to write to the socket.
|
|
179
|
+
*/
|
|
180
|
+
declare function encodeWsFrame(opcode: number, payload: Buffer): Buffer;
|
|
181
|
+
/** One decoded inbound WS frame. */
|
|
182
|
+
interface DecodedFrame {
|
|
183
|
+
/** The frame opcode. */
|
|
184
|
+
opcode: number;
|
|
185
|
+
/** The unmasked payload bytes. */
|
|
186
|
+
payload: Buffer;
|
|
187
|
+
/** Total bytes consumed from the input buffer (header + payload). */
|
|
188
|
+
consumed: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Decode a single inbound (client→server, MASKED) WS frame from a buffer.
|
|
192
|
+
*
|
|
193
|
+
* Returns `null` when the buffer does not yet hold a complete frame (the caller
|
|
194
|
+
* waits for more bytes). Client frames MUST be masked (RFC 6455 §5.3); the mask
|
|
195
|
+
* is applied to recover the payload. Fragmentation is not expected from a
|
|
196
|
+
* terminal client, so a non-FIN frame is decoded as-is (its opcode handled by
|
|
197
|
+
* the caller). NEVER throws on a partial buffer.
|
|
198
|
+
*
|
|
199
|
+
* @param buf - The accumulated inbound bytes.
|
|
200
|
+
* @returns The decoded frame, or `null` when more bytes are needed.
|
|
201
|
+
*/
|
|
202
|
+
declare function decodeWsFrame(buf: Buffer): DecodedFrame | null;
|
|
203
|
+
/** Configuration for the WS terminal/PTY endpoint. */
|
|
204
|
+
interface WsPtyOptions {
|
|
205
|
+
/**
|
|
206
|
+
* Optional bearer token an upgrade MUST present (as `Authorization: Bearer
|
|
207
|
+
* <token>` or `?token=<token>`). When omitted, no token is required (loopback
|
|
208
|
+
* is still enforced unconditionally).
|
|
209
|
+
*/
|
|
210
|
+
token?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Optional `Origin` allowlist. When non-empty, an upgrade MUST present an
|
|
213
|
+
* `Origin` header whose value is in the set, else it is rejected `403`. When
|
|
214
|
+
* omitted/empty, the `Origin` header is not checked (loopback already bounds
|
|
215
|
+
* the surface).
|
|
216
|
+
*/
|
|
217
|
+
allowedOrigins?: readonly string[];
|
|
218
|
+
/** The shell launched for the PTY (default `process.env.SHELL` or `/bin/sh`). */
|
|
219
|
+
shell?: string;
|
|
220
|
+
/** Working directory for the PTY (default `process.cwd()`). */
|
|
221
|
+
cwd?: string;
|
|
222
|
+
}
|
|
223
|
+
/** The decision of the upgrade-edge gate. */
|
|
224
|
+
type GateDecision = {
|
|
225
|
+
ok: true;
|
|
226
|
+
} | {
|
|
227
|
+
ok: false;
|
|
228
|
+
status: number;
|
|
229
|
+
reason: string;
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Authorize a WS upgrade at the edge (AC4) — BEFORE any PTY is spawned.
|
|
233
|
+
*
|
|
234
|
+
* Enforced unconditionally:
|
|
235
|
+
* - the connection MUST originate from loopback (`127.0.0.1` / `::1`); a
|
|
236
|
+
* non-loopback peer is rejected `403` (the gateway is local-process-facing).
|
|
237
|
+
*
|
|
238
|
+
* Enforced when configured:
|
|
239
|
+
* - a `token` (matched against `Authorization: Bearer` or `?token=`); a missing
|
|
240
|
+
* or mismatched token is rejected `401`.
|
|
241
|
+
* - an `Origin` allowlist; a missing or disallowed `Origin` is rejected `403`.
|
|
242
|
+
*
|
|
243
|
+
* @param req - The inbound upgrade request.
|
|
244
|
+
* @param remoteAddress - The socket's remote address.
|
|
245
|
+
* @param opts - The endpoint auth configuration.
|
|
246
|
+
* @returns A {@link GateDecision} — `{ok:true}` to proceed, else a typed reject.
|
|
247
|
+
*/
|
|
248
|
+
declare function authorizeUpgrade(req: IncomingMessage, remoteAddress: string | undefined, opts: WsPtyOptions): GateDecision;
|
|
249
|
+
/** Whether a request path is the WS terminal/PTY endpoint. */
|
|
250
|
+
declare function isWsPtyPath(url: string | undefined): boolean;
|
|
251
|
+
/**
|
|
252
|
+
* A handle returned by {@link attachWsPtyEndpoint} so the caller can detach the
|
|
253
|
+
* `'upgrade'` listener and tear down every live session on server close (AC3).
|
|
254
|
+
*/
|
|
255
|
+
interface WsPtyEndpointHandle {
|
|
256
|
+
/** The number of live WS-PTY sessions (for tests / health). */
|
|
257
|
+
readonly sessionCount: number;
|
|
258
|
+
/** Detach the listener and tear down every live session. Idempotent. */
|
|
259
|
+
close(): void;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The shape of the HTTP server's `'upgrade'` event surface this module needs.
|
|
263
|
+
* Declared structurally so the module does not couple to a concrete `Server`.
|
|
264
|
+
*/
|
|
265
|
+
interface UpgradableServer {
|
|
266
|
+
on(event: 'upgrade', listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): void;
|
|
267
|
+
removeListener(event: 'upgrade', listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): void;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Attach the WS terminal/PTY endpoint (AC1) to an existing HTTP server's
|
|
271
|
+
* `'upgrade'` event.
|
|
272
|
+
*
|
|
273
|
+
* Every upgrade to {@link WS_PTY_PATH} is gated ({@link authorizeUpgrade}, AC4)
|
|
274
|
+
* BEFORE any PTY is spawned; a rejected upgrade gets a raw `401`/`403` and the
|
|
275
|
+
* socket is destroyed. An accepted upgrade completes the RFC 6455 handshake and
|
|
276
|
+
* bridges a PTY bidirectionally ({@link bridgePty}, AC2). An upgrade to any OTHER
|
|
277
|
+
* path is left for other upgrade handlers (this listener only claims its own
|
|
278
|
+
* path and never destroys a foreign socket).
|
|
279
|
+
*
|
|
280
|
+
* Each live session is tracked so {@link WsPtyEndpointHandle.close} tears every
|
|
281
|
+
* one down deterministically on server close (AC3).
|
|
282
|
+
*
|
|
283
|
+
* @param server - The HTTP server whose `'upgrade'` event to attach to.
|
|
284
|
+
* @param opts - The endpoint auth + PTY configuration.
|
|
285
|
+
* @returns A handle to detach + tear down on server close.
|
|
286
|
+
*/
|
|
287
|
+
declare function attachWsPtyEndpoint(server: UpgradableServer, opts?: WsPtyOptions): WsPtyEndpointHandle;
|
|
288
|
+
|
|
48
289
|
/**
|
|
49
290
|
* `node:http` server binding for the framework-agnostic HTTP gateway adapter.
|
|
50
291
|
*
|
|
@@ -96,6 +337,33 @@ interface HttpServerOptions {
|
|
|
96
337
|
* explicit caller decision.
|
|
97
338
|
*/
|
|
98
339
|
host?: string;
|
|
340
|
+
/**
|
|
341
|
+
* WebSocket terminal/PTY endpoint configuration (T11922 · M5 WS half). When
|
|
342
|
+
* provided, a WS upgrade handler for {@link WS_PTY_PATH} (`/v1/terminal/pty`)
|
|
343
|
+
* is attached to this server's `'upgrade'` event — gated at the edge (loopback
|
|
344
|
+
* + optional token/origin) and bridging a PTY bidirectionally over WS frames.
|
|
345
|
+
* When OMITTED, no upgrade handler is attached and the server stays a pure
|
|
346
|
+
* unary+SSE HTTP listener (the capability is opt-in — the daemon enables it
|
|
347
|
+
* explicitly). The optional `node-pty` backend is loaded dynamically; if it is
|
|
348
|
+
* absent, the endpoint still upgrades and reports a clean "PTY backend
|
|
349
|
+
* unavailable" close rather than crashing.
|
|
350
|
+
*/
|
|
351
|
+
wsPty?: WsPtyOptions;
|
|
352
|
+
/**
|
|
353
|
+
* Studio static asset directory for the bundled Web Studio (T11979).
|
|
354
|
+
*
|
|
355
|
+
* When provided, requests whose path starts with `/studio` are served as
|
|
356
|
+
* static files from this directory (the `build/client/` output from an
|
|
357
|
+
* `adapter-node` Studio build), with SPA fallback to `index.html` for
|
|
358
|
+
* sub-routes. When `undefined` or the path does not exist on disk, a clean
|
|
359
|
+
* JSON 503 envelope is returned instead of a stack trace — allowing the
|
|
360
|
+
* gateway to start without a Studio bundle present (dev checkouts).
|
|
361
|
+
*
|
|
362
|
+
* The value is resolved and injected by the caller (`cleo daemon serve`)
|
|
363
|
+
* using {@link resolveStudioStaticDir} from `@cleocode/cleo` so the runtime
|
|
364
|
+
* layer stays free of any path-resolution logic.
|
|
365
|
+
*/
|
|
366
|
+
studioStaticDir?: string;
|
|
99
367
|
}
|
|
100
368
|
/**
|
|
101
369
|
* A live HTTP server handle returned by {@link startHttpServer}.
|
|
@@ -115,14 +383,24 @@ interface HttpServerHandle {
|
|
|
115
383
|
close(): Promise<void>;
|
|
116
384
|
}
|
|
117
385
|
/**
|
|
118
|
-
* The result of parsing a request line into routing coordinates:
|
|
119
|
-
*
|
|
386
|
+
* The result of parsing a request line into routing coordinates: a resolved
|
|
387
|
+
* unary triple, a recognized health probe, or a rejection for the wire edge.
|
|
120
388
|
*/
|
|
121
389
|
type ParsedRoute = {
|
|
122
390
|
ok: true;
|
|
391
|
+
kind: 'unary';
|
|
392
|
+
gateway: Gateway;
|
|
393
|
+
domain: string;
|
|
394
|
+
operation: string;
|
|
395
|
+
} | {
|
|
396
|
+
ok: true;
|
|
397
|
+
kind: 'stream';
|
|
123
398
|
gateway: Gateway;
|
|
124
399
|
domain: string;
|
|
125
400
|
operation: string;
|
|
401
|
+
} | {
|
|
402
|
+
ok: true;
|
|
403
|
+
kind: 'health';
|
|
126
404
|
} | {
|
|
127
405
|
ok: false;
|
|
128
406
|
status: number;
|
|
@@ -130,12 +408,33 @@ type ParsedRoute = {
|
|
|
130
408
|
message: string;
|
|
131
409
|
};
|
|
132
410
|
/**
|
|
133
|
-
* Parse
|
|
411
|
+
* Parse a gateway request line into a routing decision.
|
|
412
|
+
*
|
|
413
|
+
* Two POST routing vocabularies are accepted (the wire payload is identical to
|
|
414
|
+
* every other transport — only the framing differs):
|
|
415
|
+
*
|
|
416
|
+
* - `POST /v1/<domain>/<operation>` — the **versioned REST facade** (T11919).
|
|
417
|
+
* The gateway (`query` | `mutate`) is INFERRED from the registry, so the
|
|
418
|
+
* client need not name it. An unregistered `(domain, operation)` pair is a
|
|
419
|
+
* `404`.
|
|
420
|
+
* - `POST /<gateway>/<domain>/<operation>` — the **legacy** form, where the
|
|
421
|
+
* client names the gateway segment explicitly (validated against the two
|
|
422
|
+
* CQRS gateways so a client cannot smuggle an arbitrary value).
|
|
423
|
+
*
|
|
424
|
+
* Two GET routing vocabularies are accepted:
|
|
134
425
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
426
|
+
* - `GET /v1/health` — the liveness probe.
|
|
427
|
+
* - `GET /v1/<domain>/<operation>` — the **SSE streaming** endpoint (T11921),
|
|
428
|
+
* recognized only when the `(domain, operation)` pair is registered AND
|
|
429
|
+
* flagged `streaming: true` in {@link OPERATIONS} (resolved via
|
|
430
|
+
* {@link resolveStreamRoute}). The SAME pair is dispatched UNARY over POST,
|
|
431
|
+
* so the streaming GET co-exists with the unary POST route. A GET to a
|
|
432
|
+
* unary-only op is a `405` (method-not-allowed), distinguishing "route
|
|
433
|
+
* exists, this method is unsupported" from "no such route" (`404`).
|
|
434
|
+
*
|
|
435
|
+
* A non-POST/GET method is rejected at the edge (`405`); a path that resolves to
|
|
436
|
+
* neither vocabulary is a `404`. The injected {@link GatewayHandler} is never
|
|
437
|
+
* invoked with a malformed request.
|
|
139
438
|
*
|
|
140
439
|
* @param method - The HTTP request method.
|
|
141
440
|
* @param url - The HTTP request URL (path + query).
|
|
@@ -1031,6 +1330,239 @@ type SseSource = (emitter: SseEmitter) => SseTeardown | undefined;
|
|
|
1031
1330
|
*/
|
|
1032
1331
|
declare function createSseStream(source: SseSource, signal?: AbortSignal): ReadableStream<Uint8Array>;
|
|
1033
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* Streaming-operation source adapters for the gateway HTTP/SSE transport (T11921).
|
|
1335
|
+
*
|
|
1336
|
+
* A streaming operation (`OperationDef.streaming === true`, served as
|
|
1337
|
+
* `GET /v1/<domain>/<operation>`) needs a *producer*: an async source that tails
|
|
1338
|
+
* an event origin (the agent/worker/board lifecycle bus) and feeds
|
|
1339
|
+
* {@link GatewayStreamEvent} frames into the shared, abort-safe
|
|
1340
|
+
* {@link createSseStream} builder. This module owns ONLY that producer concern —
|
|
1341
|
+
* the wire encoding + stream lifecycle live in `./sse.ts`, and the route parsing
|
|
1342
|
+
* lives in `./listen.ts`.
|
|
1343
|
+
*
|
|
1344
|
+
* The runtime is deliberately DB-free (the runtime invariant — no drizzle, no
|
|
1345
|
+
* `@cleocode/cleo`), so a source here MUST NOT open a store directly. Instead the
|
|
1346
|
+
* listener resolves a source by `<domain>.<operation>` key from the
|
|
1347
|
+
* {@link STREAM_SOURCES} registry; an embedder that owns a richer origin (the
|
|
1348
|
+
* daemon, a SvelteKit route) can inject its own source into the listener at
|
|
1349
|
+
* boot. The default source shipped here is a bounded tick/heartbeat producer that
|
|
1350
|
+
* proves the pipe end-to-end without reaching across the package boundary — and
|
|
1351
|
+
* is exactly what the in-process test exercises.
|
|
1352
|
+
*
|
|
1353
|
+
* Secrets never touch the wire: a source emits only the public lifecycle payload
|
|
1354
|
+
* it is handed; credential resolution (if any) happens server-side before the
|
|
1355
|
+
* frame is built, exactly as it does for a unary dispatch.
|
|
1356
|
+
*
|
|
1357
|
+
* @packageDocumentation
|
|
1358
|
+
* @module @cleocode/runtime/gateway/http/stream-sources
|
|
1359
|
+
*
|
|
1360
|
+
* @task T11921
|
|
1361
|
+
* @epic T11769
|
|
1362
|
+
* @saga T10400
|
|
1363
|
+
*/
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* The bind coordinates a {@link GatewayStreamSource} receives: the resolved
|
|
1367
|
+
* `(domain, operation)` of the streaming route, the already-validated request
|
|
1368
|
+
* params, and the request id (so emitted frames correlate back to the origin
|
|
1369
|
+
* request).
|
|
1370
|
+
*/
|
|
1371
|
+
interface StreamSourceContext {
|
|
1372
|
+
/** The canonical domain segment of the streaming route. */
|
|
1373
|
+
domain: string;
|
|
1374
|
+
/** The operation segment of the streaming route. */
|
|
1375
|
+
operation: string;
|
|
1376
|
+
/** The decoded query params for the streaming request. */
|
|
1377
|
+
params: Record<string, unknown>;
|
|
1378
|
+
/** The request id stamped on every emitted {@link GatewayStreamEvent}. */
|
|
1379
|
+
requestId: string;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* A teardown callback a {@link GatewayStreamSource} MAY return; invoked exactly
|
|
1383
|
+
* once when the stream ends (client disconnect, terminal frame, completion).
|
|
1384
|
+
*/
|
|
1385
|
+
type StreamSourceTeardown = () => void;
|
|
1386
|
+
/**
|
|
1387
|
+
* A producer for one streaming operation. Receives an {@link SseEmitter}
|
|
1388
|
+
* (`sendStreamEvent` emits one canonical {@link GatewayStreamEvent}) plus the
|
|
1389
|
+
* resolved {@link StreamSourceContext}, and MAY return a teardown callback.
|
|
1390
|
+
*
|
|
1391
|
+
* The source is started synchronously inside the stream builder, mirroring the
|
|
1392
|
+
* Studio SSE endpoints; it may push frames immediately and/or schedule async
|
|
1393
|
+
* work. It MUST drop frames once `emitter.closed` is true (the emitter already
|
|
1394
|
+
* guards this) and MUST clean up timers/listeners in its teardown so a client
|
|
1395
|
+
* disconnect closes the stream leak-free.
|
|
1396
|
+
*/
|
|
1397
|
+
type GatewayStreamSource = (emitter: SseEmitter, context: StreamSourceContext) => StreamSourceTeardown | undefined;
|
|
1398
|
+
/**
|
|
1399
|
+
* The default `orchestrate.events` source: a bounded tick/heartbeat producer.
|
|
1400
|
+
*
|
|
1401
|
+
* Emits a monotonic sequence of `data` {@link GatewayStreamEvent} frames at
|
|
1402
|
+
* {@link DEFAULT_TICK_INTERVAL_MS}, then a terminal `done` frame once the
|
|
1403
|
+
* `ticks` budget is exhausted; a client disconnect tears the timer down before
|
|
1404
|
+
* then. It carries no DB handle and no secret — each frame's payload is a public
|
|
1405
|
+
* heartbeat (`{ tick, ts }`) standing in for an agent/worker/board lifecycle
|
|
1406
|
+
* event until the daemon injects its real origin-tailing source.
|
|
1407
|
+
*
|
|
1408
|
+
* A first frame is emitted SYNCHRONOUSLY so a client (and the test) observes a
|
|
1409
|
+
* well-formed `data:` frame immediately, without waiting a full interval.
|
|
1410
|
+
*
|
|
1411
|
+
* @param emitter - The SSE emitter for this stream.
|
|
1412
|
+
* @param context - The resolved streaming-route context.
|
|
1413
|
+
* @returns A teardown that clears the tick timer.
|
|
1414
|
+
*/
|
|
1415
|
+
declare const tickStreamSource: GatewayStreamSource;
|
|
1416
|
+
/**
|
|
1417
|
+
* Register (or override) the {@link GatewayStreamSource} for a streaming op.
|
|
1418
|
+
*
|
|
1419
|
+
* Lets an embedder that owns a richer event origin (the daemon tailing the
|
|
1420
|
+
* agent/worker/board lifecycle bus, a SvelteKit route reading a store) replace
|
|
1421
|
+
* the default tick source for a given `(domain, operation)` without changing the
|
|
1422
|
+
* listener. Idempotent — a later call wins.
|
|
1423
|
+
*
|
|
1424
|
+
* @param domain - The canonical domain of the streaming op.
|
|
1425
|
+
* @param operation - The operation name of the streaming op.
|
|
1426
|
+
* @param source - The producer to serve this streaming route with.
|
|
1427
|
+
*/
|
|
1428
|
+
declare function registerStreamSource(domain: string, operation: string, source: GatewayStreamSource): void;
|
|
1429
|
+
/**
|
|
1430
|
+
* Resolve the {@link GatewayStreamSource} registered for a streaming op.
|
|
1431
|
+
*
|
|
1432
|
+
* @param domain - The canonical domain of the streaming op.
|
|
1433
|
+
* @param operation - The operation name of the streaming op.
|
|
1434
|
+
* @returns The registered source, or `undefined` when none is registered (the
|
|
1435
|
+
* listener then closes the stream with a single terminal `error` frame).
|
|
1436
|
+
*/
|
|
1437
|
+
declare function resolveStreamSource(domain: string, operation: string): GatewayStreamSource | undefined;
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Static file serving for the bundled CLEO Web Studio assets.
|
|
1441
|
+
*
|
|
1442
|
+
* The gateway HTTP server calls {@link serveStudioStatic} for any request whose
|
|
1443
|
+
* path starts with the `/studio` prefix. The handler:
|
|
1444
|
+
*
|
|
1445
|
+
* 1. Strips the `/studio` prefix and resolves the remainder against the
|
|
1446
|
+
* `studioStaticDir` (the `build/client/` output of the Studio
|
|
1447
|
+
* `adapter-node` build bundled inside `@cleocode/cleo`).
|
|
1448
|
+
* 2. Serves the matched file with the correct MIME type (inferred from the
|
|
1449
|
+
* extension via the built-in {@link MIME_TYPES} map).
|
|
1450
|
+
* 3. Returns `index.html` for any path that does not resolve to a static file
|
|
1451
|
+
* — the SPA fallback that lets the SvelteKit client-side router handle
|
|
1452
|
+
* sub-routes.
|
|
1453
|
+
* 4. Returns a clean JSON error envelope (not a stack trace) when
|
|
1454
|
+
* `studioStaticDir` is absent (dev checkout without a Studio build, or an
|
|
1455
|
+
* npm install where the postbuild copy was skipped).
|
|
1456
|
+
*
|
|
1457
|
+
* The handler deliberately has NO `process.exit`, NO drizzle, NO
|
|
1458
|
+
* `@cleocode/cleo` dependency — it is a pure file-system responder that can be
|
|
1459
|
+
* embedded in any `node:http` server (the gateway daemon, a test harness).
|
|
1460
|
+
*
|
|
1461
|
+
* The `build/client/` static tree is served under `/studio/` so all of its
|
|
1462
|
+
* relative asset references (`/_app/immutable/...`) resolve correctly. The root
|
|
1463
|
+
* `/studio` (no trailing slash) redirects to `/studio/` to keep the base URL
|
|
1464
|
+
* consistent with the SvelteKit assets.
|
|
1465
|
+
*
|
|
1466
|
+
* @packageDocumentation
|
|
1467
|
+
* @module @cleocode/runtime/gateway/http
|
|
1468
|
+
*
|
|
1469
|
+
* @task T11979
|
|
1470
|
+
* @epic T11261
|
|
1471
|
+
*/
|
|
1472
|
+
|
|
1473
|
+
/** The URL prefix the Studio static handler claims. */
|
|
1474
|
+
declare const STUDIO_PREFIX = "/studio";
|
|
1475
|
+
/**
|
|
1476
|
+
* Options for the Studio static handler, injected at server startup.
|
|
1477
|
+
*
|
|
1478
|
+
* The caller resolves the absolute path to the static asset directory and
|
|
1479
|
+
* passes it here — the handler never reads environment variables or resolves
|
|
1480
|
+
* paths itself, keeping concerns separated and the handler testable.
|
|
1481
|
+
*/
|
|
1482
|
+
interface StudioStaticOptions {
|
|
1483
|
+
/**
|
|
1484
|
+
* Absolute path to the Studio static asset directory. This is the
|
|
1485
|
+
* `build/client/` subdirectory from an `adapter-node` Studio build, bundled
|
|
1486
|
+
* inside `@cleocode/cleo` at `studio-dist/client/`.
|
|
1487
|
+
*
|
|
1488
|
+
* When `undefined` or the path does not exist on disk, every request under
|
|
1489
|
+
* `/studio` returns a clean JSON 503 "bundle absent" envelope.
|
|
1490
|
+
*/
|
|
1491
|
+
staticDir: string | undefined;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Determine whether a request path falls under the Studio prefix and should be
|
|
1495
|
+
* handled by the static asset handler rather than the API gateway.
|
|
1496
|
+
*
|
|
1497
|
+
* @param pathname - The URL pathname (without query string).
|
|
1498
|
+
* @returns `true` when the path is `/studio`, `/studio/`, or any
|
|
1499
|
+
* `/studio/<subpath>`.
|
|
1500
|
+
*/
|
|
1501
|
+
declare function isStudioPath(pathname: string): boolean;
|
|
1502
|
+
/**
|
|
1503
|
+
* Resolve the file-system path for a Studio request within `staticDir`.
|
|
1504
|
+
*
|
|
1505
|
+
* Strips the `/studio` prefix and normalises the remainder; guards against
|
|
1506
|
+
* path-traversal (a normalised path that escapes `staticDir` is rejected).
|
|
1507
|
+
*
|
|
1508
|
+
* @param staticDir - Absolute path to the Studio static asset directory.
|
|
1509
|
+
* @param pathname - The request path (already verified to start with `/studio`).
|
|
1510
|
+
* @returns The absolute candidate path, or `undefined` when path traversal is
|
|
1511
|
+
* detected.
|
|
1512
|
+
*/
|
|
1513
|
+
declare function resolveStudioAssetPath(staticDir: string, pathname: string): string | undefined;
|
|
1514
|
+
/**
|
|
1515
|
+
* Infer the MIME type for a file from its extension.
|
|
1516
|
+
*
|
|
1517
|
+
* @param filePath - The file path (only the extension is used).
|
|
1518
|
+
* @returns The appropriate `Content-Type` header value.
|
|
1519
|
+
*/
|
|
1520
|
+
declare function mimeType(filePath: string): string;
|
|
1521
|
+
/**
|
|
1522
|
+
* Write the "bundle absent" JSON error envelope and close the response.
|
|
1523
|
+
*
|
|
1524
|
+
* Used when `staticDir` is `undefined` or does not exist on disk — the Studio
|
|
1525
|
+
* build was not found. The response is a `503 Service Unavailable` with a clean
|
|
1526
|
+
* LAFS-flavoured JSON body so clients can detect the condition programmatically.
|
|
1527
|
+
*
|
|
1528
|
+
* @param res - The server response to write to.
|
|
1529
|
+
* @param hint - Optional developer hint (e.g. the expected bundle path).
|
|
1530
|
+
*/
|
|
1531
|
+
declare function writeBundleAbsentError(res: ServerResponse, hint?: string): void;
|
|
1532
|
+
/**
|
|
1533
|
+
* Handle a single HTTP request whose path falls under `/studio`.
|
|
1534
|
+
*
|
|
1535
|
+
* Routing logic:
|
|
1536
|
+
* - `GET /studio` → 302 redirect to `/studio/` (keep asset refs consistent)
|
|
1537
|
+
* - `GET /studio/<static-asset>` → serve the file from `staticDir`
|
|
1538
|
+
* - `GET /studio/*` (no matching file) → serve `index.html` (SPA fallback)
|
|
1539
|
+
* - Any request when `staticDir` is absent → 503 JSON envelope
|
|
1540
|
+
* - Non-GET method → 405 JSON envelope (Studio is read-only from the gateway)
|
|
1541
|
+
*
|
|
1542
|
+
* @param req - The inbound HTTP request.
|
|
1543
|
+
* @param res - The outbound HTTP response.
|
|
1544
|
+
* @param opts - Static handler options (resolved at server startup).
|
|
1545
|
+
* @returns A promise that resolves once the response has been written.
|
|
1546
|
+
*/
|
|
1547
|
+
declare function serveStudioStatic(req: IncomingMessage, res: ServerResponse, opts: StudioStaticOptions): Promise<void>;
|
|
1548
|
+
/**
|
|
1549
|
+
* Resolve the bundled Studio static directory from the `@cleocode/cleo` package
|
|
1550
|
+
* location.
|
|
1551
|
+
*
|
|
1552
|
+
* When running from an npm install, the Studio build is bundled at
|
|
1553
|
+
* `<cleo-package-root>/studio-dist/client/`. When running from a monorepo dev
|
|
1554
|
+
* checkout, the directory may not exist (developers must run
|
|
1555
|
+
* `pnpm --filter @cleocode/studio run build` manually) — `undefined` is
|
|
1556
|
+
* returned in that case, and the gateway handler will serve the bundle-absent
|
|
1557
|
+
* error instead of crashing.
|
|
1558
|
+
*
|
|
1559
|
+
* @param cleoPackageDir - Absolute path to the `@cleocode/cleo` package root.
|
|
1560
|
+
* Typically derived from `import.meta.url` at the call site.
|
|
1561
|
+
* @returns The absolute path to `studio-dist/client/`, or `undefined` if the
|
|
1562
|
+
* directory does not exist.
|
|
1563
|
+
*/
|
|
1564
|
+
declare function resolveStudioStaticDir(cleoPackageDir: string): string | undefined;
|
|
1565
|
+
|
|
1034
1566
|
/**
|
|
1035
1567
|
* Singleton accessor for the gateway {@link BackgroundJobManager}.
|
|
1036
1568
|
*
|
|
@@ -1508,6 +2040,51 @@ declare function getGatewayDomains(gateway: Gateway): string[];
|
|
|
1508
2040
|
* Resolves a domain + operation to its registered definition.
|
|
1509
2041
|
*/
|
|
1510
2042
|
declare function resolve(gateway: Gateway, domain: string, operation: string): Resolution | undefined;
|
|
2043
|
+
/**
|
|
2044
|
+
* Infer the CQRS {@link Gateway} for a `(domain, operation)` pair from the
|
|
2045
|
+
* registry, without the caller naming `query`/`mutate` up front.
|
|
2046
|
+
*
|
|
2047
|
+
* This backs the versioned `/v1/<domain>/<operation>` REST facade (T11919),
|
|
2048
|
+
* where — unlike the legacy `/<gateway>/<domain>/<operation>` form — the client
|
|
2049
|
+
* does not spell the gateway segment: the registry is the single source of truth
|
|
2050
|
+
* for whether an operation is a read (`query`) or a write (`mutate`). Each
|
|
2051
|
+
* `(domain, operation)` pair maps to exactly one gateway in {@link OPERATIONS},
|
|
2052
|
+
* so the inference is unambiguous.
|
|
2053
|
+
*
|
|
2054
|
+
* @param domain - The canonical domain segment (e.g. `'tasks'`).
|
|
2055
|
+
* @param operation - The operation segment (e.g. `'show'`).
|
|
2056
|
+
* @returns The owning gateway, or `undefined` when no registered operation
|
|
2057
|
+
* matches the pair (the wire edge then renders a `404`).
|
|
2058
|
+
*/
|
|
2059
|
+
declare function inferGateway(domain: string, operation: string): Gateway | undefined;
|
|
2060
|
+
/**
|
|
2061
|
+
* The result of resolving a streaming route: the owning gateway plus the matched
|
|
2062
|
+
* {@link OperationDef} (so the caller has the op's tier/params on hand without a
|
|
2063
|
+
* second lookup).
|
|
2064
|
+
*/
|
|
2065
|
+
interface StreamResolution {
|
|
2066
|
+
/** The CQRS gateway the streaming op is registered under. */
|
|
2067
|
+
gateway: Gateway;
|
|
2068
|
+
/** The matched streaming operation definition. */
|
|
2069
|
+
def: OperationDef;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Resolve a `(domain, operation)` pair to a registered **streaming** operation.
|
|
2073
|
+
*
|
|
2074
|
+
* Backs the `GET /v1/<domain>/<operation>` SSE route (T11921): the listener only
|
|
2075
|
+
* opens a `text/event-stream` for a `(domain, operation)` pair that is BOTH
|
|
2076
|
+
* registered AND explicitly flagged `streaming: true` in {@link OPERATIONS}. A
|
|
2077
|
+
* unary-only op (or an unknown pair) resolves to `undefined`, so the listener
|
|
2078
|
+
* never streams an operation that was not declared streamable — a GET to a
|
|
2079
|
+
* unary op stays a `405` (method-not-allowed) rather than silently opening a
|
|
2080
|
+
* never-ending stream.
|
|
2081
|
+
*
|
|
2082
|
+
* @param domain - The canonical domain segment (e.g. `'orchestrate'`).
|
|
2083
|
+
* @param operation - The operation segment (e.g. `'events'`).
|
|
2084
|
+
* @returns The streaming resolution, or `undefined` when no registered op
|
|
2085
|
+
* matches the pair OR the matched op is not flagged `streaming: true`.
|
|
2086
|
+
*/
|
|
2087
|
+
declare function resolveStreamRoute(domain: string, operation: string): StreamResolution | undefined;
|
|
1511
2088
|
/**
|
|
1512
2089
|
* Validates that all required parameters are present in the request.
|
|
1513
2090
|
* Returns an array of missing parameter keys.
|
|
@@ -1530,6 +2107,114 @@ declare function getCounts(): {
|
|
|
1530
2107
|
total: number;
|
|
1531
2108
|
};
|
|
1532
2109
|
|
|
2110
|
+
/**
|
|
2111
|
+
* Daemon-side gateway server bootstrap — the call-site `cleo daemon serve`
|
|
2112
|
+
* drives (T11919).
|
|
2113
|
+
*
|
|
2114
|
+
* The HTTP gateway adapter (`./http/listen.ts`, T11254) and the supervised
|
|
2115
|
+
* `defineGatewaySubsystem` unit (`./daemon-subsystem.ts`, T11451) both already
|
|
2116
|
+
* exist, but nothing started them: the daemon had no `serve` call-site. This
|
|
2117
|
+
* module is that thin bootstrap — it assembles a {@link SubsystemRegistry} with
|
|
2118
|
+
* one scoped gateway subsystem hosting the HTTP transport (loopback by default)
|
|
2119
|
+
* over an INJECTED {@link GatewayHandler}, starts it, and returns a live handle
|
|
2120
|
+
* with the bound port + an idempotent `close()`.
|
|
2121
|
+
*
|
|
2122
|
+
* It carries NO `@cleocode/cleo` dependency and NO drizzle (the runtime
|
|
2123
|
+
* invariant): the caller (`cleo daemon serve`) injects the assembled CLI gateway
|
|
2124
|
+
* handler and the resolved bind coordinates. Secrets never touch the wire —
|
|
2125
|
+
* sealed-handle resolution happens server-side inside the dispatched handler,
|
|
2126
|
+
* exactly as it does for an in-process CLI call. The HTTP listener binds
|
|
2127
|
+
* `127.0.0.1` by default (loopback only); exposing it on a public interface is
|
|
2128
|
+
* an explicit caller decision.
|
|
2129
|
+
*
|
|
2130
|
+
* The streaming transports (SSE — T11921, WS — T11922) extend the SAME
|
|
2131
|
+
* subsystem: when their listeners land they bind alongside the unary HTTP server
|
|
2132
|
+
* here, so this bootstrap deliberately owns only transport assembly + lifecycle,
|
|
2133
|
+
* leaving the per-route seam to the adapter (`parseHttpRoute`).
|
|
2134
|
+
*
|
|
2135
|
+
* @packageDocumentation
|
|
2136
|
+
* @module @cleocode/runtime/gateway
|
|
2137
|
+
*
|
|
2138
|
+
* @task T11919
|
|
2139
|
+
* @epic T11769
|
|
2140
|
+
* @saga T10400
|
|
2141
|
+
*/
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* Options for {@link serveGateway}.
|
|
2145
|
+
*
|
|
2146
|
+
* The caller injects the assembled {@link GatewayHandler} and resolves the bind
|
|
2147
|
+
* coordinates so the runtime stays free of `@cleocode/paths`/`@cleocode/cleo`.
|
|
2148
|
+
*/
|
|
2149
|
+
interface ServeGatewayOptions {
|
|
2150
|
+
/** The transport-neutral gateway handler every hosted transport routes through. */
|
|
2151
|
+
handler: GatewayHandler;
|
|
2152
|
+
/** TCP port to bind. `0` selects an ephemeral port (readable from the handle). */
|
|
2153
|
+
port: number;
|
|
2154
|
+
/** Host/interface to bind. Defaults to `127.0.0.1` (loopback only). */
|
|
2155
|
+
host?: string;
|
|
2156
|
+
/** The scope this gateway process serves. Defaults to `'global'`. */
|
|
2157
|
+
scope?: GatewayScope;
|
|
2158
|
+
/**
|
|
2159
|
+
* Absolute path to the Studio static asset directory (T11979).
|
|
2160
|
+
*
|
|
2161
|
+
* When set, the HTTP server serves the bundled CLEO Web Studio at `/studio`
|
|
2162
|
+
* using the `build/client/` static assets from an `adapter-node` Studio build.
|
|
2163
|
+
* When `undefined`, `/studio` routes return a clean 503 "bundle absent" JSON
|
|
2164
|
+
* envelope. The caller resolves this path (typically via
|
|
2165
|
+
* `resolveStudioStaticDir` from `@cleocode/cleo`) so the runtime stays free of
|
|
2166
|
+
* any path-resolution policy.
|
|
2167
|
+
*/
|
|
2168
|
+
studioStaticDir?: string;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* A live gateway server handle returned by {@link serveGateway}.
|
|
2172
|
+
*
|
|
2173
|
+
* Exposes the actually-bound `port`/`host` (resolved even when `0` was
|
|
2174
|
+
* requested) and an idempotent {@link close} for deterministic teardown
|
|
2175
|
+
* (daemon shutdown, tests). Mirrors the lower-level
|
|
2176
|
+
* {@link import('./http/listen.js').HttpServerHandle} so a test can boot, probe,
|
|
2177
|
+
* and tear down on an ephemeral port.
|
|
2178
|
+
*/
|
|
2179
|
+
interface ServeGatewayHandle {
|
|
2180
|
+
/** The actually-bound HTTP port (resolved when `0` was requested). */
|
|
2181
|
+
port: number;
|
|
2182
|
+
/** Host/interface the HTTP listener is bound to. */
|
|
2183
|
+
host: string;
|
|
2184
|
+
/** The scope the gateway process serves. */
|
|
2185
|
+
scope: GatewayScope;
|
|
2186
|
+
/** Stop every bound listener (best-effort, idempotent); resolves once closed. */
|
|
2187
|
+
close(): Promise<void>;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Boot the HTTP gateway as a supervised subsystem and return a live handle.
|
|
2191
|
+
*
|
|
2192
|
+
* Declares one scoped gateway subsystem hosting the HTTP transport (loopback
|
|
2193
|
+
* default) over the injected handler via {@link defineGatewaySubsystem}, starts
|
|
2194
|
+
* it, and reads the actually-bound port from the start context (so the caller —
|
|
2195
|
+
* and tests — learn the ephemeral port when `0` was requested). The returned
|
|
2196
|
+
* `close()` drives the subsystem's `shutdown(context)` so teardown is the same
|
|
2197
|
+
* lifecycle the daemon registry uses for every other supervised concern.
|
|
2198
|
+
*
|
|
2199
|
+
* Using `defineGatewaySubsystem` directly (rather than a full
|
|
2200
|
+
* `SubsystemRegistry`) keeps the bound HTTP handle in reach for the port
|
|
2201
|
+
* resolution while still routing through the canonical start→shutdown contract.
|
|
2202
|
+
*
|
|
2203
|
+
* @param opts - The injected handler + resolved bind coordinates.
|
|
2204
|
+
* @returns A promise resolving to the live {@link ServeGatewayHandle}.
|
|
2205
|
+
*
|
|
2206
|
+
* @example
|
|
2207
|
+
* ```ts
|
|
2208
|
+
* const handle = await serveGateway({
|
|
2209
|
+
* handler: createCliGatewayHandler(),
|
|
2210
|
+
* port: 0, // ephemeral
|
|
2211
|
+
* });
|
|
2212
|
+
* // POST http://127.0.0.1:${handle.port}/v1/tasks/show
|
|
2213
|
+
* await handle.close();
|
|
2214
|
+
* ```
|
|
2215
|
+
*/
|
|
2216
|
+
declare function serveGateway(opts: ServeGatewayOptions): Promise<ServeGatewayHandle>;
|
|
2217
|
+
|
|
1533
2218
|
/**
|
|
1534
2219
|
* `@cleocode/runtime/gateway` — the transport-agnostic CQRS dispatch core.
|
|
1535
2220
|
*
|
|
@@ -1569,4 +2254,4 @@ interface GatewayHandler {
|
|
|
1569
2254
|
*/
|
|
1570
2255
|
declare function createGatewayHandler(config: DispatcherConfig): GatewayHandler;
|
|
1571
2256
|
|
|
1572
|
-
export {
|
|
2257
|
+
export { operationToMcpTool as $, registerStreamSource as A, resolveStreamSource as B, resolveStudioAssetPath as C, resolveStudioStaticDir as D, type DecodeResult, routeUnary as E, scrubPtyEnv as F, type GateDecision as G, type HttpServerHandle as H, serveStudioStatic as I, startHttpServer as J, statusForResponse as K, tickStreamSource as L, LineBuffer, writeBundleAbsentError as M, NODE_PTY_INSTALL_HINT as N, type GatewayOperationKey as O, MCP_TOOL_PREFIX as P, type McpContent as Q, type McpInputProperty as R, type RpcServerHandle, type RpcServerOptions, SSE_HEADERS as S, type McpServerOptions as T, type UpgradableServer as U, type McpTool as V, WS_PTY_PATH as W, type McpToolResult as X, buildToolsList as Y, callTool as Z, exposedOperations as _, type GatewayStreamSource as a, operationToToolName as a0, startMcpServer as a1, toolNameToOperationKey as a2, BRAIN_DB_FILENAME as a3, CLEO_DIR_NAME as a4, Dispatcher as a5, type DispatcherConfig as a6, type ErrorMeta as a7, type GatewayHandler as a8, type GatewayScope as a9, getTemplateForSubcommand as aA, inferGateway as aB, mapNumericExitCodeToString as aC, parseIssueTemplates as aD, pickDecoratorMetaExtensions as aE, resolve as aF, resolveStreamRoute as aG, serveGateway as aH, setJobManager as aI, stampNexusMeta as aJ, validateLabels as aK, validateRequiredParams as aL, validateSuggestedNext as aM, type GatewaySubsystemContext as aa, type GatewaySubsystemOptions as ab, OPERATIONS as ac, STRING_TO_EXIT as ad, type ServeGatewayHandle as ae, type ServeGatewayOptions as af, type StreamResolution as ag, WORKFLOWS_SUBDIR as ah, buildNexusMetaExtensions as ai, cleoErrorToEngineError as aj, compose as ak, createDispatchMeta as al, createGatewayHandler as am, defineGatewaySubsystem as an, deriveGatewayMatrix as ao, engineError as ap, formatSuggestedNext as aq, gatewaySubsystemName as ar, generateTemplateConfig as as, getActiveDomains as at, getByDomain as au, getByGateway as av, getByTier as aw, getCounts as ax, getGatewayDomains as ay, getJobManager as az, type HttpServerOptions as b, buildErrorFrame, type HttpUnaryRequest as c, type HttpUnaryResult as d, decodeLine, STUDIO_PREFIX as e, encodeFrame, type SseEmitter as f, type SseFrame as g, type SseSource as h, type StreamSourceContext as i, type StreamSourceTeardown as j, type StudioStaticOptions as k, type WsPtyEndpointHandle as l, type WsPtyOptions as m, attachWsPtyEndpoint as n, authorizeUpgrade as o, computeAcceptKey as p, createSseStream as q, decodeWsFrame as r, routeFrame, encodeSseFrame as s, startRpcServer, encodeStreamEvent as t, encodeWsFrame as u, isStudioPath as v, isWsPtyPath as w, loadNodePty as x, mimeType as y, parseHttpRoute as z };
|