@agentproto/acp 0.1.0-alpha.1 → 0.1.0-alpha.2

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.
@@ -100,6 +100,52 @@ interface ResizeFrame {
100
100
  cols: number;
101
101
  rows: number;
102
102
  }
103
+ /**
104
+ * Generic HTTP-over-tunnel request (HOST → DAEMON). Lets the host
105
+ * proxy arbitrary HTTP traffic (MCP JSON-RPC today, anything tomorrow)
106
+ * through the daemon's local network stack without exposing a public
107
+ * URL. The daemon forwards to its configured "http upstream" (default:
108
+ * its own gateway on `127.0.0.1:<port>`) and replies with an
109
+ * `HttpResponseFrame` carrying the same `reqId`.
110
+ */
111
+ interface HttpRequestFrame {
112
+ t: "http_request";
113
+ /** Correlates the response. Host-chosen, MUST be unique per inflight request. */
114
+ reqId: string;
115
+ /** HTTP method (GET / POST / PUT / DELETE / PATCH / OPTIONS). */
116
+ method: string;
117
+ /** Path + querystring, e.g. `/mcp` or `/mcp?session=abc`.
118
+ * Daemon prepends its upstream base. Absolute URLs are rejected. */
119
+ path: string;
120
+ /** Forwarded request headers. Hop-by-hop headers (Connection,
121
+ * Keep-Alive, Transfer-Encoding, Upgrade, Proxy-*) MUST be stripped
122
+ * before forwarding; daemon SHOULD also drop them defensively. */
123
+ headers?: Readonly<Record<string, string>>;
124
+ /** Base64-encoded request body. Omitted when no body. */
125
+ body?: string;
126
+ /** Per-request timeout in ms. Daemon SHOULD enforce + reply with
127
+ * `error: { code: "timeout" }` when exceeded. Default 30_000. */
128
+ timeoutMs?: number;
129
+ }
130
+ /**
131
+ * Generic HTTP-over-tunnel response (DAEMON → HOST). Mirrors
132
+ * `HttpRequestFrame` — see its docs.
133
+ */
134
+ interface HttpResponseFrame {
135
+ t: "http_response";
136
+ reqId: string;
137
+ /** HTTP status code (e.g. 200, 404, 500). Set even on transport
138
+ * failure (then `error` is also set and status SHOULD be 502/504). */
139
+ status: number;
140
+ headers?: Readonly<Record<string, string>>;
141
+ /** Base64-encoded response body. */
142
+ body?: string;
143
+ /** Set when the daemon failed to complete the upstream call. */
144
+ error?: Readonly<{
145
+ code: string;
146
+ message: string;
147
+ }>;
148
+ }
103
149
  interface HelloFrame {
104
150
  t: "hello";
105
151
  version: typeof TUNNEL_VERSION;
@@ -163,8 +209,8 @@ interface PongFrame {
163
209
  t: "pong";
164
210
  nonce: string;
165
211
  }
166
- type HostToDaemonFrame = SpawnFrame | StdinFrame | KillFrame | ResizeFrame | PingFrame | PongFrame | ErrorFrame;
167
- type DaemonToHostFrame = HelloFrame | SpawnedFrame | StdoutFrame | StderrFrame | ExitFrame | PingFrame | PongFrame | ErrorFrame;
212
+ type HostToDaemonFrame = SpawnFrame | StdinFrame | KillFrame | ResizeFrame | HttpRequestFrame | PingFrame | PongFrame | ErrorFrame;
213
+ type DaemonToHostFrame = HelloFrame | SpawnedFrame | StdoutFrame | StderrFrame | ExitFrame | HttpResponseFrame | PingFrame | PongFrame | ErrorFrame;
168
214
  type TunnelFrame = HostToDaemonFrame | DaemonToHostFrame;
169
215
  /**
170
216
  * Parse a raw WS message into a `TunnelFrame`. Returns null when the
@@ -237,6 +283,14 @@ interface TunnelServerOptions {
237
283
  * everything (useful for tests; never for production).
238
284
  */
239
285
  authorize: (req: SpawnFrame) => SpawnFrame | null | Promise<SpawnFrame | null>;
286
+ /**
287
+ * Upstream HTTP base URL for `http_request` frames. The daemon
288
+ * appends the frame's `path` and forwards. Typically the local
289
+ * gateway address — `http://127.0.0.1:18790`. When omitted, the
290
+ * daemon replies to any `http_request` with
291
+ * `error: { code: "http_upstream_not_configured" }`.
292
+ */
293
+ httpUpstream?: string;
240
294
  /** User-friendly label sent in the hello frame. */
241
295
  label?: string;
242
296
  /**
@@ -245,6 +299,23 @@ interface TunnelServerOptions {
245
299
  * haven't wired that side yet.
246
300
  */
247
301
  pty?: boolean;
302
+ /**
303
+ * Optional hook fired once per successful spawn — after authorize
304
+ * passed and the ChildProcess has emitted its `spawn` event.
305
+ * Lets the daemon adopt this child into a higher-level registry
306
+ * (e.g. @agentproto/runtime's sessions registry, AIP-46) so the
307
+ * spawn shows up in /sessions, the LocalDaemonSessionsCard, and
308
+ * the CLI TUI alongside spawns originated locally.
309
+ *
310
+ * The hook is fire-and-forget — the tunnel doesn't await its
311
+ * return. Errors from the hook are swallowed so an observer
312
+ * registry hiccup never breaks the tunnel exchange.
313
+ */
314
+ onChildSpawned?: (info: {
315
+ execId: string;
316
+ child: ChildProcess;
317
+ request: SpawnFrame;
318
+ }) => void;
248
319
  }
