@agentproto/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,546 @@
1
+ import { DoctypeSpec } from '@agentproto/manifest';
2
+ import { B as BuildHeartbeatAgent } from './heartbeat-COGpMrJS.js';
3
+ export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-COGpMrJS.js';
4
+ import { ChildProcess } from 'node:child_process';
5
+ import { WorkspaceFs } from './workspace-fs.js';
6
+ export { createWorkspaceFs } from './workspace-fs.js';
7
+ export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
8
+
9
+ /**
10
+ * Sessions registry — tracks long-lived child processes spawned by
11
+ * the agentproto daemon (terminals, agent CLIs, custom commands).
12
+ *
13
+ * The registry lives in-memory for the daemon's lifetime; a recent
14
+ * snapshot also persists to `~/.agentproto/sessions.json` so a fresh
15
+ * daemon restart can show "you had these running before" instead of
16
+ * losing all visibility on a crash.
17
+ *
18
+ * Each session owns:
19
+ * - identity: id, kind, workspace slug, command + args, started time
20
+ * - lifecycle: status (starting/running/exited/killed/error)
21
+ * - output: last N lines (ring buffer) for instant attach + a
22
+ * Node EventEmitter for live streaming consumers
23
+ *
24
+ * Consumers:
25
+ * - HTTP routes (GET /sessions, /sessions/:id, /sessions/:id/stream,
26
+ * POST /sessions/:id/kill)
27
+ * - CLI `agentproto sessions` (TUI navigation + attach)
28
+ * - guilde-web Active tab (session cards + terminal viewer)
29
+ */
30
+
31
+ /**
32
+ * Minimal shape we need from a driver-agent-cli session — kept as a
33
+ * structural type so the runtime package doesn't take a hard
34
+ * dependency on @agentproto/driver-agent-cli (the http-server route
35
+ * imports it and passes the constructed session in).
36
+ */
37
+ interface AgentSessionLike {
38
+ sessionId: string;
39
+ send(message: unknown): AsyncIterable<AgentStreamEvent>;
40
+ cancel(): Promise<void>;
41
+ close(): Promise<void>;
42
+ }
43
+ /**
44
+ * Minimal PTY surface — structurally compatible with
45
+ * @agentproto/acp/tunnel's PtyProcess (node-pty's IPty wrapper). The
46
+ * runtime declares its own so this package stays native-free; the
47
+ * cli's pty-factory util constructs values that satisfy both.
48
+ */
49
+ interface PtyProcess {
50
+ readonly pid: number;
51
+ write(data: string): void;
52
+ resize(cols: number, rows: number): void;
53
+ kill(signal?: string): void;
54
+ onData(handler: (data: string) => void): void;
55
+ onExit(handler: (event: {
56
+ exitCode: number;
57
+ signal?: number;
58
+ }) => void): void;
59
+ }
60
+ interface PtyFactoryOptions {
61
+ command: string;
62
+ args: string[];
63
+ cwd?: string;
64
+ env?: Record<string, string>;
65
+ cols: number;
66
+ rows: number;
67
+ }
68
+ type PtyFactory = (opts: PtyFactoryOptions) => PtyProcess;
69
+ interface AgentStreamEvent {
70
+ kind: string;
71
+ text?: string;
72
+ toolName?: string;
73
+ isError?: boolean;
74
+ reason?: string;
75
+ error?: {
76
+ message: string;
77
+ code?: number;
78
+ data?: unknown;
79
+ };
80
+ }
81
+ type SessionKind = "terminal" | "agent-cli" | "command";
82
+ type SessionStatus = "starting" | "running" | "exited" | "killed" | "error";
83
+ interface SessionDescriptor {
84
+ id: string;
85
+ kind: SessionKind;
86
+ workspaceSlug: string;
87
+ /** What was actually run — quoted joined for display purposes
88
+ * (`bash -lc "claude --resume xyz"`). */
89
+ command: string;
90
+ pid: number | null;
91
+ status: SessionStatus;
92
+ startedAt: string;
93
+ endedAt?: string;
94
+ exitCode?: number;
95
+ /** Last time anything was written to stdout/stderr. Lets the UI
96
+ * spot stuck sessions ("running for 2h, last output 12min ago"). */
97
+ lastOutputAt?: string;
98
+ /** Free-text label the spawner can attach (e.g. conversation id,
99
+ * operator name) so the UI can group/filter. */
100
+ label?: string;
101
+ /** True when the session was spawned under a real PTY (node-pty)
102
+ * instead of `child_process.spawn`. PTY sessions carry raw ANSI
103
+ * bytes (alt-screen, key bindings, colors); attach goes through
104
+ * WS /sessions/:id/pty rather than SSE /sessions/:id/stream. */
105
+ pty?: boolean;
106
+ /** User-friendly slug supplied at spawn time. Optional; when set,
107
+ * attach/stop/kill commands accept it as an alias for the id. */
108
+ name?: string;
109
+ /** Argv that was spawned. Persisted so `agentproto sessions restart`
110
+ * can clone the original shape without re-tokenizing the display
111
+ * `command` string. Empty for agent-cli sessions (we record
112
+ * `adapterSlug` separately). */
113
+ argv?: readonly string[];
114
+ /** Working directory the session was spawned in. Cloned by restart. */
115
+ cwd?: string;
116
+ /** Adapter slug for agent-cli sessions — restart uses this with
117
+ * `/sessions/agent` to spin up a fresh ACP runtime. Undefined for
118
+ * pty/command kinds. */
119
+ adapterSlug?: string;
120
+ /** ACP-level session id (the adapter's own handle — claude-code's
121
+ * conversation id, hermes' chat id, …). Set at spawnAgent time
122
+ * from `agentSession.sessionId`. Survives across daemon restarts
123
+ * via sessions.json so `restart` can pass it as `resumeSessionId`
124
+ * and reattach to the prior conversation history. */
125
+ adapterSessionId?: string;
126
+ /** Provider-specific resume hints sniffed from the session's
127
+ * output. claude-code prints `claude --resume <uuid>` on exit;
128
+ * we capture that uuid as `claudeResumeId`. On `restart`, when a
129
+ * hint is present, agentproto prefers a PTY spawn with the
130
+ * provider's native resume command (`claude --resume <id>`) over
131
+ * the ACP-level resume — works wherever the provider persisted
132
+ * the session, not just when the ACP wrapper did.
133
+ *
134
+ * Keys are adapter-specific so future adapters can add their own
135
+ * ("hermesResumeId", etc.) without changing this type. */
136
+ resumeMetadata?: Record<string, string>;
137
+ }
138
+ interface SessionsRegistry {
139
+ spawn(input: SpawnSessionInput): SessionDescriptor;
140
+ /** Adopt a ChildProcess that was spawned outside the registry —
141
+ * e.g. by the tunnel server when the host dispatches a spawn
142
+ * frame. The registry attaches its stdout/stderr listeners and
143
+ * tracks the descriptor so the child shows up in /sessions and
144
+ * the LocalDaemonSessionsCard. The caller keeps lifecycle
145
+ * ownership (kill / wait); this is purely for observability.
146
+ * Returns the descriptor for the registered session. */
147
+ register(input: RegisterSessionInput): SessionDescriptor;
148
+ /** Register an already-built agent session (caller resolves the
149
+ * adapter + builds the runtime). Sets kind="agent-cli", consumes
150
+ * the optional initial prompt synchronously, returns the
151
+ * descriptor. The agent stays alive — call `sendPrompt(id, ...)`
152
+ * for follow-up turns until `kill(id)` closes it. */
153
+ spawnAgent(input: SpawnAgentInput): SessionDescriptor;
154
+ /** Spawn a process under a real PTY (node-pty). Bytes flow through
155
+ * the registry's byte ring buffer + emitter; attach with
156
+ * `attachPty(id, ...)`. Throws when the registry was constructed
157
+ * without a `spawnPty` factory (node-pty optional dep missing). */
158
+ spawnPty(input: SpawnPtyInput): SessionDescriptor;
159
+ /** Send a follow-up turn to a live agent session. Throws when the
160
+ * session is missing, not an agent-cli kind, or busy. The events
161
+ * stream into the existing ring buffer + line emitter so /stream
162
+ * consumers see them as they arrive. */
163
+ sendPrompt(id: string, message: unknown): Promise<void>;
164
+ /** Fire-and-forget variant of `sendPrompt`. Validates the same
165
+ * preconditions synchronously — throws on missing session / wrong
166
+ * kind / busy — but returns immediately after the turn is started,
167
+ * letting the caller stream output via the SSE endpoint instead of
168
+ * blocking on the full turn. Async errors during the turn are
169
+ * pushed into the ring buffer as `[error]` lines so `/stream`
170
+ * subscribers see them. Used by the web UI's chat input where a
171
+ * long turn would otherwise freeze the textbox. */
172
+ enqueuePrompt(id: string, message: unknown): void;
173
+ list(): SessionDescriptor[];
174
+ get(id: string): SessionDescriptor | undefined;
175
+ /** Subscribe to a session's output. Returns an unsubscribe fn.
176
+ * Initial backfill: synchronously invokes `onLine` once for each
177
+ * line currently in the ring buffer so attaches show context. */
178
+ attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
179
+ /** Subscribe to a PTY session's byte stream. Returns a control
180
+ * handle (write/resize/detach) and null when the session is
181
+ * missing or not a PTY kind. Replays the ring buffer
182
+ * synchronously before live frames start. Initial `cols`/`rows`
183
+ * are required so the registry can compute min-size across
184
+ * subscribers; recompute on every `resize` call. */
185
+ attachPty(id: string, initial: {
186
+ cols: number;
187
+ rows: number;
188
+ }, onData: (chunk: Buffer) => void, onExit: (event: {
189
+ exitCode: number;
190
+ signal?: number;
191
+ }) => void): {
192
+ write(data: string): void;
193
+ resize(cols: number, rows: number): void;
194
+ detach(): void;
195
+ } | null;
196
+ /** Find a session by exact id OR exact name match. Returns the
197
+ * descriptor or undefined. Used by HTTP/MCP routes so callers
198
+ * can refer to "claude-main" instead of "sess_a3f8c1b2". */
199
+ findByIdOrName(query: string): SessionDescriptor | undefined;
200
+ /** Write a chunk of text to a PTY session's stdin. Returns true
201
+ * on success, false when the session is missing or not a PTY.
202
+ * Single-shot variant of attachPty().write — useful for MCP
203
+ * tools that drive a session through tool calls. */
204
+ writeTerminalInput(id: string, data: string): boolean;
205
+ /** Snapshot the recent PTY byte buffer as one Buffer. Newest
206
+ * bytes at the end; `lastBytes` caps the returned size from the
207
+ * tail. Returns null when the session is missing or not a PTY. */
208
+ readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
209
+ kill(id: string, signal?: NodeJS.Signals): boolean;
210
+ /** Stop tracking a session (after it exited and the user clicked
211
+ * "clear"). Doesn't kill — use `kill` first. */
212
+ forget(id: string): boolean;
213
+ /** Stop persisting + close all sessions. Killed children are not
214
+ * awaited — the daemon shutdown loop handles process tree teardown. */
215
+ shutdown(): void;
216
+ }
217
+ interface RegisterSessionInput {
218
+ /** Pre-existing child process to adopt (e.g. from a tunnel spawn). */
219
+ child: ChildProcess;
220
+ /** Stable id — typically the tunnel's execId so callers cross-ref. */
221
+ id: string;
222
+ workspaceSlug: string;
223
+ /** Display label for the descriptor's `command` field. */
224
+ command: string;
225
+ kind?: SessionKind;
226
+ label?: string;
227
+ }
228
+ interface SpawnAgentInput {
229
+ workspaceSlug: string;
230
+ cwd: string;
231
+ /** Driver-built session ready to receive turns. Caller resolves
232
+ * the adapter, calls createAgentCliRuntime(handle).start({cwd}),
233
+ * and hands the result here. */
234
+ agentSession: AgentSessionLike;
235
+ /** Adapter slug for the descriptor (display only). */
236
+ adapterSlug: string;
237
+ /** Optional initial prompt to dispatch immediately. The promise
238
+ * the registry returns resolves AFTER the spawn — the prompt
239
+ * runs in the background, projecting events into the ring
240
+ * buffer. Skip to spawn idle. */
241
+ initialPrompt?: string;
242
+ label?: string;
243
+ /** Pretty command for the descriptor (display only). */
244
+ commandPreview?: string;
245
+ }
246
+ interface SpawnSessionInput {
247
+ kind: SessionKind;
248
+ workspaceSlug: string;
249
+ /** Path of the workspace root (for the child's cwd). The registry
250
+ * doesn't validate it against the workspaces config — caller has
251
+ * already resolved this. */
252
+ cwd: string;
253
+ /** Argv. First element is the binary; rest are passed verbatim. */
254
+ argv: string[];
255
+ /** Extra env on top of the daemon's process.env. */
256
+ env?: Record<string, string>;
257
+ /** Optional pre-set id (lets the spawner reference the session
258
+ * before the spawn returns — useful for telemetry). */
259
+ id?: string;
260
+ label?: string;
261
+ /** Keep the child's stdin open (default: closed). Most spawned
262
+ * agents are non-interactive and emit a warning when stdin is a
263
+ * pipe with no writer (claude-code prints "no stdin data
264
+ * received in 3s, proceeding without it"). Closing stdin lets
265
+ * them skip the wait. PTY/terminal kinds that genuinely need
266
+ * bidirectional IO should opt in via `keepStdin: true` and the
267
+ * caller is then responsible for writing to `child.stdin`. */
268
+ keepStdin?: boolean;
269
+ }
270
+ interface SpawnPtyInput {
271
+ workspaceSlug: string;
272
+ cwd: string;
273
+ argv: string[];
274
+ /** Initial PTY dimensions. The registry uses min(cols, rows) across
275
+ * active subscribers thereafter — these are the seed values used
276
+ * before anyone attaches. */
277
+ cols: number;
278
+ rows: number;
279
+ /** Extra env on top of process.env. TERM/LANG/PATH are forced by
280
+ * the factory layer (node-pty.spawn options); don't try to clear
281
+ * them here. */
282
+ env?: Record<string, string>;
283
+ /** User-friendly slug. Used by `findByIdOrName(query)`. Must not
284
+ * collide with an existing session's name. */
285
+ name?: string;
286
+ label?: string;
287
+ }
288
+
289
+ /**
290
+ * Tiny node:http server that fronts the runtime gateway.
291
+ *
292
+ * Routes:
293
+ * GET /health — { status, workspace, registered, uptime }
294
+ * GET /events — SSE stream of RuntimeEvent
295
+ * POST /mcp — MCP Streamable HTTP transport (POST)
296
+ * GET /mcp — MCP SSE response stream (GET)
297
+ * DELETE /mcp — close MCP session
298
+ * GET /conversations — JSON list of conversation summaries
299
+ * GET /conversations/<id> — markdown body of one conversation
300
+ * POST /heartbeat/tick — force-fire one heartbeat tick
301
+ *
302
+ * No auth in `mode: "none"` (loopback default). `mode: "bearer"`
303
+ * checks `Authorization: Bearer <token>` against the configured
304
+ * token. Health is always public so external monitors can probe.
305
+ *
306
+ * MCP transport: stateless mode for v1 — each request is independent.
307
+ * Stateful session pinning can come later when long-running streaming
308
+ * tool calls become a real use case.
309
+ */
310
+
311
+ /**
312
+ * Pluggable adapter resolver — keeps the runtime package free of any
313
+ * @agentproto/cli dep. The host (cli `serve`, playground, embedding
314
+ * apps) builds the resolver and hands it in. Returning null means
315
+ * "adapter not found" → 404.
316
+ */
317
+ type AgentAdapterResolver = (slug: string) => Promise<{
318
+ /** Build a fresh AgentSessionLike for the given cwd. The driver
319
+ * spawns the adapter binary, opens the protocol, and the
320
+ * registry holds the result.
321
+ *
322
+ * When `resumeSessionId` is set, the driver reattaches to that
323
+ * pre-existing adapter session (claude-code's conversation id,
324
+ * hermes' chat handle, …) rather than starting blank. Same
325
+ * mechanism as `agentproto run --resume <id>` — exposed over
326
+ * HTTP so `agentproto sessions restart` works on agent-cli
327
+ * sessions, not just PTY ones. */
328
+ startSession(opts: {
329
+ cwd: string;
330
+ resumeSessionId?: string;
331
+ /** Model identifier forwarded from `start_agent_session`. Adapters
332
+ * that support model selection (e.g. via a `--model` CLI flag) may
333
+ * honour this; others silently ignore it. */
334
+ model?: string;
335
+ }): Promise<AgentSessionLike>;
336
+ /** Display label for the descriptor's `command` field. */
337
+ commandPreview?: string;
338
+ } | null>;
339
+ /**
340
+ * Compact adapter metadata for the discovery endpoints. Independent
341
+ * of the resolver function above — hosts that can list installed
342
+ * adapters wire this; hosts that can only resolve by-slug skip it
343
+ * (the routes 501).
344
+ */
345
+ interface AdapterListEntry {
346
+ slug: string;
347
+ name: string;
348
+ version: string;
349
+ description: string;
350
+ protocol: string;
351
+ streaming: boolean;
352
+ packageName: string;
353
+ }
354
+ type AgentAdapterLister = () => Promise<AdapterListEntry[]>;
355
+ interface AuthOptions {
356
+ mode: "none" | "bearer";
357
+ token?: string;
358
+ }
359
+
360
+ /**
361
+ * `.agentproto/` config dir — runtime-managed state at the root of
362
+ * every workspace. Mirrors the `.git/` model: user-content stays at
363
+ * the workspace root (HEARTBEAT.md, conversations/, .agents/) while
364
+ * everything the runtime owns lives in `<workspace>/.agentproto/`.
365
+ *
366
+ * What goes here today:
367
+ * - `runtime.json` — boot-time snapshot of the live config (PID,
368
+ * port, resolved workspace, MCP server name, startedAt). Useful
369
+ * for `cat .agentproto/runtime.json` diagnostics + future tooling
370
+ * that wants to discover a running gateway from disk.
371
+ *
372
+ * What does NOT go here:
373
+ * - HEARTBEAT.md — user-edited content
374
+ * - conversations/ — user-readable chat history
375
+ * - .agents/ — user-authored manifests
376
+ *
377
+ * The `.agentproto/` dir is safe to gitignore. The `runtime.json`
378
+ * file contains a per-boot bearer token used to gate mutating HTTP
379
+ * routes (POST /sessions/*, WS /sessions/:id/pty). The token lives
380
+ * in RAM + on disk only — never logged. File mode 0600 below; only
381
+ * processes running as the workspace user (and the daemon itself)
382
+ * can read it. A browser-loaded localhost page CAN'T read this file,
383
+ * which is what defends against the localhost-DNS-rebinding drive-by
384
+ * spawn vector.
385
+ */
386
+ interface RuntimeMeta {
387
+ /** Absolute path to the workspace root the gateway is bound to. */
388
+ workspace: string;
389
+ /** HTTP listener port. */
390
+ port: number;
391
+ /** HTTP bind address (loopback or `0.0.0.0`). */
392
+ bind: string;
393
+ /** Process id of the running gateway. */
394
+ pid: number;
395
+ /** Wall-clock ISO timestamp at boot. */
396
+ startedAt: string;
397
+ /** MCP server name advertised in the initialize handshake. */
398
+ name: string;
399
+ /** AIP doctype names + extensions registered as MCP CRUD tools. */
400
+ registered: readonly string[];
401
+ /**
402
+ * Per-boot bearer token. Required on mutating routes (POST
403
+ * /sessions/*, POST /sessions/:id/kill, DELETE /sessions/:id, and
404
+ * WS upgrade to /sessions/:id/pty). Random uuid generated at boot
405
+ * by createGateway; regenerated each restart.
406
+ */
407
+ token: string;
408
+ }
409
+ /**
410
+ * Delete `<workspace>/.agentproto/runtime.json`. Best-effort — called
411
+ * from the daemon's graceful shutdown path so a CLI looking for the
412
+ * live daemon doesn't pick up a stale token after this process exits.
413
+ * Missing file is a no-op (the daemon may never have written one).
414
+ */
415
+ declare function unlinkRuntimeMeta(workspace: string): Promise<void>;
416
+ /**
417
+ * Read a runtime.json from `<workspace>/.agentproto/`. Returns the
418
+ * parsed object (no validation) plus the on-disk mtime, or null when
419
+ * the file is missing / unreadable / malformed.
420
+ */
421
+ declare function readRuntimeMeta(workspace: string): Promise<{
422
+ meta: Partial<RuntimeMeta> & Record<string, unknown>;
423
+ mtime: Date;
424
+ } | null>;
425
+ /**
426
+ * For each `workspace` path, check whether its runtime.json points
427
+ * at a now-dead PID; if so, delete it. Returns the list of paths
428
+ * that were cleaned. Called by `agentproto serve` at boot to keep
429
+ * the discovery layer's view of "live daemons" honest — a stale
430
+ * file with the same port as the new daemon is the classic source
431
+ * of `sessions_unauthorized` 401s.
432
+ *
433
+ * Skips the running daemon's own workspace (passed in as
434
+ * `currentWorkspace`) since that file is OURS to write.
435
+ */
436
+ declare function sweepStaleRuntimeMetas(workspaces: readonly string[], currentWorkspace: string): Promise<string[]>;
437
+
438
+ /**
439
+ * @agentproto/runtime — long-running gateway around an agentproto
440
+ * workspace dir.
441
+ *
442
+ * Composes:
443
+ * - `@agentproto/mcp-server` (CRUD verbs over registered specs)
444
+ * - HTTP transport (Streamable HTTP) on a configurable port
445
+ * - HEARTBEAT.md autonomy loop
446
+ * - Append-only conversation persistence (`conversations/<id>.md`)
447
+ * - Workspace filesystem adapter (compatible with the
448
+ * `McpWorkspace.filesystem` shape used by `@guilde/mcp`)
449
+ *
450
+ * Single entry point: `createGateway(opts)`. Returns a handle with
451
+ * `url` and `stop()` — the rest of the surface lives on the HTTP
452
+ * server.
453
+ */
454
+
455
+ type AnySpec = DoctypeSpec<any, any>;
456
+ interface CreateGatewayOptions {
457
+ /** Absolute path to the workspace dir. */
458
+ workspace: string;
459
+ /** AIP doctype specs to expose as MCP CRUD verbs. */
460
+ specs: readonly AnySpec[];
461
+ /** Port to bind. Default 18790. */
462
+ port?: number;
463
+ /** Bind host. Default `127.0.0.1` (loopback). Set `0.0.0.0` for LAN. */
464
+ bind?: string;
465
+ /** Auth mode. Default `none` (safe only on loopback). */
466
+ auth?: AuthOptions;
467
+ /**
468
+ * Resolves a heartbeat-runnable agent from its workspace id.
469
+ * Required for HEARTBEAT.md to do anything; without it ticks emit
470
+ * `heartbeat-error` events instead of generating.
471
+ */
472
+ buildAgent?: BuildHeartbeatAgent;
473
+ /** Server name advertised over MCP. */
474
+ name?: string;
475
+ /** Server version advertised over MCP. */
476
+ version?: string;
477
+ /**
478
+ * Run BOOT.md once at startup. Pass `false` to disable. Defaults to
479
+ * `true`. The boot file is plain markdown — frontmatter-free; the
480
+ * agent named in `defaultBootAgent` (or skipped if unset) gets the
481
+ * body as a single prompt and the reply is appended to a `boot-<iso>`
482
+ * conversation.
483
+ */
484
+ boot?: boolean;
485
+ /** Agent id used for BOOT.md if no per-file frontmatter. */
486
+ defaultBootAgent?: string;
487
+ /** Optional adapter resolver — when provided, enables
488
+ * `POST /sessions/agent` (long-running agent CLIs like
489
+ * claude-code / hermes via the @agentproto/cli adapter system).
490
+ * Without this, /sessions still works for raw `argv` spawns. */
491
+ resolveAgentAdapter?: AgentAdapterResolver;
492
+ /** Optional adapter lister — when provided, enables
493
+ * `GET /adapters` HTTP route + `list_adapters` MCP tool so UIs
494
+ * can discover what's installed on the host. */
495
+ listAgentAdapters?: AgentAdapterLister;
496
+ /** Optional PTY factory (node-pty wrapper, typically from the cli
497
+ * layer's `loadNodePtyFactory()`). When provided, enables
498
+ * `POST /sessions/terminal`, the `start_terminal_session` MCP
499
+ * tool family, and the `/sessions/:id/pty` WebSocket. Without it,
500
+ * those routes return 501 / the MCP tools aren't registered. */
501
+ spawnPty?: PtyFactory;
502
+ /** Override the per-boot bearer token. Default: `randomUUID()`.
503
+ * Tests can pin a known value; production should always let
504
+ * the gateway generate fresh. The token is written into
505
+ * `<workspace>/.agentproto/runtime.json` (mode 0600) and required
506
+ * on mutating `/sessions/*` routes + the PTY WS upgrade. */
507
+ token?: string;
508
+ /** Trusted browser origins allowed to drive mutating routes
509
+ * without a bearer token. See `RuntimeHttpServerOptions.allowedOrigins`
510
+ * for match semantics. Default: localhost on any port. */
511
+ allowedOrigins?: readonly string[];
512
+ /** When true, drop the localhost-wildcard defaults — only the
513
+ * explicit `allowedOrigins` list is honoured. */
514
+ strictOrigins?: boolean;
515
+ }
516
+ interface GatewayHandle {
517
+ url: string;
518
+ workspace: string;
519
+ workspaceFs: WorkspaceFs;
520
+ registered: readonly string[];
521
+ /** Sessions registry — exposed so MCP tools / external spawners
522
+ * inside the same process can register their child processes for
523
+ * visibility through /sessions and the CLI TUI. */
524
+ sessions: SessionsRegistry;
525
+ /** Per-boot bearer token required on mutating /sessions/* routes
526
+ * + WS PTY upgrades. Exposed so an embedding host (e.g. the CLI
527
+ * shell that hosts the gateway in-process) can pass it to child
528
+ * tools without re-reading the runtime.json file. */
529
+ token: string;
530
+ stop(): Promise<void>;
531
+ }
532
+ /**
533
+ * Spin up the gateway. Order:
534
+ * 1. Build MCP server + load AIP-40 extensions
535
+ * 2. Build conversation store + workspace fs adapter
536
+ * 3. (optional) Run BOOT.md once
537
+ * 4. Start HTTP server
538
+ * 5. Start heartbeat ticker
539
+ *
540
+ * `stop()` reverses 4–5 (heartbeat first, then HTTP). The MCP server
541
+ * is owned by the HTTP server's per-session transports and is closed
542
+ * implicitly when those close.
543
+ */
544
+ declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
545
+
546
+ export { type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, BuildHeartbeatAgent, type CreateGatewayOptions, type GatewayHandle, type RuntimeMeta, type SessionDescriptor, type SessionKind, type SessionStatus, type SessionsRegistry, type SpawnAgentInput, type SpawnSessionInput, WorkspaceFs, createGateway, readRuntimeMeta, sweepStaleRuntimeMetas, unlinkRuntimeMeta };