{"version":3,"sources":["../src/tunnel/aead.ts","../src/tunnel/bytes.ts","../src/tunnel/frames.ts","../src/tunnel/e2e.ts","../src/tunnel/tunnel-e2e.ts","../src/tunnel/ws-adapter.ts"],"names":["buf"],"mappings":"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","file":"chunk-FNJRJZRG.mjs","sourcesContent":["/**\n * The AEAD seam behind `wrapE2E`: AES-256-GCM with a 16-byte tag, and nothing\n * else. `@agentproto/acp` never touches key agreement — it only receives the two\n * symmetric session keys — so this is the whole crypto surface it needs.\n *\n * Structurally a subset of `@agentproto/secrets`' `CryptoProvider`, so either\n * package's provider objects can be passed where an `E2eAead` is expected.\n *\n * Two implementations: `nodeAead` (./aead-node.ts, `node:crypto`; the default\n * through the `@agentproto/acp/tunnel` Node entry) and `webCryptoAead` (here;\n * the default through `@agentproto/acp/tunnel/browser`). Both produce\n * `ciphertext ‖ tag` — AES-GCM is deterministic, so for the same key, nonce,\n * AAD and plaintext they are byte-identical.\n *\n * Browser-safe: no `node:` import.\n */\nexport interface E2eAead {\n  /** Returns `ciphertext ‖ 16-byte tag`. */\n  aesGcmEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>\n  /** Opens `ciphertext ‖ tag`; rejects on any authentication failure. */\n  aesGcmDecrypt(key: Uint8Array, iv: Uint8Array, sealed: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>\n}\n\nconst TAG_BITS = 128\n\nfunction subtle(): SubtleCrypto {\n  const s = globalThis.crypto?.subtle\n  if (!s) {\n    throw new Error(\"WebCrypto (globalThis.crypto.subtle) is not available in this environment\")\n  }\n  return s\n}\n\n/** Narrow to an ArrayBuffer-backed view, as WebCrypto's `BufferSource` wants. */\nfunction buf(u8: Uint8Array): Uint8Array<ArrayBuffer> {\n  return u8.buffer instanceof ArrayBuffer ? (u8 as Uint8Array<ArrayBuffer>) : new Uint8Array(u8)\n}\n\ntype GcmParams = Parameters<SubtleCrypto[\"encrypt\"]>[0]\n\nfunction params(iv: Uint8Array, aad?: Uint8Array): GcmParams {\n  return aad\n    ? { name: \"AES-GCM\", iv: buf(iv), additionalData: buf(aad), tagLength: TAG_BITS }\n    : { name: \"AES-GCM\", iv: buf(iv), tagLength: TAG_BITS }\n}\n\n/**\n * WebCrypto AES-GCM. Imported `CryptoKey`s are cached per key buffer, since\n * `wrapE2E` encrypts every frame under the same two keys.\n */\nexport function createWebCryptoAead(): E2eAead {\n  const keys = new WeakMap<Uint8Array, Promise<CryptoKey>>()\n  const keyFor = (raw: Uint8Array): Promise<CryptoKey> => {\n    let k = keys.get(raw)\n    if (!k) {\n      k = subtle().importKey(\"raw\", buf(raw), { name: \"AES-GCM\" }, false, [\"encrypt\", \"decrypt\"])\n      keys.set(raw, k)\n    }\n    return k\n  }\n  return {\n    aesGcmEncrypt: async (key, iv, plaintext, aad) =>\n      new Uint8Array(await subtle().encrypt(params(iv, aad), await keyFor(key), buf(plaintext))),\n    aesGcmDecrypt: async (key, iv, sealed, aad) =>\n      new Uint8Array(await subtle().decrypt(params(iv, aad), await keyFor(key), buf(sealed))),\n  }\n}\n\nexport const webCryptoAead: E2eAead = createWebCryptoAead()\n","/**\n * Byte helpers for the tunnel core — the `Uint8Array` replacements for the\n * `Buffer` calls the frame codec and the E2E wrapper used to make.\n *\n * Browser-safe: no `node:` import. Under Node (a global `Buffer` exists) the\n * base64 helpers delegate to `Buffer`, so Node output is byte-identical to the\n * calls they replace by construction; elsewhere a portable implementation with\n * the same semantics (including Buffer's lenient decode) takes over.\n *\n * `@agentproto/acp` deliberately does not depend on `@agentproto/secrets`, so\n * this is a copy of the base64/UTF-8 half of `secrets/src/crypto/bytes.ts`;\n * both copies are pinned against `Buffer` by their package's tests.\n */\n\ninterface BufferLike {\n  from(input: string, encoding: \"base64\"): Uint8Array\n  from(input: Uint8Array): Uint8Array & { toString(encoding: \"base64\"): string }\n}\n\nfunction nodeBuffer(): BufferLike | undefined {\n  const b = (globalThis as { Buffer?: BufferLike }).Buffer\n  return typeof b?.from === \"function\" ? b : undefined\n}\n\nconst ENC_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n\n/** char code → 6-bit value; -1 = not in either base64 alphabet. */\nconst DEC_TABLE: Int16Array = (() => {\n  const t = new Int16Array(128).fill(-1)\n  for (let i = 0; i < ENC_ALPHABET.length; i++) t[ENC_ALPHABET.charCodeAt(i)] = i\n  t[\"-\".charCodeAt(0)] = 62\n  t[\"_\".charCodeAt(0)] = 63\n  return t\n})()\n\n/** Portable standard base64 (padded). Exported for the parity test. */\nexport function base64EncodePortable(bytes: Uint8Array): string {\n  let out = \"\"\n  let i = 0\n  for (; i + 2 < bytes.length; i += 3) {\n    const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!\n    out +=\n      ENC_ALPHABET[(n >> 18) & 63]! +\n      ENC_ALPHABET[(n >> 12) & 63]! +\n      ENC_ALPHABET[(n >> 6) & 63]! +\n      ENC_ALPHABET[n & 63]!\n  }\n  const rest = bytes.length - i\n  if (rest === 1) {\n    const n = bytes[i]! << 16\n    out += ENC_ALPHABET[(n >> 18) & 63]! + ENC_ALPHABET[(n >> 12) & 63]! + \"==\"\n  } else if (rest === 2) {\n    const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8)\n    out +=\n      ENC_ALPHABET[(n >> 18) & 63]! +\n      ENC_ALPHABET[(n >> 12) & 63]! +\n      ENC_ALPHABET[(n >> 6) & 63]! +\n      \"=\"\n  }\n  return out\n}\n\n/** Portable lenient base64 decode with `Buffer.from(s, \"base64\")` semantics.\n *  Exported for the parity test. */\nexport function base64DecodePortable(s: string): Uint8Array {\n  const out = new Uint8Array(Math.floor((s.length * 3) / 4) + 3)\n  let len = 0\n  let acc = 0\n  let bits = 0\n  for (let i = 0; i < s.length; i++) {\n    const c = s.charCodeAt(i)\n    if (c === 61) break // \"=\" ends the data, as in Buffer\n    const v = c < 128 ? DEC_TABLE[c]! : -1\n    if (v < 0) continue\n    acc = ((acc << 6) | v) & 0xffffff\n    bits += 6\n    if (bits >= 8) {\n      bits -= 8\n      out[len++] = (acc >> bits) & 0xff\n    }\n  }\n  return out.slice(0, len)\n}\n\n/** Standard base64 (padded) — identical to `Buffer#toString(\"base64\")`. */\nexport function base64Encode(bytes: Uint8Array): string {\n  const B = nodeBuffer()\n  return B ? B.from(bytes).toString(\"base64\") : base64EncodePortable(bytes)\n}\n\n/** Lenient base64 decode — identical to `Buffer.from(s, \"base64\")`. Returns a\n *  plain `Uint8Array` view (a `Buffer` under Node). Never throws. */\nexport function base64Decode(s: string): Uint8Array {\n  const B = nodeBuffer()\n  return B ? B.from(s, \"base64\") : base64DecodePortable(s)\n}\n\nconst textEncoder = new TextEncoder()\n// ignoreBOM: keep a leading U+FEFF in the output, as Buffer#toString does.\nconst textDecoder = new TextDecoder(\"utf-8\", { ignoreBOM: true })\n\nexport function utf8Encode(s: string): Uint8Array {\n  return textEncoder.encode(s)\n}\n\n/** UTF-8 decode, replacing invalid sequences with U+FFFD (like Buffer). */\nexport function utf8Decode(bytes: Uint8Array): string {\n  return textDecoder.decode(bytes)\n}\n","/**\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\nimport { base64Decode, base64Encode, utf8Encode } from \"./bytes.js\"\n\nexport const TUNNEL_VERSION = \"agentproto/tunnel/v1\" as const\n\n/**\n * The most raw bytes one frame may carry in its base64 payload (`body`,\n * `data`). Anything larger is split across frames (see `http_response_head` /\n * `http_response_chunk`, `http_request.bodyChunked` / `http_request_chunk`,\n * and `ws_message.more`).\n *\n * Why: the rendezvous closes any WebSocket message above its\n * `maxMessageBytes` (1 MiB by default, including the hosted broker). Over a\n * pairing, a payload is base64'd into the frame, and that frame is sealed\n * and base64'd again into the `e2e` envelope, so wire size ≈ 16/9 × raw. At\n * 256 KiB that is ~466 KiB per message: under 1 MiB with room for headers.\n */\nexport const MAX_FRAME_PAYLOAD_BYTES = 256 * 1024\n\n/** Split `bytes` into pieces of at most `max` bytes (views, no copies). One\n *  empty piece for an empty input. */\nexport function splitPayload(bytes: Uint8Array, max: number = MAX_FRAME_PAYLOAD_BYTES): Uint8Array[] {\n  if (bytes.length <= max) return [bytes]\n  const out: Uint8Array[] = []\n  for (let off = 0; off < bytes.length; off += max) out.push(bytes.subarray(off, off + max))\n  return out\n}\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  /** Initial PTY column width. Ignored when `pty` is false. Default 80. */\n  cols?: number\n  /** Initial PTY row height. Ignored when `pty` is false. Default 24. */\n  rows?: number\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  /** The body follows as `http_request_chunk` frames (the last with\n   *  `end: true`) instead of inline — for bodies above\n   *  `MAX_FRAME_PAYLOAD_BYTES`. `body` is then omitted. Only sent to a daemon\n   *  whose hello advertises `capabilities.httpRequestChunks`. */\n  bodyChunked?: boolean\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 * One piece of a chunked request body (HOST → DAEMON), after an\n * `http_request` with `bodyChunked: true` for the same `reqId`. The daemon\n * buffers the pieces and forwards the request upstream at `end: true`.\n */\nexport interface HttpRequestChunkFrame {\n  t: \"http_request_chunk\"\n  reqId: string\n  /** Base64 bytes (at most `MAX_FRAME_PAYLOAD_BYTES` raw). */\n  data?: string\n  /** Set on the last piece. */\n  end?: boolean\n}\n\n/**\n * Cancel an in-flight `http_request` (HOST → DAEMON) — the host's consumer\n * aborted (an `AbortSignal`, a cancelled response stream, a closed\n * EventSource). The daemon aborts its upstream call (and stops reading a\n * streamed body) and sends nothing further for `reqId`; the host has already\n * forgotten it, so any frame still in flight for it is dropped there.\n *\n * Backward-compatible: a daemon that predates it drops the unknown frame\n * (`parseFrame` → null), and the request simply runs to completion as before.\n */\nexport interface HttpCancelFrame {\n  t: \"http_cancel\"\n  reqId: string\n}\n\n/**\n * Generic HTTP-over-tunnel response (DAEMON → HOST). Mirrors\n * `HttpRequestFrame` — see its docs.\n *\n * Two-frame protocol:\n *   - SHORT RESPONSES (most HTTP): daemon emits a single `http_response`\n *     with body inline. The buffered `forwardHttp` resolves directly\n *     from this frame.\n *   - STREAMED RESPONSES (text/event-stream, long-lived chunked\n *     transfers, large downloads): daemon emits `http_response_head`\n *     first with status+headers, then one or more `http_response_chunk`\n *     frames, the last of which has `end: true`. The streaming\n *     `forwardHttpStream` builds a `ReadableStream` from the chunks.\n *\n * Hosts that only support the legacy buffered API can detect the\n * stream protocol by looking for `http_response_head` and either\n * pull the chunks into a buffer (compat) or refuse to handle.\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/**\n * Streaming HTTP response head (DAEMON → HOST). Sent ONCE per\n * streaming response, before any chunk frames. Carries status +\n * headers so the host can finalize the response shape (e.g. write\n * Content-Type) before any body bytes arrive.\n */\nexport interface HttpResponseHeadFrame {\n  t: \"http_response_head\"\n  reqId: string\n  status: number\n  headers?: Readonly<Record<string, string>>\n}\n\n/**\n * Streaming HTTP response body chunk (DAEMON → HOST). Each chunk\n * carries some bytes of the response body. The terminal chunk has\n * `end: true` and MAY include final bytes. An immediate `end: true`\n * with no data signals empty-body close.\n *\n * `error` set on a chunk frame indicates the daemon's upstream\n * failed mid-stream; host SHOULD propagate it as a stream error.\n */\nexport interface HttpResponseChunkFrame {\n  t: \"http_response_chunk\"\n  reqId: string\n  /** Base64-encoded chunk bytes. Omitted on pure end-of-stream\n   *  markers (those carry `end: true` only). */\n  data?: string\n  /** Set on the last chunk. After this frame the host SHOULD ignore\n   *  any further frames for this reqId. */\n  end?: boolean\n  /** Set when the upstream failed mid-stream. Host surfaces this as\n   *  a stream error and closes the readable. */\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    /** Daemon can bridge browser-WS upgrades to its local upstream via\n     *  `ws_open` / `ws_message` / `ws_close`. Hosts MUST gate\n     *  `forwardWebSocket()` on this — older daemons ignore the frames. */\n    wsForward?: boolean\n    /** Daemon accepts `http_request.bodyChunked` + `http_request_chunk`.\n     *  Hosts send large bodies inline to daemons without it. */\n    httpRequestChunks?: boolean\n    /** Daemon reassembles fragmented host→daemon `ws_message`s (`more`).\n     *  Hosts send large WS messages whole to daemons without it. */\n    wsFragments?: boolean\n    /**\n     * Identifiers of the capabilities the daemon serves — its registered\n     * agent adapters / tool surfaces. Lets a host that fronts several\n     * daemons enumerate and route by what each one can do, without a\n     * round-trip. Omitted by older daemons (host treats as \"unknown\",\n     * not \"none\"). Advisory: the authoritative surface is still the\n     * daemon's live `tools/list` over the HTTP relay.\n     */\n    tools?: ReadonlyArray<string>\n    /**\n     * Set when this daemon's tunnel channel is E2E-encrypted (design:\n     * tunnel-e2e/v1). Informational confirmation only — this `hello` is\n     * itself carried INSIDE the already-established encrypted channel (the\n     * `wrapE2E` box), so a host that decrypts it has, by definition, already\n     * completed the token-authenticated handshake. The negotiation that turns\n     * a channel encrypted happens BEFORE this frame, over `e2e_handshake`\n     * frames on the raw sink — not by advertising a capability here. A host\n     * may surface this flag in its UI (\"this daemon is end-to-end encrypted\").\n     * Absent ⇒ plaintext tunnel (today's behaviour). See ./tunnel-e2e.ts.\n     */\n    e2e?: 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/**\n * HOST → DAEMON. Graceful drain signal sent during host rollover (e.g.\n * Kubernetes preStop). On receipt the daemon SHOULD:\n *   1. Stop accepting new in-flight work where it can.\n *   2. Close the WS cleanly (does not need to wait — host will follow\n *      up with a `close(1012, \"service_restart\")` ~2 s later as a\n *      hard backstop).\n *   3. Reconnect IMMEDIATELY without the usual exponential backoff —\n *      the host is mid-deploy, the new replica is already listening.\n *\n * Backward-compatible: daemons that don't know the frame just ignore\n * it (parseFrame returns null for unknown types). The host's close\n * then triggers the daemon's normal backoff loop — worst case ~30 s\n * reconnect gap instead of ~2 s with the handler.\n */\nexport interface ReconnectSoonFrame {\n  t: \"reconnect_soon\"\n  /** Hint of how long the host plans to wait before forcing close.\n   *  Optional — daemons MAY use it to decide whether to finish\n   *  in-flight work or close immediately. */\n  reasonMs?: number\n}\n\n// ─── WebSocket forwarding ───────────────────────────────────────\n//\n// Mirrors the HTTP-forwarding shape but for full-duplex upgrades.\n// The daemon opens a WS connection to its local upstream (typically\n// `ws://127.0.0.1:<port>${path}`), then proxies frames in both\n// directions. Each open is correlated by `reqId`.\n//\n// Capabilities: daemons MUST set `hello.capabilities.wsForward = true`\n// to opt in. Hosts that don't see it MUST NOT send `ws_open`. Older\n// daemons silently ignore unknown frames, so misrouted `ws_open` on a\n// non-capable daemon is benign (the host's open will time out).\n\n/**\n * HOST → DAEMON. Open a WS connection to the daemon's upstream.\n * Path semantics match `HttpRequestFrame` — absolute URLs rejected,\n * daemon prepends its `ws://<upstream>` base. Headers forwarded as-is\n * (cookies are typically NOT forwarded since the daemon's upstream is\n * loopback; cookie passthrough is opt-in via daemon config).\n */\nexport interface WsOpenFrame {\n  t: \"ws_open\"\n  /** Correlates every WS frame in both directions for this connection. */\n  reqId: string\n  /** Path + querystring on the daemon-local upstream, e.g.\n   *  `/sessions/abc/pty?cols=80&rows=24`. */\n  path: string\n  /** Optional registered upstream to dial instead of the daemon's\n   *  default gateway — a NAME the daemon resolves to a base URL (never\n   *  a URL itself, so the host can't point the daemon at an arbitrary\n   *  origin). Used to reach an imported local service (e.g. a browser\n   *  capability server) by alias. Absent ⇒ the daemon's own gateway. */\n  upstream?: string\n  /** Headers to set on the upstream WS request. Hop-by-hop headers\n   *  (Connection, Upgrade, Sec-WebSocket-*) MUST be stripped — the\n   *  daemon's `ws` client manages those. */\n  headers?: Readonly<Record<string, string>>\n  /** Subprotocols requested by the browser; forwarded to upstream. */\n  protocols?: readonly string[]\n  /** The host reassembles fragmented `ws_message`s (`more`), so the daemon may\n   *  split large upstream messages for this connection. Older hosts omit it\n   *  and get every message whole. */\n  fragments?: boolean\n}\n\n/**\n * DAEMON → HOST. Result of a `ws_open`. On success the upstream WS is\n * connected and frames can flow. On failure `error` is set; no further\n * frames for this `reqId` will arrive.\n */\nexport interface WsOpenAckFrame {\n  t: \"ws_open_ack\"\n  reqId: string\n  /** HTTP status from the upstream upgrade. 101 on success; 4xx/5xx\n   *  when the upstream refused the upgrade. */\n  status: number\n  /** Selected subprotocol, if any. */\n  protocol?: string\n  /** Set when the daemon couldn't open the upstream (DNS failure,\n   *  refused, timeout). Distinct from a clean upstream-side close,\n   *  which arrives as `ws_close`. */\n  error?: Readonly<{\n    code: string\n    message: string\n  }>\n}\n\n/**\n * Either direction. One application-level WS frame's payload. Text\n * frames carry UTF-8 bytes (`binary: false`); binary frames carry\n * arbitrary bytes (`binary: true`). The `data` field is base64-encoded\n * either way so the transport stays JSON.\n */\nexport interface WsMessageFrame {\n  t: \"ws_message\"\n  reqId: string\n  /** Base64-encoded WS frame payload. */\n  data: string\n  /** True for binary frames; false (or omitted) for text. */\n  binary?: boolean\n  /** Not the last fragment of this WS message: the receiver buffers until a\n   *  frame without `more`, then delivers the whole message (with the last\n   *  fragment's `binary`). Only sent to a peer that negotiated fragments\n   *  (`ws_open.fragments` / `hello.capabilities.wsFragments`). */\n  more?: boolean\n}\n\n/**\n * Either direction. Close the bridged WS connection. Includes the\n * close code (1000 normal, 1006 abnormal, etc.) and optional reason.\n * After sending or receiving this frame, neither side will send\n * further frames for `reqId`.\n */\nexport interface WsCloseFrame {\n  t: \"ws_close\"\n  reqId: string\n  /** WebSocket close code. */\n  code: number\n  reason?: string\n}\n\n// ─── E2E pairing envelopes ──────────────────────────────────────\n//\n// When a tunnel runs over an end-to-end pairing (design: DESIGN §5), the\n// real tunnel/v1 frames above are AEAD-encrypted and carried inside these\n// two envelope frames — the rendezvous relaying the bytes sees only opaque\n// ciphertext. `wrapE2E` in ./e2e.ts produces and consumes them; the tunnel\n// client/server never see them, they only ever see decrypted inner frames.\n//\n// These are added to the frame union (and KNOWN_TYPES) so the transport\n// adapter — which drops any frame `parseFrame` doesn't recognise — forwards\n// them intact to the E2E layer. A tunnel peer that is NOT E2E-wrapped will\n// parse them (they're known types) but has no handler for them, so it ignores\n// them cleanly rather than misinterpreting the bytes as a real frame. The\n// wrapped side is the strict one: it rejects any inbound frame that is not an\n// `e2e` envelope (see `wrapE2E`), which is what prevents a downgrade to\n// plaintext.\n\n/**\n * Either direction. One AEAD-encrypted inner tunnel frame. `n` is the\n * sender's per-direction, strictly-monotonic frame counter — it is the basis\n * of the GCM nonce and lets the receiver detect replay (a repeated/older `n`),\n * reorder, or drops (a gap in `n`). `d` is base64(ciphertext ‖ 16-byte GCM\n * tag). The plaintext, once decrypted, is itself an `encodeFrame`'d\n * `TunnelFrame`.\n */\nexport interface E2eFrame {\n  t: \"e2e\"\n  n: number\n  d: string\n}\n\n/**\n * Either direction. An opaque handshake message exchanged BEFORE the channel\n * is wrapped. `d` is base64 of a transport-agnostic handshake message (the\n * `pair/v1` hello or reply, produced by `@agentproto/secrets/pairing`). The\n * E2E layer here treats it as opaque bytes handed to an injected driver, so\n * this package never depends on the crypto package.\n */\nexport interface E2eHandshakeFrame {\n  t: \"e2e_handshake\"\n  d: string\n}\n\n// ─── union + guards ─────────────────────────────────────────────\n\nexport type HostToDaemonFrame =\n  | SpawnFrame\n  | StdinFrame\n  | KillFrame\n  | ResizeFrame\n  | HttpRequestFrame\n  | HttpRequestChunkFrame\n  | HttpCancelFrame\n  | WsOpenFrame\n  | WsMessageFrame\n  | WsCloseFrame\n  | PingFrame\n  | PongFrame\n  | ErrorFrame\n  | ReconnectSoonFrame\n  | E2eFrame\n  | E2eHandshakeFrame\n\nexport type DaemonToHostFrame =\n  | HelloFrame\n  | SpawnedFrame\n  | StdoutFrame\n  | StderrFrame\n  | ExitFrame\n  | HttpResponseFrame\n  | HttpResponseHeadFrame\n  | HttpResponseChunkFrame\n  | WsOpenAckFrame\n  | WsMessageFrame\n  | WsCloseFrame\n  | PingFrame\n  | PongFrame\n  | ErrorFrame\n  | E2eFrame\n  | E2eHandshakeFrame\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_request_chunk\",\n  \"http_cancel\",\n  \"http_response\",\n  \"http_response_head\",\n  \"http_response_chunk\",\n  \"ws_open\",\n  \"ws_open_ack\",\n  \"ws_message\",\n  \"ws_close\",\n  \"ping\",\n  \"pong\",\n  \"error\",\n  \"hello\",\n  \"spawned\",\n  \"stdout\",\n  \"stderr\",\n  \"exit\",\n  \"reconnect_soon\",\n  \"e2e\",\n  \"e2e_handshake\",\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  return base64Encode(typeof bytes === \"string\" ? utf8Encode(bytes) : bytes)\n}\n\n/**\n * Decode a frame's base64 `data` field. Returns a `Uint8Array` (at runtime a\n * `Buffer` under Node); the `@agentproto/acp/tunnel` Node entry re-exports a\n * `Buffer`-typed wrapper for existing callers.\n */\nexport function decodeData(data: string): Uint8Array {\n  return base64Decode(data)\n}\n","/**\n * End-to-end AEAD wrapper for a `FrameSink` (design: DESIGN §5).\n *\n * `wrapE2E(inner, keys)` returns a `FrameSink` that is byte-for-byte\n * transparent to the tunnel client/server above it — they send and receive\n * ordinary `TunnelFrame`s — while everything crossing `inner` (the real\n * transport, typically a WebSocket spliced through an untrusted rendezvous) is\n * AES-256-GCM ciphertext wrapped in `e2e` envelope frames. The rendezvous sees\n * only opaque bytes, their sizes, and their timing.\n *\n * There is deliberately NO new application protocol: the existing\n * `agentproto/tunnel/v1` frames are reused verbatim, serialized with\n * `encodeFrame`, encrypted, and carried inside `e2e` envelopes. So\n * `createTunnelClient` / `createTunnelServer` work unchanged over a wrapped\n * sink.\n *\n * Nonce discipline (the security-critical part):\n *   - Two independent AES-256-GCM keys, one per direction (`sendKey` /\n *     `recvKey`), already assigned by the handshake — so a frame can never be\n *     reflected back and decrypt.\n *   - A per-direction, strictly-monotonic 64-bit counter `n` is the GCM nonce\n *     (never randomly chosen — GCM nonce reuse is catastrophic, and a counter\n *     under one key can never repeat). `n` is also bound as AEAD associated\n *     data, so it can't be edited without failing the tag.\n *   - The receiver requires `n` to be exactly the next expected value.\n *     Anything else — an older/repeated `n` (replay), a higher `n` (a dropped\n *     or reordered frame) — is a typed `E2eError` that closes the channel.\n *     The GCM tag itself catches any bit-flip inside the ciphertext.\n *\n * Async crypto, ordered delivery: the AES-GCM calls go through an `E2eAead`\n * (node:crypto or WebCrypto — ./aead.ts), which is Promise-based because\n * WebCrypto is. `send` stays a synchronous, fire-and-forget call: the counter\n * is assigned at call time, encryption starts immediately, and ciphertexts are\n * written to `inner` strictly in counter order. Inbound frames are checked\n * (envelope, counter) synchronously on arrival and delivered, after\n * decryption, strictly in arrival order. A graceful close — local or remote —\n * lets frames already in flight drain first; a security failure drops them.\n *\n * This module is browser-safe: it depends only on the `E2eAead` seam, the frame\n * codec and portable byte helpers — never on `node:*` or on the\n * crypto/handshake package. It consumes opaque key material and, for the\n * handshake-over-sink helpers, an opaque driver callback.\n */\n\nimport { webCryptoAead, type E2eAead } from \"./aead.js\"\nimport { base64Decode, base64Encode, utf8Decode, utf8Encode } from \"./bytes.js\"\nimport {\n  encodeFrame,\n  parseFrame,\n  type TunnelFrame,\n} from \"./frames.js\"\nimport type { FrameSink } from \"./transport.js\"\n\n/** The two symmetric session keys, already role-adjusted by the handshake\n *  (this side's `sendKey` is the peer's `recvKey`). Each is 32 bytes for\n *  AES-256-GCM. */\nexport interface E2eKeys {\n  sendKey: Uint8Array\n  recvKey: Uint8Array\n}\n\n/** Stable failure codes for the AEAD channel. */\nexport type E2eErrorCode =\n  | \"replay\" // an inbound `n` we have already passed — a replayed/duplicated frame\n  | \"reorder\" // an inbound `n` ahead of the next expected — a dropped or reordered frame\n  | \"auth\" // GCM tag mismatch — the ciphertext or its bound counter was altered\n  | \"decode\" // the envelope, or the decrypted plaintext, was not a valid frame\n  | \"not_e2e\" // an inbound frame that is not an `e2e` envelope — a downgrade attempt\n  | \"overflow\" // the send counter reached the rekey limit (2^32); rekey required\n\n/** Raised for every E2E channel failure. Surfaced via `onSecurityError` and as\n *  the `onClose` reason — the channel always closes on any of these, so no\n *  tampered or out-of-order frame is ever delivered to the tunnel above. */\nexport class E2eError extends Error {\n  readonly code: E2eErrorCode\n  constructor(code: E2eErrorCode, message: string) {\n    super(message)\n    this.name = \"E2eError\"\n    this.code = code\n  }\n}\n\nexport interface WrapE2EOptions {\n  /**\n   * Called once, with the typed error, the first time an inbound frame fails\n   * an AEAD or counter check (or a send overflows). The channel is closed\n   * immediately afterwards. Optional — the same reason is also delivered to\n   * `onClose` listeners, so consumers that only care about closure can ignore\n   * this.\n   */\n  onSecurityError?: (err: E2eError) => void\n  /**\n   * Frame count at which sends must stop and rekey. Defaults to 2^32 — the\n   * point at which a 64-bit-counter/one-key regime should rotate keys. v1 has\n   * no rekey, so it errors here instead of ever reusing a nonce. Unreachable\n   * in practice; the guard exists so it can never be violated.\n   */\n  maxFrames?: number\n  /**\n   * The AES-256-GCM implementation. Defaults to WebCrypto here; the\n   * `@agentproto/acp/tunnel` Node entry defaults it to `node:crypto`. Both\n   * produce identical bytes.\n   */\n  aead?: E2eAead\n}\n\n/** A wrapped sink also exposes its direction counters, so a later phase can\n *  trigger a rekey as they approach the limit. */\nexport interface E2eFrameSink extends FrameSink {\n  /** Number of frames this side has accepted for sending (each one has been\n   *  assigned its counter; encryption and the write to `inner` follow in\n   *  order). */\n  readonly sentCount: number\n  /** Number of frames this side has decrypted and delivered. */\n  readonly recvCount: number\n}\n\n/** Default rekey ceiling: 2^32 frames per direction. */\nexport const DEFAULT_E2E_MAX_FRAMES = 0x1_0000_0000\n\nconst TAG_LEN = 16\nconst NONCE_LEN = 12\n\n/** GCM nonce for counter `n`: 12 bytes, the 64-bit big-endian counter in the\n *  low 8 bytes (high 4 bytes zero). Direction separation comes from the key,\n *  not the nonce, so a per-direction counter never collides with itself. */\nfunction nonceFor(n: number): Uint8Array {\n  const nonce = new Uint8Array(NONCE_LEN)\n  new DataView(nonce.buffer).setBigUint64(NONCE_LEN - 8, BigInt(n))\n  return nonce\n}\n\n/** Associated data binding the counter into the tag: the 64-bit BE counter. */\nfunction aadFor(n: number): Uint8Array {\n  const aad = new Uint8Array(8)\n  new DataView(aad.buffer).setBigUint64(0, BigInt(n))\n  return aad\n}\n\n/** Re-throw outside the promise chain, so a throwing listener surfaces as an\n *  uncaught error (as it did when delivery was synchronous) without stalling\n *  the ordered queue behind it. */\nfunction rethrowAsync(err: unknown): void {\n  queueMicrotask(() => {\n    throw err\n  })\n}\n\n/**\n * Wrap `inner` so the tunnel above it exchanges plaintext `TunnelFrame`s while\n * the wire carries AEAD ciphertext. Transparent: pass the result anywhere a\n * `FrameSink` is expected.\n */\nexport function wrapE2E(\n  inner: FrameSink,\n  keys: E2eKeys,\n  opts: WrapE2EOptions = {}\n): E2eFrameSink {\n  const maxFrames = opts.maxFrames ?? DEFAULT_E2E_MAX_FRAMES\n  const aead = opts.aead ?? webCryptoAead\n  // Private copies: the caller's buffers can't be mutated under us, and a\n  // stable identity lets the WebCrypto AEAD cache its imported keys.\n  const sendKey = Uint8Array.from(keys.sendKey)\n  const recvKey = Uint8Array.from(keys.recvKey)\n  const frameHandlers = new Set<(frame: TunnelFrame) => void>()\n  const closeHandlers = new Set<(reason?: string) => void>()\n\n  let sentCount = 0\n  let recvExpected = 0\n  let recvCount = 0\n  /** Accepting sends (and, until a local close/failure, delivering). */\n  let open = inner.isOpen\n  let failed = false\n  let locallyClosed = false\n  /** Set on the first inbound violation: nothing after it is looked at. */\n  let recvPoisoned = false\n  let sendBlocked = false\n  let closeNotified = false\n  /** Ordered pipelines: each step waits for the previous one. */\n  let sendChain: Promise<void> = Promise.resolve()\n  let recvChain: Promise<void> = Promise.resolve()\n  /** Frames decrypted before anyone subscribed. Held for the first subscriber\n   *  only — so a caller that attaches right after an `await` misses nothing. */\n  let everSubscribed = false\n  const early: TunnelFrame[] = []\n\n  const notifyClose = (reason?: string): void => {\n    if (closeNotified) return\n    closeNotified = true\n    for (const h of closeHandlers) h(reason)\n    frameHandlers.clear()\n    closeHandlers.clear()\n  }\n\n  /** Fail the channel: fire the typed error, close the transport, notify\n   *  close listeners. Idempotent — the first failure wins. In-flight frames in\n   *  either direction are dropped. */\n  const fail = (err: E2eError): void => {\n    if (failed) return\n    failed = true\n    const wasOpen = open\n    open = false\n    opts.onSecurityError?.(err)\n    if (wasOpen) inner.close(err.message)\n    notifyClose(err.message)\n  }\n\n  /** Fail on an inbound violation detected on arrival — but in order, after\n   *  the frames accepted before it have been delivered, exactly as when\n   *  delivery was synchronous. Nothing arriving after it is processed. */\n  const failInOrder = (err: E2eError): void => {\n    recvPoisoned = true\n    recvChain = recvChain.then(() => fail(err))\n  }\n\n  const deliver = (frame: TunnelFrame): void => {\n    if (!everSubscribed) {\n      early.push(frame)\n      return\n    }\n    for (const h of frameHandlers) h(frame)\n  }\n\n  const onInner = (frame: TunnelFrame): void => {\n    if (failed || locallyClosed || recvPoisoned) return\n    if (frame.t !== \"e2e\") {\n      // A wrapped endpoint must never accept a plaintext (or handshake) frame\n      // once the channel is live — that would be a downgrade. Fail closed.\n      failInOrder(\n        new E2eError(\n          \"not_e2e\",\n          `expected an e2e envelope, received a plaintext \"${frame.t}\" frame`\n        )\n      )\n      return\n    }\n\n    const n = frame.n\n    if (typeof n !== \"number\" || !Number.isInteger(n) || n < 0) {\n      failInOrder(new E2eError(\"decode\", \"e2e envelope has an invalid counter\"))\n      return\n    }\n    if (n < recvExpected) {\n      failInOrder(new E2eError(\"replay\", `replayed frame: counter ${n} < expected ${recvExpected}`))\n      return\n    }\n    if (n > recvExpected) {\n      failInOrder(new E2eError(\"reorder\", `out-of-order or dropped frame: counter ${n} > expected ${recvExpected}`))\n      return\n    }\n    if (typeof frame.d !== \"string\") {\n      failInOrder(new E2eError(\"decode\", \"e2e envelope payload is not a string\"))\n      return\n    }\n    const buf = base64Decode(frame.d)\n    if (buf.length < TAG_LEN) {\n      failInOrder(new E2eError(\"decode\", \"e2e envelope payload is too short to hold a tag\"))\n      return\n    }\n\n    // The counter is checked on arrival, in arrival order, so it advances\n    // here. That can't be abused to skip a frame: if this frame then fails\n    // authentication the whole channel fails closed, and nothing after it is\n    // ever delivered (the failure is sequenced in the same queue).\n    recvExpected = n + 1\n\n    const opened = aead.aesGcmDecrypt(recvKey, nonceFor(n), buf, aadFor(n))\n    opened.catch(() => {}) // observed in order below; don't flag it unhandled meanwhile\n    recvChain = recvChain\n      .then(async () => {\n        let plaintext: Uint8Array\n        try {\n          plaintext = await opened\n        } catch {\n          fail(new E2eError(\"auth\", `frame ${n} failed authentication — tampered ciphertext or wrong key`))\n          return\n        }\n        if (failed || locallyClosed) return\n        recvCount += 1\n\n        const decoded = parseFrame(utf8Decode(plaintext))\n        if (!decoded) {\n          // AEAD guarantees integrity, so this means an authenticated-but-garbage\n          // payload — only reachable via a bug on the far side. Fail closed rather\n          // than hand undefined up to the tunnel.\n          fail(new E2eError(\"decode\", \"decrypted payload was not a valid tunnel frame\"))\n          return\n        }\n        deliver(decoded)\n      })\n      .catch(rethrowAsync)\n  }\n\n  const unsubInner = inner.onFrame(onInner)\n  const unsubClose = inner.onClose(reason => {\n    if (!open) return\n    open = false\n    // The peer hung up: deliver what already arrived, then report the close.\n    recvChain = recvChain.then(() => notifyClose(reason))\n  })\n\n  return {\n    get isOpen() {\n      return open\n    },\n    get sentCount() {\n      return sentCount\n    },\n    get recvCount() {\n      return recvCount\n    },\n    send(frame) {\n      if (!open || failed || sendBlocked) return\n      if (sentCount >= maxFrames) {\n        // Let the frames already queued go out, then fail — never reuse a nonce.\n        sendBlocked = true\n        const err = new E2eError(\n          \"overflow\",\n          `send counter reached the rekey limit (${maxFrames}); a rekey is required`\n        )\n        sendChain = sendChain.then(() => fail(err))\n        return\n      }\n      const n = sentCount\n      sentCount += 1\n      const sealed = aead.aesGcmEncrypt(sendKey, nonceFor(n), utf8Encode(encodeFrame(frame)), aadFor(n))\n      sealed.catch(() => {}) // observed in order below\n      sendChain = sendChain\n        .then(async () => {\n          let ct: Uint8Array\n          try {\n            ct = await sealed\n          } catch {\n            fail(new E2eError(\"auth\", `frame ${n} could not be encrypted`))\n            return\n          }\n          if (failed) return\n          inner.send({ t: \"e2e\", n, d: base64Encode(ct) })\n        })\n        .catch(rethrowAsync)\n    },\n    close(reason) {\n      unsubInner()\n      unsubClose()\n      locallyClosed = true\n      if (!open) return\n      open = false\n      notifyClose(reason)\n      // Frames already handed to `send` still go out before the transport closes.\n      sendChain = sendChain.then(() => inner.close(reason))\n    },\n    onFrame(handler) {\n      frameHandlers.add(handler)\n      if (!everSubscribed) {\n        everSubscribed = true\n        for (const f of early.splice(0)) handler(f)\n      }\n      return () => frameHandlers.delete(handler)\n    },\n    onClose(handler) {\n      closeHandlers.add(handler)\n      return () => closeHandlers.delete(handler)\n    },\n  }\n}\n\n// ─── handshake over a raw sink ──────────────────────────────────\n//\n// A tiny driver-based helper that runs the two-message `pair/v1` handshake\n// over a raw `FrameSink` using `e2e_handshake` envelopes, then hands back a\n// `wrapE2E`-wrapped sink. The crypto lives entirely in the injected callbacks\n// (backed by `@agentproto/secrets/pairing`), so this stays crypto-agnostic.\n\nexport interface HandshakeOverSinkOptions {\n  /** How long to wait for the peer's handshake message before failing.\n   *  Default 10s. */\n  timeoutMs?: number\n  /** Forwarded to `wrapE2E`. */\n  wrap?: WrapE2EOptions\n  /**\n   * Optional diagnostic sink for the POST-HANDSHAKE close (BOOTSTRAP P4 item\n   * 2, field evidence 2026-09-30): the observed E2E flaps break exactly here\n   * — the Noise handshake completes, the reply is sent, and THEN the remote\n   * hangs up. Without this hook the close reason dies inside the returned\n   * sink unless the caller remembers to subscribe, and a caller that races\n   * (or the pre-subscription close gap) loses it entirely. Fired at most\n   * once per handshake, with the remote's close reason when it gave one.\n   * `daemonHandshakeOverSink` fires it for the daemon side — the field\n   * evidence's exact hook (the reason was captured here and discarded).\n   */\n  log?: (line: string) => void\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000\n\n/**\n * A view of `inner` that holds frames arriving while it has no subscriber and\n * replays them, in order, to the next one. The handshake helpers read the\n * peer's handshake message, then spend a moment (async key derivation) before\n * `wrapE2E` subscribes; a peer that starts talking the instant its side is\n * wrapped must not lose those first frames to that gap. Transports like a raw\n * WebSocket don't buffer on their own.\n */\nexport function holdFrames(inner: FrameSink): FrameSink {\n  const handlers = new Set<(frame: TunnelFrame) => void>()\n  const held: TunnelFrame[] = []\n  inner.onFrame(frame => {\n    if (handlers.size === 0) {\n      held.push(frame)\n      return\n    }\n    for (const h of [...handlers]) h(frame)\n  })\n  return {\n    get isOpen() {\n      return inner.isOpen\n    },\n    send(frame) {\n      inner.send(frame)\n    },\n    close(reason) {\n      inner.close(reason)\n    },\n    onFrame(handler) {\n      handlers.add(handler)\n      while (held.length > 0 && handlers.has(handler)) handler(held.shift()!)\n      return () => handlers.delete(handler)\n    },\n    onClose(handler) {\n      return inner.onClose(handler)\n    },\n  }\n}\n\n/**\n * Wait for a single `e2e_handshake` frame on `sink`, returning its decoded\n * payload bytes. Rejects on timeout or if the transport closes first. Any\n * other frame type arriving mid-handshake is a protocol violation and rejects\n * — the channel is not yet wrapped, so a stray plaintext frame is illegitimate.\n */\nfunction awaitHandshakeFrame(\n  sink: FrameSink,\n  timeoutMs: number\n): Promise<Uint8Array> {\n  return new Promise((resolve, reject) => {\n    let settled = false\n    let unsubFrame = (): void => {}\n    let unsubClose = (): void => {}\n    const finish = (fn: () => void): void => {\n      if (settled) return\n      settled = true\n      clearTimeout(timer)\n      unsubFrame()\n      unsubClose()\n      fn()\n    }\n    const timer = setTimeout(\n      () => finish(() => reject(new E2eError(\"decode\", \"handshake timed out\"))),\n      timeoutMs\n    )\n    unsubFrame = sink.onFrame(frame => {\n      if (frame.t !== \"e2e_handshake\") {\n        finish(() =>\n          reject(\n            new E2eError(\n              \"not_e2e\",\n              `expected an e2e_handshake frame, received \"${frame.t}\"`\n            )\n          )\n        )\n        return\n      }\n      finish(() => resolve(base64Decode(frame.d)))\n    })\n    if (settled) unsubFrame()\n    unsubClose = sink.onClose(reason =>\n      finish(() =>\n        reject(new E2eError(\"decode\", `transport closed during handshake: ${reason ?? \"unknown\"}`))\n      )\n    )\n  })\n}\n\n/**\n * Client side of the handshake-over-sink. Sends the `hello` bytes, awaits the\n * daemon's single reply, runs `deriveKeys` (which verifies the reply and yields\n * the session keys; it may be async), and returns a wrapped sink. Rejects —\n * closing the sink — if `deriveKeys` throws (e.g. a bad daemon signature).\n * Frames the daemon sends while the keys are being derived are held and\n * delivered once wrapped.\n */\nexport async function clientHandshakeOverSink(\n  sink: FrameSink,\n  hello: Uint8Array,\n  deriveKeys: (reply: Uint8Array) => E2eKeys | Promise<E2eKeys>,\n  opts: HandshakeOverSinkOptions = {}\n): Promise<E2eFrameSink> {\n  const timeoutMs = opts.timeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS\n  const view = holdFrames(sink)\n  const replyPromise = awaitHandshakeFrame(view, timeoutMs)\n  view.send({ t: \"e2e_handshake\", d: base64Encode(hello) })\n  const reply = await replyPromise\n  let keys: E2eKeys\n  try {\n    keys = await deriveKeys(reply)\n  } catch (err) {\n    view.close(\"handshake failed\")\n    throw err\n  }\n  return wrapE2E(view, keys, opts.wrap)\n}\n\n/**\n * Daemon side of the handshake-over-sink. Awaits the client `hello`, runs\n * `respond` (which validates it and yields the reply bytes + session keys; it\n * may be async), sends the reply, and returns a wrapped sink. Rejects —\n * closing the sink — if `respond` throws (e.g. a rejected offer token or\n * tampered hello).\n */\nexport async function daemonHandshakeOverSink(\n  sink: FrameSink,\n  respond: (\n    hello: Uint8Array\n  ) => { reply: Uint8Array; keys: E2eKeys } | Promise<{ reply: Uint8Array; keys: E2eKeys }>,\n  opts: HandshakeOverSinkOptions = {}\n): Promise<E2eFrameSink> {\n  const timeoutMs = opts.timeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS\n  const view = holdFrames(sink)\n  const hello = await awaitHandshakeFrame(view, timeoutMs)\n  let result: { reply: Uint8Array; keys: E2eKeys }\n  try {\n    result = await respond(hello)\n  } catch (err) {\n    view.close(\"handshake failed\")\n    throw err\n  }\n  view.send({ t: \"e2e_handshake\", d: base64Encode(result.reply) })\n  // The reply is out — from here to the caller's first read of the wrapped\n  // sink, a remote hang-up (a controller closing after a failed exchange, a\n  // flap) would otherwise be invisible: the reason only lived on the sink's\n  // own onClose. Surface it once, with the reason, if a log was given.\n  if (opts.log) {\n    const log = opts.log\n    let logged = false\n    view.onClose(reason => {\n      if (logged) return\n      logged = true\n      log(`daemon handshake channel closed by remote after reply: ${reason ?? \"unknown\"}`)\n    })\n  }\n  return wrapE2E(view, result.keys, opts.wrap)\n}\n","/**\n * Opt-in E2E negotiation for the reverse `serve --connect` tunnel\n * (design: tunnel-e2e/v1).\n *\n * The plaintext reverse tunnel lets a host terminate the WS and read every\n * `agentproto/tunnel/v1` frame. These helpers upgrade that channel to the\n * `wrapE2E` AEAD box using a short, token-authenticated ephemeral handshake, so\n * even a trusted host loses plaintext visibility — while staying fully\n * backward-compatible: with an old peer (or the feature off) the channel stays\n * exactly as it is today.\n *\n * ## Negotiation, and why it lives before the tunnel `hello`\n *\n * `createTunnelServer` emits the tunnel `hello` as its very first frame, and we\n * want that `hello` — and everything after it — INSIDE the encrypted box. So the\n * negotiation cannot ride on the `hello`; it runs first, on the raw sink, over\n * `e2e_handshake` frames:\n *\n * ```\n *   daemon → host:  e2e_handshake(offer)     // daemon advertises e2e + its ephemeral\n *   host   → daemon: e2e_handshake(accept)   // host advertises e2e + its ephemeral\n *   … both wrapE2E(sink) and speak tunnel/v1 encrypted from here …\n * ```\n *\n * The daemon (initiator) offers e2e ONLY when configured to; the host accepts\n * ONLY when it too supports it. If either doesn't, we fall back to plaintext:\n *   - **Old host**: never sends an `accept`. `e2e_handshake` is a known frame\n *     type with no handler on an unwrapped peer, so the daemon's offer is\n *     ignored, and after `timeoutMs` the daemon falls back to plaintext on the\n *     same still-open sink. One stray, ignored frame — behaviour is otherwise\n *     identical to today.\n *   - **Daemon with e2e off**: never sends an `offer`; it emits the plaintext\n *     `hello` first. An e2e-capable host detects a non-handshake first frame and\n *     falls back, re-injecting that frame into its plaintext client.\n *\n * ## Fail-closed vs. fall-back\n *\n * A silent peer (timeout) is a COMPATIBILITY signal → fall back to plaintext. A\n * peer that DID answer but whose handshake is invalid (a wrong `tunnel.token`, a\n * tampered message) is a SECURITY signal → the injected crypto throws and we\n * close the sink and propagate, never downgrading to plaintext. Distinguishing\n * these two is the whole point of returning `null` on timeout but throwing on a\n * bad handshake.\n *\n * This module is crypto-agnostic: all key material comes from injected callbacks\n * (backed by `@agentproto/secrets/pairing`'s `tunnel-handshake`). It depends only\n * on `wrapE2E` + the frame codec, so `@agentproto/acp` never imports the crypto\n * package — exactly as the pairing handshake-over-sink helpers do.\n */\n\nimport { base64Decode, base64Encode } from \"./bytes.js\"\nimport {\n  wrapE2E,\n  holdFrames,\n  E2eError,\n  type E2eKeys,\n  type E2eFrameSink,\n  type WrapE2EOptions,\n} from \"./e2e.js\"\nimport type { FrameSink } from \"./transport.js\"\nimport type { TunnelFrame } from \"./frames.js\"\n\n/** Default negotiation timeout: how long the daemon waits for the host's\n *  `accept` (or the host waits for the daemon's first frame) before concluding\n *  the peer does not support e2e and falling back to plaintext. */\nexport const DEFAULT_TUNNEL_E2E_TIMEOUT_MS = 8_000\n\nexport interface TunnelE2EOptions {\n  /** Negotiation timeout in ms. Default `DEFAULT_TUNNEL_E2E_TIMEOUT_MS`. */\n  timeoutMs?: number\n  /** Forwarded to `wrapE2E`. */\n  wrap?: WrapE2EOptions\n}\n\n/**\n * Await the first frame on `sink`. Resolves with the frame, or `null` on\n * timeout, and rejects (with the close reason) if the transport closes first.\n * Leaves the sink untouched on timeout so the caller can fall back on it.\n */\nfunction awaitFirstFrame(sink: FrameSink, timeoutMs: number): Promise<TunnelFrame | null> {\n  return new Promise((resolve, reject) => {\n    let settled = false\n    const finish = (fn: () => void): void => {\n      if (settled) return\n      settled = true\n      clearTimeout(timer)\n      unsubFrame()\n      unsubClose()\n      fn()\n    }\n    const timer = setTimeout(() => finish(() => resolve(null)), timeoutMs)\n    const unsubFrame = sink.onFrame(frame => finish(() => resolve(frame)))\n    const unsubClose = sink.onClose(reason =>\n      finish(() =>\n        reject(new E2eError(\"decode\", `transport closed during e2e negotiation: ${reason ?? \"unknown\"}`)),\n      ),\n    )\n  })\n}\n\n// ─── daemon (initiator) side ────────────────────────────────────\n\n/**\n * Daemon side of the tunnel-e2e negotiation. Sends the `offer` bytes as an\n * `e2e_handshake` frame, awaits the host's single reply, and:\n *\n *   - **reply arrives** → runs `deriveKeys` (which verifies the reply and yields\n *     the session keys; it may be async) and returns a `wrapE2E`-wrapped sink. If `deriveKeys`\n *     throws — a wrong `tunnel.token`, a tampered accept — this CLOSES the sink\n *     and rethrows: a security failure must NOT silently downgrade to plaintext.\n *   - **timeout (peer silent)** → returns `null`. The peer is an old/plaintext\n *     host; the sink is left open so the caller falls back to plaintext on it.\n *   - **a non-handshake frame arrives first** → protocol violation (a compliant\n *     old host stays silent until it receives the tunnel `hello`); closes the\n *     sink and throws.\n */\nexport async function connectSinkE2E(\n  sink: FrameSink,\n  offer: Uint8Array,\n  deriveKeys: (reply: Uint8Array) => E2eKeys | Promise<E2eKeys>,\n  opts: TunnelE2EOptions = {},\n): Promise<E2eFrameSink | null> {\n  const timeoutMs = opts.timeoutMs ?? DEFAULT_TUNNEL_E2E_TIMEOUT_MS\n  const firstPromise = awaitFirstFrame(sink, timeoutMs)\n  sink.send({ t: \"e2e_handshake\", d: base64Encode(offer) })\n  const first = await firstPromise\n  if (first === null) {\n    // Peer never answered — treat as \"does not support e2e\" and fall back.\n    return null\n  }\n  if (first.t !== \"e2e_handshake\") {\n    sink.close(\"e2e negotiation: unexpected frame\")\n    throw new E2eError(\n      \"not_e2e\",\n      `expected an e2e_handshake accept, received \"${first.t}\"`,\n    )\n  }\n  // Hold anything the host sends while the keys are derived (async).\n  const view = holdFrames(sink)\n  let keys: E2eKeys\n  try {\n    keys = await deriveKeys(base64Decode(first.d))\n  } catch (err) {\n    // Wrong token / tampered accept → fail closed, never downgrade.\n    sink.close(\"e2e handshake failed\")\n    throw err\n  }\n  return wrapE2E(view, keys, opts.wrap)\n}\n\n// ─── host (responder) side ──────────────────────────────────────\n\n/** Outcome of the host-side negotiation. On the plaintext branch, `sink` is a\n *  view of the original transport that will replay any already-read frame to the\n *  first subscriber — so the host can hand it straight to a plaintext tunnel\n *  client without losing the daemon's `hello`. */\nexport type AcceptSinkE2EResult =\n  | { e2e: true; sink: E2eFrameSink }\n  | { e2e: false; sink: FrameSink }\n\n/**\n * Host side of the tunnel-e2e negotiation. Awaits the daemon's first frame:\n *\n *   - **`e2e_handshake` offer** → runs `respond` (which verifies the offer and\n *     yields the reply bytes + session keys; it may be async), sends the reply, and returns a\n *     `wrapE2E`-wrapped sink. A `respond` throw — wrong token, tampered offer —\n *     closes the sink and rethrows (fail closed).\n *   - **any other frame** (the daemon emitted a plaintext `hello` — it isn't\n *     doing e2e) → returns `{ e2e: false }` with a sink that replays that frame,\n *     so the caller's plaintext client still sees it.\n *   - **timeout** → `{ e2e: false }` on the raw sink (nothing to replay).\n */\nexport async function acceptSinkE2E(\n  sink: FrameSink,\n  respond: (\n    offer: Uint8Array,\n  ) => { reply: Uint8Array; keys: E2eKeys } | Promise<{ reply: Uint8Array; keys: E2eKeys }>,\n  opts: TunnelE2EOptions = {},\n): Promise<AcceptSinkE2EResult> {\n  const timeoutMs = opts.timeoutMs ?? DEFAULT_TUNNEL_E2E_TIMEOUT_MS\n  const first = await awaitFirstFrame(sink, timeoutMs)\n  if (first === null) {\n    return { e2e: false, sink }\n  }\n  if (first.t !== \"e2e_handshake\") {\n    // Daemon isn't offering e2e — hand back a sink that re-delivers this frame.\n    return { e2e: false, sink: prependFrame(sink, first) }\n  }\n  const view = holdFrames(sink)\n  let result: { reply: Uint8Array; keys: E2eKeys }\n  try {\n    result = await respond(base64Decode(first.d))\n  } catch (err) {\n    sink.close(\"e2e handshake failed\")\n    throw err\n  }\n  sink.send({ t: \"e2e_handshake\", d: base64Encode(result.reply) })\n  return { e2e: true, sink: wrapE2E(view, result.keys, opts.wrap) }\n}\n\n/**\n * A thin `FrameSink` view of `inner` that re-delivers one already-consumed\n * `frame` to the FIRST `onFrame` subscriber before forwarding live frames. Used\n * on the host's plaintext-fallback path: the negotiation read the daemon's first\n * frame (its `hello`) off the raw sink, so it must be replayed to the plaintext\n * client that subscribes next. The replay runs BEFORE we subscribe to `inner`,\n * so a buffering transport's queued frames still arrive in order after it.\n */\nfunction prependFrame(inner: FrameSink, frame: TunnelFrame): FrameSink {\n  let replayed = false\n  return {\n    get isOpen() {\n      return inner.isOpen\n    },\n    send(f) {\n      inner.send(f)\n    },\n    close(reason) {\n      inner.close(reason)\n    },\n    onFrame(handler) {\n      if (!replayed) {\n        replayed = true\n        handler(frame)\n      }\n      return inner.onFrame(handler)\n    },\n    onClose(handler) {\n      return inner.onClose(handler)\n    },\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 { utf8Decode } from \"./bytes.js\"\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 | Uint8Array }) => 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 (a Uint8Array); the browser delivers\n  // string or ArrayBuffer. We coerce to string and parse — everything we send\n  // is JSON text, so non-text payloads are protocol violations and get\n  // dropped.\n  const onMessage = (ev: { data: string | ArrayBuffer | Uint8Array }): void => {\n    let text: string\n    if (typeof ev.data === \"string\") text = ev.data\n    else if (ev.data instanceof ArrayBuffer) text = utf8Decode(new Uint8Array(ev.data))\n    else text = utf8Decode(ev.data)\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"]}