@cleocode/runtime 2026.10.4 → 2026.10.5
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-L5BLVQDA.js → chunk-4PUSKGDO.js} +2 -2
- package/dist/chunk-4PUSKGDO.js.map +1 -0
- package/dist/{chunk-KQBVXGOL.js → chunk-GULKX44R.js} +5 -3
- package/dist/chunk-GULKX44R.js.map +1 -0
- package/dist/{chunk-7Q7BEWM7.js → chunk-HXTRXEFI.js} +7 -9
- package/dist/chunk-HXTRXEFI.js.map +1 -0
- package/dist/{chunk-U2ESNIJ5.js → chunk-NOPLC5WS.js} +4 -2
- package/dist/chunk-NOPLC5WS.js.map +1 -0
- package/dist/chunk-PDZ5T4LO.js +597 -0
- package/dist/chunk-PDZ5T4LO.js.map +1 -0
- package/dist/{chunk-XJOMQIXL.js → chunk-WO6LJMDC.js} +4 -2
- package/dist/chunk-WO6LJMDC.js.map +1 -0
- package/dist/daemon/index.js +1 -1
- package/dist/dispatch-BWjVeeMg.d.ts +290 -0
- package/dist/gateway/dispatch.d.ts +5 -0
- package/dist/gateway/dispatch.js +53 -0
- package/dist/gateway/dispatch.js.map +1 -0
- package/dist/gateway/http/index.d.ts +897 -11
- package/dist/gateway/http/index.js +2 -2
- package/dist/gateway/index.d.ts +549 -4
- package/dist/gateway/index.js +26 -581
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mcp/index.d.ts +232 -10
- package/dist/gateway/mcp/index.js +2 -2
- package/dist/gateway/rpc/index.d.ts +5 -2036
- package/dist/gateway/rpc/index.js +1 -1
- package/dist/gateway-handler-DCU6E0a2.d.ts +80 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-7Q7BEWM7.js.map +0 -1
- package/dist/chunk-KQBVXGOL.js.map +0 -1
- package/dist/chunk-L5BLVQDA.js.map +0 -1
- package/dist/chunk-U2ESNIJ5.js.map +0 -1
- 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
|
|
10
|
-
import {
|
|
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
|
|
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 };
|