249
320
  interface TunnelServer {
250
321
  /** Currently-live execId → ChildProcess. Read-only diagnostic. */
@@ -269,6 +340,26 @@ declare function createTunnelServer(opts: TunnelServerOptions): TunnelServer;
269
340
  * this with a node:stream.Readable shim.
270
341
  */
271
342
 
343
+ /** Resolved response from `forwardHttp`. Mirrors `HttpResponseFrame`
344
+ * but with the body decoded back to a Buffer for the caller. */
345
+ interface TunnelHttpResponse {
346
+ status: number;
347
+ headers: Readonly<Record<string, string>>;
348
+ body: Buffer;
349
+ /** Set when the daemon failed to complete the upstream call. */
350
+ error?: Readonly<{
351
+ code: string;
352
+ message: string;
353
+ }>;
354
+ }
355
+ interface TunnelHttpRequest {
356
+ method: string;
357
+ path: string;
358
+ headers?: Readonly<Record<string, string>>;
359
+ body?: Buffer | Uint8Array | string;
360
+ /** Per-request timeout in ms. Default 30_000. */
361
+ timeoutMs?: number;
362
+ }
272
363
  interface TunnelClientOptions {
273
364
  sink: FrameSink;
274
365
  /**
@@ -307,6 +398,15 @@ interface TunnelClient {
307
398
  */
308
399
  ready(): Promise<HelloFrame>;
309
400
  spawn(command: string, args: readonly string[], opts?: TunnelSpawnOptions): Promise<TunnelChildProcess>;
401
+ /**
402
+ * Forward an HTTP request through the tunnel to the daemon's
403
+ * configured upstream (typically `http://127.0.0.1:<port>`). The
404
+ * daemon completes the upstream call and replies with the response,
405
+ * which this promise resolves to. Rejects on tunnel-transport
406
+ * failure; the daemon-reported HTTP status (incl. 5xx) is returned
407
+ * in the resolved value.
408
+ */
409
+ forwardHttp(req: TunnelHttpRequest): Promise<TunnelHttpResponse>;
310
410
  close(): Promise<void>;
311
411
  }
312
412
  declare function createTunnelClient(opts: TunnelClientOptions): TunnelClient;
@@ -16,6 +16,8 @@ var KNOWN_TYPES = /* @__PURE__ */ new Set([
16
16
  "stdin",
17
17
  "kill",
18
18
  "resize",
19
+ "http_request",
20
+ "http_response",
19
21
  "ping",
20
22
  "pong",
21
23
  "error",
@@ -112,6 +114,9 @@ function createTunnelServer(opts) {
112
114
  }
113
115
  case "resize":
114
116
  return;
117
+ case "http_request":
118
+ await handleHttpRequest(frame);
119
+ return;
115
120
  case "ping":
116
121
  opts.sink.send({ t: "pong", nonce: frame.nonce });
117
122
  return;
@@ -174,6 +179,15 @@ function createTunnelServer(opts) {
174
179
  } else {
175
180
  opts.sink.send({ t: "spawned", execId: req.execId, pid: -1 });
176
181
  }
182
+ if (opts.onChildSpawned) {
183
+ try {
184
+ opts.onChildSpawned({ execId: req.execId, child, request: approved });
185
+ } catch (err) {
186
+ console.warn(
187
+ `[tunnel-server] onChildSpawned hook threw for execId=${req.execId}: ${err instanceof Error ? err.message : String(err)}`
188
+ );
189
+ }
190
+ }
177
191
  child.stdout?.on("data", (chunk) => {
178
192
  const f = {
179
193
  t: "stdout",
@@ -209,6 +223,92 @@ function createTunnelServer(opts) {
209
223
  children.delete(req.execId);
210
224
  });
211
225
  }
226
+ async function handleHttpRequest(req) {
227
+ if (closed) return;
228
+ const respond = (partial) => {
229
+ opts.sink.send({
230
+ t: "http_response",
231
+ reqId: req.reqId,
232
+ ...partial
233
+ });
234
+ };
235
+ if (!opts.httpUpstream) {
236
+ respond({
237
+ status: 502,
238
+ error: {
239
+ code: "http_upstream_not_configured",
240
+ message: "Daemon was not started with an http upstream \u2014 cannot forward HTTP frames."
241
+ }
242
+ });
243
+ return;
244
+ }
245
+ if (!req.path.startsWith("/") || req.path.startsWith("//") || req.path.includes("..")) {
246
+ respond({
247
+ status: 400,
248
+ error: {
249
+ code: "invalid_path",
250
+ message: `Daemon http forward requires a safe relative path; got '${req.path}'.`
251
+ }
252
+ });
253
+ return;
254
+ }
255
+ const HOP_BY_HOP = /* @__PURE__ */ new Set([
256
+ "connection",
257
+ "keep-alive",
258
+ "transfer-encoding",
259
+ "upgrade",
260
+ "proxy-authenticate",
261
+ "proxy-authorization",
262
+ "te",
263
+ "trailer",
264
+ "host",
265
+ // upstream sets its own based on the URL
266
+ "content-length"
267
+ // fetch recomputes
268
+ ]);
269
+ const headers = {};
270
+ for (const [k, v] of Object.entries(req.headers ?? {})) {
271
+ if (HOP_BY_HOP.has(k.toLowerCase())) continue;
272
+ headers[k] = v;
273
+ }
274
+ const url = `${opts.httpUpstream.replace(/\/$/, "")}${req.path}`;
275
+ const controller = new AbortController();
276
+ const timeoutMs = req.timeoutMs ?? 3e4;
277
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
278
+ try {
279
+ const upstreamRes = await fetch(url, {
280
+ method: req.method,
281
+ headers,
282
+ body: req.body === void 0 ? void 0 : decodeData(req.body),
283
+ signal: controller.signal
284
+ // node fetch follows redirects by default — fine for MCP, the
285
+ // upstream gateway doesn't redirect anyway.
286
+ });
287
+ const buf = Buffer.from(await upstreamRes.arrayBuffer());
288
+ const outHeaders = {};
289
+ upstreamRes.headers.forEach((value, key) => {
290
+ if (HOP_BY_HOP.has(key.toLowerCase())) return;
291
+ outHeaders[key] = value;
292
+ });
293
+ respond({
294
+ status: upstreamRes.status,
295
+ headers: outHeaders,
296
+ body: buf.length > 0 ? encodeData(buf) : ""
297
+ });
298
+ } catch (err) {
299
+ const message = err instanceof Error ? err.message : String(err);
300
+ const aborted = err instanceof Error && err.name === "AbortError";
301
+ respond({
302
+ status: aborted ? 504 : 502,
303
+ error: {
304
+ code: aborted ? "timeout" : "upstream_fetch_failed",
305
+ message
306
+ }
307
+ });
308
+ } finally {
309
+ clearTimeout(timer);
310
+ }
311
+ }
212
312
  return {
213
313
  children,
214
314
  async close() {
@@ -226,6 +326,7 @@ function createTunnelClient(opts) {
226
326
  let helloPromise = null;
227
327
  const childByExec = /* @__PURE__ */ new Map();
228
328
  const spawnPending = /* @__PURE__ */ new Map();
329
+ const httpPending = /* @__PURE__ */ new Map();
229
330
  const offFrame = opts.sink.onFrame((frame) => routeIncoming(frame));
230
331
  const offClose = opts.sink.onClose(() => {
231
332
  for (const [, duck] of childByExec) duck.__handleSinkClosed();
@@ -234,6 +335,11 @@ function createTunnelClient(opts) {
234
335
  p.reject(new Error("Tunnel closed before spawn completed."));
235
336
  }
236
337
  spawnPending.clear();
338
+ for (const [, p] of httpPending) {
339
+ clearTimeout(p.timer);
340
+ p.reject(new Error("Tunnel closed before HTTP response arrived."));
341
+ }
342
+ httpPending.clear();
237
343
  offFrame();
238
344
  offClose();
239
345
  });
@@ -285,6 +391,19 @@ function createTunnelClient(opts) {
285
391
  }
286
392
  return;
287
393
  }
394
+ case "http_response": {
395
+ const pending = httpPending.get(frame.reqId);
396
+ if (!pending) return;
397
+ httpPending.delete(frame.reqId);
398
+ clearTimeout(pending.timer);
399
+ pending.resolve({
400
+ status: frame.status,
401
+ headers: frame.headers ?? {},
402
+ body: frame.body ? decodeData(frame.body) : Buffer.alloc(0),
403
+ ...frame.error ? { error: frame.error } : {}
404
+ });
405
+ return;
406
+ }
288
407
  case "ping":
289
408
  opts.sink.send({ t: "pong", nonce: frame.nonce });
290
409
  return;
@@ -293,6 +412,7 @@ function createTunnelClient(opts) {
293
412
  case "stdin":
294
413
  case "kill":
295
414
  case "resize":
415
+ case "http_request":
296
416
  return;
297
417
  }
298
418
  }
@@ -353,6 +473,35 @@ function createTunnelClient(opts) {
353
473
  childByExec.set(execId, duck);
354
474
  return duck;
355
475
  },
476
+ async forwardHttp(req) {
477
+ if (!hello) await this.ready();
478
+ const reqId = randomUUID();
479
+ const timeoutMs = req.timeoutMs ?? 3e4;
480
+ const body = req.body === void 0 ? void 0 : encodeData(
481
+ typeof req.body === "string" ? req.body : Buffer.from(req.body)
482
+ );
483
+ const frame = {
484
+ t: "http_request",
485
+ reqId,
486
+ method: req.method,
487
+ path: req.path,
488
+ ...req.headers ? { headers: req.headers } : {},
489
+ ...body !== void 0 ? { body } : {},
490
+ timeoutMs
491
+ };
492
+ return new Promise((resolve, reject) => {
493
+ const timer = setTimeout(() => {
494
+ httpPending.delete(reqId);
495
+ reject(
496
+ new Error(
497
+ `Tunnel forwardHttp(${req.method} ${req.path}) timed out after ${timeoutMs + 5e3}ms.`
498
+ )
499
+ );
500
+ }, timeoutMs + 5e3);
501
+ httpPending.set(reqId, { resolve, reject, timer });
502
+ opts.sink.send(frame);
503
+ });
504
+ },
356
505
  async close() {
357
506
  opts.sink.close("client.close");
358
507
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tunnel/frames.ts","../../src/tunnel/server.ts","../../src/tunnel/client.ts","../../src/tunnel/ws-adapter.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Tunnel frame protocol — agentproto/tunnel/v1.\n *\n * Bidirectional, event-shaped frames over a single duplex transport\n * (typically a WebSocket). Each frame is one JSON object per message.\n * The protocol is deliberately NOT JSON-RPC: process I/O is a\n * continuous event stream, not a request/response pattern, so we drop\n * the `id` / response correlation and let the underlying child-process\n * lifecycle drive everything.\n *\n * # Roles\n *\n * host — the orchestrator (e.g. Guilde API). Sends `spawn`,\n * `stdin`, `kill`, `resize`. Receives `stdout`, `stderr`,\n * `exit`, `error`. Holds the ACP client; sees the duplex\n * stream as a `ChildProcess`-shaped duck.\n *\n * daemon — the local executor (e.g. `agentproto serve` on a user's\n * laptop). Receives spawn-side frames; spawns real\n * subprocesses; relays I/O back. Owns the actual file\n * system, environment, network egress.\n *\n * # Identifiers\n *\n * execId — host-chosen UUID per spawned process. Lets one tunnel\n * multiplex many concurrent agent CLIs (one daemon serving\n * several conversations).\n *\n * # Encoding\n *\n * - Each WS message is one JSON object. Binary frames are not used;\n * stdout/stdin payloads are base64-encoded utf-safe strings.\n * - Why base64 and not raw text: claude-code & friends emit JSON-RPC\n * ACP traffic over stdio. A child can also emit non-utf8 bytes\n * (debug logs, terminal control codes when in TTY mode). Base64\n * keeps the wire JSON-clean and bytewise-faithful.\n * - All frames carry `t` (type) and a per-type payload. Versioning\n * is per-tunnel via `hello.version`, not per-frame.\n *\n * # Lifecycle\n *\n * 1. Daemon connects to host's WS endpoint with bearer token.\n * 2. Daemon sends `hello { version, capabilities, label? }`.\n * 3. Host sends `spawn` for each child it wants on this daemon.\n * 4. Daemon spawns, replies with `spawned { execId, pid }` (or\n * `error { execId, message }`).\n * 5. stdout/stderr/exit notifications flow daemon→host;\n * stdin/resize/kill flow host→daemon.\n * 6. Either side can send `ping`; receiver MUST `pong` back. Used\n * to detect half-open connections.\n * 7. On disconnect, daemon SHOULD kill all children associated with\n * this tunnel (host can re-spawn after reconnect).\n */\n\nexport const TUNNEL_VERSION = \"agentproto/tunnel/v1\" as const\n\n// ─── host → daemon ──────────────────────────────────────────────\n\nexport interface SpawnFrame {\n t: \"spawn\"\n execId: string\n command: string\n args: readonly string[]\n /**\n * Working directory on the daemon side. Daemon SHOULD reject\n * absolute paths that escape its declared `--root` (see\n * `agentproto serve --root`); host MUST pass paths that exist\n * on the daemon.\n */\n cwd?: string\n /**\n * Environment overrides. Daemon merges these atop its own\n * process.env. Daemon MAY redact values from logs but MUST forward\n * them verbatim into the child.\n */\n env?: Readonly<Record<string, string>>\n /**\n * When true, the daemon allocates a PTY instead of plain pipes.\n * Required for binaries that detect `isTTY` (REPLs, claude\n * interactive). When false (default), stdin/stdout/stderr are\n * separate pipes — the right choice for ACP wrappers that speak\n * line-delimited JSON-RPC.\n */\n pty?: boolean\n}\n\nexport interface StdinFrame {\n t: \"stdin\"\n execId: string\n /** Base64-encoded payload bytes. Empty string is a no-op. */\n data: string\n}\n\nexport interface KillFrame {\n t: \"kill\"\n execId: string\n /** Default \"SIGTERM\". POSIX names; Windows daemons translate. */\n signal?: string\n}\n\nexport interface ResizeFrame {\n t: \"resize\"\n execId: string\n cols: number\n rows: number\n}\n\n// ─── daemon → host ──────────────────────────────────────────────\n\nexport interface HelloFrame {\n t: \"hello\"\n version: typeof TUNNEL_VERSION\n /**\n * Daemon-declared capabilities. Hosts MAY refuse to dispatch\n * features the daemon hasn't claimed.\n */\n capabilities: Readonly<{\n pty: boolean\n /** Future: file-transfer, port-forward, etc. */\n }>\n /** User-friendly daemon label, surfaced in host UIs. */\n label?: string\n /** Daemon process info — diagnostics only, not authoritative. */\n daemon?: Readonly<{\n name: string\n version: string\n platform: string\n nodeVersion?: string\n }>\n}\n\nexport interface SpawnedFrame {\n t: \"spawned\"\n execId: string\n pid: number\n}\n\nexport interface StdoutFrame {\n t: \"stdout\"\n execId: string\n /** Base64-encoded payload bytes. */\n data: string\n}\n\nexport interface StderrFrame {\n t: \"stderr\"\n execId: string\n /** Base64-encoded payload bytes. */\n data: string\n}\n\nexport interface ExitFrame {\n t: \"exit\"\n execId: string\n /** Null when killed by signal. */\n code: number | null\n /** POSIX signal name when killed by signal; null otherwise. */\n signal: string | null\n}\n\n// ─── either direction ───────────────────────────────────────────\n\nexport interface ErrorFrame {\n t: \"error\"\n /** Omitted for tunnel-level errors not tied to a specific exec. */\n execId?: string\n /** Stable machine-readable code, e.g. \"spawn_failed\", \"unknown_exec\". */\n code: string\n /** Human-readable diagnostic. */\n message: string\n}\n\nexport interface PingFrame {\n t: \"ping\"\n /** Echoed verbatim in the matching pong. */\n nonce: string\n}\n\nexport interface PongFrame {\n t: \"pong\"\n nonce: string\n}\n\n// ─── union + guards ─────────────────────────────────────────────\n\nexport type HostToDaemonFrame =\n | SpawnFrame\n | StdinFrame\n | KillFrame\n | ResizeFrame\n | PingFrame\n | PongFrame\n | ErrorFrame\n\nexport type DaemonToHostFrame =\n | HelloFrame\n | SpawnedFrame\n | StdoutFrame\n | StderrFrame\n | ExitFrame\n | PingFrame\n | PongFrame\n | ErrorFrame\n\nexport type TunnelFrame = HostToDaemonFrame | DaemonToHostFrame\n\nconst KNOWN_TYPES = new Set<TunnelFrame[\"t\"]>([\n \"spawn\",\n \"stdin\",\n \"kill\",\n \"resize\",\n \"ping\",\n \"pong\",\n \"error\",\n \"hello\",\n \"spawned\",\n \"stdout\",\n \"stderr\",\n \"exit\",\n])\n\n/**\n * Parse a raw WS message into a `TunnelFrame`. Returns null when the\n * payload isn't a valid frame — callers MUST handle null defensively\n * (typically: send an `error` frame and ignore). We don't throw so\n * one bad frame can't tear down the tunnel.\n */\nexport function parseFrame(raw: string): TunnelFrame | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !(\"t\" in parsed) ||\n typeof (parsed as { t: unknown }).t !== \"string\"\n ) {\n return null\n }\n const t = (parsed as { t: string }).t\n if (!KNOWN_TYPES.has(t as TunnelFrame[\"t\"])) return null\n return parsed as TunnelFrame\n}\n\nexport function encodeFrame(frame: TunnelFrame): string {\n return JSON.stringify(frame)\n}\n\n/**\n * Encode raw bytes for an `stdin` / `stdout` / `stderr` frame's `data`\n * field. Centralised so both sides agree on the encoding.\n */\nexport function encodeData(bytes: Uint8Array | string): string {\n const buf =\n typeof bytes === \"string\" ? Buffer.from(bytes, \"utf8\") : Buffer.from(bytes)\n return buf.toString(\"base64\")\n}\n\nexport function decodeData(data: string): Buffer {\n return Buffer.from(data, \"base64\")\n}\n","/**\n * Tunnel server (daemon side).\n *\n * Sits on the local machine that actually owns the binaries (e.g. the\n * user's laptop running `agentproto serve`). Accepts spawn frames from\n * a remote host, spawns the requested child via node:child_process,\n * pipes stdio back as `stdout`/`stderr` frames, and reports lifecycle\n * via `spawned`/`exit`/`error`.\n *\n * Transport-agnostic: the caller passes a `FrameSink`. For WebSocket\n * callers, see `wrapWebSocket()` in ./ws-adapter.ts.\n *\n * Security: this process MUST refuse spawn requests by default unless\n * the caller installed an `authorize(spawn)` hook. We don't expose a\n * \"trust everything\" mode — every host integration is responsible for\n * deciding what the remote may exec. The hook gets the full SpawnFrame\n * and can mutate it (e.g. force `cwd` into a workspace) or reject.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\"\nimport { hostname, platform } from \"node:os\"\nimport {\n TUNNEL_VERSION,\n encodeData,\n encodeFrame,\n parseFrame,\n type ExitFrame,\n type SpawnFrame,\n type StderrFrame,\n type StdoutFrame,\n type TunnelFrame,\n} from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\nexport interface TunnelServerOptions {\n sink: FrameSink\n /**\n * Authorization hook. Called for each `spawn` frame BEFORE the\n * subprocess is created. Return the (possibly mutated) frame to\n * proceed; throw or return null to reject. Rejection causes an\n * `error` frame with code \"spawn_unauthorized\".\n *\n * No default — callers MUST decide. Pass `(req) => req` to allow\n * everything (useful for tests; never for production).\n */\n authorize: (req: SpawnFrame) => SpawnFrame | null | Promise<SpawnFrame | null>\n /** User-friendly label sent in the hello frame. */\n label?: string\n /**\n * Whether this daemon advertises PTY support. v0 always reports\n * false — PTY allocation needs node-pty (optional dep) and we\n * haven't wired that side yet.\n */\n pty?: boolean\n}\n\nexport interface TunnelServer {\n /** Currently-live execId → ChildProcess. Read-only diagnostic. */\n readonly children: ReadonlyMap<string, ChildProcess>\n /** Kill all live children and close the sink. */\n close(): Promise<void>\n}\n\nexport function createTunnelServer(opts: TunnelServerOptions): TunnelServer {\n const children = new Map<string, ChildProcess>()\n let closed = false\n\n // Greet the host immediately so it can fail fast on version\n // mismatch before issuing spawns.\n opts.sink.send({\n t: \"hello\",\n version: TUNNEL_VERSION,\n capabilities: { pty: opts.pty === true },\n label: opts.label,\n daemon: {\n name: \"agentproto\",\n version: \"0.1.0-alpha\",\n platform: `${platform()}/${hostname()}`,\n nodeVersion: process.version,\n },\n })\n\n const offFrame = opts.sink.onFrame((frame) => {\n handleFrame(frame).catch((err) => {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({ t: \"error\", code: \"internal\", message })\n })\n })\n\n const offClose = opts.sink.onClose(() => {\n closed = true\n for (const [, child] of children) child.kill(\"SIGTERM\")\n children.clear()\n offFrame()\n offClose()\n })\n\n async function handleFrame(frame: TunnelFrame): Promise<void> {\n switch (frame.t) {\n case \"spawn\":\n await handleSpawn(frame)\n return\n case \"stdin\": {\n const child = children.get(frame.execId)\n if (!child || !child.stdin) {\n opts.sink.send({\n t: \"error\",\n execId: frame.execId,\n code: \"unknown_exec\",\n message: `No live exec '${frame.execId}'`,\n })\n return\n }\n child.stdin.write(Buffer.from(frame.data, \"base64\"))\n return\n }\n case \"kill\": {\n const child = children.get(frame.execId)\n if (!child) {\n opts.sink.send({\n t: \"error\",\n execId: frame.execId,\n code: \"unknown_exec\",\n message: `No live exec '${frame.execId}'`,\n })\n return\n }\n // Cast: NodeJS.Signals is a string union; users may send any\n // POSIX signal name. We trust the host.\n child.kill((frame.signal as NodeJS.Signals | undefined) ?? \"SIGTERM\")\n return\n }\n case \"resize\":\n // PTY resize — needs node-pty wiring. Silently no-op for now;\n // hosts that care can check `hello.capabilities.pty`.\n return\n case \"ping\":\n opts.sink.send({ t: \"pong\", nonce: frame.nonce })\n return\n case \"pong\":\n case \"error\":\n // Daemon doesn't act on these; host-side concern.\n return\n default:\n opts.sink.send({\n t: \"error\",\n code: \"unknown_frame\",\n message: `Daemon received unexpected frame type '${(frame as { t: string }).t}'`,\n })\n }\n }\n\n async function handleSpawn(req: SpawnFrame): Promise<void> {\n if (closed) return\n let approved: SpawnFrame | null\n try {\n approved = await opts.authorize(req)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_unauthorized\",\n message,\n })\n return\n }\n if (!approved) {\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_unauthorized\",\n message: \"Authorize hook rejected the spawn request.\",\n })\n return\n }\n\n let child: ChildProcess\n try {\n child = spawn(approved.command, [...approved.args], {\n cwd: approved.cwd,\n env: { ...process.env, ...(approved.env ?? {}) },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n // detached:false so SIGINT/SIGTERM on the daemon kills children too.\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_failed\",\n message,\n })\n return\n }\n\n children.set(req.execId, child)\n\n if (typeof child.pid === \"number\") {\n opts.sink.send({ t: \"spawned\", execId: req.execId, pid: child.pid })\n } else {\n // Pid is null when spawn synchronously failed. The 'error'\n // event below will fire with the real cause; we still report\n // a placeholder spawned to keep the lifecycle predictable.\n opts.sink.send({ t: \"spawned\", execId: req.execId, pid: -1 })\n }\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n const f: StdoutFrame = {\n t: \"stdout\",\n execId: req.execId,\n data: encodeData(chunk),\n }\n opts.sink.send(f)\n })\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n const f: StderrFrame = {\n t: \"stderr\",\n execId: req.execId,\n data: encodeData(chunk),\n }\n opts.sink.send(f)\n })\n\n child.on(\"error\", (err) => {\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"child_error\",\n message: err.message,\n })\n })\n\n child.on(\"exit\", (code, signal) => {\n const f: ExitFrame = {\n t: \"exit\",\n execId: req.execId,\n code,\n signal,\n }\n opts.sink.send(f)\n children.delete(req.execId)\n })\n }\n\n return {\n children,\n async close() {\n if (closed) return\n closed = true\n for (const [, child] of children) child.kill(\"SIGTERM\")\n children.clear()\n opts.sink.close(\"server.close\")\n },\n }\n}\n\n// Re-exports for convenience — server-only callers don't need to\n// reach into ./frames for these.\nexport { encodeFrame, parseFrame }\n","/**\n * Tunnel client (host side).\n *\n * Sits on the orchestrator (e.g. Guilde API). Speaks to a remote daemon\n * over a `FrameSink`. Exposes `spawn(command, args, opts)` that returns\n * an object shaped like Node's `ChildProcess` — same `.stdin` /\n * `.stdout` / `.stderr` / `.on('exit')` / `.kill()` surface. This lets\n * the existing ACP protocol arm drive remote subprocesses with zero\n * code changes; the arm thinks it's talking to a local child.\n *\n * The duck is intentionally minimal — only the methods the ACP arm\n * actually calls. If you need the full ChildProcess interface, wrap\n * this with a node:stream.Readable shim.\n */\n\nimport { EventEmitter } from \"node:events\"\nimport { Readable, Writable } from \"node:stream\"\nimport { randomUUID } from \"node:crypto\"\nimport {\n TUNNEL_VERSION,\n decodeData,\n encodeData,\n type ExitFrame,\n type HelloFrame,\n type SpawnFrame,\n type SpawnedFrame,\n type StderrFrame,\n type StdoutFrame,\n type TunnelFrame,\n} from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\nexport interface TunnelClientOptions {\n sink: FrameSink\n /**\n * Reject the `spawn` call if the daemon hasn't sent its `hello`\n * within this window. Defaults to 10s — generous for high-latency\n * tunnels but fast enough that misconfigured daemons fail loudly.\n */\n helloTimeoutMs?: number\n}\n\nexport interface TunnelSpawnOptions {\n cwd?: string\n env?: Readonly<Record<string, string>>\n /** Allocate a PTY on the daemon. Requires `hello.capabilities.pty`. */\n pty?: boolean\n}\n\n/**\n * Minimal ChildProcess-shaped duck. Covers the surface our ACP arm\n * calls; expand only when a real consumer needs more.\n */\nexport interface TunnelChildProcess {\n readonly execId: string\n readonly pid: number | null\n readonly stdin: Writable\n readonly stdout: Readable\n readonly stderr: Readable\n /** \"exit\" → (code, signal). \"error\" → (Error). */\n on(event: \"exit\", listener: (code: number | null, signal: string | null) => void): this\n on(event: \"error\", listener: (err: Error) => void): this\n kill(signal?: NodeJS.Signals | number): boolean\n}\n\nexport interface TunnelClient {\n /**\n * Resolves once the daemon has sent its `hello` frame, OR rejects\n * with a tunnel-level error. Subsequent calls return the cached\n * promise.\n */\n ready(): Promise<HelloFrame>\n spawn(\n command: string,\n args: readonly string[],\n opts?: TunnelSpawnOptions\n ): Promise<TunnelChildProcess>\n close(): Promise<void>\n}\n\nexport function createTunnelClient(opts: TunnelClientOptions): TunnelClient {\n const helloTimeoutMs = opts.helloTimeoutMs ?? 10_000\n let hello: HelloFrame | null = null\n let helloPromise: Promise<HelloFrame> | null = null\n\n // Per-execId routing tables. We dispatch incoming frames to the\n // right child duck by execId; spawn awaits its own `spawned` /\n // `error` reply via these one-shot resolvers.\n const childByExec = new Map<string, TunnelChildDuck>()\n const spawnPending = new Map<\n string,\n { resolve: (frame: SpawnedFrame) => void; reject: (err: Error) => void }\n >()\n\n const offFrame = opts.sink.onFrame((frame) => routeIncoming(frame))\n const offClose = opts.sink.onClose(() => {\n for (const [, duck] of childByExec) duck.__handleSinkClosed()\n childByExec.clear()\n for (const [, p] of spawnPending) {\n p.reject(new Error(\"Tunnel closed before spawn completed.\"))\n }\n spawnPending.clear()\n offFrame()\n offClose()\n })\n\n function routeIncoming(frame: TunnelFrame): void {\n switch (frame.t) {\n case \"hello\":\n if (frame.version !== TUNNEL_VERSION) {\n // Mismatch — surface via the ready() rejection. We don't\n // tear down the sink because the caller may want to\n // negotiate a fallback.\n throw new Error(\n `Tunnel daemon speaks ${frame.version}; this client speaks ${TUNNEL_VERSION}.`\n )\n }\n hello = frame\n return\n case \"spawned\": {\n const pending = spawnPending.get(frame.execId)\n if (pending) {\n spawnPending.delete(frame.execId)\n pending.resolve(frame)\n }\n return\n }\n case \"stdout\": {\n const duck = childByExec.get(frame.execId)\n duck?.__pushStdout(decodeData((frame as StdoutFrame).data))\n return\n }\n case \"stderr\": {\n const duck = childByExec.get(frame.execId)\n duck?.__pushStderr(decodeData((frame as StderrFrame).data))\n return\n }\n case \"exit\": {\n const duck = childByExec.get(frame.execId)\n duck?.__handleExit(frame as ExitFrame)\n childByExec.delete(frame.execId)\n return\n }\n case \"error\": {\n if (frame.execId) {\n const pending = spawnPending.get(frame.execId)\n if (pending) {\n spawnPending.delete(frame.execId)\n pending.reject(new Error(`${frame.code}: ${frame.message}`))\n return\n }\n const duck = childByExec.get(frame.execId)\n duck?.__pushError(new Error(`${frame.code}: ${frame.message}`))\n return\n }\n // Tunnel-level error: nowhere to surface it cleanly here.\n // Callers that care should wrap the FrameSink and observe\n // tunnel-error frames themselves.\n return\n }\n case \"ping\":\n opts.sink.send({ t: \"pong\", nonce: frame.nonce })\n return\n case \"pong\":\n case \"spawn\":\n case \"stdin\":\n case \"kill\":\n case \"resize\":\n // Not expected on the host side; ignore.\n return\n }\n }\n\n return {\n ready() {\n if (hello) return Promise.resolve(hello)\n if (helloPromise) return helloPromise\n helloPromise = new Promise<HelloFrame>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `Tunnel daemon did not send hello within ${helloTimeoutMs}ms.`\n )\n )\n }, helloTimeoutMs)\n // Rebind sink listener so we resolve on the next hello.\n const off = opts.sink.onFrame((frame) => {\n if (frame.t !== \"hello\") return\n clearTimeout(timer)\n off()\n if (frame.version !== TUNNEL_VERSION) {\n reject(\n new Error(\n `Tunnel daemon speaks ${frame.version}; client speaks ${TUNNEL_VERSION}.`\n )\n )\n return\n }\n hello = frame\n resolve(frame)\n })\n })\n return helloPromise\n },\n\n async spawn(command, args, spawnOpts) {\n if (!hello) await this.ready()\n if (spawnOpts?.pty && hello?.capabilities.pty !== true) {\n throw new Error(\n `Tunnel daemon '${hello?.label ?? \"(unnamed)\"}' does not advertise PTY support.`\n )\n }\n const execId = randomUUID()\n const req: SpawnFrame = {\n t: \"spawn\",\n execId,\n command,\n args,\n cwd: spawnOpts?.cwd,\n env: spawnOpts?.env,\n pty: spawnOpts?.pty,\n }\n const spawnedFrame = await new Promise<SpawnedFrame>(\n (resolve, reject) => {\n spawnPending.set(execId, { resolve, reject })\n opts.sink.send(req)\n }\n )\n const duck = new TunnelChildDuck(execId, spawnedFrame.pid, opts.sink)\n childByExec.set(execId, duck)\n return duck\n },\n\n async close() {\n opts.sink.close(\"client.close\")\n },\n }\n}\n\n/**\n * Internal ChildProcess duck. Public methods mirror the subset of\n * Node's ChildProcess that our ACP arm calls; double-underscored\n * methods are control hooks for the client.\n */\nclass TunnelChildDuck extends EventEmitter implements TunnelChildProcess {\n readonly execId: string\n pid: number | null\n readonly stdin: Writable\n readonly stdout: Readable\n readonly stderr: Readable\n private exited = false\n\n constructor(execId: string, pid: number, sink: FrameSink) {\n super()\n this.execId = execId\n this.pid = pid > 0 ? pid : null\n\n // stdout / stderr are passive Readables — consumers attach\n // listeners and we push() data as it arrives.\n this.stdout = new Readable({ read() {} })\n this.stderr = new Readable({ read() {} })\n\n // stdin is a Writable that converts each chunk to a `stdin` frame.\n this.stdin = new Writable({\n write(chunk: Buffer | string, _enc, cb) {\n sink.send({\n t: \"stdin\",\n execId,\n data: encodeData(chunk),\n })\n cb()\n },\n // Sending an empty stdin frame on `final` mirrors the local\n // semantics of closing a child's stdin (signal EOF). Daemons\n // ignore empty payloads, so this is safe even if the child\n // doesn't care about EOF.\n final(cb) {\n sink.send({ t: \"stdin\", execId, data: \"\" })\n cb()\n },\n })\n\n // Reference sink for kill().\n Object.defineProperty(this, \"__sink\", {\n value: sink,\n enumerable: false,\n })\n }\n\n __pushStdout(buf: Buffer): void {\n this.stdout.push(buf)\n }\n\n __pushStderr(buf: Buffer): void {\n this.stderr.push(buf)\n }\n\n __pushError(err: Error): void {\n this.emit(\"error\", err)\n }\n\n __handleExit(frame: ExitFrame): void {\n if (this.exited) return\n this.exited = true\n this.stdout.push(null)\n this.stderr.push(null)\n this.emit(\"exit\", frame.code, frame.signal)\n }\n\n __handleSinkClosed(): void {\n if (this.exited) return\n this.exited = true\n this.stdout.push(null)\n this.stderr.push(null)\n this.emit(\n \"exit\",\n null,\n \"SIGHUP\" // surrogate signal — the tunnel went away\n )\n }\n\n kill(signal: NodeJS.Signals | number = \"SIGTERM\"): boolean {\n if (this.exited) return false\n const sink = (this as unknown as { __sink: FrameSink }).__sink\n sink.send({\n t: \"kill\",\n execId: this.execId,\n signal: typeof signal === \"string\" ? signal : `SIG${signal}`,\n })\n return true\n }\n}\n","/**\n * Adapter from a WebSocket-shaped object to a `FrameSink`. Works with\n * both the browser-native WebSocket API and the `ws` library's server\n * sockets — anything with `send`, `close`, and `addEventListener`-style\n * `message`/`close` events.\n *\n * Why a duck-typed shape and not `import(\"ws\")`: the cli, the host,\n * and (eventually) browser hosts all need to wrap WS instances they\n * obtained themselves. Importing `ws` in this package would force\n * every consumer to install it even when running in environments\n * (Bun, Deno, browser) that ship native WebSocket.\n */\n\nimport { encodeFrame, parseFrame, type TunnelFrame } from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\nexport interface WebSocketLike {\n send(data: string): void\n close(code?: number, reason?: string): void\n readyState: number\n addEventListener(\n event: \"message\",\n handler: (ev: { data: string | ArrayBuffer | Buffer }) => void\n ): void\n addEventListener(event: \"close\", handler: () => void): void\n addEventListener(event: \"error\", handler: (ev: { message?: string }) => void): void\n removeEventListener(event: string, handler: (...args: never[]) => void): void\n}\n\nconst READY_OPEN = 1\n\nexport function wrapWebSocket(ws: WebSocketLike): FrameSink {\n const frameHandlers = new Set<(frame: TunnelFrame) => void>()\n const closeHandlers = new Set<(reason?: string) => void>()\n let isOpen = ws.readyState === READY_OPEN\n\n // The `ws` library delivers Buffer; the browser delivers string or\n // ArrayBuffer. We coerce to string and parse — everything we send is\n // JSON text, so non-text payloads are protocol violations and get\n // dropped.\n const onMessage = (ev: { data: string | ArrayBuffer | Buffer }): void => {\n let text: string\n if (typeof ev.data === \"string\") text = ev.data\n else if (ev.data instanceof ArrayBuffer) text = Buffer.from(ev.data).toString(\"utf8\")\n else text = (ev.data as Buffer).toString(\"utf8\")\n const frame = parseFrame(text)\n if (!frame) return\n for (const h of frameHandlers) h(frame)\n }\n\n const onClose = (): void => {\n if (!isOpen) return\n isOpen = false\n for (const h of closeHandlers) h(\"transport.closed\")\n frameHandlers.clear()\n closeHandlers.clear()\n }\n\n const onError = (ev: { message?: string }): void => {\n if (!isOpen) return\n isOpen = false\n for (const h of closeHandlers) h(ev.message ?? \"transport.error\")\n frameHandlers.clear()\n closeHandlers.clear()\n }\n\n ws.addEventListener(\"message\", onMessage)\n ws.addEventListener(\"close\", onClose)\n ws.addEventListener(\"error\", onError)\n\n return {\n get isOpen() {\n return isOpen\n },\n send(frame) {\n if (!isOpen) return\n try {\n ws.send(encodeFrame(frame))\n } catch {\n // Send-on-closed-socket is a benign race; rely on the close\n // handler to flip isOpen and let listeners drain naturally.\n }\n },\n close(reason) {\n if (!isOpen) return\n try {\n ws.close(1000, reason)\n } catch {\n // ignore\n }\n },\n onFrame(handler) {\n frameHandlers.add(handler)\n return () => frameHandlers.delete(handler)\n },\n onClose(handler) {\n closeHandlers.add(handler)\n return () => closeHandlers.delete(handler)\n },\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/tunnel/frames.ts","../../src/tunnel/server.ts","../../src/tunnel/client.ts","../../src/tunnel/ws-adapter.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Tunnel frame protocol — agentproto/tunnel/v1.\n *\n * Bidirectional, event-shaped frames over a single duplex transport\n * (typically a WebSocket). Each frame is one JSON object per message.\n * The protocol is deliberately NOT JSON-RPC: process I/O is a\n * continuous event stream, not a request/response pattern, so we drop\n * the `id` / response correlation and let the underlying child-process\n * lifecycle drive everything.\n *\n * # Roles\n *\n * host — the orchestrator (e.g. Guilde API). Sends `spawn`,\n * `stdin`, `kill`, `resize`. Receives `stdout`, `stderr`,\n * `exit`, `error`. Holds the ACP client; sees the duplex\n * stream as a `ChildProcess`-shaped duck.\n *\n * daemon — the local executor (e.g. `agentproto serve` on a user's\n * laptop). Receives spawn-side frames; spawns real\n * subprocesses; relays I/O back. Owns the actual file\n * system, environment, network egress.\n *\n * # Identifiers\n *\n * execId — host-chosen UUID per spawned process. Lets one tunnel\n * multiplex many concurrent agent CLIs (one daemon serving\n * several conversations).\n *\n * # Encoding\n *\n * - Each WS message is one JSON object. Binary frames are not used;\n * stdout/stdin payloads are base64-encoded utf-safe strings.\n * - Why base64 and not raw text: claude-code & friends emit JSON-RPC\n * ACP traffic over stdio. A child can also emit non-utf8 bytes\n * (debug logs, terminal control codes when in TTY mode). Base64\n * keeps the wire JSON-clean and bytewise-faithful.\n * - All frames carry `t` (type) and a per-type payload. Versioning\n * is per-tunnel via `hello.version`, not per-frame.\n *\n * # Lifecycle\n *\n * 1. Daemon connects to host's WS endpoint with bearer token.\n * 2. Daemon sends `hello { version, capabilities, label? }`.\n * 3. Host sends `spawn` for each child it wants on this daemon.\n * 4. Daemon spawns, replies with `spawned { execId, pid }` (or\n * `error { execId, message }`).\n * 5. stdout/stderr/exit notifications flow daemon→host;\n * stdin/resize/kill flow host→daemon.\n * 6. Either side can send `ping`; receiver MUST `pong` back. Used\n * to detect half-open connections.\n * 7. On disconnect, daemon SHOULD kill all children associated with\n * this tunnel (host can re-spawn after reconnect).\n */\n\nexport const TUNNEL_VERSION = \"agentproto/tunnel/v1\" as const\n\n// ─── host → daemon ──────────────────────────────────────────────\n\nexport interface SpawnFrame {\n t: \"spawn\"\n execId: string\n command: string\n args: readonly string[]\n /**\n * Working directory on the daemon side. Daemon SHOULD reject\n * absolute paths that escape its declared `--root` (see\n * `agentproto serve --root`); host MUST pass paths that exist\n * on the daemon.\n */\n cwd?: string\n /**\n * Environment overrides. Daemon merges these atop its own\n * process.env. Daemon MAY redact values from logs but MUST forward\n * them verbatim into the child.\n */\n env?: Readonly<Record<string, string>>\n /**\n * When true, the daemon allocates a PTY instead of plain pipes.\n * Required for binaries that detect `isTTY` (REPLs, claude\n * interactive). When false (default), stdin/stdout/stderr are\n * separate pipes — the right choice for ACP wrappers that speak\n * line-delimited JSON-RPC.\n */\n pty?: boolean\n}\n\nexport interface StdinFrame {\n t: \"stdin\"\n execId: string\n /** Base64-encoded payload bytes. Empty string is a no-op. */\n data: string\n}\n\nexport interface KillFrame {\n t: \"kill\"\n execId: string\n /** Default \"SIGTERM\". POSIX names; Windows daemons translate. */\n signal?: string\n}\n\nexport interface ResizeFrame {\n t: \"resize\"\n execId: string\n cols: number\n rows: number\n}\n\n/**\n * Generic HTTP-over-tunnel request (HOST → DAEMON). Lets the host\n * proxy arbitrary HTTP traffic (MCP JSON-RPC today, anything tomorrow)\n * through the daemon's local network stack without exposing a public\n * URL. The daemon forwards to its configured \"http upstream\" (default:\n * its own gateway on `127.0.0.1:<port>`) and replies with an\n * `HttpResponseFrame` carrying the same `reqId`.\n */\nexport interface HttpRequestFrame {\n t: \"http_request\"\n /** Correlates the response. Host-chosen, MUST be unique per inflight request. */\n reqId: string\n /** HTTP method (GET / POST / PUT / DELETE / PATCH / OPTIONS). */\n method: string\n /** Path + querystring, e.g. `/mcp` or `/mcp?session=abc`.\n * Daemon prepends its upstream base. Absolute URLs are rejected. */\n path: string\n /** Forwarded request headers. Hop-by-hop headers (Connection,\n * Keep-Alive, Transfer-Encoding, Upgrade, Proxy-*) MUST be stripped\n * before forwarding; daemon SHOULD also drop them defensively. */\n headers?: Readonly<Record<string, string>>\n /** Base64-encoded request body. Omitted when no body. */\n body?: string\n /** Per-request timeout in ms. Daemon SHOULD enforce + reply with\n * `error: { code: \"timeout\" }` when exceeded. Default 30_000. */\n timeoutMs?: number\n}\n\n/**\n * Generic HTTP-over-tunnel response (DAEMON → HOST). Mirrors\n * `HttpRequestFrame` — see its docs.\n */\nexport interface HttpResponseFrame {\n t: \"http_response\"\n reqId: string\n /** HTTP status code (e.g. 200, 404, 500). Set even on transport\n * failure (then `error` is also set and status SHOULD be 502/504). */\n status: number\n headers?: Readonly<Record<string, string>>\n /** Base64-encoded response body. */\n body?: string\n /** Set when the daemon failed to complete the upstream call. */\n error?: Readonly<{\n code: string\n message: string\n }>\n}\n\n// ─── daemon → host ──────────────────────────────────────────────\n\nexport interface HelloFrame {\n t: \"hello\"\n version: typeof TUNNEL_VERSION\n /**\n * Daemon-declared capabilities. Hosts MAY refuse to dispatch\n * features the daemon hasn't claimed.\n */\n capabilities: Readonly<{\n pty: boolean\n /** Future: file-transfer, port-forward, etc. */\n }>\n /** User-friendly daemon label, surfaced in host UIs. */\n label?: string\n /** Daemon process info — diagnostics only, not authoritative. */\n daemon?: Readonly<{\n name: string\n version: string\n platform: string\n nodeVersion?: string\n }>\n}\n\nexport interface SpawnedFrame {\n t: \"spawned\"\n execId: string\n pid: number\n}\n\nexport interface StdoutFrame {\n t: \"stdout\"\n execId: string\n /** Base64-encoded payload bytes. */\n data: string\n}\n\nexport interface StderrFrame {\n t: \"stderr\"\n execId: string\n /** Base64-encoded payload bytes. */\n data: string\n}\n\nexport interface ExitFrame {\n t: \"exit\"\n execId: string\n /** Null when killed by signal. */\n code: number | null\n /** POSIX signal name when killed by signal; null otherwise. */\n signal: string | null\n}\n\n// ─── either direction ───────────────────────────────────────────\n\nexport interface ErrorFrame {\n t: \"error\"\n /** Omitted for tunnel-level errors not tied to a specific exec. */\n execId?: string\n /** Stable machine-readable code, e.g. \"spawn_failed\", \"unknown_exec\". */\n code: string\n /** Human-readable diagnostic. */\n message: string\n}\n\nexport interface PingFrame {\n t: \"ping\"\n /** Echoed verbatim in the matching pong. */\n nonce: string\n}\n\nexport interface PongFrame {\n t: \"pong\"\n nonce: string\n}\n\n// ─── union + guards ─────────────────────────────────────────────\n\nexport type HostToDaemonFrame =\n | SpawnFrame\n | StdinFrame\n | KillFrame\n | ResizeFrame\n | HttpRequestFrame\n | PingFrame\n | PongFrame\n | ErrorFrame\n\nexport type DaemonToHostFrame =\n | HelloFrame\n | SpawnedFrame\n | StdoutFrame\n | StderrFrame\n | ExitFrame\n | HttpResponseFrame\n | PingFrame\n | PongFrame\n | ErrorFrame\n\nexport type TunnelFrame = HostToDaemonFrame | DaemonToHostFrame\n\nconst KNOWN_TYPES = new Set<TunnelFrame[\"t\"]>([\n \"spawn\",\n \"stdin\",\n \"kill\",\n \"resize\",\n \"http_request\",\n \"http_response\",\n \"ping\",\n \"pong\",\n \"error\",\n \"hello\",\n \"spawned\",\n \"stdout\",\n \"stderr\",\n \"exit\",\n])\n\n/**\n * Parse a raw WS message into a `TunnelFrame`. Returns null when the\n * payload isn't a valid frame — callers MUST handle null defensively\n * (typically: send an `error` frame and ignore). We don't throw so\n * one bad frame can't tear down the tunnel.\n */\nexport function parseFrame(raw: string): TunnelFrame | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !(\"t\" in parsed) ||\n typeof (parsed as { t: unknown }).t !== \"string\"\n ) {\n return null\n }\n const t = (parsed as { t: string }).t\n if (!KNOWN_TYPES.has(t as TunnelFrame[\"t\"])) return null\n return parsed as TunnelFrame\n}\n\nexport function encodeFrame(frame: TunnelFrame): string {\n return JSON.stringify(frame)\n}\n\n/**\n * Encode raw bytes for an `stdin` / `stdout` / `stderr` frame's `data`\n * field. Centralised so both sides agree on the encoding.\n */\nexport function encodeData(bytes: Uint8Array | string): string {\n const buf =\n typeof bytes === \"string\" ? Buffer.from(bytes, \"utf8\") : Buffer.from(bytes)\n return buf.toString(\"base64\")\n}\n\nexport function decodeData(data: string): Buffer {\n return Buffer.from(data, \"base64\")\n}\n","/**\n * Tunnel server (daemon side).\n *\n * Sits on the local machine that actually owns the binaries (e.g. the\n * user's laptop running `agentproto serve`). Accepts spawn frames from\n * a remote host, spawns the requested child via node:child_process,\n * pipes stdio back as `stdout`/`stderr` frames, and reports lifecycle\n * via `spawned`/`exit`/`error`.\n *\n * Transport-agnostic: the caller passes a `FrameSink`. For WebSocket\n * callers, see `wrapWebSocket()` in ./ws-adapter.ts.\n *\n * Security: this process MUST refuse spawn requests by default unless\n * the caller installed an `authorize(spawn)` hook. We don't expose a\n * \"trust everything\" mode — every host integration is responsible for\n * deciding what the remote may exec. The hook gets the full SpawnFrame\n * and can mutate it (e.g. force `cwd` into a workspace) or reject.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\"\nimport { hostname, platform } from \"node:os\"\nimport {\n TUNNEL_VERSION,\n decodeData,\n encodeData,\n encodeFrame,\n parseFrame,\n type ExitFrame,\n type HttpRequestFrame,\n type HttpResponseFrame,\n type SpawnFrame,\n type StderrFrame,\n type StdoutFrame,\n type TunnelFrame,\n} from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\nexport interface TunnelServerOptions {\n sink: FrameSink\n /**\n * Authorization hook. Called for each `spawn` frame BEFORE the\n * subprocess is created. Return the (possibly mutated) frame to\n * proceed; throw or return null to reject. Rejection causes an\n * `error` frame with code \"spawn_unauthorized\".\n *\n * No default — callers MUST decide. Pass `(req) => req` to allow\n * everything (useful for tests; never for production).\n */\n authorize: (req: SpawnFrame) => SpawnFrame | null | Promise<SpawnFrame | null>\n /**\n * Upstream HTTP base URL for `http_request` frames. The daemon\n * appends the frame's `path` and forwards. Typically the local\n * gateway address — `http://127.0.0.1:18790`. When omitted, the\n * daemon replies to any `http_request` with\n * `error: { code: \"http_upstream_not_configured\" }`.\n */\n httpUpstream?: string\n /** User-friendly label sent in the hello frame. */\n label?: string\n /**\n * Whether this daemon advertises PTY support. v0 always reports\n * false — PTY allocation needs node-pty (optional dep) and we\n * haven't wired that side yet.\n */\n pty?: boolean\n /**\n * Optional hook fired once per successful spawn — after authorize\n * passed and the ChildProcess has emitted its `spawn` event.\n * Lets the daemon adopt this child into a higher-level registry\n * (e.g. @agentproto/runtime's sessions registry, AIP-46) so the\n * spawn shows up in /sessions, the LocalDaemonSessionsCard, and\n * the CLI TUI alongside spawns originated locally.\n *\n * The hook is fire-and-forget — the tunnel doesn't await its\n * return. Errors from the hook are swallowed so an observer\n * registry hiccup never breaks the tunnel exchange.\n */\n onChildSpawned?: (info: {\n execId: string\n child: ChildProcess\n request: SpawnFrame\n }) => void\n}\n\nexport interface TunnelServer {\n /** Currently-live execId → ChildProcess. Read-only diagnostic. */\n readonly children: ReadonlyMap<string, ChildProcess>\n /** Kill all live children and close the sink. */\n close(): Promise<void>\n}\n\nexport function createTunnelServer(opts: TunnelServerOptions): TunnelServer {\n const children = new Map<string, ChildProcess>()\n let closed = false\n\n // Greet the host immediately so it can fail fast on version\n // mismatch before issuing spawns.\n opts.sink.send({\n t: \"hello\",\n version: TUNNEL_VERSION,\n capabilities: { pty: opts.pty === true },\n label: opts.label,\n daemon: {\n name: \"agentproto\",\n version: \"0.1.0-alpha\",\n platform: `${platform()}/${hostname()}`,\n nodeVersion: process.version,\n },\n })\n\n const offFrame = opts.sink.onFrame((frame) => {\n handleFrame(frame).catch((err) => {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({ t: \"error\", code: \"internal\", message })\n })\n })\n\n const offClose = opts.sink.onClose(() => {\n closed = true\n for (const [, child] of children) child.kill(\"SIGTERM\")\n children.clear()\n offFrame()\n offClose()\n })\n\n async function handleFrame(frame: TunnelFrame): Promise<void> {\n switch (frame.t) {\n case \"spawn\":\n await handleSpawn(frame)\n return\n case \"stdin\": {\n const child = children.get(frame.execId)\n if (!child || !child.stdin) {\n opts.sink.send({\n t: \"error\",\n execId: frame.execId,\n code: \"unknown_exec\",\n message: `No live exec '${frame.execId}'`,\n })\n return\n }\n child.stdin.write(Buffer.from(frame.data, \"base64\"))\n return\n }\n case \"kill\": {\n const child = children.get(frame.execId)\n if (!child) {\n opts.sink.send({\n t: \"error\",\n execId: frame.execId,\n code: \"unknown_exec\",\n message: `No live exec '${frame.execId}'`,\n })\n return\n }\n // Cast: NodeJS.Signals is a string union; users may send any\n // POSIX signal name. We trust the host.\n child.kill((frame.signal as NodeJS.Signals | undefined) ?? \"SIGTERM\")\n return\n }\n case \"resize\":\n // PTY resize — needs node-pty wiring. Silently no-op for now;\n // hosts that care can check `hello.capabilities.pty`.\n return\n case \"http_request\":\n // Fire-and-forget — the handler emits its own http_response\n // frame (or error). Errors that escape handleHttpRequest\n // bubble to the outer .catch and become a generic `error`\n // frame, but the per-request error path inside the handler\n // produces an http_response with `error:` set, which is what\n // the host's forwardHttp expects.\n await handleHttpRequest(frame)\n return\n case \"ping\":\n opts.sink.send({ t: \"pong\", nonce: frame.nonce })\n return\n case \"pong\":\n case \"error\":\n // Daemon doesn't act on these; host-side concern.\n return\n default:\n opts.sink.send({\n t: \"error\",\n code: \"unknown_frame\",\n message: `Daemon received unexpected frame type '${(frame as { t: string }).t}'`,\n })\n }\n }\n\n async function handleSpawn(req: SpawnFrame): Promise<void> {\n if (closed) return\n let approved: SpawnFrame | null\n try {\n approved = await opts.authorize(req)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_unauthorized\",\n message,\n })\n return\n }\n if (!approved) {\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_unauthorized\",\n message: \"Authorize hook rejected the spawn request.\",\n })\n return\n }\n\n let child: ChildProcess\n try {\n child = spawn(approved.command, [...approved.args], {\n cwd: approved.cwd,\n env: { ...process.env, ...(approved.env ?? {}) },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n // detached:false so SIGINT/SIGTERM on the daemon kills children too.\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"spawn_failed\",\n message,\n })\n return\n }\n\n children.set(req.execId, child)\n\n if (typeof child.pid === \"number\") {\n opts.sink.send({ t: \"spawned\", execId: req.execId, pid: child.pid })\n } else {\n // Pid is null when spawn synchronously failed. The 'error'\n // event below will fire with the real cause; we still report\n // a placeholder spawned to keep the lifecycle predictable.\n opts.sink.send({ t: \"spawned\", execId: req.execId, pid: -1 })\n }\n\n // Notify the higher-level registry (when wired) — this is what\n // makes tunnel-driven spawns visible in the daemon's\n // /sessions list alongside spawns originated locally. Errors\n // from the observer never break the tunnel.\n if (opts.onChildSpawned) {\n try {\n opts.onChildSpawned({ execId: req.execId, child, request: approved })\n } catch (err) {\n // Hook is fire-and-forget — log + continue.\n // Use console.warn directly because tunnel server has no\n // logger context.\n console.warn(\n `[tunnel-server] onChildSpawned hook threw for execId=${req.execId}: ${\n err instanceof Error ? err.message : String(err)\n }`\n )\n }\n }\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n const f: StdoutFrame = {\n t: \"stdout\",\n execId: req.execId,\n data: encodeData(chunk),\n }\n opts.sink.send(f)\n })\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n const f: StderrFrame = {\n t: \"stderr\",\n execId: req.execId,\n data: encodeData(chunk),\n }\n opts.sink.send(f)\n })\n\n child.on(\"error\", (err) => {\n opts.sink.send({\n t: \"error\",\n execId: req.execId,\n code: \"child_error\",\n message: err.message,\n })\n })\n\n child.on(\"exit\", (code, signal) => {\n const f: ExitFrame = {\n t: \"exit\",\n execId: req.execId,\n code,\n signal,\n }\n opts.sink.send(f)\n children.delete(req.execId)\n })\n }\n\n async function handleHttpRequest(req: HttpRequestFrame): Promise<void> {\n if (closed) return\n const respond = (\n partial:\n | { status: number; headers?: HttpResponseFrame[\"headers\"]; body?: string }\n | { error: HttpResponseFrame[\"error\"]; status: number }\n ): void => {\n opts.sink.send({\n t: \"http_response\",\n reqId: req.reqId,\n ...partial,\n } as HttpResponseFrame)\n }\n\n // Reject if no upstream is configured. Better explicit error\n // than a confusing 502 from a missing fetch target.\n if (!opts.httpUpstream) {\n respond({\n status: 502,\n error: {\n code: \"http_upstream_not_configured\",\n message:\n \"Daemon was not started with an http upstream — cannot forward HTTP frames.\",\n },\n })\n return\n }\n\n // Path-only is required to keep the daemon from being a generic\n // SSRF amplifier. Absolute URLs (including protocol-relative `//`)\n // and parent-traversal are rejected. Querystrings are fine.\n if (\n !req.path.startsWith(\"/\") ||\n req.path.startsWith(\"//\") ||\n req.path.includes(\"..\")\n ) {\n respond({\n status: 400,\n error: {\n code: \"invalid_path\",\n message: `Daemon http forward requires a safe relative path; got '${req.path}'.`,\n },\n })\n return\n }\n\n // Strip hop-by-hop headers defensively before forwarding —\n // they're meaningful only to the previous transport hop.\n const HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"upgrade\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"host\", // upstream sets its own based on the URL\n \"content-length\", // fetch recomputes\n ])\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(req.headers ?? {})) {\n if (HOP_BY_HOP.has(k.toLowerCase())) continue\n headers[k] = v\n }\n\n const url = `${opts.httpUpstream.replace(/\\/$/, \"\")}${req.path}`\n const controller = new AbortController()\n const timeoutMs = req.timeoutMs ?? 30_000\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const upstreamRes = await fetch(url, {\n method: req.method,\n headers,\n body:\n req.body === undefined ? undefined : decodeData(req.body),\n signal: controller.signal,\n // node fetch follows redirects by default — fine for MCP, the\n // upstream gateway doesn't redirect anyway.\n })\n const buf = Buffer.from(await upstreamRes.arrayBuffer())\n const outHeaders: Record<string, string> = {}\n upstreamRes.headers.forEach((value, key) => {\n if (HOP_BY_HOP.has(key.toLowerCase())) return\n outHeaders[key] = value\n })\n respond({\n status: upstreamRes.status,\n headers: outHeaders,\n body: buf.length > 0 ? encodeData(buf) : \"\",\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const aborted = err instanceof Error && err.name === \"AbortError\"\n respond({\n status: aborted ? 504 : 502,\n error: {\n code: aborted ? \"timeout\" : \"upstream_fetch_failed\",\n message,\n },\n })\n } finally {\n clearTimeout(timer)\n }\n }\n\n return {\n children,\n async close() {\n if (closed) return\n closed = true\n for (const [, child] of children) child.kill(\"SIGTERM\")\n children.clear()\n opts.sink.close(\"server.close\")\n },\n }\n}\n\n// Re-exports for convenience — server-only callers don't need to\n// reach into ./frames for these.\nexport { encodeFrame, parseFrame }\n","/**\n * Tunnel client (host side).\n *\n * Sits on the orchestrator (e.g. Guilde API). Speaks to a remote daemon\n * over a `FrameSink`. Exposes `spawn(command, args, opts)` that returns\n * an object shaped like Node's `ChildProcess` — same `.stdin` /\n * `.stdout` / `.stderr` / `.on('exit')` / `.kill()` surface. This lets\n * the existing ACP protocol arm drive remote subprocesses with zero\n * code changes; the arm thinks it's talking to a local child.\n *\n * The duck is intentionally minimal — only the methods the ACP arm\n * actually calls. If you need the full ChildProcess interface, wrap\n * this with a node:stream.Readable shim.\n */\n\nimport { EventEmitter } from \"node:events\"\nimport { Readable, Writable } from \"node:stream\"\nimport { randomUUID } from \"node:crypto\"\nimport {\n TUNNEL_VERSION,\n decodeData,\n encodeData,\n type ExitFrame,\n type HelloFrame,\n type HttpRequestFrame,\n type HttpResponseFrame,\n type SpawnFrame,\n type SpawnedFrame,\n type StderrFrame,\n type StdoutFrame,\n type TunnelFrame,\n} from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\n/** Resolved response from `forwardHttp`. Mirrors `HttpResponseFrame`\n * but with the body decoded back to a Buffer for the caller. */\nexport interface TunnelHttpResponse {\n status: number\n headers: Readonly<Record<string, string>>\n body: Buffer\n /** Set when the daemon failed to complete the upstream call. */\n error?: Readonly<{\n code: string\n message: string\n }>\n}\n\nexport interface TunnelHttpRequest {\n method: string\n path: string\n headers?: Readonly<Record<string, string>>\n body?: Buffer | Uint8Array | string\n /** Per-request timeout in ms. Default 30_000. */\n timeoutMs?: number\n}\n\nexport interface TunnelClientOptions {\n sink: FrameSink\n /**\n * Reject the `spawn` call if the daemon hasn't sent its `hello`\n * within this window. Defaults to 10s — generous for high-latency\n * tunnels but fast enough that misconfigured daemons fail loudly.\n */\n helloTimeoutMs?: number\n}\n\nexport interface TunnelSpawnOptions {\n cwd?: string\n env?: Readonly<Record<string, string>>\n /** Allocate a PTY on the daemon. Requires `hello.capabilities.pty`. */\n pty?: boolean\n}\n\n/**\n * Minimal ChildProcess-shaped duck. Covers the surface our ACP arm\n * calls; expand only when a real consumer needs more.\n */\nexport interface TunnelChildProcess {\n readonly execId: string\n readonly pid: number | null\n readonly stdin: Writable\n readonly stdout: Readable\n readonly stderr: Readable\n /** \"exit\" → (code, signal). \"error\" → (Error). */\n on(event: \"exit\", listener: (code: number | null, signal: string | null) => void): this\n on(event: \"error\", listener: (err: Error) => void): this\n kill(signal?: NodeJS.Signals | number): boolean\n}\n\nexport interface TunnelClient {\n /**\n * Resolves once the daemon has sent its `hello` frame, OR rejects\n * with a tunnel-level error. Subsequent calls return the cached\n * promise.\n */\n ready(): Promise<HelloFrame>\n spawn(\n command: string,\n args: readonly string[],\n opts?: TunnelSpawnOptions\n ): Promise<TunnelChildProcess>\n /**\n * Forward an HTTP request through the tunnel to the daemon's\n * configured upstream (typically `http://127.0.0.1:<port>`). The\n * daemon completes the upstream call and replies with the response,\n * which this promise resolves to. Rejects on tunnel-transport\n * failure; the daemon-reported HTTP status (incl. 5xx) is returned\n * in the resolved value.\n */\n forwardHttp(req: TunnelHttpRequest): Promise<TunnelHttpResponse>\n close(): Promise<void>\n}\n\nexport function createTunnelClient(opts: TunnelClientOptions): TunnelClient {\n const helloTimeoutMs = opts.helloTimeoutMs ?? 10_000\n let hello: HelloFrame | null = null\n let helloPromise: Promise<HelloFrame> | null = null\n\n // Per-execId routing tables. We dispatch incoming frames to the\n // right child duck by execId; spawn awaits its own `spawned` /\n // `error` reply via these one-shot resolvers.\n const childByExec = new Map<string, TunnelChildDuck>()\n const spawnPending = new Map<\n string,\n { resolve: (frame: SpawnedFrame) => void; reject: (err: Error) => void }\n >()\n\n // Inflight HTTP forwards keyed by reqId. Each resolves with the\n // first matching `http_response` frame; daemon timeouts are surfaced\n // either via `error` (then we resolve with status=502) or by the\n // per-request setTimeout below (then we reject with a timeout error).\n const httpPending = new Map<\n string,\n {\n resolve: (resp: TunnelHttpResponse) => void\n reject: (err: Error) => void\n timer: ReturnType<typeof setTimeout>\n }\n >()\n\n const offFrame = opts.sink.onFrame((frame) => routeIncoming(frame))\n const offClose = opts.sink.onClose(() => {\n for (const [, duck] of childByExec) duck.__handleSinkClosed()\n childByExec.clear()\n for (const [, p] of spawnPending) {\n p.reject(new Error(\"Tunnel closed before spawn completed.\"))\n }\n spawnPending.clear()\n for (const [, p] of httpPending) {\n clearTimeout(p.timer)\n p.reject(new Error(\"Tunnel closed before HTTP response arrived.\"))\n }\n httpPending.clear()\n offFrame()\n offClose()\n })\n\n function routeIncoming(frame: TunnelFrame): void {\n switch (frame.t) {\n case \"hello\":\n if (frame.version !== TUNNEL_VERSION) {\n // Mismatch — surface via the ready() rejection. We don't\n // tear down the sink because the caller may want to\n // negotiate a fallback.\n throw new Error(\n `Tunnel daemon speaks ${frame.version}; this client speaks ${TUNNEL_VERSION}.`\n )\n }\n hello = frame\n return\n case \"spawned\": {\n const pending = spawnPending.get(frame.execId)\n if (pending) {\n spawnPending.delete(frame.execId)\n pending.resolve(frame)\n }\n return\n }\n case \"stdout\": {\n const duck = childByExec.get(frame.execId)\n duck?.__pushStdout(decodeData((frame as StdoutFrame).data))\n return\n }\n case \"stderr\": {\n const duck = childByExec.get(frame.execId)\n duck?.__pushStderr(decodeData((frame as StderrFrame).data))\n return\n }\n case \"exit\": {\n const duck = childByExec.get(frame.execId)\n duck?.__handleExit(frame as ExitFrame)\n childByExec.delete(frame.execId)\n return\n }\n case \"error\": {\n if (frame.execId) {\n const pending = spawnPending.get(frame.execId)\n if (pending) {\n spawnPending.delete(frame.execId)\n pending.reject(new Error(`${frame.code}: ${frame.message}`))\n return\n }\n const duck = childByExec.get(frame.execId)\n duck?.__pushError(new Error(`${frame.code}: ${frame.message}`))\n return\n }\n // Tunnel-level error: nowhere to surface it cleanly here.\n // Callers that care should wrap the FrameSink and observe\n // tunnel-error frames themselves.\n return\n }\n case \"http_response\": {\n const pending = httpPending.get(frame.reqId)\n if (!pending) return\n httpPending.delete(frame.reqId)\n clearTimeout(pending.timer)\n pending.resolve({\n status: frame.status,\n headers: frame.headers ?? {},\n body: frame.body ? decodeData(frame.body) : Buffer.alloc(0),\n ...(frame.error ? { error: frame.error } : {}),\n })\n return\n }\n case \"ping\":\n opts.sink.send({ t: \"pong\", nonce: frame.nonce })\n return\n case \"pong\":\n case \"spawn\":\n case \"stdin\":\n case \"kill\":\n case \"resize\":\n case \"http_request\":\n // Not expected on the host side; ignore.\n return\n }\n }\n\n return {\n ready() {\n if (hello) return Promise.resolve(hello)\n if (helloPromise) return helloPromise\n helloPromise = new Promise<HelloFrame>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `Tunnel daemon did not send hello within ${helloTimeoutMs}ms.`\n )\n )\n }, helloTimeoutMs)\n // Rebind sink listener so we resolve on the next hello.\n const off = opts.sink.onFrame((frame) => {\n if (frame.t !== \"hello\") return\n clearTimeout(timer)\n off()\n if (frame.version !== TUNNEL_VERSION) {\n reject(\n new Error(\n `Tunnel daemon speaks ${frame.version}; client speaks ${TUNNEL_VERSION}.`\n )\n )\n return\n }\n hello = frame\n resolve(frame)\n })\n })\n return helloPromise\n },\n\n async spawn(command, args, spawnOpts) {\n if (!hello) await this.ready()\n if (spawnOpts?.pty && hello?.capabilities.pty !== true) {\n throw new Error(\n `Tunnel daemon '${hello?.label ?? \"(unnamed)\"}' does not advertise PTY support.`\n )\n }\n const execId = randomUUID()\n const req: SpawnFrame = {\n t: \"spawn\",\n execId,\n command,\n args,\n cwd: spawnOpts?.cwd,\n env: spawnOpts?.env,\n pty: spawnOpts?.pty,\n }\n const spawnedFrame = await new Promise<SpawnedFrame>(\n (resolve, reject) => {\n spawnPending.set(execId, { resolve, reject })\n opts.sink.send(req)\n }\n )\n const duck = new TunnelChildDuck(execId, spawnedFrame.pid, opts.sink)\n childByExec.set(execId, duck)\n return duck\n },\n\n async forwardHttp(req: TunnelHttpRequest): Promise<TunnelHttpResponse> {\n if (!hello) await this.ready()\n const reqId = randomUUID()\n const timeoutMs = req.timeoutMs ?? 30_000\n const body =\n req.body === undefined\n ? undefined\n : encodeData(\n typeof req.body === \"string\" ? req.body : Buffer.from(req.body)\n )\n const frame: HttpRequestFrame = {\n t: \"http_request\",\n reqId,\n method: req.method,\n path: req.path,\n ...(req.headers ? { headers: req.headers } : {}),\n ...(body !== undefined ? { body } : {}),\n timeoutMs,\n }\n return new Promise<TunnelHttpResponse>((resolve, reject) => {\n // Belt-and-suspenders timeout: in addition to the daemon-side\n // enforcement, we time out client-side at timeoutMs + 5s so a\n // misbehaving daemon can't stall this promise forever.\n const timer = setTimeout(() => {\n httpPending.delete(reqId)\n reject(\n new Error(\n `Tunnel forwardHttp(${req.method} ${req.path}) timed out after ${timeoutMs + 5_000}ms.`\n )\n )\n }, timeoutMs + 5_000)\n httpPending.set(reqId, { resolve, reject, timer })\n opts.sink.send(frame)\n })\n },\n\n async close() {\n opts.sink.close(\"client.close\")\n },\n }\n}\n\n/**\n * Internal ChildProcess duck. Public methods mirror the subset of\n * Node's ChildProcess that our ACP arm calls; double-underscored\n * methods are control hooks for the client.\n */\nclass TunnelChildDuck extends EventEmitter implements TunnelChildProcess {\n readonly execId: string\n pid: number | null\n readonly stdin: Writable\n readonly stdout: Readable\n readonly stderr: Readable\n private exited = false\n\n constructor(execId: string, pid: number, sink: FrameSink) {\n super()\n this.execId = execId\n this.pid = pid > 0 ? pid : null\n\n // stdout / stderr are passive Readables — consumers attach\n // listeners and we push() data as it arrives.\n this.stdout = new Readable({ read() {} })\n this.stderr = new Readable({ read() {} })\n\n // stdin is a Writable that converts each chunk to a `stdin` frame.\n this.stdin = new Writable({\n write(chunk: Buffer | string, _enc, cb) {\n sink.send({\n t: \"stdin\",\n execId,\n data: encodeData(chunk),\n })\n cb()\n },\n // Sending an empty stdin frame on `final` mirrors the local\n // semantics of closing a child's stdin (signal EOF). Daemons\n // ignore empty payloads, so this is safe even if the child\n // doesn't care about EOF.\n final(cb) {\n sink.send({ t: \"stdin\", execId, data: \"\" })\n cb()\n },\n })\n\n // Reference sink for kill().\n Object.defineProperty(this, \"__sink\", {\n value: sink,\n enumerable: false,\n })\n }\n\n __pushStdout(buf: Buffer): void {\n this.stdout.push(buf)\n }\n\n __pushStderr(buf: Buffer): void {\n this.stderr.push(buf)\n }\n\n __pushError(err: Error): void {\n this.emit(\"error\", err)\n }\n\n __handleExit(frame: ExitFrame): void {\n if (this.exited) return\n this.exited = true\n this.stdout.push(null)\n this.stderr.push(null)\n this.emit(\"exit\", frame.code, frame.signal)\n }\n\n __handleSinkClosed(): void {\n if (this.exited) return\n this.exited = true\n this.stdout.push(null)\n this.stderr.push(null)\n this.emit(\n \"exit\",\n null,\n \"SIGHUP\" // surrogate signal — the tunnel went away\n )\n }\n\n kill(signal: NodeJS.Signals | number = \"SIGTERM\"): boolean {\n if (this.exited) return false\n const sink = (this as unknown as { __sink: FrameSink }).__sink\n sink.send({\n t: \"kill\",\n execId: this.execId,\n signal: typeof signal === \"string\" ? signal : `SIG${signal}`,\n })\n return true\n }\n}\n","/**\n * Adapter from a WebSocket-shaped object to a `FrameSink`. Works with\n * both the browser-native WebSocket API and the `ws` library's server\n * sockets — anything with `send`, `close`, and `addEventListener`-style\n * `message`/`close` events.\n *\n * Why a duck-typed shape and not `import(\"ws\")`: the cli, the host,\n * and (eventually) browser hosts all need to wrap WS instances they\n * obtained themselves. Importing `ws` in this package would force\n * every consumer to install it even when running in environments\n * (Bun, Deno, browser) that ship native WebSocket.\n */\n\nimport { encodeFrame, parseFrame, type TunnelFrame } from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\nexport interface WebSocketLike {\n send(data: string): void\n close(code?: number, reason?: string): void\n readyState: number\n addEventListener(\n event: \"message\",\n handler: (ev: { data: string | ArrayBuffer | Buffer }) => void\n ): void\n addEventListener(event: \"close\", handler: () => void): void\n addEventListener(event: \"error\", handler: (ev: { message?: string }) => void): void\n removeEventListener(event: string, handler: (...args: never[]) => void): void\n}\n\nconst READY_OPEN = 1\n\nexport function wrapWebSocket(ws: WebSocketLike): FrameSink {\n const frameHandlers = new Set<(frame: TunnelFrame) => void>()\n const closeHandlers = new Set<(reason?: string) => void>()\n let isOpen = ws.readyState === READY_OPEN\n\n // The `ws` library delivers Buffer; the browser delivers string or\n // ArrayBuffer. We coerce to string and parse — everything we send is\n // JSON text, so non-text payloads are protocol violations and get\n // dropped.\n const onMessage = (ev: { data: string | ArrayBuffer | Buffer }): void => {\n let text: string\n if (typeof ev.data === \"string\") text = ev.data\n else if (ev.data instanceof ArrayBuffer) text = Buffer.from(ev.data).toString(\"utf8\")\n else text = (ev.data as Buffer).toString(\"utf8\")\n const frame = parseFrame(text)\n if (!frame) return\n for (const h of frameHandlers) h(frame)\n }\n\n const onClose = (): void => {\n if (!isOpen) return\n isOpen = false\n for (const h of closeHandlers) h(\"transport.closed\")\n frameHandlers.clear()\n closeHandlers.clear()\n }\n\n const onError = (ev: { message?: string }): void => {\n if (!isOpen) return\n isOpen = false\n for (const h of closeHandlers) h(ev.message ?? \"transport.error\")\n frameHandlers.clear()\n closeHandlers.clear()\n }\n\n ws.addEventListener(\"message\", onMessage)\n ws.addEventListener(\"close\", onClose)\n ws.addEventListener(\"error\", onError)\n\n return {\n get isOpen() {\n return isOpen\n },\n send(frame) {\n if (!isOpen) return\n try {\n ws.send(encodeFrame(frame))\n } catch {\n // Send-on-closed-socket is a benign race; rely on the close\n // handler to flip isOpen and let listeners drain naturally.\n }\n },\n close(reason) {\n if (!isOpen) return\n try {\n ws.close(1000, reason)\n } catch {\n // ignore\n }\n },\n onFrame(handler) {\n frameHandlers.add(handler)\n return () => frameHandlers.delete(handler)\n },\n onClose(handler) {\n closeHandlers.add(handler)\n return () => closeHandlers.delete(handler)\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/acp",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "@agentproto/acp — AIP-44 ACP.md reference implementation. An agentproto profile of the Agent Client Protocol (agentclientprotocol.com). Wraps @agentclientprotocol/sdk with createAcpClient (drives subprocess agents) and createAcpServer (exposes AIP-9 operators to ACP-speaking IDEs).",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -64,11 +64,11 @@
64
64
  "dependencies": {
65
65
  "@agentclientprotocol/sdk": "^0.21.0",
66
66
  "gray-matter": "^4.0.3",
67
- "zod": "^4.3.6",
67
+ "zod": "^4.4.3",
68
68
  "@agentproto/define-doctype": "0.1.0-alpha.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@types/node": "^25.1.0",
71
+ "@types/node": "^25.6.2",
72
72
  "tsup": "^8.5.1",
73
73
  "typescript": "^5.9.3",
74
74
  "vitest": "^3.2.4",