@workerdeck/server 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#maxFileBytes","#maxSessionBytes","#bySession","#bySession","#options","#sessions","#options","#chains","#emit","#send","#deliver","#options","#bridge","#sessions","#options","#configs","#track","#forget","#park","#closed","#detachTimers","#owners","#settled","#queue","#storeOps","#resume","#clearTimer","#timers","#resuming","#rebuild","#records","resolvePath","profiles"],"sources":["../src/host-files.ts","../src/host-file-search.ts","../src/attachments.ts","../src/produced-files.ts","../src/registry.ts","../src/notifications.ts","../src/bridge.ts","../src/parking.ts","../src/session-store.ts","../src/server.ts","../src/profile-store.ts"],"sourcesContent":["import type { Dirent } from 'node:fs'\nimport {\n closeSync,\n constants,\n fstatSync,\n ftruncateSync,\n lstatSync,\n openSync,\n readFileSync,\n realpathSync,\n writeFileSync,\n} from 'node:fs'\nimport { basename, dirname, isAbsolute, join, relative, sep } from 'node:path'\n\n/**\n * Containment core for the operator's host-filesystem routes (`/v1/fs/*`).\n *\n * The routes are operator privilege — gated by the auth key, outside the agent\n * permission flow — but the trees they expose are written BY the agent, so every\n * requested path is adversarial: a session can plant `root/notes -> ~/.ssh` and\n * wait for the operator's phone to browse into it. That is why `cwdAllowed` in\n * server.ts (resolve + prefix compare) is not reused here: it vets an\n * operator-typed cwd, where a lexical check is enough; here the *route* a path\n * takes matters, so containment is decided only on the realpath'd form.\n *\n * Disclosure policy: every refusal that consulted the filesystem is a uniform\n * `404 'not found'` — outside every root, escaping via symlink, dangling link,\n * and genuinely absent are indistinguishable. Anything finer would let a planted\n * link turn this API into an existence probe for paths outside the roots (403\n * iff `~/.ssh/id_rsa` exists). 403 is reserved for verdicts that reveal nothing\n * beyond the roots: malformed requests, and in-root targets of the wrong kind.\n *\n * TOCTOU: resolve-then-open is a race by construction and no check at this\n * layer closes it — between a resolve and the caller's open, the agent can swap\n * a verified component for a symlink. What resolve guarantees is that the\n * *returned* path was canonical and contained at resolve time. Callers must\n * open exactly `outcome.path` (never the requested string), preferably through\n * {@link readContained}/{@link writeContained}, which pin the final component\n * with O_NOFOLLOW, defuse fifo/device swaps with O_NONBLOCK + fstat-before-io,\n * and truncate only after the fd is proven to be a regular file. A *parent*\n * directory swapped inside the window can still redirect the open — closing\n * that needs openat2(RESOLVE_BENEATH), which Node does not expose. The window\n * is microseconds wide and the exposure is accepted and documented rather than\n * pretended away.\n */\n\nexport type HostFileRoot = {\n /** The operator's spelling, kept for display (`GET /v1/fs/roots`). */\n readonly configured: string\n /** What containment is checked against — and the prefix `ResolveOutcome.path`\n * is guaranteed to carry, so relative display paths are computed from this. */\n readonly canonical: string\n}\n\nexport type HostFileRoots = { readonly roots: readonly HostFileRoot[] }\n\n/**\n * Built once at startup from operator config. Roots are canonicalized here\n * because resolution produces realpath'd targets: a root that is itself a\n * symlink (`/tmp` -> `/private/tmp` on macOS) would otherwise contain nothing.\n * A misdeclared root throws rather than silently guarding the wrong tree —\n * same stance as profile config dirs in server.ts. An empty list is legal and\n * refuses everything; \"no roots means allow all\" is `cwdAllowed`'s contract,\n * never this module's.\n */\nexport function createHostFileRoots(roots: string[]): HostFileRoots {\n return {\n roots: roots.map((configured) => {\n if (invalidRequest(configured)) {\n throw new Error(\n `createHostFileRoots: root must be an absolute path: ${JSON.stringify(configured)}`,\n )\n }\n let canonical: string\n try {\n canonical = realpathSync(configured)\n } catch {\n throw new Error(`createHostFileRoots: root does not exist: ${configured}`)\n }\n if (!lstatSync(canonical).isDirectory()) {\n throw new Error(`createHostFileRoots: root is not a directory: ${configured}`)\n }\n return { configured, canonical }\n }),\n }\n}\n\nexport type ResolveOutcome =\n | { ok: true; path: string; root: string; kind: 'file' | 'dir' }\n | { ok: false; status: 403 | 404; error: string }\n\ntype Refusal = { ok: false; status: 403 | 404; error: string }\n\nfunction refuse(status: 403 | 404, error: string): Refusal {\n return { ok: false, status, error }\n}\n\n/** The uniform filesystem refusal — see the disclosure policy in the header.\n * The string is deliberately constant: a distinct message is as much an oracle\n * as a distinct status. */\nfunction notFound(): Refusal {\n return refuse(404, 'not found')\n}\n\n/** NUL is rejected before any fs call — Node throws a TypeError on NUL paths,\n * and that must surface as a refusal, not a 500. Relative paths are refused\n * outright rather than resolved against a cwd this API never promised. */\nfunction invalidRequest(requested: string): boolean {\n return requested.length === 0 || requested.includes('\\0') || !isAbsolute(requested)\n}\n\n/** Both sides are realpath output, so this is a pure lexical question — but a\n * bare prefix check gets the boundary wrong (`/x/app` would swallow\n * `/x/application`). `relative` answers it exactly: inside iff the walk from\n * root to candidate is empty or never has to leave through `..`. */\nfunction contained(rootCanonical: string, candidate: string): boolean {\n const rel = relative(rootCanonical, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nfunction rootContaining(roots: HostFileRoots, canonical: string): HostFileRoot | undefined {\n return roots.roots.find((root) => contained(root.canonical, canonical))\n}\n\n/**\n * For read/list: the target must exist. realpath is handed the request whole —\n * no lexical `..` collapsing first, because `root/link/..` is lexically `root`\n * but physically the link target's parent, and only the physical answer is the\n * true one. Symlinks that canonicalize *inside* a root are followed and served:\n * containment is a property of the canonical target, not of the route to it —\n * the operator granted the whole subtree, so nothing new becomes reachable.\n */\nexport function resolveExisting(roots: HostFileRoots, requested: string): ResolveOutcome {\n if (invalidRequest(requested)) return refuse(403, 'invalid path')\n let canonical: string\n try {\n canonical = realpathSync(requested)\n } catch {\n // Absent, unreadable, looped, name-too-long — uniformly absent.\n return notFound()\n }\n const root = rootContaining(roots, canonical)\n if (!root) return notFound()\n let target\n try {\n // realpath output cannot name a symlink, so lstat === stat here modulo a\n // race; lstat anyway, so a swap inside the window classifies as neither\n // file nor dir and refuses.\n target = lstatSync(canonical)\n } catch {\n return notFound()\n }\n if (target.isFile()) return { ok: true, path: canonical, root: root.canonical, kind: 'file' }\n if (target.isDirectory()) return { ok: true, path: canonical, root: root.canonical, kind: 'dir' }\n // In-root fifo/device/socket: a 403 here reveals nothing beyond the roots\n // (the operator can list the entry anyway), and reading one would hang the\n // request or stream zeros forever.\n return refuse(403, 'not a regular file or directory')\n}\n\n/**\n * For write: the target may not exist, so realpath cannot be asked directly.\n * An existing target reuses read semantics — writing *through* a symlink that\n * canonicalizes inside a root is allowed (`root/link -> root/real.txt` edits\n * real.txt), same reasoning as {@link resolveExisting}. A missing target\n * canonicalizes its immediate parent and re-checks: only the final component\n * may be new, and anything already sitting there — in practice a dangling\n * symlink — is refused, because open(2) with O_CREAT follows it and would\n * create the file wherever it points. That refusal is `not found`, not 403: a\n * link to an existing outside file already answers 404 via the exists branch,\n * so a distinct status for the dangling case would hand back exactly the\n * existence bit the uniform 404 exists to withhold.\n */\nexport function resolveForWrite(roots: HostFileRoots, requested: string): ResolveOutcome {\n if (invalidRequest(requested)) return refuse(403, 'invalid path')\n try {\n const canonical = realpathSync(requested)\n const root = rootContaining(roots, canonical)\n if (!root) return notFound()\n const target = lstatSync(canonical)\n if (target.isDirectory()) return refuse(403, 'is a directory')\n if (!target.isFile()) return refuse(403, 'not a regular file')\n return { ok: true, path: canonical, root: root.canonical, kind: 'file' }\n } catch {\n // Target absent or unresolvable — fall through and try it as a create.\n }\n const base = basename(requested)\n if (base === '' || base === '.' || base === '..') return refuse(403, 'invalid path')\n let parent: string\n try {\n parent = realpathSync(dirname(requested))\n } catch {\n return notFound()\n }\n const root = rootContaining(roots, parent)\n if (!root) return notFound()\n try {\n // Parent exists but is not a directory (`root/file.txt/x`): no such\n // location, same answer as a missing parent.\n if (!lstatSync(parent).isDirectory()) return notFound()\n } catch {\n return notFound()\n }\n const path = join(parent, base)\n // basename() cannot smuggle a separator, so this re-check is redundant by\n // construction — kept because this is the line an escape would have to cross.\n if (!contained(root.canonical, path)) return notFound()\n try {\n lstatSync(path)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ok: true, path, root: root.canonical, kind: 'file' }\n }\n return notFound()\n }\n // Something IS at the final component even though realpath(requested) failed:\n // a dangling symlink, or a concurrent create. Refuse both — see the header.\n return notFound()\n}\n\nexport type HostEntryKind = 'file' | 'dir' | 'symlink' | 'other'\n\n/** lstat semantics on purpose: a listing shows a symlink AS a symlink — the\n * server never follows one while rendering a directory. Following happens only\n * when the entry is itself requested, through {@link resolveExisting}, which\n * refuses it if it escapes. `readdir(withFileTypes)` already answers without\n * following, so this is classification, not I/O. */\nexport function entryKind(entry: Dirent): HostEntryKind {\n if (entry.isSymbolicLink()) return 'symlink'\n if (entry.isFile()) return 'file'\n if (entry.isDirectory()) return 'dir'\n return 'other'\n}\n\n// win32 lacks O_NOFOLLOW/O_NONBLOCK (undefined at runtime despite the typing);\n// there they contribute 0 and the fstat gates below stand alone.\nconst O_NOFOLLOW: number = constants.O_NOFOLLOW ?? 0\nconst O_NONBLOCK: number = constants.O_NONBLOCK ?? 0\n\nexport type ReadOutcome = { ok: true; data: Buffer } | Refusal\n\n/**\n * The open half of the resolve→open discipline; pass `ResolveOutcome.path`,\n * never the requested string. O_NOFOLLOW turns a final component swapped for a\n * symlink inside the race window into ELOOP instead of a follow; O_NONBLOCK\n * makes a swapped-in fifo open instantly instead of parking the request until a\n * writer appears (it is inert for regular files); the fstat gate refuses\n * anything that is not a plain file before a byte is read — `/dev/zero` would\n * otherwise be an unbounded read.\n */\nexport function readContained(path: string): ReadOutcome {\n let fd: number\n try {\n fd = openSync(path, constants.O_RDONLY | O_NOFOLLOW | O_NONBLOCK)\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? notFound() : refuse(403, 'refused')\n }\n try {\n if (!fstatSync(fd).isFile()) return refuse(403, 'not a regular file')\n return { ok: true, data: readFileSync(fd) }\n } catch {\n return refuse(403, 'refused')\n } finally {\n closeSync(fd)\n }\n}\n\nexport type WriteOutcome = { ok: true } | Refusal\n\n/**\n * O_CREAT|O_NOFOLLOW refuses (ELOOP) a symlink planted at the final component\n * after resolve — the exact swap that would land the write at the link's\n * target. Truncation happens via ftruncate only AFTER the fd is proven to be a\n * regular file, so a swapped-in device or fifo is never truncated or written;\n * O_NONBLOCK turns the reader-less-fifo open from a hang into ENXIO.\n */\nexport function writeContained(path: string, data: string | Uint8Array): WriteOutcome {\n let fd: number\n try {\n fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | O_NOFOLLOW | O_NONBLOCK, 0o644)\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? notFound() : refuse(403, 'refused')\n }\n try {\n if (!fstatSync(fd).isFile()) return refuse(403, 'not a regular file')\n ftruncateSync(fd)\n writeFileSync(fd, data)\n return { ok: true }\n } catch {\n return refuse(403, 'refused')\n } finally {\n closeSync(fd)\n }\n}\n","import { readdirSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { entryKind } from './host-files.ts'\n\n/**\n * The recursive half of the host-file routes: what `@file` autocomplete needs and\n * `/fs/list` deliberately isn't. Listing answers \"what is in this directory\"; this\n * answers \"which file in this tree did you mean\", which is a different query and a\n * different cost model.\n *\n * Kept out of `host-files.ts` on purpose. That module is the audited containment\n * core; this one walks *inside* an already-resolved, already-contained directory\n * and never resolves a path of its own. Its one security-relevant rule is that it\n * does not follow symlinks — see the walk below.\n */\n\n/**\n * Directories a source tree keeps that nobody types `@` looking for, and that are\n * usually most of the entries on disk. Skipping them is what makes the walk cheap\n * enough to run per keystroke; the operator can replace the list via\n * `hostFiles.ignore`.\n */\nexport const DEFAULT_IGNORED_DIRS = [\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n '.next',\n '.nuxt',\n '.svelte-kit',\n '.turbo',\n '.cache',\n 'dist',\n 'build',\n 'out',\n 'target',\n '.venv',\n 'venv',\n '__pycache__',\n '.pytest_cache',\n '.gradle',\n 'Pods',\n 'DerivedData',\n // Swift Package Manager's build directory. Its object files carry the source\n // names, so without this a Swift project's `@` search answers with\n // `Transcript.o` before `Transcript.swift`.\n '.build',\n]\n\nexport type FoundFile = {\n /** Absolute path, for a follow-up read. */\n path: string\n /** Path relative to the searched directory — what a picker shows and inserts. */\n relative: string\n}\n\nexport type SearchResult = {\n matches: FoundFile[]\n /** More matched (or more of the tree existed) than was returned. */\n truncated: boolean\n}\n\nexport type SearchOptions = {\n /** Fuzzy needle. Empty returns the shallowest files, which is what a bare `@` wants. */\n query?: string\n /** Max results returned. */\n limit?: number\n /** Directory names not to descend into. Defaults to {@link DEFAULT_IGNORED_DIRS}. */\n ignore?: readonly string[]\n /** Hard bound on entries examined, so a walk can't be turned into a DoS by\n * pointing it at a huge tree. Reaching it truncates rather than erroring. */\n maxScanned?: number\n}\n\n/**\n * Breadth-first so shallow files rank first before scoring even runs — for a bare\n * `@` that ordering *is* the ranking, and for a query it breaks ties the way a\n * person expects (`src/index.ts` over `src/a/b/c/index.ts`).\n *\n * Symlinks are skipped outright, as files and as directories. As directories it is\n * the difference between a bounded walk and an unbounded one (a cycle, or a link\n * to `/`); as files it keeps this function's output within the tree it was handed,\n * so nothing it offers can be a path that `resolveExisting` would later refuse.\n * A tree that genuinely lives behind symlinks is not autocompletable — an accepted\n * cost for not having to re-derive containment here.\n */\nexport function searchFiles(base: string, options: SearchOptions = {}): SearchResult {\n const limit = options.limit ?? 50\n const maxScanned = options.maxScanned ?? 20_000\n const ignore = new Set(options.ignore ?? DEFAULT_IGNORED_DIRS)\n const needle = (options.query ?? '').toLowerCase()\n\n const found: { file: FoundFile; score: number; depth: number }[] = []\n const queue: { dir: string; depth: number }[] = [{ dir: base, depth: 0 }]\n let scanned = 0\n let exhausted = true\n\n while (queue.length > 0) {\n const { dir, depth } = queue.shift()!\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n // Unreadable directory — skip it rather than failing the whole search.\n continue\n }\n for (const entry of entries) {\n if (++scanned > maxScanned) {\n exhausted = false\n queue.length = 0\n break\n }\n const kind = entryKind(entry)\n if (kind === 'dir') {\n if (!ignore.has(entry.name)) queue.push({ dir: join(dir, entry.name), depth: depth + 1 })\n continue\n }\n if (kind !== 'file') continue\n const path = join(dir, entry.name)\n const rel = relative(base, path)\n const score = scoreMatch(rel, entry.name, needle)\n if (score !== null) found.push({ file: { path, relative: rel }, score, depth })\n }\n }\n\n found.sort(\n (a, b) =>\n b.score - a.score ||\n a.depth - b.depth ||\n a.file.relative.length - b.file.relative.length ||\n a.file.relative.localeCompare(b.file.relative),\n )\n return {\n matches: found.slice(0, limit).map((f) => f.file),\n truncated: !exhausted || found.length > limit,\n }\n}\n\n/**\n * Subsequence matching, like every `@`-picker worth using: `seslist` finds\n * `SessionListView.swift`. Returns null for no match.\n *\n * Scored so the two things people actually mean win — a hit in the filename beats\n * one buried in the directory path, and characters typed consecutively beat the\n * same characters scattered — rather than trying to be a ranking engine.\n */\nfunction scoreMatch(relativePath: string, name: string, needle: string): number | null {\n if (needle === '') return 0\n const inName = subsequenceScore(name.toLowerCase(), needle)\n if (inName !== null) return inName + 1000\n return subsequenceScore(relativePath.toLowerCase(), needle)\n}\n\nfunction subsequenceScore(haystack: string, needle: string): number | null {\n let score = 0\n let from = 0\n let previous = -2\n for (const char of needle) {\n const at = haystack.indexOf(char, from)\n if (at === -1) return null\n if (at === previous + 1) score += 8\n if (at === 0) score += 4\n from = at + 1\n previous = at\n }\n // Shorter haystacks matched the same needle more tightly.\n return score - haystack.length / 100\n}\n","import { randomUUID } from 'node:crypto'\nimport { attachmentKind, normalizeMediaType, type AttachmentInput } from '@workerdeck/core'\nimport type { MessageAttachment } from '@workerdeck/protocol'\n\nexport type AttachmentStoreOptions = {\n /** Largest single upload. Default 10 MiB. */\n maxFileBytes?: number\n /** Ceiling on everything one session is holding. Default 64 MiB. */\n maxSessionBytes?: number\n}\n\nexport type AttachmentRejection =\n | { code: 'too_large'; message: string }\n | { code: 'session_full'; message: string }\n | { code: 'unsupported_type'; message: string }\n | { code: 'empty'; message: string }\n\nexport type PutResult =\n | { ok: true; attachment: MessageAttachment }\n | { ok: false; error: AttachmentRejection }\n\nconst DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024\nconst DEFAULT_MAX_SESSION_BYTES = 64 * 1024 * 1024\n\n/**\n * Per-session hold for files the user attached to a message.\n *\n * In memory, and deliberately so. An attachment is only *needed* for the instant\n * between the upload and the message that names it; everything after that is\n * convenience (a client re-rendering a thumbnail after a reattach). That is the\n * same bargain `GET /sessions/:id/files` makes — the session's lifetime, no\n * durability tier — and it keeps the gateway from accumulating a photo library\n * on disk that nobody asked it to look after.\n *\n * Both caps are enforced here rather than at the route, so a host embedding the\n * server cannot forget one: a single file that is too big is a 413, and so is a\n * session whose total would go over.\n */\nexport class AttachmentStore {\n #bySession = new Map<string, Map<string, AttachmentInput>>()\n #maxFileBytes: number\n #maxSessionBytes: number\n\n constructor(options: AttachmentStoreOptions = {}) {\n this.#maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES\n this.#maxSessionBytes = options.maxSessionBytes ?? DEFAULT_MAX_SESSION_BYTES\n }\n\n get maxFileBytes(): number {\n return this.#maxFileBytes\n }\n\n put(sessionId: string, name: string, mediaType: string, body: Buffer): PutResult {\n if (body.length === 0) {\n return { ok: false, error: { code: 'empty', message: 'attachment is empty' } }\n }\n if (body.length > this.#maxFileBytes) {\n return {\n ok: false,\n error: {\n code: 'too_large',\n message: `attachment is larger than the ${this.#maxFileBytes}-byte limit`,\n },\n }\n }\n const type = normalizeMediaType(mediaType)\n if (!attachmentKind(type)) {\n return {\n ok: false,\n error: { code: 'unsupported_type', message: `unsupported media type: ${type}` },\n }\n }\n const held = this.#bySession.get(sessionId) ?? new Map<string, AttachmentInput>()\n const heldBytes = [...held.values()].reduce((sum, a) => sum + a.bytes, 0)\n if (heldBytes + body.length > this.#maxSessionBytes) {\n return {\n ok: false,\n error: {\n code: 'session_full',\n message: `session is already holding ${heldBytes} bytes of attachments (limit ${this.#maxSessionBytes})`,\n },\n }\n }\n const attachment: AttachmentInput = {\n id: randomUUID(),\n name: safeName(name),\n mediaType: type,\n bytes: body.length,\n data: body.toString('base64'),\n }\n held.set(attachment.id, attachment)\n this.#bySession.set(sessionId, held)\n return { ok: true, attachment: ref(attachment) }\n }\n\n /** The stored record, bytes included — for the download route and for the send\n * path that turns ids into content blocks. */\n get(sessionId: string, id: string): AttachmentInput | undefined {\n return this.#bySession.get(sessionId)?.get(id)\n }\n\n /**\n * Resolve the ids a `user_message` named, in the order given.\n *\n * Missing ids are reported rather than skipped: a message that quietly lost its\n * picture reads as the model ignoring it, which is a far worse failure than a\n * command that errors.\n */\n resolve(sessionId: string, ids: readonly string[]): { ok: true; attachments: AttachmentInput[] } | { ok: false; missing: string[] } {\n const held = this.#bySession.get(sessionId)\n const attachments: AttachmentInput[] = []\n const missing: string[] = []\n for (const id of ids) {\n const found = held?.get(id)\n if (found) attachments.push(found)\n else missing.push(id)\n }\n return missing.length ? { ok: false, missing } : { ok: true, attachments }\n }\n\n drop(sessionId: string): void {\n this.#bySession.delete(sessionId)\n }\n}\n\nfunction ref(attachment: AttachmentInput): MessageAttachment {\n return {\n id: attachment.id,\n name: attachment.name,\n mediaType: attachment.mediaType,\n bytes: attachment.bytes,\n }\n}\n\n/**\n * A display name, not a path. The name is echoed back to clients and put in front\n * of the model in the text-attachment envelope, so directory separators, control\n * characters and unbounded length all come off here.\n */\nfunction safeName(name: string): string {\n const leaf = name.split(/[/\\\\]/).pop() ?? ''\n // Control characters, plus the two delimiters of the text-attachment envelope.\n // The control range is exactly what makes this worth doing: the name is\n // client-supplied and ends up both in a response header and in front of a model.\n // oxlint-disable-next-line no-control-regex\n const cleaned = leaf.replace(/[\\u0000-\\u001f\\u007f\"<>]/g, '').trim()\n if (cleaned === '' || cleaned === '.' || cleaned === '..') return 'attachment'\n return cleaned.length > 120 ? cleaned.slice(0, 120) : cleaned\n}\n","import type { Runner } from '@workerdeck/core'\n\n/** One host file an engine reported writing, as the store holds it. */\nexport type ProducedFile = {\n fileId: string\n /** Absolute host path, exactly as the runner reported it. */\n path: string\n mediaType?: string\n /** Size when the runner announced it — advisory, and re-read at serve time. */\n bytes?: number\n sessionId: string\n}\n\n/**\n * The paths this gateway will serve from `GET /sessions/:id/produced/:fileId`.\n *\n * **This is the whole access-control model, so it is worth being precise about\n * what it is.** The store is an allowlist built from one source and one only:\n * `file_produced` events, which a runner emits about a file its own engine just\n * wrote. It is not a directory grant. Nothing else can add to it — not a\n * request, not a config, and in particular not the agent, whose own path claims\n * go through `/fs/*` and that route's root allowlist.\n *\n * That is why the route needs neither `hostFiles.roots` nor `maxFileBytes`:\n * \"somewhere under a root the operator declared\" is a guess about which paths\n * are safe, while \"the exact path this session's runner reported producing\" is\n * a fact about one file. A 2 MB generated PNG is the common case, and making\n * the operator raise a byte cap to see their own picture was the bug this\n * replaces.\n *\n * Lifetime is the session's, like `AttachmentStore`'s: in memory, dropped when\n * the session is removed. The bytes are never held here — only the path, so a\n * gateway serving a long session accumulates a few hundred bytes per picture\n * rather than the pictures.\n */\nexport class ProducedFileStore {\n #bySession = new Map<string, Map<string, ProducedFile>>()\n\n /**\n * Register a runner's produced files for its lifetime.\n *\n * Subscribes from seq 0, which is the opposite of what `SessionNotifier` wants\n * and correct for the same reason: registration is idempotent (a `fileId` is\n * derived from its path, so re-registering overwrites with itself), and a\n * session rebuilt from a park must re-learn every file it produced before the\n * park — otherwise a client's transcript keeps rendering image cards whose\n * bytes have quietly become unreachable.\n */\n watch(runner: Runner): void {\n runner.subscribe((event) => {\n if (event.type !== 'file_produced') return\n const held = this.#bySession.get(runner.id) ?? new Map<string, ProducedFile>()\n held.set(event.fileId, {\n fileId: event.fileId,\n path: event.path,\n ...(event.mediaType ? { mediaType: event.mediaType } : {}),\n ...(event.bytes !== undefined ? { bytes: event.bytes } : {}),\n sessionId: runner.id,\n })\n this.#bySession.set(runner.id, held)\n }, 0)\n }\n\n get(sessionId: string, fileId: string): ProducedFile | undefined {\n return this.#bySession.get(sessionId)?.get(fileId)\n }\n\n /** Everything one session has produced, newest registration last. */\n list(sessionId: string): ProducedFile[] {\n return [...(this.#bySession.get(sessionId)?.values() ?? [])]\n }\n\n drop(sessionId: string): void {\n this.#bySession.delete(sessionId)\n }\n}\n","import { SessionRunner, type Runner, type SessionRunnerConfig } from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\nexport type SessionRegistryOptions = {\n /**\n * Called once per runner as it enters the table, before it starts — the one\n * seam every path goes through (create, prepare, adopt, and the rebuild of a\n * parked session), which is what a watcher that must not miss a session needs.\n */\n onRegister?: (runner: Runner) => void\n}\n\n/** In-memory session table. Terminal sessions stay listed until removed or the process exits. */\nexport class SessionRegistry {\n #sessions = new Map<string, Runner>()\n #options: SessionRegistryOptions\n\n constructor(options: SessionRegistryOptions = {}) {\n this.#options = options\n }\n\n create(config: SessionRunnerConfig): Runner {\n return this.adopt(new SessionRunner(config))\n }\n\n /** Build and list a Claude-engine runner without starting it, so watchers can\n * subscribe first. Call `start()` once they have. */\n prepare(config: SessionRunnerConfig): Runner {\n return this.register(new SessionRunner(config))\n }\n\n /** Register an already-built runner (a non-Claude engine) and start it. */\n adopt(runner: Runner): Runner {\n this.register(runner)\n void runner.start()\n return runner\n }\n\n /** List a runner without starting it — for a rehydrated session, whose watchers\n * must be subscribed before it comes back up. */\n register(runner: Runner): Runner {\n // Re-registering the same runner is normal — `prepare()` lists it and the\n // caller registers what it returned — so the hook fires on the *runner*, not\n // on the call. A different object under a known id (the rebuild of a parked\n // session) is a new runner and does fire.\n const existing = this.#sessions.get(runner.id)\n this.#sessions.set(runner.id, runner)\n if (existing !== runner) this.#options.onRegister?.(runner)\n return runner\n }\n\n get(id: string): Runner | undefined {\n return this.#sessions.get(id)\n }\n\n list(): SessionInfo[] {\n return [...this.#sessions.values()].map((r) => r.info())\n }\n\n remove(id: string): boolean {\n const runner = this.#sessions.get(id)\n if (!runner) return false\n runner.close('server')\n return this.#sessions.delete(id)\n }\n\n /** Drop a runner WITHOUT closing it: the session isn't ending, it parked and\n * lives on in its snapshot. Closing here would tell every client it was over. */\n evict(id: string): boolean {\n return this.#sessions.delete(id)\n }\n\n closeAll(): void {\n for (const runner of this.#sessions.values()) runner.close('server')\n }\n}\n","import type { SessionNotification, SessionWebhookConfig } from '@workerdeck/protocol'\nimport type { Runner } from '@workerdeck/core'\n\nexport type SessionNotificationOptions = {\n /** POST target for every notification. */\n webhook?: SessionWebhookConfig\n /** Local observer, invoked for every notification whether or not a webhook is\n * configured — the in-process seam a host (or the CLI's APNs forwarder) hooks.\n * Unfiltered: `webhook.events` narrows POST deliveries, not this. */\n onNotification?: (notification: SessionNotification) => void\n /** Delivery attempts per notification (exponential backoff). Default 3. */\n attempts?: number\n /** Initial backoff between attempts. Default 500ms. */\n retryDelayMs?: number\n}\n\n/**\n * Turns session events into the handful of notifications a human away from the\n * screen cares about, and delivers them to a webhook and/or a local observer.\n *\n * This is the *primitive*, deliberately transport-agnostic: the server stays\n * credential-free and knows nothing about APNs, Slack or email. Turning a\n * notification into a push is a forwarder's job (the turnkey CLI's), and one that\n * needs credentials, so it does not live here.\n *\n * Delivery is best-effort and ordered per session, mirroring the job queue's\n * webhook behaviour — a consumer that missed one can always attach to the session\n * WS with `afterSeq` and see the truth.\n */\nexport class SessionNotifier {\n readonly #options: SessionNotificationOptions\n /** Per-session delivery chain, so a session's notifications arrive in order. */\n readonly #chains = new Map<string, Promise<void>>()\n\n constructor(options: SessionNotificationOptions) {\n this.#options = options\n }\n\n /** True when nothing is listening — lets the caller skip subscribing at all. */\n get idle(): boolean {\n return !this.#options.webhook && !this.#options.onNotification\n }\n\n /**\n * Subscribe to a runner for its lifetime.\n *\n * `afterSeq` defaults to whatever the runner has already emitted, which is what\n * makes this safe on a *rehydrated* session: `subscribe` replays the log from\n * `afterSeq`, so subscribing at 0 to a session rebuilt from a park would\n * re-announce every permission request it ever made.\n */\n watch(runner: Runner, afterSeq = runner.info().lastSeq): void {\n if (this.idle) return\n runner.subscribe((event) => {\n switch (event.type) {\n case 'permission_requested':\n this.#emit(runner, event.seq, event.ts, {\n type: 'permission_requested',\n preview: event.request.title ?? event.request.toolName,\n request: event.request,\n })\n return\n case 'turn_result':\n this.#emit(runner, event.seq, event.ts, {\n type: 'turn_completed',\n preview: event.isError ? event.errors?.join('\\n') : event.result,\n result: {\n isError: event.isError,\n durationMs: event.durationMs,\n numTurns: event.numTurns,\n totalCostUsd: event.totalCostUsd,\n },\n })\n return\n case 'session_error':\n this.#emit(runner, event.seq, event.ts, {\n type: 'session_error',\n preview: event.message,\n })\n return\n case 'session_closed':\n this.#emit(runner, event.seq, event.ts, {\n type: 'session_closed',\n reason: event.reason,\n })\n return\n default:\n return\n }\n }, afterSeq)\n }\n\n #emit(\n runner: Runner,\n seq: number,\n ts: number,\n body: Omit<SessionNotification, 'sessionId' | 'session' | 'seq' | 'ts'>,\n ): void {\n // A microtask late, deliberately. Listeners run *inside* the emit, before the\n // runner has applied what the event means — `session_closed` is delivered\n // while the status still says 'starting', `session_error` before 'failed'.\n // The event supplies seq/ts, so identity and ordering are unaffected; only the\n // snapshot moves, and it moves to the truth.\n queueMicrotask(() => this.#send(runner, seq, ts, body))\n }\n\n #send(\n runner: Runner,\n seq: number,\n ts: number,\n body: Omit<SessionNotification, 'sessionId' | 'session' | 'seq' | 'ts'>,\n ): void {\n const webhook = this.#options.webhook\n const wanted = !webhook?.events || webhook.events.includes(body.type)\n if (!webhook && !this.#options.onNotification) return\n\n // `info()` is read here, not at delivery time: the snapshot must describe the\n // session as the event left it, not as it is after three retries.\n const notification: SessionNotification = {\n ...body,\n sessionId: runner.id,\n session: runner.info(),\n seq,\n ts,\n }\n\n try {\n this.#options.onNotification?.(notification)\n } catch {\n // An observer that throws must not take the session down with it.\n }\n\n if (!webhook || !wanted) return\n const previous = this.#chains.get(runner.id) ?? Promise.resolve()\n const next = previous.then(() => this.#deliver(webhook, notification))\n this.#chains.set(runner.id, next)\n // Drop the chain once it drains, so a long-lived server doesn't accumulate one\n // resolved promise per session it ever ran.\n void next.then(() => {\n if (this.#chains.get(runner.id) === next) this.#chains.delete(runner.id)\n })\n }\n\n /**\n * Best-effort POST with exponential backoff. Deliberately a near-copy of the\n * queue's job-webhook delivery rather than a shared helper: the two channels\n * have different payloads and different consumers, and coupling them would mean\n * a change to job deliveries silently changing session deliveries.\n */\n async #deliver(webhook: SessionWebhookConfig, notification: SessionNotification): Promise<void> {\n const attempts = this.#options.attempts ?? 3\n const baseDelay = this.#options.retryDelayMs ?? 500\n for (let attempt = 0; attempt < attempts; attempt++) {\n try {\n const res = await fetch(webhook.url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...webhook.headers },\n body: JSON.stringify(notification),\n })\n if (res.ok) return\n } catch {\n // network error — retry below\n }\n if (attempt < attempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** attempt))\n }\n }\n }\n}\n","import { BrowserBridgeExecutor, type BridgeAnswer, type ToolExecutionResult } from '@workerdeck/core'\nimport type { ServerFrame, ToolCallRequestFrame } from '@workerdeck/protocol'\n\ntype SessionBridge = {\n /** Every client currently attached to this session, in attach order. */\n sockets: Array<(frame: ServerFrame) => void>\n executor: BrowserBridgeExecutor\n}\n\nexport type BridgeHubOptions = {\n /** How long a bridged call may stay unanswered before it fails. Default 60000. */\n timeoutMs?: number\n /** Called when a bridged execution reaches a terminal result — the host feeds\n * it back into the runner's loop. */\n onResult?: (sessionId: string, executionId: string, result: ToolExecutionResult) => void\n}\n\n/**\n * Routes tool executions between a session and the browser tabs attached to it.\n *\n * A session may have several clients attached (dashboard plus embedded panel);\n * the bridge asks the **first attached** one, which is the closest thing to \"the\n * client driving this session\". If none is attached, dispatch fails fast rather\n * than hanging — an autonomous job simply never bridges, it uses the server\n * executor instead.\n */\nexport class BridgeHub {\n #sessions = new Map<string, SessionBridge>()\n #options: BridgeHubOptions\n\n constructor(options: BridgeHubOptions = {}) {\n this.#options = options\n }\n\n /** The executor to hand a runner for this session. Created on first use and\n * reused, so results routed back always reach the same pending table. */\n executorFor(sessionId: string): BrowserBridgeExecutor {\n return this.#bridge(sessionId).executor\n }\n\n /** How many clients are watching this session. Parking consults it: a session\n * someone is watching stays live. */\n attachedCount(sessionId: string): number {\n return this.#sessions.get(sessionId)?.sockets.length ?? 0\n }\n\n /** Register an attached client. Returns a detach function. */\n attach(sessionId: string, send: (frame: ServerFrame) => void): () => void {\n const bridge = this.#bridge(sessionId)\n bridge.sockets.push(send)\n return () => {\n const index = bridge.sockets.indexOf(send)\n if (index >= 0) bridge.sockets.splice(index, 1)\n }\n }\n\n /**\n * Deliver a client's answer to a bridged call. Returns false when the id is\n * unknown or already settled — late and duplicate answers are ignored.\n */\n resolve(sessionId: string, executionId: string, answer: BridgeAnswer): boolean {\n return this.#sessions.get(sessionId)?.executor.resolve(executionId, answer) ?? false\n }\n\n /** Drop a session's bridge, failing anything still in flight. */\n remove(sessionId: string): void {\n const bridge = this.#sessions.get(sessionId)\n if (!bridge) return\n bridge.executor.registry.cancelAll('session_closed', 'the session was closed')\n this.#sessions.delete(sessionId)\n }\n\n #bridge(sessionId: string): SessionBridge {\n const existing = this.#sessions.get(sessionId)\n if (existing) return existing\n const bridge: SessionBridge = {\n sockets: [],\n executor: new BrowserBridgeExecutor({\n timeoutMs: this.#options.timeoutMs,\n send: (frame: ToolCallRequestFrame) => {\n const target = bridge.sockets[0]\n if (!target) return false\n target(frame)\n return true\n },\n cancel: (executionId, reason) => {\n for (const send of bridge.sockets) send({ type: 'tool_call_canceled', executionId, reason })\n },\n onResult: (executionId, result) => {\n this.#options.onResult?.(sessionId, executionId, result)\n },\n }),\n }\n this.#sessions.set(sessionId, bridge)\n return bridge\n }\n}\n","import type {\n ParkedExecution,\n Runner,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\nimport type { SessionRegistry } from './registry.ts'\nimport type { ParkedSessionRecord, SessionStore } from './session-store.ts'\n\nexport type SessionParkOptions = {\n registry: SessionRegistry\n store: SessionStore\n /** Rebuild a parked session's runner. The snapshot rides in on\n * `config.restore`, so the engine adopts the id, event log, and history. */\n rebuild: (record: ParkedSessionRecord) => Promise<Runner>\n /** How many clients are attached to this session. A watched session stays live:\n * parking would pull the runner out from under the socket. */\n attachedCount: (sessionId: string) => number\n /** Wait this long after the last client detaches before parking, so a reconnect\n * (a wifi blip, a page reload) doesn't cost a teardown. Default 2000. */\n parkDelayMs?: number\n /** Grace given at {@link SessionParkManager.hydrate} to an execution whose\n * deadline passed while the server was down. Its result could not have been\n * delivered during the outage, so failing it the instant the process is back\n * would throw away an answer that is very likely seconds behind. Extends a\n * deadline, never shortens one. Default 60000. */\n expiredGraceMs?: number\n /** Veto + accounting hook, called before the teardown: the job queue frees the\n * run's concurrency slot here, and refuses (false) when the run is finalizing. */\n onParking?: (sessionId: string, executionId: string) => boolean\n /** The session is live again under a NEW runner object — anything holding the\n * old reference must rebind. */\n onResumed?: (sessionId: string, runner: Runner) => void\n /** Park/resume failures. These are not session errors — the session is intact,\n * the host's storage or engine assembly isn't. */\n onError?: (error: unknown, context: { sessionId: string; phase: 'park' | 'resume' }) => void\n}\n\n/**\n * Deferred execution's other half: parking a session that is waiting on work no\n * process in this server is doing.\n *\n * The runner announces the moment with `status_changed: 'parked'` — emitted only\n * once every dispatch of the batch has been handed over, so the snapshot can never\n * miss a call that was still being dispatched. From there this class snapshots,\n * evicts, and persists; delivering a result rebuilds the runner under the same id\n * and hands the result to it. The session's identity, event log, and seq numbering\n * survive intact, so a client reattaching with `afterSeq` sees one unbroken stream.\n */\nexport class SessionParkManager {\n #options: SessionParkOptions\n /** executionId → sessionId, for routing a result to its session. Kept in memory\n * across the park; rebuilt from the store by {@link hydrate}. */\n #owners = new Map<string, string>()\n /** Executions already settled, kept until their session ends so a late or\n * duplicate delivery answers \"already settled\" instead of \"never heard of it\". */\n #settled = new Map<string, string>()\n #timers = new Map<string, ReturnType<typeof setTimeout>>()\n /** One resume per session, ever: two results arriving together must not build\n * two runners under the same id (the second would orphan the first, leaking the\n * MCP connection the park existed to release). */\n #resuming = new Map<string, Promise<Runner | undefined>>()\n #detachTimers = new Map<string, ReturnType<typeof setTimeout>>()\n /** The config each live session was built from — what a rebuild needs, and the\n * one thing a runner doesn't carry on its public surface. */\n #configs = new Map<string, SessionRunnerConfig>()\n /**\n * In-flight store work per session, so operations on one record run in order.\n *\n * Load-bearing with any store whose writes are real I/O. `#park` must evict the\n * runner *before* the save completes (an attach between `park()` and `evict()`\n * would bind a client to an inert runner), which leaves a window where the\n * session is in neither the registry nor the store. A delivery arriving inside it\n * would read past the write: `store.get` misses, the result is answered 404, the\n * execution is filed as settled with its watchdog cleared — and then the record\n * lands on disk with nothing left alive that could ever wake it. A `discard`\n * inside the same window would delete nothing and leave the save to resurrect a\n * session the caller was told was closed.\n */\n #storeOps = new Map<string, Promise<void>>()\n #closed = false\n\n constructor(options: SessionParkOptions) {\n this.#options = options\n }\n\n /** Record the config a session was created with. Only sessions the host\n * remembers can be parked — there is no way to rebuild the others. */\n remember(sessionId: string, config: SessionRunnerConfig): void {\n this.#configs.set(sessionId, config)\n }\n\n /** Adopt the store's contents (a durable store after a restart): re-index the\n * executions and re-arm their watchdogs, no deadline sooner than the grace\n * window — nothing could have been delivered while the process was down. */\n async hydrate(): Promise<void> {\n const floor = Date.now() + (this.#options.expiredGraceMs ?? 60_000)\n for (const record of await this.#options.store.list()) {\n for (const execution of record.executions) this.#track(record.id, execution, floor)\n }\n }\n\n /**\n * Follow a session's lifecycle: index its deferred executions, park it when the\n * engine says the turn has come to rest on them, and clean up when it ends.\n * `afterSeq` skips a rehydrated runner's replayed history (re-arming a watchdog\n * from an event whose deadline already passed would fail the execution instantly).\n */\n watch(runner: Runner, afterSeq = 0): () => void {\n return runner.subscribe((event) => {\n switch (event.type) {\n case 'execution_dispatched':\n if (!event.deferred) return\n this.#track(runner.id, {\n executionId: event.executionId,\n toolName: event.toolName,\n expiresAt: event.expiresAt,\n })\n return\n case 'execution_result':\n case 'execution_failed':\n this.#forget(event.executionId)\n // One call of a multi-call park settling leaves the session parked on\n // the rest — it has to go back down, and no new status_changed will say so.\n if (runner.info().status === 'parked') void this.#park(runner)\n return\n case 'status_changed':\n if (event.status === 'parked') void this.#park(runner)\n return\n case 'session_closed':\n void this.discard(runner.id)\n return\n default:\n return\n }\n }, afterSeq)\n }\n\n /** A client detached: park the session if that was the last one watching. */\n onDetach(sessionId: string): void {\n if (this.#closed) return\n const runner = this.#options.registry.get(sessionId)\n if (!runner || runner.info().status !== 'parked') return\n clearTimeout(this.#detachTimers.get(sessionId))\n const timer = setTimeout(() => {\n this.#detachTimers.delete(sessionId)\n void this.#park(runner)\n }, this.#options.parkDelayMs ?? 2000)\n timer.unref?.()\n this.#detachTimers.set(sessionId, timer)\n }\n\n /** Which session this execution belongs to — still waiting, or already settled. */\n sessionFor(executionId: string): string | undefined {\n return this.#owners.get(executionId) ?? this.#settled.get(executionId)\n }\n\n /** The parked session's record, for the read paths (GET, list, attach). */\n get(id: string): Promise<ParkedSessionRecord | null> {\n return this.#queue(id, () => this.#options.store.get(id))\n }\n\n /** Every parked session's info, to merge into `GET {basePath}/sessions`. */\n async listInfo(): Promise<SessionInfo[]> {\n // A park mid-save belongs in the listing: it is neither in the registry nor\n // readable yet, and a caller that saw it there a moment ago must not see it\n // vanish. Waiting on the writes in flight is what keeps the two views joined.\n await Promise.all(this.#storeOps.values())\n return (await this.#options.store.list()).map((record) => record.info)\n }\n\n /** The live runner for a session, rehydrating a parked one on demand. Undefined\n * when the session is neither live nor parked. */\n async ensureLive(id: string): Promise<Runner | undefined> {\n const live = this.#options.registry.get(id)\n if (live) return live\n return this.#resume(id)\n }\n\n /**\n * Deliver a deferred execution's result. Rehydrates the session if needed and\n * folds the result into its agent loop.\n *\n * Undefined = no session is waiting on that id. `applied: false` = it was already\n * settled: a duplicate delivery, or one racing the watchdog. Both are expected,\n * neither is an error.\n */\n async submitResult(\n executionId: string,\n result: ToolExecutionResult,\n ): Promise<{ applied: boolean; sessionId: string } | undefined> {\n const sessionId = this.#owners.get(executionId)\n if (sessionId === undefined) {\n // Already answered — by an earlier delivery, or by the watchdog it raced.\n // Waking the session to be told again would be worse than useless.\n const settled = this.#settled.get(executionId)\n return settled === undefined ? undefined : { applied: false, sessionId: settled }\n }\n const runner = await this.ensureLive(sessionId)\n if (!runner) {\n this.#forget(executionId)\n return undefined\n }\n // Clear the watchdog first: settling re-enters the agent loop, and a timeout\n // firing behind it would try to fail a call that no longer exists.\n this.#clearTimer(executionId)\n const applied = runner.settleExecution?.(executionId, result) ?? false\n if (applied) this.#forget(executionId)\n return { applied, sessionId }\n }\n\n /** Drop a parked session for good: the run is over (closed, canceled, killed). */\n async discard(sessionId: string): Promise<void> {\n clearTimeout(this.#detachTimers.get(sessionId))\n this.#detachTimers.delete(sessionId)\n this.#configs.delete(sessionId)\n for (const [executionId, owner] of this.#owners) {\n if (owner === sessionId) this.#forget(executionId)\n }\n for (const [executionId, owner] of this.#settled) {\n if (owner === sessionId) this.#settled.delete(executionId)\n }\n await this.#queue(sessionId, () => this.#options.store.delete(sessionId))\n }\n\n close(): void {\n this.#closed = true\n for (const timer of this.#timers.values()) clearTimeout(timer)\n for (const timer of this.#detachTimers.values()) clearTimeout(timer)\n this.#timers.clear()\n this.#detachTimers.clear()\n }\n\n async #park(runner: Runner): Promise<void> {\n if (this.#closed || !runner.park) return\n const id = runner.id\n if (this.#options.registry.get(id) !== runner) return\n if (runner.info().status !== 'parked') return\n // Someone is watching: keep it live and reconsider when they leave.\n if (this.#options.attachedCount(id) > 0) return\n const executions = [...this.#owners].filter(([, owner]) => owner === id).map(([e]) => e)\n if (executions.length === 0) return\n const config = this.#configs.get(id)\n if (!config) return\n if (this.#options.onParking && !this.#options.onParking(id, executions[0]!)) return\n // park() → evict in one tick: an attach landing between them would bind a\n // client to a runner that is already inert.\n const snapshot = runner.park()\n if (!snapshot) return\n const info = { ...runner.info(), status: 'parked' as const }\n this.#options.registry.evict(id)\n const record: ParkedSessionRecord = {\n id,\n info,\n profile: info.profile,\n config,\n snapshot,\n executions: snapshot.parked,\n parkedAt: Date.now(),\n }\n for (const execution of snapshot.parked) this.#track(id, execution)\n try {\n await this.#queue(id, () => this.#options.store.save(record))\n } catch (error) {\n // The runner is already inert and evicted; without the record the session\n // cannot come back. Nothing to roll back to — surface it loudly.\n this.#options.onError?.(error, { sessionId: id, phase: 'park' })\n }\n }\n\n async #resume(id: string): Promise<Runner | undefined> {\n const inFlight = this.#resuming.get(id)\n if (inFlight) return inFlight\n const attempt = this.#rebuild(id)\n this.#resuming.set(id, attempt)\n try {\n return await attempt\n } finally {\n this.#resuming.delete(id)\n }\n }\n\n async #rebuild(id: string): Promise<Runner | undefined> {\n const record = await this.#queue(id, () => this.#options.store.get(id))\n if (!record) return undefined\n let runner: Runner\n try {\n runner = await this.#options.rebuild(record)\n } catch (error) {\n // Keep the record: the failure may be transient (an MCP server down), and\n // the next delivery — or a retried one — gets another chance.\n this.#options.onError?.(error, { sessionId: id, phase: 'resume' })\n throw error\n }\n if (runner.id !== id) {\n // The factory ignored `restore` and built a fresh session: it has none of\n // this one's history and would strand the execution we are waking for.\n runner.close('error')\n const error = new Error(\n `rebuilt session has id '${runner.id}', expected '${id}' — the engine factory must ` +\n 'forward EngineRunnerContext.restore to the runner config',\n )\n this.#options.onError?.(error, { sessionId: id, phase: 'resume' })\n throw error\n }\n // Registered and subscribed before it starts, so nothing it emits on the way\n // back up is missed by the queue or the watchers.\n this.#options.registry.register(runner)\n this.remember(id, record.config)\n this.watch(runner, record.snapshot.seq)\n this.#options.onResumed?.(id, runner)\n await this.#queue(id, () => this.#options.store.delete(id))\n void runner.start()\n return runner\n }\n\n /** Run a store operation after whatever is already in flight for this session.\n * The chain is per session and drops itself once idle; a failed operation never\n * poisons the ones behind it (each caller handles its own). */\n #queue<T>(sessionId: string, op: () => Promise<T>): Promise<T> {\n const previous = this.#storeOps.get(sessionId) ?? Promise.resolve()\n const result = previous.then(op)\n const settled = result.then(\n () => {},\n () => {},\n )\n this.#storeOps.set(sessionId, settled)\n void settled.then(() => {\n if (this.#storeOps.get(sessionId) === settled) this.#storeOps.delete(sessionId)\n })\n return result\n }\n\n #track(sessionId: string, execution: ParkedExecution, notBefore = 0): void {\n this.#owners.set(execution.executionId, sessionId)\n if (execution.expiresAt === undefined || this.#timers.has(execution.executionId)) return\n const expiresAt = Math.max(execution.expiresAt, notBefore)\n const timer = setTimeout(\n () => {\n this.#timers.delete(execution.executionId)\n // A result that never came is ordinary tool output, not a session error:\n // the agent gets told and adapts, exactly like a sandbox failure.\n void this.submitResult(execution.executionId, {\n status: 'failed',\n reason: 'timeout',\n error: `deferred execution '${execution.toolName}' produced no result before its deadline`,\n }).catch((error: unknown) => {\n this.#options.onError?.(error, { sessionId, phase: 'resume' })\n })\n },\n Math.max(0, expiresAt - Date.now()),\n )\n timer.unref?.()\n this.#timers.set(execution.executionId, timer)\n }\n\n #forget(executionId: string): void {\n this.#clearTimer(executionId)\n const owner = this.#owners.get(executionId)\n if (owner !== undefined) this.#settled.set(executionId, owner)\n this.#owners.delete(executionId)\n }\n\n #clearTimer(executionId: string): void {\n const timer = this.#timers.get(executionId)\n if (timer === undefined) return\n clearTimeout(timer)\n this.#timers.delete(executionId)\n }\n}\n","import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { ParkedExecution, RunnerSnapshot, SessionRunnerConfig } from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\n/**\n * A session with its live runner torn down, waiting on deferred executions.\n *\n * Everything needed to bring it back: the wire-visible info (so it still lists and\n * reads over REST while parked), the config to rebuild the runner, the engine's\n * snapshot, and what it is waiting for.\n */\nexport type ParkedSessionRecord = {\n id: string\n /** Session info as of the park, with `status: 'parked'`. */\n info: SessionInfo\n profile?: string\n /** The config the session was created with (profile defaults already applied). */\n config: SessionRunnerConfig\n snapshot: RunnerSnapshot\n executions: ParkedExecution[]\n parkedAt: number\n}\n\n/**\n * Where parked sessions live. Two implementations ship: {@link MemorySessionStore}\n * (a park survives a disconnect, not a restart) and {@link createFileSessionStore}\n * (it survives both, on one host); a redis/sqlite/table store implements the same\n * four operations.\n *\n * Two things to know before writing one: the record holds the session's whole\n * transcript and tool I/O, and `config` may carry host-injected values (env, hooks,\n * injected functions) that a JSON round-trip silently drops or, worse, persists.\n * {@link toDurableRecord} is the filter the bundled file store applies — reuse it.\n */\nexport interface SessionStore {\n save(record: ParkedSessionRecord): Promise<void>\n get(id: string): Promise<ParkedSessionRecord | null>\n list(): Promise<ParkedSessionRecord[]>\n delete(id: string): Promise<boolean>\n}\n\n/** Single-process, no persistence: parks survive a client disconnect, not a restart. */\nexport class MemorySessionStore implements SessionStore {\n #records = new Map<string, ParkedSessionRecord>()\n\n save(record: ParkedSessionRecord): Promise<void> {\n this.#records.set(record.id, record)\n return Promise.resolve()\n }\n\n get(id: string): Promise<ParkedSessionRecord | null> {\n return Promise.resolve(this.#records.get(id) ?? null)\n }\n\n list(): Promise<ParkedSessionRecord[]> {\n return Promise.resolve([...this.#records.values()])\n }\n\n delete(id: string): Promise<boolean> {\n return Promise.resolve(this.#records.delete(id))\n }\n}\n\n/**\n * Config fields that must not be written to durable storage: two are functions\n * (JSON drops them silently), `extraOptions` is SDK `Options` and may hold hooks\n * and callbacks, and `env` is a credential-bearing map — the same rule\n * `profile-store.ts` follows, for the same reason.\n *\n * Dropping them costs a rehydrated session nothing: all four are consumed by the\n * Claude engine alone, and the Claude engine cannot park (the CLI owns its process\n * state — `buildRunner` refuses a `restore` for it). A provider session's\n * credentials are resolved by `createEngineRunner` from the operator's environment\n * on every build, wake included.\n */\nconst EPHEMERAL_CONFIG_KEYS = ['queryFn', 'historyFn', 'extraOptions', 'env'] as const\n\n/** The record as it may be persisted: same session, config narrowed to what is\n * safe and meaningful to keep (see {@link EPHEMERAL_CONFIG_KEYS}). */\nexport function toDurableRecord(record: ParkedSessionRecord): ParkedSessionRecord {\n const config: SessionRunnerConfig = { ...record.config }\n for (const key of EPHEMERAL_CONFIG_KEYS) delete config[key]\n return { ...record, config }\n}\n\n/** Bump when the on-disk shape changes incompatibly; records written by another\n * version are ignored rather than half-read into a broken session. */\nconst FORMAT_VERSION = 1\n\nexport type FileSessionStoreOptions = {\n /** Directory holding one JSON file per parked session.\n * Default `<cwd>/.workerdeck/parked`. */\n dir?: string\n /** A record that could not be read or written. Losing one is losing a session's\n * way back, so this is worth logging — the store itself stays quiet and skips it. */\n onError?: (error: unknown, context: { path: string; op: 'save' | 'read' | 'delete' }) => void\n}\n\n/**\n * Durable single-host store: one JSON file per parked session under `dir`, written\n * through a temp file and a rename so a crash mid-write cannot truncate a session.\n * `hydrate()` at `listen()` picks them up, re-indexes their executions, and re-arms\n * the watchdogs, so a restart no longer loses parked work.\n *\n * Know what is on that disk: **the record holds the session's entire transcript** —\n * prompts, model output, and tool I/O — in plaintext. Put it somewhere with the same\n * protection as the SDK's own transcripts (`~/.claude/projects`), not in a directory\n * that gets served, synced, or backed up somewhere looser.\n *\n * Single-process by design, exactly like the bundled queue adapter and profile\n * store: two servers sharing one directory would both hydrate the same records and\n * race to rebuild them. That is what the seam is for.\n *\n * Nothing here reaps: a record leaves only when its session wakes or is deleted.\n * An execution dispatched without a deadline (a `DeferredExecutor` with no\n * `timeoutMs`) has no watchdog to end the wait, so its record — and its transcript\n * — stays until `DELETE /sessions/:id`. Give deferred calls a deadline, or sweep.\n */\nexport function createFileSessionStore(options: FileSessionStoreOptions = {}): SessionStore {\n const dir = options.dir ?? join(process.cwd(), '.workerdeck', 'parked')\n // Encoded, not interpolated: an id is a runner-assigned string, and a '/' in one\n // would otherwise write outside `dir`.\n const fileFor = (id: string): string => join(dir, `${encodeURIComponent(id)}.json`)\n\n const read = async (path: string): Promise<ParkedSessionRecord | null> => {\n let raw: string\n try {\n raw = await readFile(path, 'utf8')\n } catch (error) {\n // Absent is the ordinary case: nothing parked under that id. Anything else\n // (EACCES, EIO) is a session we cannot reach, and reporting it as \"nothing\n // is parked\" is how a restart guard ends up saying the coast is clear.\n if (isMissing(error)) return null\n options.onError?.(error, { path, op: 'read' })\n return null\n }\n try {\n return parseRecord(JSON.parse(raw))\n } catch (error) {\n options.onError?.(error, { path, op: 'read' })\n return null\n }\n }\n\n return {\n save: async (record) => {\n const path = fileFor(record.id)\n let payload: string\n try {\n payload = JSON.stringify({ version: FORMAT_VERSION, record: toDurableRecord(record) })\n } catch (error) {\n options.onError?.(error, { path, op: 'save' })\n throw new Error(\n `parked session '${record.id}' is not JSON-serializable — a host-injected value ` +\n `reached its config or snapshot: ${String(error)}`,\n )\n }\n try {\n // Transcripts, so owner-only: the docs say protect this like\n // `~/.claude/projects`, and the default 0755/0644 wouldn't.\n await mkdir(dir, { recursive: true, mode: 0o700 })\n const temp = `${path}.${process.pid}.tmp`\n await writeFile(temp, payload, { mode: 0o600 })\n await rename(temp, path)\n } catch (error) {\n options.onError?.(error, { path, op: 'save' })\n throw error\n }\n },\n\n get: (id) => read(fileFor(id)),\n\n list: async () => {\n let names: string[]\n try {\n names = await readdir(dir)\n } catch (error) {\n if (isMissing(error)) return [] // No directory yet: nothing has parked here.\n // An unreadable directory is not an empty one. `hydrate()` runs inside\n // `listen()`, so throwing here refuses the boot instead of coming up as a\n // server that has quietly forgotten every parked session.\n options.onError?.(error, { path: dir, op: 'read' })\n throw error\n }\n const records = await Promise.all(\n names\n .filter((name) => name.endsWith('.json'))\n .map(async (name) => {\n const record = await read(join(dir, name))\n if (!record) return null\n // A record only answers `get`/`delete` under the name its id encodes to.\n // One that got here some other way (a copy, an ops rename) would list\n // forever and be unreachable — better to say so than to serve a ghost.\n if (`${encodeURIComponent(record.id)}.json` === name) return record\n options.onError?.(\n new Error(`parked record '${record.id}' is stored as '${name}' and cannot be read back by id`),\n { path: join(dir, name), op: 'read' },\n )\n return null\n }),\n )\n return records.filter((record): record is ParkedSessionRecord => record !== null)\n },\n\n delete: async (id) => {\n const path = fileFor(id)\n try {\n await rm(path)\n return true\n } catch (error) {\n // Missing is not an error — a discard racing a wake-up hits this.\n if (isMissing(error)) return false\n options.onError?.(error, { path, op: 'delete' })\n return false\n }\n },\n }\n}\n\nconst isMissing = (error: unknown): boolean => (error as NodeJS.ErrnoException).code === 'ENOENT'\n\n/** Shape-check a parsed file. A record missing any of these could not be rebuilt,\n * and half-restoring one is worse than skipping it. */\nfunction parseRecord(value: unknown): ParkedSessionRecord | null {\n if (!value || typeof value !== 'object') return null\n const envelope = value as { version?: unknown; record?: unknown }\n if (envelope.version !== FORMAT_VERSION) return null\n const record = envelope.record as Partial<ParkedSessionRecord> | null\n if (!record || typeof record !== 'object') return null\n if (typeof record.id !== 'string' || typeof record.parkedAt !== 'number') return null\n if (!record.info || !record.config || !record.snapshot) return null\n if (!Array.isArray(record.executions)) return null\n return record as ParkedSessionRecord\n}\n","import { createHash } from 'node:crypto'\nimport {\n createReadStream,\n existsSync,\n lstatSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n type Dirent,\n} from 'node:fs'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { homedir } from 'node:os'\nimport type { Duplex } from 'node:stream'\nimport { basename, join, resolve as resolvePath, sep } from 'node:path'\nimport { WebSocketServer, type WebSocket } from 'ws'\nimport { attachmentKind, checkClaudeAuth, getEngineAdapter } from '@workerdeck/core'\nimport type {\n ClaudeAuthProbe,\n EngineAdapter,\n EngineAvailability,\n Runner,\n RunnerSnapshot,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport { JobQueue, type QueueAdapter } from '@workerdeck/queue'\nimport {\n ENGINE_CAPABILITIES,\n PROTOCOL_VERSION,\n supportsPermissionMode,\n type ClientFrame,\n type CreateJobRequest,\n type CreateSessionRequest,\n type JobEvent,\n type PermissionMode,\n type ProfileEngine,\n type ProfileConfigSnapshot,\n type ProfileInfo,\n type QueueServerFrame,\n type McpServerActionRequest,\n type ResolvePermissionRequest,\n type UpdateSessionRequest,\n type SdkSessionSummary,\n type ServerFrame,\n type SessionInfo,\n type SubmitExecutionResultRequest,\n type UpdateProfileRequest,\n type WriteHostFileRequest,\n} from '@workerdeck/protocol'\nimport { searchFiles } from './host-file-search.ts'\nimport {\n createHostFileRoots,\n entryKind,\n readContained,\n resolveExisting,\n resolveForWrite,\n writeContained,\n} from './host-files.ts'\nimport { AttachmentStore } from './attachments.ts'\nimport { ProducedFileStore } from './produced-files.ts'\nimport { SessionRegistry } from './registry.ts'\nimport { SessionNotifier, type SessionNotificationOptions } from './notifications.ts'\nimport { BridgeHub, type BridgeHubOptions } from './bridge.ts'\nimport { SessionParkManager } from './parking.ts'\nimport { MemorySessionStore, type SessionStore } from './session-store.ts'\nimport type { ProfileStore } from './profile-store.ts'\n\nexport type SdkSessionLister = (options: {\n dir?: string\n limit?: number\n offset?: number\n}) => Promise<SdkSessionSummary[]>\n\n/**\n * Return a principal (any truthy value) to accept the request, or null/undefined to\n * reject with 401. The host app supplies this — the worker has no auth story of its\n * own. A principal object may carry `allowedProfiles: string[]` to restrict which\n * profiles the caller can create sessions/jobs under (and see in GET /profiles) —\n * without it the caller may use every declared profile. It may also carry\n * `canManageProfiles: true` to allow creating/editing/deleting managed profiles\n * (requires the `profileStore` option); anything else means no.\n */\nexport type Authenticator = (\n req: IncomingMessage,\n) => unknown | Promise<unknown>\n\nexport type WorkerServerOptions = {\n /** Required unless `allowUnauthenticated: true` — the worker must never be exposed bare. */\n authenticate?: Authenticator\n /** Explicit opt-in to run without auth (local dev only). */\n allowUnauthenticated?: boolean\n /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */\n allowedCwdRoots?: string[]\n /**\n * The host filesystem routes (`{basePath}/fs/*`) — browse and read the\n * operator's real project tree, and optionally write to it.\n *\n * **Reading follows {@link allowedCwdRoots} and needs no grant of its own.**\n * A caller holding the auth key can already start a session in any allowed root\n * and have the agent read whatever is in it, so serving those same trees over\n * `/fs` adds no authority — it only removes the absurdity of going through a\n * language model to `cat` a file. Set `roots` here only to *narrow* that (or to\n * expose a tree sessions may not run in).\n *\n * With neither set the routes 404. That is not the same as inheriting\n * `allowedCwdRoots`' permissive \"unset means anywhere\": no cwd policy means\n * there is nothing to inherit, and \"anywhere\" is a statement about paths the\n * operator types at a keyboard, not one about what a phone may read.\n *\n * **Writing is a separate opt-in**, because it is the one part that is not\n * already implied. An agent's writes go through the permission flow; a `PUT` to\n * `/fs/write` does not. These routes are operator-privileged by design — the\n * caller is the operator — but that is a reason to make the bypass deliberate,\n * not a reason to skip the switch.\n *\n * Containment is *not* `cwdAllowed`, whichever roots are in play: these routes\n * walk paths the agent may have authored, so a symlink can escape a lexical\n * prefix check. See `host-files.ts` — canonicalize, then re-check.\n */\n hostFiles?: {\n /** Absolute paths. Unset inherits {@link allowedCwdRoots}; an explicit empty\n * array disables the routes (a policy, not an absence). */\n roots?: string[]\n /** Enable `PUT {basePath}/fs/write`. Default false — read-only. */\n write?: boolean\n /** Refuse reads above this (413) rather than streaming a gigabyte to a phone.\n * Default 1 MiB. Writes are bounded by {@link maxBodyBytes} instead. */\n maxFileBytes?: number\n /** Cap on entries returned per directory (the response says `truncated`).\n * Default 5000. */\n maxEntries?: number\n /** Directory names `GET /fs/find` will not descend into. Defaults to\n * `DEFAULT_IGNORED_DIRS` (`.git`, `node_modules`, build output…) — the thing\n * that keeps a per-keystroke search cheap on a real source tree. */\n ignore?: string[]\n }\n /**\n * Message attachments (`{basePath}/sessions/:id/attachments`) — the photos and\n * files a client sends alongside a message. Always on; these knobs only size it.\n *\n * There is no grant to make here the way `hostFiles.write` is one: an upload\n * lands in the session's own in-memory hold and reaches the model as message\n * content, which is exactly what typing does. What it *can* do is cost memory,\n * so both caps default low enough that a phone camera roll cannot fill the\n * gateway.\n */\n attachments?: {\n /** Largest single upload; over it is a 413. Default 10 MiB. */\n maxFileBytes?: number\n /** Ceiling on what one session holds at once. Default 64 MiB. */\n maxSessionBytes?: number\n }\n /**\n * Named Claude Code config directories sessions can run under (each becomes the\n * session's CLAUDE_CONFIG_DIR — settings, memory, skills, and the credentials the\n * SDK resolves from it). Declared here at startup; the API only reads them\n * (GET {basePath}/profiles). With more than one declared, every session/job create\n * must name its profile; with exactly one it is implicit. Unset: a 'default'\n * profile is auto-created from $CLAUDE_CONFIG_DIR or ~/.claude when that directory\n * exists. Pass [] to run without profiles (no env pinning at all).\n */\n profiles?: ProfileInfo[]\n /**\n * Persistence for dashboard-managed profiles, which mounts the profile\n * management routes (`POST /profiles`, `PATCH`/`DELETE /profiles/:name`).\n * Without it the profile set is startup config and the API stays read-only.\n *\n * Profiles declared in `profiles` are never stored and never editable over\n * HTTP — they are code. The two sets are unioned by name, declared winning.\n * Callers still need `canManageProfiles` on their principal.\n */\n profileStore?: ProfileStore\n /**\n * Config-dir roots a *managed* Claude profile's `configDir` must resolve inside\n * (mirrors {@link allowedCwdRoots}). Unset — the default — means the management\n * routes create provider profiles only: naming a config directory is choosing\n * which credential store a session runs on, so it stays operator-bounded.\n * Declared profiles are unaffected.\n */\n allowedConfigDirRoots?: string[]\n /** Map/patch the incoming CreateSessionRequest into the runner config (inject queryFn,\n * env, tool policy, per-skill constraints...). Defaults to identity.\n *\n * What this hook injects is **not** durable: a session rebuilt from a parked\n * record is built from the stored config, and a durable store persists neither\n * `env` nor injected functions (see `toDurableRecord` in session-store.ts). That costs the\n * Claude engine nothing, since it cannot park — but a provider host that\n * resolves credentials into `config.env` here for its `createEngineRunner` to\n * read back loses them on the wake. Resolve them in the factory instead. */\n buildRunnerConfig?: (req: CreateSessionRequest) => SessionRunnerConfig\n /** URL prefix for all routes. Default '/v1'. */\n basePath?: string\n /**\n * Handle requests that fall outside `basePath` instead of 404ing them. The\n * turnkey CLI serves the dashboard through this, which is the whole reason it\n * exists: a browser cannot put a header on a WebSocket handshake, so the only\n * credential a tab can present on a session attach is a cookie — and a cookie\n * only rides requests to the origin that set it. Serving the app and the API\n * from one origin is therefore not a convenience, it is what makes an\n * authenticated dashboard possible without a stamping proxy in front.\n *\n * Upgrades are not routed here: anything outside `basePath` is still refused.\n */\n fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n /** Max JSON body size in bytes. Default 1 MiB. */\n maxBodyBytes?: number\n /**\n * Server-wide bypass policy: refuse `permissionMode: 'bypassPermissions'` on\n * session/job creation (403), and strip the `allowDangerouslySkipPermissions`\n * pre-authorization from requests (so clients that ask for the capability by\n * default keep working — their later switch attempt fails with the CLI's own\n * visible error instead). Mirrors Claude Code's\n * `permissions.disableBypassPermissionsMode` setting, enforced at the gateway.\n */\n disableBypassPermissions?: boolean\n /**\n * Fail closed on subscription credentials: if a session initializes with\n * `apiKeySource: 'oauth'` (a claude.ai login rather than an API key / Bedrock / Vertex),\n * it is terminated with a session_error. Recommended for services and any\n * unattended/scheduled use — Anthropic's terms require API-key auth for those.\n * Off by default: single-user personal deployments may legitimately run on the\n * operator's own subscription; the server then logs a one-time notice instead.\n */\n requireApiKey?: boolean\n /**\n * Launch-time credential sanity check: once `listen()` binds, each Claude\n * profile's session environment — exactly what `buildRunnerConfig` would hand\n * a session, host hook included — is probed with the SDK-bundled CLI's\n * `claude auth status`, concurrently and fire-and-forget, and a profile that\n * reports logged-out gets one console warning. Warn, never fail: the operator\n * may be about to log in, and a probe that cannot run at all (missing binary,\n * a CLI without `auth status`, unparseable output) stays silent — \"couldn't\n * check\" is not \"not logged in\". No credential material is read or logged.\n * Off by default (this is a library; tests must spawn nothing) — the turnkey\n * CLI turns it on. Pass an object to inject the probe (tests) or a timeout.\n */\n checkCredentials?: boolean | { probe?: ClaudeAuthProbe; timeoutMs?: number }\n /** Injectable lister for GET /sdk-sessions (tests) — honored for the CLAUDE\n * engine only, like the injectable claude auth probe (it predates the adapter\n * layer). Defaults to the claude adapter's lister (the SDK's on-disk session\n * store); other engines always answer through their adapter's\n * `listSessions`. */\n listSdkSessions?: SdkSessionLister\n /** Enable the job queue (`/jobs` + `/queue` routes). Jobs run as ordinary registry\n * sessions — attachable over the sessions WS — governed by these limits. */\n queue?: QueueServerOptions\n /**\n * Out-of-band notification for interactive sessions: the four moments a person\n * away from the screen needs (permission requested, turn done, error, closed)\n * POSTed to a webhook and/or handed to a local observer. Off unless configured.\n *\n * Server-wide, unlike the queue's per-job webhook — the point is to hear about\n * sessions you neither created nor are attached to, which is the situation a\n * mobile client is in permanently (iOS will not hold a WebSocket open in the\n * background). Every registry session qualifies, job runs included, so a job\n * carrying its own webhook is reported on both channels.\n *\n * This is the primitive, and it stays transport-agnostic on purpose: the OSS\n * server holds no push credentials. Turning a notification into an APNs push is\n * a forwarder's job — see the turnkey CLI.\n */\n notifications?: SessionNotificationOptions\n /** Browser-bridged tool execution: how long a bridged call may go unanswered\n * before it fails (default 60000), and where terminal results are delivered.\n * The hub is always available on the returned server as `bridge`. */\n bridge?: BridgeHubOptions\n /**\n * Deferred execution: a session that parks on an execution nothing here is\n * running has its state persisted and its runner torn down, and comes back when\n * the result is POSTed to `{basePath}/executions/:executionId/result`.\n *\n * On by default with an in-memory store, so a park survives a disconnect but not\n * a restart; pass `store: createFileSessionStore()` (or your own) to change that\n * — read its doc first, the record holds the whole transcript.\n */\n parking?: {\n store?: SessionStore\n /** Grace after the last client detaches before parking. Default 2000. */\n parkDelayMs?: number\n /** Grace given on boot to an execution whose deadline passed while the server\n * was down (durable stores only — nothing else survives a restart). Default 60000. */\n expiredGraceMs?: number\n /** Park/resume failures — storage or engine-assembly problems, not session errors. */\n onError?: (error: unknown, context: { sessionId: string; phase: 'park' | 'resume' }) => void\n }\n /**\n * Build a runner for a `provider` profile (the model-agnostic engine).\n * Required if any such profile is declared — the server refuses to start\n * otherwise, rather than failing at create time.\n *\n * Kept as a host hook so the server package neither imports a model SDK nor\n * decides how provider credentials are resolved: the factory reads them from\n * the operator's environment, exactly like the Claude credential chain.\n * `claude` and `codex` profiles never come through here — those engines ship\n * as in-repo adapters (`@workerdeck/core`'s `getEngineAdapter`).\n *\n * May be async: assembly that has to await — a per-session MCP connect, a\n * credential lookup — belongs here, with `AiSdkRunnerConfig.onClose` as the\n * disposer. A rejection fails the create — the session POST answers 500 with\n * the message, a job goes straight to `failed`.\n */\n createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>\n /**\n * Adapter overrides, keyed by engine — **for tests only** (the server\n * integration suite injects a fake codex engine so `pnpm test` spawns no\n * binary). Not a public extension point: third engines belong in core as\n * adapters, or behind `createEngineRunner` as provider profiles.\n */\n engines?: Partial<Record<ProfileEngine, EngineAdapter>>\n}\n\nexport type EngineRunnerContext = {\n /** The session config, with profile defaults already applied. */\n config: SessionRunnerConfig\n /** The profile that selected this engine. */\n profile: ProfileInfo\n /** Bridge hub, for handing the runner a browser-backed ToolExecutor\n * (`bridge.executorFor(sessionId)`). */\n bridge: BridgeHub\n /**\n * Set when rebuilding a session that parked on a deferred execution. Forward it\n * as `restore` on the engine config (`createEngineSession({ config: { ...config,\n * restore } })`) — the engine then adopts the session's id, event log, seq\n * numbering, history, and scratch filesystem instead of starting fresh.\n */\n restore?: RunnerSnapshot\n}\n\nexport type QueueServerOptions = {\n /** Concurrent job sessions. Default 1. */\n maxConcurrency?: number\n /** Token cap per job session (input+output+cache tokens); exceeding it kills the run. */\n sessionTokenLimit?: number\n /** Global job-token budget per UTC day; queued jobs are held once exhausted. */\n dailyTokenLimit?: number\n /** Wall-clock cap per job run — the watchdog against stuck CLIs. */\n maxJobDurationMs?: number\n /** Grace between interrupting a killed run and force-closing it. Default 5000. */\n killGraceMs?: number\n /** Expire terminal jobs after `maxAgeMs` (the in-memory adapter otherwise grows\n * unboundedly). */\n retention?: { maxAgeMs: number; sweepIntervalMs?: number }\n /** Queue backend. Defaults to the bundled in-memory adapter (single process,\n * no persistence) — redis/bullmq/pubsub adapters implement the same interface. */\n adapter?: QueueAdapter\n /** Webhook delivery attempts per event (default 3, exponential backoff). */\n webhookAttempts?: number\n webhookRetryDelayMs?: number\n /** Local observer for job lifecycle events (in addition to per-job webhooks). */\n onEvent?: (event: JobEvent) => void\n}\n\nexport type WorkerServer = {\n server: Server\n registry: SessionRegistry\n /** The job queue, when `queue` options were provided. */\n queue?: JobQueue\n /** Routes tool executions to attached browser clients. `bridge.executorFor(id)`\n * is the `ToolExecutor` to hand a runner that should execute in the tab. */\n bridge: BridgeHub\n /** Parked sessions: the store, the execution index, and the rehydration path.\n * Deliver a deferred result with `parking.submitResult(...)` in-process, or POST\n * it to `{basePath}/executions/:executionId/result`. */\n parking: SessionParkManager\n listen: (port: number, host?: string) => Promise<{ port: number }>\n close: () => Promise<void>\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(payload),\n })\n res.end(payload)\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n maxBytes: number,\n): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > maxBytes) throw new Error('request body too large')\n chunks.push(chunk as Buffer)\n }\n if (size === 0) return {}\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>\n}\n\n/** Body as bytes, refusing anything over `maxBytes`. Attachments are the one\n * thing this server takes that isn't JSON. */\nasync function readRawBody(req: IncomingMessage, maxBytes: number): Promise<Buffer> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > maxBytes) throw new Error('request body too large')\n chunks.push(chunk as Buffer)\n }\n return Buffer.concat(chunks)\n}\n\n/**\n * Curated, view-only snapshot of a profile's config dir for GET /profiles/:name.\n * Best-effort: a missing or unparseable settings.json just omits the settings block.\n * Env var VALUES are never read into the response — names only.\n *\n * Provider profiles have no config dir, so the snapshot is empty for them: their\n * configuration is the `provider` block already on ProfileInfo.\n */\nfunction readProfileConfig(profile: ProfileInfo): ProfileConfigSnapshot {\n const dir = profile.configDir\n if (!dir) return { hasUserMemory: false, skills: [], agents: [], commands: [] }\n const listDirs = (path: string): string[] => {\n try {\n return readdirSync(path, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort()\n } catch {\n return []\n }\n }\n const listMd = (path: string): string[] => {\n try {\n return readdirSync(path)\n .filter((file) => file.endsWith('.md'))\n .map((file) => file.slice(0, -3))\n .sort()\n } catch {\n return []\n }\n }\n const snapshot: ProfileConfigSnapshot = {\n hasUserMemory: existsSync(join(dir, 'CLAUDE.md')),\n skills: listDirs(join(dir, 'skills')),\n agents: listMd(join(dir, 'agents')),\n commands: listMd(join(dir, 'commands')),\n }\n try {\n const raw = JSON.parse(readFileSync(join(dir, 'settings.json'), 'utf8')) as Record<\n string,\n unknown\n >\n const permissions = (raw.permissions ?? {}) as Record<string, unknown>\n const count = (rules: unknown): number => (Array.isArray(rules) ? rules.length : 0)\n snapshot.settings = {\n model: typeof raw.model === 'string' ? raw.model : undefined,\n defaultPermissionMode:\n typeof permissions.defaultMode === 'string' ? permissions.defaultMode : undefined,\n permissionRules: {\n allow: count(permissions.allow),\n ask: count(permissions.ask),\n deny: count(permissions.deny),\n },\n envKeys:\n raw.env && typeof raw.env === 'object' ? Object.keys(raw.env).sort() : undefined,\n hooks:\n raw.hooks && typeof raw.hooks === 'object' ? Object.keys(raw.hooks).sort() : undefined,\n }\n } catch {\n // settings.json absent or unparseable — snapshot ships without the block\n }\n return snapshot\n}\n\n/** Conservative content types for VFS downloads: text formats the agent actually\n * produces; anything unrecognized ships as plain text (the VFS is string-backed). */\nconst CONTENT_TYPES: Record<string, string> = {\n json: 'application/json; charset=utf-8',\n md: 'text/markdown; charset=utf-8',\n html: 'text/html; charset=utf-8',\n csv: 'text/csv; charset=utf-8',\n xml: 'application/xml; charset=utf-8',\n svg: 'image/svg+xml; charset=utf-8',\n}\n\n/** sha256 hex — the currency of the conditional-write protocol on `/fs/write`. */\nfunction hashBytes(bytes: Buffer): string {\n return createHash('sha256').update(bytes).digest('hex')\n}\n\n/**\n * The file's text, or null if it isn't text. Decoding never fails in Node — invalid\n * bytes become U+FFFD — so the only honest test is a round trip: if re-encoding the\n * decoded string reproduces the original bytes, nothing was lost and the client can\n * safely edit and send it back. Anything else ships base64, which an editor can\n * refuse to open rather than silently corrupt on save.\n */\nfunction asUtf8(bytes: Buffer): string | null {\n const text = bytes.toString('utf8')\n return Buffer.from(text, 'utf8').equals(bytes) ? text : null\n}\n\nfunction contentTypeFor(filename: string): string {\n const ext = filename.includes('.') ? filename.split('.').pop()!.toLowerCase() : ''\n return CONTENT_TYPES[ext] ?? 'text/plain; charset=utf-8'\n}\n\n/** A profile runs the model-agnostic engine rather than Claude Code. `engine` is\n * optional so profiles written before provider support keep meaning 'claude'. */\nfunction isProviderProfile(profile: ProfileInfo): boolean {\n return profile.engine === 'provider'\n}\n\n/** The engine a profile runs, absent meaning 'claude' (pre-provider profiles). */\nfunction engineOf(profile: ProfileInfo | undefined): ProfileEngine {\n return profile?.engine ?? 'claude'\n}\n\n/** Where the CLI's own resolution lands for a given environment: an explicit\n * CLAUDE_CONFIG_DIR, else ~/.claude. */\nfunction cliConfigDir(env: Record<string, string | undefined>): string {\n return env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')\n}\n\n/** Auto-created profile when none are declared: the operator's own config dir. */\nfunction detectDefaultProfiles(): ProfileInfo[] {\n const dir = cliConfigDir(process.env)\n return existsSync(dir) ? [{ name: 'default', configDir: dir }] : []\n}\n\n/** Compare config dirs by what they name on disk: declared paths arrive with\n * trailing slashes or symlinked prefixes (`/var` vs `/private/var` on macOS); a\n * path that doesn't exist falls back to plain normalization. */\nfunction canonicalDir(path: string): string {\n try {\n return realpathSync(path)\n } catch {\n return resolvePath(path)\n }\n}\n\n/**\n * The env a Claude session under `profile` is spawned with, starting from\n * `base` (the host hook's env, else the server's own). The pin is skipped when\n * `base` would already land the CLI in the profile's dir, and that skip is\n * load-bearing, not an optimisation: CLAUDE_CONFIG_DIR *set at all* switches\n * the CLI's credential source to `<dir>/.credentials.json` — on macOS a\n * claude.ai login lives in the login Keychain, consulted only while the\n * variable is UNSET, so pinning even the CLI's own default `~/.claude` turns a\n * working login into \"Not logged in\". When `base` names a *different* dir than\n * the profile, the pin stands: the profile must win over hook- or operator-set\n * env, or sessions under two profiles quietly collapse into one identity.\n */\nfunction claudeSessionEnv(\n profile: ProfileInfo,\n base: Record<string, string | undefined>,\n): Record<string, string | undefined> {\n return canonicalDir(profile.configDir!) === canonicalDir(cliConfigDir(base))\n ? base\n : { ...base, CLAUDE_CONFIG_DIR: profile.configDir! }\n}\n\nfunction cwdAllowed(cwd: string, roots: string[] | undefined): boolean {\n if (!roots || roots.length === 0) return true\n const resolved = resolvePath(cwd)\n return roots.some((root) => {\n const r = resolvePath(root)\n return resolved === r || resolved.startsWith(r + sep)\n })\n}\n\nexport function createWorkerServer(options: WorkerServerOptions = {}): WorkerServer {\n if (!options.authenticate && !options.allowUnauthenticated) {\n throw new Error(\n 'createWorkerServer: provide `authenticate` or explicitly set `allowUnauthenticated: true`',\n )\n }\n const basePath = options.basePath ?? '/v1'\n const fallback = options.fallback\n const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024\n /** The engine's adapter, honoring the test-only `engines` override. */\n const adapterFor = (engine: ProfileEngine | undefined): EngineAdapter =>\n options.engines?.[engine ?? 'claude'] ?? getEngineAdapter(engine)\n const hostBuildRunnerConfig =\n options.buildRunnerConfig ?? ((req: CreateSessionRequest): SessionRunnerConfig => req)\n\n // Profiles: declared at startup, or a single 'default' auto-created from the\n // operator's own config dir. Misdeclared dirs fail fast — the CLI would otherwise\n // silently start from an empty config (and a different credential chain).\n const declared = options.profiles ?? detectDefaultProfiles()\n const declaredByName = new Map(declared.map((p) => [p.name, p]))\n if (declaredByName.size !== declared.length) {\n throw new Error('createWorkerServer: duplicate profile names in `profiles`')\n }\n\n /**\n * Everything wrong with a profile that the server can tell without running it.\n * Shared by startup (where it throws) and the management routes (where it 400s),\n * so a profile created over HTTP can never be one startup would have refused.\n */\n const validateProfile = (p: ProfileInfo): string | null => {\n if (isProviderProfile(p)) {\n // Provider profiles have no config dir; they need an engine factory to\n // build a runner at all, so refuse up front rather than at create time.\n if (!p.provider?.id) return `provider profile '${p.name}' is missing provider.id`\n if (!options.createEngineRunner) {\n return (\n `profile '${p.name}' uses engine 'provider' but no ` +\n '`createEngineRunner` was provided to build one'\n )\n }\n } else if (p.engine === 'codex') {\n // The codexHome pin mirrors the claude configDir rule: a declared dir\n // must exist. Unset is fine — the binary's own ~/.codex.\n if (p.codexHome && !existsSync(p.codexHome)) {\n return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`\n }\n // No instructions surface exists (codex reads AGENTS.md from the cwd);\n // refusing beats silently dropping what the operator wrote.\n if (p.session?.instructions) {\n return (\n `profile '${p.name}' declares session.instructions, which the codex engine cannot ` +\n 'deliver — put instructions in the target repo’s AGENTS.md instead'\n )\n }\n } else if (!p.configDir || !existsSync(p.configDir)) {\n return `profile '${p.name}' configDir does not exist: ${p.configDir}`\n }\n if (options.disableBypassPermissions && p.defaults?.permissionMode === 'bypassPermissions') {\n return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`\n }\n // A default the profile's own engine can't run is misconfiguration: catch it\n // here rather than on every create under that profile.\n const fallbackMode = p.defaults?.permissionMode\n if (fallbackMode && !supportsPermissionMode(p.engine, fallbackMode)) {\n return (\n `profile '${p.name}' defaults to permission mode '${fallbackMode}', which engine ` +\n `'${engineOf(p)}' does not support (supported: ` +\n `${adapterFor(engineOf(p)).capabilities.permissionModes.join(', ')})`\n )\n }\n return null\n }\n\n for (const p of options.profiles ?? []) {\n const invalid = validateProfile(p)\n if (invalid) throw new Error(`createWorkerServer: ${invalid}`)\n }\n\n /**\n * Store-managed profiles, mirrored in memory so every lookup on the request path\n * stays synchronous. Loaded once at `listen()` and refreshed after each mutation\n * — single-process, exactly like the bundled queue adapter.\n */\n const stored = new Map<string, ProfileInfo>()\n const refreshStored = async (): Promise<void> => {\n if (!options.profileStore) return\n stored.clear()\n for (const p of await options.profileStore.list()) stored.set(p.name, p)\n }\n\n /** Response-only marker so a UI knows which rows it may edit. Declared profiles\n * are code; only store-backed ones can be changed over the API. */\n const withManagedFlag = (p: ProfileInfo): ProfileInfo =>\n declaredByName.has(p.name) ? p : { ...p, managed: true }\n\n /**\n * Response shape for a profile: the managed marker, the engine's capability\n * record, its static model catalog (correct from the first request — no\n * warm-up session, no process spawned), the availability verdict when one\n * has been probed, and the learned default model (the one thing a static\n * catalog cannot know: a claude profile's default is the operator's CLI\n * config, so it stays absent until a session on the profile reports it).\n * Read-only decoration — never persisted.\n */\n const forResponse = (p: ProfileInfo): ProfileInfo => {\n const adapter = adapterFor(p.engine)\n const base: ProfileInfo = {\n ...withManagedFlag(p),\n capabilities: adapter.capabilities,\n }\n // Provider model ids are operator-declared (provider.models); an empty\n // catalog must not shadow them with an empty picker.\n if (adapter.catalog.models.length > 0) base.models = adapter.catalog.models\n const defaultModel = profileDefaultModels.get(p.name)\n if (defaultModel) base.defaultModel = defaultModel\n const probed = availability.get(p.name)?.verdict\n if (probed && probed.available !== 'unknown') {\n base.available = probed.available\n if (probed.available === false) base.unavailableReason = probed.reason\n }\n return base\n }\n\n /** Declared profiles first: a name collision means the code wins, and the stored\n * one is unreachable rather than silently overriding server options. */\n const allProfiles = (): ProfileInfo[] => [\n ...declared,\n ...[...stored.values()].filter((p) => !declaredByName.has(p.name)),\n ]\n const profileFor = (name: string): ProfileInfo | undefined =>\n declaredByName.get(name) ?? stored.get(name)\n\n type Refusal = { status: number; error: string }\n\n /** Profile management is doubly opt-in: the operator wires a store, and the host\n * marks the principal. Neither on its own is enough. */\n const manageGuard = (auth: { canManageProfiles?: boolean }): Refusal | null => {\n if (!options.profileStore) {\n return { status: 404, error: 'profile management is not enabled on this server' }\n }\n if (!auth.canManageProfiles) {\n return { status: 403, error: 'not allowed to manage profiles' }\n }\n return null\n }\n\n /** Startup-declared profiles are code. Editing one over HTTP would make the\n * server options lie about what is actually running. */\n const declaredGuard = (profile: ProfileInfo): Refusal | null =>\n declaredByName.has(profile.name)\n ? {\n status: 403,\n error:\n `profile '${profile.name}' is declared in server options and cannot be changed ` +\n 'over the API — edit the `profiles` option instead',\n }\n : null\n\n /**\n * A managed Claude profile names a config directory, and that directory is a\n * credential store. Bound it to operator-declared roots; unset roots means the\n * management routes create provider profiles only.\n */\n const configDirGuard = (profile: ProfileInfo): Refusal | null => {\n if (isProviderProfile(profile)) return null\n const roots = options.allowedConfigDirRoots\n if (!roots || roots.length === 0) {\n return {\n status: 403,\n error:\n 'managed Claude profiles are disabled: set `allowedConfigDirRoots` to the ' +\n 'directories they may point at',\n }\n }\n return profile.configDir && cwdAllowed(profile.configDir, roots)\n ? null\n : { status: 403, error: 'configDir is outside the allowed roots' }\n }\n\n /** Validate, persist, and re-read: shared by create and update so a PATCH can\n * never leave behind a profile a POST would have refused. */\n const saveManagedProfile = async (res: ServerResponse, incoming: ProfileInfo): Promise<void> => {\n // `managed` is server-computed on every response; never persist a client's copy.\n const { managed: _clientClaim, ...profile } = incoming\n const refused = configDirGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const invalid = validateProfile(profile)\n if (invalid) {\n json(res, 400, { error: invalid })\n return\n }\n await options.profileStore!.save(profile)\n await refreshStored()\n json(res, 200, { profile: withManagedFlag(profile) })\n }\n\n /** Enforce the server's bypass policy on a create request. Returns a 403 message\n * for an explicit bypass-mode request; strips the pre-authorization capability\n * silently (see the option's doc for why). */\n const applyBypassPolicy = (req: CreateSessionRequest): string | null => {\n if (!options.disableBypassPermissions) return null\n if (req.permissionMode === 'bypassPermissions') {\n return 'bypassPermissions is disabled on this server (disableBypassPermissions)'\n }\n delete req.allowDangerouslySkipPermissions\n return null\n }\n\n /** Reject a permission mode the resolved profile's engine has no meaning for.\n * The create form already filters what it offers, but the API is the boundary:\n * a provider session asked for 'plan' should be told so, not silently coerced\n * into 'default' by whatever assembles its runner. Returns an error message. */\n const checkPermissionMode = (\n mode: PermissionMode | undefined,\n profile: ProfileInfo | undefined,\n ): string | null => {\n if (mode === undefined || supportsPermissionMode(profile?.engine, mode)) return null\n return (\n `permission mode '${mode}' is not supported by profile '${profile!.name}' ` +\n `(engine '${engineOf(profile)}') — supported: ` +\n adapterFor(profile?.engine).capabilities.permissionModes.join(', ')\n )\n }\n\n /**\n * Refuse the request fields the resolved profile's engine cannot honor —\n * read off its capability record, so the create form's filtering and the\n * API boundary can never disagree. Refusing beats coercing: a caller who\n * asked for something the engine has no meaning for should be told, not\n * left wondering where the option went. Also enforces the provider grant\n * rules (capabilities narrow, never widen; MCP servers are the profile's to\n * declare — MCP tools are authoritative, server-side, with server\n * credentials, so honoring a client-supplied server would let a caller\n * point an authoritative tool anywhere it liked).\n */\n const checkEngineGrants = (\n req: CreateSessionRequest,\n profile: ProfileInfo | undefined,\n ): string | null => {\n const engine = engineOf(profile)\n const caps = adapterFor(profile?.engine).capabilities\n const name = profile?.name ?? 'default'\n if (!caps.sessionMcpServers && req.mcpServers && Object.keys(req.mcpServers).length > 0) {\n return (\n `profile '${name}' runs the ${engine} engine, whose MCP servers are declared ` +\n 'outside the session request — a request cannot add its own'\n )\n }\n if (!caps.budgets && (req.maxTurns !== undefined || req.maxBudgetUsd !== undefined)) {\n return `the ${engine} engine does not honor maxTurns/maxBudgetUsd`\n }\n if (!caps.settingSources && req.settingSources !== undefined) {\n return `the ${engine} engine does not load settingSources`\n }\n if (!caps.resume && req.resume !== undefined) {\n return `the ${engine} engine cannot resume a session`\n }\n if (req.forkSession && engine !== 'claude') {\n return `the ${engine} engine cannot fork a resumed session`\n }\n if (\n req.reasoningEffort !== undefined &&\n (!caps.reasoningEfforts || caps.reasoningEfforts.length === 0)\n ) {\n return `the ${engine} engine does not take a reasoningEffort`\n }\n if (!profile || !isProviderProfile(profile)) return null\n const granted = profile.session?.capabilities\n if (!req.capabilities || !granted) return null\n const ungranted = req.capabilities.filter((c) => !granted.includes(c))\n if (ungranted.length === 0) return null\n return (\n `profile '${profile.name}' does not grant: ${ungranted.join(', ')} ` +\n `(granted: ${granted.join(', ') || 'none'}) — a request may narrow capabilities, not widen them`\n )\n }\n\n /** Drop request fields that are meaningless (not wrong) for the engine —\n * today just `questionBehavior` where no approval channel exists, so job\n * webhooks never grow phantom permission_requested expectations. */\n const stripInertFields = (req: CreateSessionRequest, profile: ProfileInfo | undefined): void => {\n if (!adapterFor(profile?.engine).capabilities.interactiveApprovals) {\n delete req.questionBehavior\n }\n }\n\n /** Profile-aware config hook: fill the profile's defaults into unset request fields,\n * run the host hook, then pin CLAUDE_CONFIG_DIR — the profile wins even when the\n * host hook set its own env (see `claudeSessionEnv` for the one case the pin is\n * skipped, and why). Handed to the queue too, so jobs inherit profiles. */\n const buildRunnerConfig = (req: CreateSessionRequest): SessionRunnerConfig => {\n const profile = req.profile !== undefined ? profileFor(req.profile) : undefined\n if (!profile) return hostBuildRunnerConfig(req)\n const config = hostBuildRunnerConfig({\n ...req,\n model: req.model ?? profile.defaults?.model ?? profile.provider?.model,\n permissionMode: req.permissionMode ?? profile.defaults?.permissionMode,\n })\n // Only claude profiles have a config dir to pin. Provider credentials come\n // from the operator's environment through the engine factory; a codex\n // profile's CODEX_HOME pin is applied by its adapter (the runner builds the\n // child env, because CodexOptions.env replaces rather than merges).\n if (engineOf(profile) !== 'claude') return config\n const base = config.env ?? process.env\n const env = claudeSessionEnv(profile, base)\n // A skipped pin returns `base` itself — leave the config alone so an unset\n // `env` stays unset (the SDK then spawns on process.env, unmaterialized).\n return env === base ? config : { ...config, env }\n }\n\n /** Build a runner for a session, choosing the engine from its profile. Async\n * because the engine factory may be: a provider session can need an awaited\n * assembly step (per-session MCP connect) before it has a runner at all.\n *\n * `restore` rebuilds a parked session rather than creating a new one — same id,\n * same log, mid-task. */\n const buildRunner = async (\n config: SessionRunnerConfig,\n restore?: RunnerSnapshot,\n ): Promise<Runner> => {\n const name = config.profile\n const profile = name !== undefined ? profileFor(name) : undefined\n if (name !== undefined && !profile) {\n // Only reachable on a resume: profiles can be deleted between park and\n // wake-up, and the session cannot be rebuilt without the one it ran on.\n throw new Error(`unknown profile: ${name}`)\n }\n if (profile && isProviderProfile(profile)) {\n // Guaranteed present: startup refuses provider profiles without a factory.\n return options.createEngineRunner!({ config, profile, bridge, restore })\n }\n // claude and codex ship as in-repo adapters; each refuses `restore` itself\n // (neither engine can rebuild a parked session — the binary owns its state).\n return adapterFor(profile?.engine).createRunner({ config, profile, restore })\n }\n\n const createRunner = async (config: SessionRunnerConfig): Promise<Runner> => {\n const runner = registry.register(await buildRunner(config))\n // Watchers first, then start: a session must not emit anything before the\n // things that persist and account for it are listening.\n parking.remember(runner.id, config)\n parking.watch(runner)\n void runner.start()\n return runner\n }\n\n /** Resolve a request's profile: required when several are declared, implicit with\n * exactly one, scoped by the principal's allowedProfiles. Returns the resolved\n * profile (undefined when the server declares none) or a response-ready error. */\n const resolveProfile = (\n name: unknown,\n allowedProfiles: string[] | undefined,\n ): { ok: true; profile?: ProfileInfo } | { ok: false; status: number; error: string } => {\n if (name !== undefined && typeof name !== 'string') {\n return { ok: false, status: 400, error: 'profile must be a string' }\n }\n const profiles = allProfiles()\n if (profiles.length === 0) {\n return name !== undefined\n ? { ok: false, status: 400, error: 'no profiles are configured on this server' }\n : { ok: true }\n }\n const effective = name ?? (profiles.length === 1 ? profiles[0]!.name : undefined)\n if (effective === undefined) {\n const available = profiles.map((p) => p.name).join(', ')\n return { ok: false, status: 400, error: `profile is required (available: ${available})` }\n }\n const profile = profileFor(effective)\n if (!profile) return { ok: false, status: 400, error: `unknown profile: ${effective}` }\n if (allowedProfiles && !allowedProfiles.includes(profile.name)) {\n return { ok: false, status: 403, error: `profile not allowed: ${profile.name}` }\n }\n return { ok: true, profile }\n }\n\n // Notifications ride the registry hook rather than the create paths, because the\n // session that most needs to reach a phone may be one that parked and was\n // rebuilt — and that path never goes near `createRunner`.\n const notifier = new SessionNotifier(options.notifications ?? {})\n /**\n * What each claude profile's *default* model resolves to, learned from the\n * `capabilities` events of sessions that ran on it. The model *list* is the\n * adapter's static catalog now; the default is the one thing a catalog\n * cannot know (it is the operator's CLI config), so it alone is still\n * learned — and still absent on a cold server, the accepted regression.\n */\n const profileDefaultModels = new Map<string, string>()\n const producedFiles = new ProducedFileStore()\n const registry = new SessionRegistry({\n onRegister: (runner) => {\n notifier.watch(runner)\n // Same hook, opposite replay choice: from 0, because a rebuilt session's\n // earlier pictures must stay fetchable (see ProducedFileStore.watch).\n producedFiles.watch(runner)\n const profile = runner.info().profile\n if (!profile) return\n runner.subscribe((event) => {\n if (event.type !== 'capabilities' || !event.defaultModel) return\n profileDefaultModels.set(profile, event.defaultModel)\n })\n },\n })\n const attachmentStore = new AttachmentStore(options.attachments)\n const bridge = new BridgeHub({\n ...options.bridge,\n onResult: (sessionId, executionId, result) => {\n // A runner that executes out-of-band (the model-agnostic engine bridging\n // to a browser tab) gets the result fed straight back into its loop —\n // operators don't wire this themselves. The host callback still fires,\n // for observability.\n registry.get(sessionId)?.settleExecution?.(executionId, result)\n options.bridge?.onResult?.(sessionId, executionId, result)\n },\n })\n const parking = new SessionParkManager({\n registry,\n store: options.parking?.store ?? new MemorySessionStore(),\n parkDelayMs: options.parking?.parkDelayMs,\n expiredGraceMs: options.parking?.expiredGraceMs,\n onError: options.parking?.onError,\n rebuild: (record) => buildRunner(record.config, record.snapshot),\n attachedCount: (sessionId) => bridge.attachedCount(sessionId),\n // Accounting is the queue's: it frees the run's slot and stops its clock, and\n // refuses the park outright when the run is already finalizing.\n onParking: (sessionId, executionId) => queue?.onSessionParking(sessionId, executionId) ?? true,\n onResumed: (sessionId, runner) => queue?.onSessionResumed(sessionId, runner),\n })\n const wss = new WebSocketServer({ noServer: true })\n /** Profiles (by name; '' = none) whose oauth notice has been logged. */\n const subscriptionNoticeShown = new Set<string>()\n\n // Live queue watchers (`{basePath}/queue/ws`): every job event is fanned out, and\n // lifecycle changes push refreshed stats so dashboards stay current without polling.\n const queueSockets = new Set<WebSocket>()\n const sendQueueFrame = (ws: WebSocket, frame: QueueServerFrame): void => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame))\n }\n const broadcastJobEvent = (event: JobEvent): void => {\n if (queueSockets.size === 0) return\n for (const ws of queueSockets) sendQueueFrame(ws, { type: 'job_event', event })\n if (event.type !== 'job_progress') {\n void queue\n ?.stats()\n .then((stats) => {\n for (const ws of queueSockets) sendQueueFrame(ws, { type: 'queue_stats', stats })\n })\n .catch(() => {})\n }\n }\n\n const queue = options.queue\n ? new JobQueue({\n ...options.queue,\n onEvent: (event) => {\n try {\n options.queue?.onEvent?.(event)\n } finally {\n broadcastJobEvent(event)\n }\n },\n // Job sessions are ordinary registry sessions (attachable/watchable) and go\n // through the same config hook, engine selection, and auth-provenance\n // watcher as client sessions.\n createRunner: async (config) => {\n const runner = await createRunner(config)\n watchAuthSource(runner)\n return runner\n },\n buildRunnerConfig,\n // A run that ends while parked (canceled, killed) leaves a snapshot behind\n // that nothing will ever wake.\n discardSession: (sessionId) => parking.discard(sessionId),\n })\n : undefined\n\n // Watch each session's init handshake for its auth provenance ('oauth' = claude.ai\n // subscription). The listener is a no-op after the first init; not worth unsubscribing.\n const watchAuthSource = (runner: Runner): void => {\n let seen = false\n runner.subscribe((event) => {\n if (seen || event.type !== 'system_init') return\n seen = true\n if (event.apiKeySource !== 'oauth') return\n if (options.requireApiKey) {\n runner.fail(\n 'This server requires API-key auth (requireApiKey), but the session initialized ' +\n \"with claude.ai subscription credentials (apiKeySource 'oauth'). Set \" +\n 'ANTHROPIC_API_KEY (or Bedrock/Vertex auth) in the server environment.',\n )\n } else {\n // Per profile, not global: distinct profiles are distinct accounts, and each\n // operator deserves the notice once.\n const profileName = runner.info().profile ?? ''\n if (subscriptionNoticeShown.has(profileName)) return\n subscriptionNoticeShown.add(profileName)\n const scope = profileName ? `Sessions under profile '${profileName}'` : 'Sessions'\n console.warn(\n `[workerdeck] ${scope} are using claude.ai subscription credentials ` +\n \"(apiKeySource 'oauth'), not an API key. That is only appropriate for personal, \" +\n 'single-user use of your own account. Unattended/scheduled or multi-user use ' +\n \"requires an API key under Anthropic's terms — set ANTHROPIC_API_KEY in the \" +\n 'server environment, or set requireApiKey: true to fail closed.',\n )\n }\n })\n }\n\n /**\n * Availability, per profile: the adapter's probe run over the env the real\n * assembly path produces (so anything the host hook injects — a\n * CLAUDE_CODE_OAUTH_TOKEN, say — counts as logged in). Cached, and served on\n * `GET /profiles` as `available`/`unavailableReason`.\n *\n * Gated on `checkCredentials` like the old claude-only preflight (this is a\n * library; `pnpm test` must spawn nothing unless a test injects fake\n * adapters or probes). 'unknown' stays out of the cache's answers: a probe\n * that couldn't run is not evidence of a missing login. **Display-only**\n * downstream — session create against an unavailable profile still proceeds\n * and fails with the engine's own error, because the probe can be stale in\n * both directions and refusing on it would turn a probe bug into an outage.\n */\n const availability = new Map<string, { verdict: EngineAvailability; at: number }>()\n const AVAILABILITY_TTL_MS = 60_000\n /** Profiles already warned about on the console, so re-probes don't spam. */\n const availabilityWarned = new Set<string>()\n\n const sessionEnvFor = (profile: ProfileInfo): Record<string, string | undefined> => {\n try {\n return buildRunnerConfig({ cwd: process.cwd(), profile: profile.name }).env ?? process.env\n } catch {\n // A host hook may choke on a probe-shaped request; fall back to the\n // profile pin alone, applied exactly as the real path applies it.\n return engineOf(profile) === 'claude' ? claudeSessionEnv(profile, process.env) : process.env\n }\n }\n\n const probeProfile = (profile: ProfileInfo): void => {\n if (!options.checkCredentials) return\n const conf = options.checkCredentials === true ? {} : options.checkCredentials\n // Mark in-flight immediately so concurrent GET /profiles don't re-spawn.\n availability.set(profile.name, {\n verdict: availability.get(profile.name)?.verdict ?? { available: 'unknown' },\n at: Date.now(),\n })\n const adapter = adapterFor(profile.engine)\n // The injectable claude probe predates the adapter layer and is honored\n // for claude profiles (existing tests and hosts wire it).\n const claudeProbe: ClaudeAuthProbe | undefined =\n engineOf(profile) !== 'claude'\n ? undefined\n : (conf.probe ??\n (conf.timeoutMs !== undefined\n ? (env) => checkClaudeAuth(env, { timeoutMs: conf.timeoutMs })\n : undefined))\n const run: Promise<EngineAvailability> =\n claudeProbe\n ? claudeProbe(sessionEnvFor(profile)).then(\n (status): EngineAvailability =>\n status === 'logged_in'\n ? { available: true }\n : status === 'logged_out'\n ? { available: false, reason: 'no usable Claude credentials for this profile' }\n : { available: 'unknown' },\n )\n : adapter.checkAvailability(profile, sessionEnvFor(profile))\n void run\n .then((verdict) => {\n availability.set(profile.name, { verdict, at: Date.now() })\n if (verdict.available === false && !availabilityWarned.has(profile.name)) {\n availabilityWarned.add(profile.name)\n console.warn(\n `[workerdeck] Profile '${profile.name}' is unavailable: ${verdict.reason} ` +\n '(`checkCredentials: false` disables this check)',\n )\n }\n if (verdict.available === true) availabilityWarned.delete(profile.name)\n })\n .catch(() => {\n // a probe that breaks is 'unknown', and unknown stays silent\n })\n }\n\n /** Launch-time sweep, concurrent and fire-and-forget. */\n const preflightCredentials = (): void => {\n for (const profile of allProfiles()) probeProfile(profile)\n }\n\n /** Lazy re-probe on reads, so an operator who just ran `codex login` (or\n * exported a key) sees the profile go green without a restart. Serves the\n * cached verdict now; the refreshed one lands on the next request. */\n const refreshAvailability = (profiles: ProfileInfo[]): void => {\n if (!options.checkCredentials) return\n const now = Date.now()\n for (const profile of profiles) {\n const cached = availability.get(profile.name)\n if (!cached || now - cached.at > AVAILABILITY_TTL_MS) probeProfile(profile)\n }\n }\n\n type AuthContext = { ok: boolean; allowedProfiles?: string[]; canManageProfiles?: boolean }\n const authenticate = async (req: IncomingMessage): Promise<AuthContext> => {\n if (!options.authenticate) return { ok: true }\n const principal = await options.authenticate(req)\n if (principal === null || principal === undefined || principal === false) return { ok: false }\n const allowed = (principal as { allowedProfiles?: unknown }).allowedProfiles\n return {\n ok: true,\n allowedProfiles:\n Array.isArray(allowed) && allowed.every((p) => typeof p === 'string')\n ? (allowed as string[])\n : undefined,\n // Opt-in, and only ever true when the host says so: an unauthenticated dev\n // server (no `authenticate`) returns early above and manages nothing.\n canManageProfiles:\n (principal as { canManageProfiles?: unknown }).canManageProfiles === true,\n }\n }\n\n // Route pattern: {basePath}/sessions[/:id[/ws | /permissions/:requestId |\n // /files[/<path>] | /attachments[/:attachmentId] | /mcp[/:serverName] |\n // /produced[/:fileId]]]\n const parseRoute = (\n url: string,\n ): {\n id?: string\n ws?: boolean\n permissionId?: string\n files?: boolean\n filePath?: string\n attachments?: boolean\n attachmentId?: string\n mcp?: boolean\n mcpServer?: string\n produced?: boolean\n producedFileId?: string\n } | null => {\n const pathname = new URL(url, 'http://internal').pathname\n if (!pathname.startsWith(basePath + '/sessions')) return null\n const rest = pathname.slice((basePath + '/sessions').length)\n if (rest === '' || rest === '/') return {}\n const parts = rest.replace(/^\\//, '').split('/')\n if (parts.length === 1) return { id: decodeURIComponent(parts[0]!) }\n if (parts.length === 2 && parts[1] === 'ws') {\n return { id: decodeURIComponent(parts[0]!), ws: true }\n }\n if (parts.length === 3 && parts[1] === 'permissions') {\n return { id: decodeURIComponent(parts[0]!), permissionId: decodeURIComponent(parts[2]!) }\n }\n if (parts.length <= 3 && parts[1] === 'attachments') {\n return {\n id: decodeURIComponent(parts[0]!),\n attachments: true,\n attachmentId: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length <= 3 && parts[1] === 'produced') {\n return {\n id: decodeURIComponent(parts[0]!),\n produced: true,\n producedFileId: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length <= 3 && parts[1] === 'mcp') {\n // Server names are opaque and may contain ':' (plugin:gtm:gtm) — one segment,\n // decoded whole.\n return {\n id: decodeURIComponent(parts[0]!),\n mcp: true,\n mcpServer: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length >= 2 && parts[1] === 'files') {\n // The remainder is a VFS path — slashes are its separators, so segments\n // are decoded individually and rejoined.\n const filePath = parts.slice(2).map(decodeURIComponent).join('/')\n return {\n id: decodeURIComponent(parts[0]!),\n files: true,\n filePath: filePath === '' ? undefined : '/' + filePath,\n }\n }\n return null\n }\n\n // Host filesystem: built once at startup so a misdeclared root fails here rather\n // than on the first request from a phone. Null = the routes do not exist.\n //\n // Reading inherits the cwd policy — a caller who can start a session in a root\n // can already read it through the agent — so `hostFiles.roots` is a narrowing,\n // not the enabling grant. `??` and not `||`: an explicit `roots: []` is an\n // operator turning the routes off, which must not fall through to the cwd roots.\n const hostFileRootPaths = options.hostFiles?.roots ?? options.allowedCwdRoots\n const hostFiles = hostFileRootPaths?.length ? createHostFileRoots(hostFileRootPaths) : null\n const hostFilesWritable = options.hostFiles?.write === true\n const maxHostFileBytes = options.hostFiles?.maxFileBytes ?? 1024 * 1024\n const maxHostDirEntries = options.hostFiles?.maxEntries ?? 5000\n\n /**\n * `{basePath}/sessions/:id/attachments` — the files a client sends with a message.\n *\n * `POST ?name=<name>` takes the raw bytes as the body and the media type from\n * the `content-type` header; there is no multipart parsing here on purpose, so\n * a phone and a browser both upload with one plain request and this file stays\n * dependency-free. `GET /:attachmentId` hands the bytes back for thumbnails.\n *\n * The download always answers `content-disposition: attachment` and `nosniff`,\n * the same as `/files`: an upload is client-supplied content served from the\n * gateway's own origin, and it must never render as a document there. (An\n * `<img src>` is unaffected — disposition does not apply to subresources.)\n */\n const handleAttachments = async (\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n session: SessionInfo,\n attachmentId?: string,\n ): Promise<void> => {\n if (req.method === 'POST' && attachmentId === undefined) {\n const url = new URL(req.url ?? '/', 'http://internal')\n const mediaType = req.headers['content-type']\n if (!mediaType) {\n json(res, 400, { error: 'content-type header is required' })\n return\n }\n // The engine's capability record names the kinds its sendMessage can\n // deliver ('document' is the record's 'pdf'). Refusing here keeps the\n // contract at the door: an upload that succeeds is one the message can use.\n const accepted = (session.capabilities ?? ENGINE_CAPABILITIES[session.engine ?? 'claude'])\n .attachments\n const kind = attachmentKind(mediaType)\n if (kind && !accepted.includes(kind === 'document' ? 'pdf' : kind)) {\n json(res, 415, {\n error: `the ${session.engine ?? 'claude'} engine does not accept ${kind} attachments`,\n })\n return\n }\n let body: Buffer\n try {\n body = await readRawBody(req, attachmentStore.maxFileBytes)\n } catch {\n json(res, 413, { error: 'attachment is larger than the limit' })\n return\n }\n const result = attachmentStore.put(\n sessionId,\n url.searchParams.get('name') ?? 'attachment',\n mediaType,\n body,\n )\n if (!result.ok) {\n const status =\n result.error.code === 'unsupported_type'\n ? 415\n : result.error.code === 'empty'\n ? 400\n : 413\n json(res, status, { error: result.error.message })\n return\n }\n json(res, 201, { attachment: result.attachment })\n return\n }\n if (req.method === 'GET' && attachmentId !== undefined) {\n const found = attachmentStore.get(sessionId, attachmentId)\n if (!found) {\n json(res, 404, { error: 'attachment not found' })\n return\n }\n const bytes = Buffer.from(found.data, 'base64')\n res.writeHead(200, {\n 'content-type': found.mediaType,\n 'content-length': bytes.length,\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(found.name)}`,\n 'x-content-type-options': 'nosniff',\n })\n res.end(bytes)\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `{basePath}/sessions/:id/mcp` — the session's MCP servers, and the three\n * things the CLI's own `/mcp` screen can do to one (reconnect, enable, disable).\n *\n * Every answer goes through `mcpStatusInfo`, which is where the servers' `env`\n * and `headers` are dropped: reading this route must not be a way to read the\n * operator's API tokens.\n */\n const handleMcp = async (\n req: IncomingMessage,\n res: ServerResponse,\n runner: Runner,\n serverName?: string,\n ): Promise<void> => {\n const listServers = async (): Promise<boolean> => {\n const servers = await runner.mcpServers?.()\n if (!servers) {\n json(res, 501, { error: 'this session does not report MCP servers' })\n return false\n }\n json(res, 200, { servers })\n return true\n }\n if (req.method === 'GET' && serverName === undefined) {\n await listServers()\n return\n }\n if (req.method === 'POST' && serverName !== undefined) {\n const body = (await readJsonBody(req, maxBodyBytes)) as McpServerActionRequest\n if (body?.action !== 'reconnect' && body?.action !== 'enable' && body?.action !== 'disable') {\n json(res, 400, { error: \"action must be 'reconnect', 'enable' or 'disable'\" })\n return\n }\n // Checked before dispatching, because the calls below are optionally\n // chained: an engine that lists its servers but cannot act on one (codex)\n // would otherwise no-op and then answer 200 with the unchanged list —\n // a button that reports success having done nothing, which is worse than\n // an error. Clients hide these controls off\n // `ENGINE_CAPABILITIES[engine].mcpServerActions`; this is the door.\n const canAct =\n body.action === 'reconnect'\n ? typeof runner.reconnectMcpServer === 'function'\n : typeof runner.setMcpServerEnabled === 'function'\n if (!canAct) {\n json(res, 501, {\n error: `this session's engine cannot ${body.action} an MCP server`,\n })\n return\n }\n try {\n if (body.action === 'reconnect') await runner.reconnectMcpServer?.(serverName)\n else await runner.setMcpServerEnabled?.(serverName, body.action === 'enable')\n } catch (error) {\n // The CLI's own message (\"No MCP server found named x\") is the useful one.\n json(res, 400, { error: error instanceof Error ? error.message : 'MCP action failed' })\n return\n }\n await listServers()\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `{basePath}/sessions/:id/produced[/:fileId]` — files this session's ENGINE\n * wrote on the host (codex's generated images), listed and served.\n *\n * The one route here with no root allowlist and no byte cap, and the comment\n * on {@link ProducedFileStore} is the argument for why that is right rather\n * than lax: the allowlist is the exact set of paths this session's own runner\n * announced producing. It is emphatically NOT a hole in `/fs/*` — a path the\n * *agent* named is not a produced file and never enters this store.\n *\n * Everything else matches the attachment download: `nosniff` and an attachment\n * disposition, because these bytes are model-authored and must not render as a\n * document on the gateway's origin. (`<img src>` is unaffected — disposition\n * does not apply to subresources, which is the whole point.)\n */\n const handleProducedFiles = async (\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n fileId?: string,\n ): Promise<void> => {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (fileId === undefined) {\n json(res, 200, {\n files: producedFiles.list(sessionId).map(({ fileId: id, path, mediaType, bytes }) => ({\n fileId: id,\n path,\n ...(mediaType ? { mediaType } : {}),\n ...(bytes !== undefined ? { bytes } : {}),\n })),\n })\n return\n }\n const found = producedFiles.get(sessionId, fileId)\n if (!found) {\n json(res, 404, { error: 'no such produced file' })\n return\n }\n // Re-checked at serve time, not trusted from the announcement: the engine\n // reported this path when it wrote the file, and the file may since have\n // been moved, replaced, or turned into a directory. `statSync` follows\n // symlinks deliberately — a link the engine itself created is part of what\n // it produced, and there is no containment root here for a realpath check\n // to compare against.\n let stat\n try {\n stat = statSync(found.path)\n } catch {\n json(res, 404, { error: 'produced file is no longer on disk' })\n return\n }\n if (!stat.isFile()) {\n json(res, 404, { error: 'produced file is not a regular file' })\n return\n }\n const filename = basename(found.path) || 'file'\n res.writeHead(200, {\n 'content-type': found.mediaType ?? contentTypeFor(filename),\n 'content-length': stat.size,\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n 'x-content-type-options': 'nosniff',\n })\n // Streamed rather than read whole: a generated PNG is a couple of megabytes\n // and there is no cap on this route, so buffering would put the file size\n // straight into the gateway's heap.\n await new Promise<void>((done) => {\n const stream = createReadStream(found.path)\n stream.on('error', () => {\n // Headers are already out; the only honest signal left is a truncated\n // response, which is what destroying the socket produces.\n res.destroy()\n done()\n })\n stream.on('close', () => done())\n stream.pipe(res)\n })\n }\n\n /**\n * `{basePath}/fs/*` — the operator's real tree. Authorized by the auth key alone\n * and deliberately outside the agent permission flow: the caller is the operator.\n *\n * Every path in here goes through `host-files.ts` first, which canonicalizes and\n * *then* re-checks containment. The naive prefix compare `cwdAllowed` does would\n * be wrong at this door — the agent writes into these trees, and a symlink it\n * created is a path the operator never typed.\n */\n const handleHostFiles = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n ): Promise<void> => {\n if (!hostFiles) {\n json(res, 404, { error: 'host file access is not configured on this server' })\n return\n }\n const route = pathname.slice((basePath + '/fs/').length)\n const url = new URL(req.url ?? '/', 'http://internal')\n const requested = url.searchParams.get('path')\n\n if (route === 'roots') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n // The canonical spelling, not the operator's: every other route answers in\n // canonical paths, and a client that round-trips a root it was given must\n // land on the same tree.\n json(res, 200, {\n roots: hostFiles.roots.map(({ canonical }) => ({\n path: canonical,\n name: basename(canonical) || canonical,\n })),\n canWrite: hostFilesWritable,\n })\n return\n }\n\n if (route === 'find') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!requested) {\n json(res, 400, { error: 'path is required' })\n return\n }\n const resolved = resolveExisting(hostFiles, requested)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n if (resolved.kind !== 'dir') {\n json(res, 400, { error: 'not a directory' })\n return\n }\n // Clamped, not validated: this runs per keystroke from a phone, and a\n // client asking for 10,000 matches is a client that made a typo.\n const asked = Number(url.searchParams.get('limit') ?? '')\n const limit = Number.isFinite(asked) && asked > 0 ? Math.min(asked, 200) : 50\n const result = searchFiles(resolved.path, {\n query: url.searchParams.get('q') ?? '',\n limit,\n ignore: options.hostFiles?.ignore,\n })\n json(res, 200, { base: resolved.path, ...result })\n return\n }\n\n if (route === 'list' || route === 'read') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!requested) {\n json(res, 400, { error: 'path is required' })\n return\n }\n const resolved = resolveExisting(hostFiles, requested)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n if (route === 'list') {\n if (resolved.kind !== 'dir') {\n json(res, 400, { error: 'not a directory' })\n return\n }\n let names: Dirent[]\n try {\n names = readdirSync(resolved.path, { withFileTypes: true })\n } catch {\n json(res, 403, { error: 'directory is not readable' })\n return\n }\n const truncated = names.length > maxHostDirEntries\n const entries = names.slice(0, maxHostDirEntries).map((entry) => {\n const path = join(resolved.path, entry.name)\n const type = entryKind(entry)\n // Size/mtime for regular files only, and via lstat — a listing must\n // never stat *through* a link, or a directory holding a link to a fifo\n // becomes an unlistable directory.\n let bytes: number | undefined\n let modifiedAt: number | undefined\n if (type === 'file') {\n try {\n const s = lstatSync(path)\n bytes = s.size\n modifiedAt = s.mtimeMs\n } catch {\n // Raced with a delete, or unreadable — the entry still lists.\n }\n }\n return { name: entry.name, path, type, bytes, modifiedAt }\n })\n entries.sort((a, b) => {\n const rank = (t: string): number => (t === 'dir' ? 0 : 1)\n return rank(a.type) - rank(b.type) || a.name.localeCompare(b.name)\n })\n json(res, 200, { path: resolved.path, entries, ...(truncated ? { truncated } : {}) })\n return\n }\n\n if (resolved.kind !== 'file') {\n json(res, 400, { error: 'not a regular file' })\n return\n }\n // Cheap pre-check so a gigabyte is refused rather than buffered. Advisory\n // only — the authoritative cap is on the bytes actually read, since the\n // file can grow between the stat and the open.\n let modifiedAt = 0\n try {\n const stats = lstatSync(resolved.path)\n if (stats.size > maxHostFileBytes) {\n json(res, 413, { error: `file is larger than ${maxHostFileBytes} bytes` })\n return\n }\n modifiedAt = stats.mtimeMs\n } catch {\n json(res, 404, { error: 'not found' })\n return\n }\n // Opens the canonical path with O_NOFOLLOW and gates on fstat, so a\n // component swapped for a symlink or a fifo after the resolve is refused\n // rather than followed or blocked on.\n const read = readContained(resolved.path)\n if (!read.ok) {\n json(res, read.status, { error: read.error })\n return\n }\n if (read.data.length > maxHostFileBytes) {\n json(res, 413, { error: `file is larger than ${maxHostFileBytes} bytes` })\n return\n }\n const text = asUtf8(read.data)\n json(res, 200, {\n path: resolved.path,\n content: text ?? read.data.toString('base64'),\n encoding: text === null ? 'base64' : 'utf8',\n bytes: read.data.length,\n hash: hashBytes(read.data),\n modifiedAt,\n })\n return\n }\n\n if (route === 'write') {\n if (req.method !== 'PUT') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!hostFilesWritable) {\n json(res, 403, { error: 'host file writes are not enabled on this server' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as WriteHostFileRequest\n if (!body.path || typeof body.path !== 'string') {\n json(res, 400, { error: 'path is required' })\n return\n }\n if (typeof body.content !== 'string') {\n json(res, 400, { error: 'content is required' })\n return\n }\n if (body.encoding !== undefined && body.encoding !== 'utf8' && body.encoding !== 'base64') {\n json(res, 400, { error: \"encoding must be 'utf8' or 'base64'\" })\n return\n }\n const resolved = resolveForWrite(hostFiles, body.path)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const next = Buffer.from(body.content, body.encoding ?? 'utf8')\n if (next.length > maxHostFileBytes) {\n json(res, 413, { error: `content is larger than ${maxHostFileBytes} bytes` })\n return\n }\n // The agent is editing this same tree. Every write is conditional: either it\n // creates a file that does not exist, or it names the hash it is replacing.\n // There is no unconditional overwrite to reach for from a phone.\n //\n // Existence is decided by the read, not by a stat: `readContained` answers\n // 404 only for ENOENT, so anything else — an unreadable file, a swapped-in\n // device — refuses here instead of being mistaken for \"not there yet\" and\n // then clobbered as a create.\n const current = readContained(resolved.path)\n if (!current.ok && current.status !== 404) {\n json(res, current.status, { error: current.error })\n return\n }\n const existing = current.ok ? current.data : null\n if (existing && !body.expectedHash) {\n json(res, 409, { error: 'file exists — pass expectedHash to overwrite it' })\n return\n }\n if (existing && hashBytes(existing) !== body.expectedHash) {\n json(res, 409, { error: 'file changed on disk since it was read' })\n return\n }\n if (!existing && body.expectedHash) {\n json(res, 409, { error: 'file no longer exists' })\n return\n }\n const written = writeContained(resolved.path, next)\n if (!written.ok) {\n json(res, written.status, { error: written.error })\n return\n }\n let writtenAt = 0\n try {\n writtenAt = lstatSync(resolved.path).mtimeMs\n } catch {\n // The write landed; a stat that loses a race with a delete is not a\n // reason to report failure.\n }\n json(res, 200, {\n path: resolved.path,\n bytes: next.length,\n hash: hashBytes(next),\n modifiedAt: writtenAt,\n })\n return\n }\n\n json(res, 404, { error: 'not found' })\n }\n\n /**\n * `GET /sdk-sessions`, engine-aware: `?profile=` names whose on-disk store to\n * list, and the profile's engine adapter answers (for codex, over a\n * short-lived `thread/list` child — no live session involved). Absent\n * `profile`, the choice is implicit when the server declares exactly one\n * profile (the resolveProfile rule); with several, the Claude engine's\n * global store is listed — the pre-engine-aware behavior every existing\n * caller already gets, kept because old clients cannot answer a new 400.\n * The injectable `listSdkSessions` option predates the adapter layer and is\n * honored for the claude engine only (existing tests and hosts wire it),\n * exactly like the injectable claude auth probe.\n */\n const handleSdkSessions = async (\n req: IncomingMessage,\n res: ServerResponse,\n auth: AuthContext,\n ): Promise<void> => {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const url = new URL(req.url ?? '/', 'http://internal')\n const dir = url.searchParams.get('dir') ?? undefined\n const roots = options.allowedCwdRoots\n const limit = Number(url.searchParams.get('limit') ?? '') || undefined\n const offset = Number(url.searchParams.get('offset') ?? '') || undefined\n const requested = url.searchParams.get('profile') ?? undefined\n let profile: ProfileInfo | undefined\n if (requested !== undefined) {\n const resolved = resolveProfile(requested, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n profile = resolved.profile\n } else {\n // Implicit only when unambiguous AND permitted — a caller scoped away\n // from the server's one profile falls back to the legacy listing rather\n // than being handed a store it may not create sessions in.\n const all = allProfiles()\n if (all.length === 1 && (!auth.allowedProfiles || auth.allowedProfiles.includes(all[0]!.name))) {\n profile = all[0]\n }\n }\n const adapter = adapterFor(profile?.engine)\n if (!adapter.capabilities.listSessions) {\n json(res, 400, {\n error:\n `profile '${profile?.name ?? 'default'}' runs the ${engineOf(profile)} engine, ` +\n 'which has no browsable session store',\n })\n return\n }\n const lister: SdkSessionLister =\n engineOf(profile) === 'claude' && options.listSdkSessions\n ? options.listSdkSessions\n : (params) => {\n if (!adapter.listSessions) {\n throw new Error(`the ${engineOf(profile)} engine does not implement session listing`)\n }\n return adapter.listSessions({\n ...params,\n profile,\n env: profile ? sessionEnvFor(profile) : process.env,\n })\n }\n try {\n if (roots && roots.length > 0) {\n if (dir) {\n if (!cwdAllowed(dir, roots)) {\n json(res, 403, { error: 'dir is outside the allowed roots' })\n return\n }\n } else {\n // A bare listing spans ALL projects on the host, which is wider than the\n // cwd policy. Rather than refuse — a client with no directory to name (the\n // iOS session list) has no other way to ask — list them and drop the ones\n // outside the roots.\n //\n // Filtering, not fanning out over the roots: `dir` selects one project\n // directory and its worktrees, not everything beneath it, so asking for\n // `/Users/me/projects` finds nothing when the sessions belong to\n // `/Users/me/projects/some-app`. Pagination is applied after the filter for\n // the same reason, which is why the underlying call takes neither bound.\n json(res, 200, { sdkSessions: withinRoots(await lister({}), roots, limit, offset) })\n return\n }\n }\n json(res, 200, { sdkSessions: await lister({ dir, limit, offset }) })\n } catch (error) {\n // The engine's own message (binary missing, store unreadable) is the\n // useful one; listing is read-only, so surfacing it verbatim is safe.\n json(res, 500, { error: error instanceof Error ? error.message : 'failed to list sessions' })\n }\n }\n\n /** The sessions whose `cwd` is inside the roots, newest first, then paged. A\n * summary with no `cwd` cannot be shown to be inside them, so it is dropped. */\n const withinRoots = (\n sessions: SdkSessionSummary[],\n roots: string[],\n limit?: number,\n offset = 0,\n ): SdkSessionSummary[] => {\n const allowed = sessions\n .filter((s) => s.cwd !== undefined && cwdAllowed(s.cwd, roots))\n .sort((a, b) => b.lastModified - a.lastModified)\n return limit === undefined ? allowed.slice(offset) : allowed.slice(offset, offset + limit)\n }\n\n const handleJobs = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n ): Promise<void> => {\n if (!queue) {\n json(res, 404, { error: 'job queue not configured' })\n return\n }\n if (pathname === basePath + '/queue') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n json(res, 200, { stats: await queue.stats() })\n return\n }\n const rest = pathname.slice((basePath + '/jobs').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n json(res, 200, { jobs: await queue.list() })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, maxBodyBytes)) as CreateJobRequest\n if (!body.session || typeof body.session !== 'object') {\n json(res, 400, { error: 'session is required' })\n return\n }\n if (!body.session.cwd || typeof body.session.cwd !== 'string') {\n json(res, 400, { error: 'session.cwd is required' })\n return\n }\n if (!body.session.prompt || typeof body.session.prompt !== 'string') {\n json(res, 400, { error: 'session.prompt is required' })\n return\n }\n if (!cwdAllowed(body.session.cwd, options.allowedCwdRoots)) {\n json(res, 403, { error: 'cwd is outside the allowed roots' })\n return\n }\n const refused = applyBypassPolicy(body.session)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = resolveProfile(body.session.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const badRequest =\n checkPermissionMode(body.session.permissionMode, resolved.profile) ??\n checkEngineGrants(body.session, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n stripInertFields(body.session, resolved.profile)\n // Normalize to the resolved name so an implicit single profile still lands\n // on JobInfo.profile and reaches the runner config at claim time.\n body.session.profile = resolved.profile?.name\n try {\n json(res, 201, { job: await queue.submit(body) })\n } catch (error) {\n json(res, 400, { error: error instanceof Error ? error.message : 'invalid job' })\n }\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const id = decodeURIComponent(rest)\n if (id.includes('/')) {\n json(res, 404, { error: 'not found' })\n return\n }\n if (req.method === 'GET') {\n const job = await queue.get(id)\n if (job) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n if (req.method === 'DELETE') {\n const job = await queue.cancel(id)\n if (job) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `POST {basePath}/executions/:executionId/result` — a deferred executor\n * delivering its outcome. Wakes the parked session, applies the result to its\n * agent loop, and lets the run continue.\n *\n * Scoped like every other session route: a principal restricted to certain\n * profiles cannot settle an execution belonging to a session outside them —\n * a result is trusted tool input, and injecting one into another tenant's loop\n * would be a way to steer it.\n */\n const handleExecutionResult = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n ): Promise<void> => {\n const rest = pathname.slice((basePath + '/executions/').length).split('/')\n if (rest.length !== 2 || rest[1] !== 'result' || !rest[0]) {\n json(res, 404, { error: 'not found' })\n return\n }\n if (req.method !== 'POST') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const executionId = decodeURIComponent(rest[0])\n const body = (await readJsonBody(req, maxBodyBytes)) as SubmitExecutionResultRequest\n let result: ToolExecutionResult\n if (body?.status === 'ok') {\n if (!body.output || typeof body.output !== 'object') {\n json(res, 400, { error: \"output is required for status 'ok'\" })\n return\n }\n result = { status: 'ok', output: body.output.value, logs: body.logs }\n } else if (body?.status === 'failed') {\n if (typeof body.reason !== 'string' || typeof body.error !== 'string') {\n json(res, 400, { error: \"reason and error are required for status 'failed'\" })\n return\n }\n result = { status: 'failed', reason: body.reason, error: body.error, logs: body.logs }\n } else {\n json(res, 400, { error: \"status must be 'ok' or 'failed'\" })\n return\n }\n if (auth.allowedProfiles) {\n const owner = parking.sessionFor(executionId)\n const profile =\n owner === undefined\n ? undefined\n : (registry.get(owner)?.info().profile ?? (await parking.get(owner))?.profile)\n // Indistinguishable from an unknown id on purpose: whether an execution\n // exists elsewhere is not this caller's business.\n if (owner === undefined || (profile !== undefined && !auth.allowedProfiles.includes(profile))) {\n json(res, 404, { error: 'execution not found' })\n return\n }\n }\n const applied = await parking.submitResult(executionId, result)\n if (!applied) {\n json(res, 404, { error: 'execution not found (unknown id, or its session has ended)' })\n return\n }\n json(res, 200, applied)\n }\n\n const handleRequest = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const pathname = new URL(req.url ?? '/', 'http://internal').pathname\n // Everything outside basePath belongs to the host, if it wants it. Checked\n // first so the fallback owns a total, contiguous namespace rather than\n // whatever the route table happens to leave over.\n if (fallback && pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n await fallback(req, res)\n return\n }\n if (\n pathname === basePath + '/jobs' ||\n pathname.startsWith(basePath + '/jobs/') ||\n pathname === basePath + '/queue'\n ) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleJobs(req, res, pathname, auth)\n return\n }\n if (pathname === basePath + '/profiles' || pathname.startsWith(basePath + '/profiles/')) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n const rest = pathname.slice((basePath + '/profiles').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n const visible = auth.allowedProfiles\n ? allProfiles().filter((p) => auth.allowedProfiles!.includes(p.name))\n : allProfiles()\n // Stale-while-revalidate: answer from the cache, re-probe anything\n // older than the TTL so the next read reflects a fresh login.\n refreshAvailability(visible)\n json(res, 200, {\n profiles: visible.map(forResponse),\n canManage: manageGuard(auth) === null,\n })\n return\n }\n if (req.method === 'POST') {\n const refused = manageGuard(auth)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as ProfileInfo\n if (!body.name || typeof body.name !== 'string') {\n json(res, 400, { error: 'name is required' })\n return\n }\n if (profileFor(body.name)) {\n json(res, 409, { error: `profile already exists: ${body.name}` })\n return\n }\n await saveManagedProfile(res, body)\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const name = decodeURIComponent(rest)\n const profile = name.includes('/') ? undefined : profileFor(name)\n if (!profile) {\n json(res, 404, { error: 'profile not found' })\n return\n }\n if (auth.allowedProfiles && !auth.allowedProfiles.includes(profile.name)) {\n json(res, 403, { error: `profile not allowed: ${profile.name}` })\n return\n }\n if (req.method === 'GET') {\n json(res, 200, { profile: withManagedFlag(profile), config: readProfileConfig(profile) })\n return\n }\n if (req.method === 'PATCH' || req.method === 'DELETE') {\n const refused = manageGuard(auth) ?? declaredGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n if (req.method === 'DELETE') {\n await options.profileStore!.delete(profile.name)\n await refreshStored()\n res.writeHead(204).end()\n return\n }\n const patch = (await readJsonBody(req, maxBodyBytes)) as UpdateProfileRequest\n // `name` is the route, not the body — a rename would orphan every session\n // and job already pinned to the old one.\n await saveManagedProfile(res, { ...profile, ...patch, name: profile.name })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (pathname.startsWith(basePath + '/executions/')) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleExecutionResult(req, res, pathname, auth)\n return\n }\n if (pathname === basePath + '/sdk-sessions') {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleSdkSessions(req, res, auth)\n return\n }\n if (pathname.startsWith(basePath + '/fs/')) {\n // Authenticated before the 404-when-unconfigured answer, so an unauthenticated\n // caller cannot learn whether this server exposes a filesystem at all.\n if (!(await authenticate(req)).ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleHostFiles(req, res, pathname)\n return\n }\n const route = parseRoute(req.url ?? '/')\n if (!route || route.ws) {\n json(res, 404, { error: 'not found' })\n return\n }\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n\n if (!route.id) {\n if (req.method === 'GET') {\n // Parked sessions are live sessions that happen to have no runner right\n // now — leaving them out would read as \"gone\".\n json(res, 200, { sessions: [...registry.list(), ...(await parking.listInfo())] })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, maxBodyBytes)) as CreateSessionRequest\n if (!body.cwd || typeof body.cwd !== 'string') {\n json(res, 400, { error: 'cwd is required' })\n return\n }\n if (!cwdAllowed(body.cwd, options.allowedCwdRoots)) {\n json(res, 403, { error: 'cwd is outside the allowed roots' })\n return\n }\n const refused = applyBypassPolicy(body)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = resolveProfile(body.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const badRequest =\n checkPermissionMode(body.permissionMode, resolved.profile) ??\n checkEngineGrants(body, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n stripInertFields(body, resolved.profile)\n // Resolved name (even when implicit) so SessionInfo.profile is always set.\n body.profile = resolved.profile?.name\n const runner = await createRunner(buildRunnerConfig(body))\n watchAuthSource(runner)\n json(res, 201, { session: runner.info() })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n\n const runner = registry.get(route.id)\n // A parked session has no runner but is very much alive: it reads, lists, and\n // serves its files from the snapshot, and only waking it needs a rebuild.\n const parked = runner ? null : await parking.get(route.id)\n if (!runner && !parked) {\n json(res, 404, { error: 'session not found' })\n return\n }\n if (route.attachments) {\n await handleAttachments(req, res, route.id, runner?.info() ?? parked!.info, route.attachmentId)\n return\n }\n if (route.mcp) {\n if (!runner) {\n json(res, 409, { error: 'session is parked (wake it before asking about MCP)' })\n return\n }\n await handleMcp(req, res, runner, route.mcpServer)\n return\n }\n if (route.files) {\n // Deliverables live in the session's in-memory VFS — downloadable while\n // the session lives (durability is a persistence-tier concern, not ours).\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const snapshotFiles = parked?.snapshot.vfs\n const vfs = runner?.vfs ?? (snapshotFiles && {\n list: () => Object.keys(snapshotFiles).sort(),\n read: (path: string) => snapshotFiles[path],\n })\n if (!vfs) {\n json(res, 404, { error: 'session has no file store' })\n return\n }\n if (route.filePath === undefined) {\n const files = vfs.list().map((path) => ({ path, bytes: vfs.read(path)?.length ?? 0 }))\n json(res, 200, { files })\n return\n }\n const content = vfs.read(route.filePath)\n if (content === undefined) {\n json(res, 404, { error: `no such file: ${route.filePath}` })\n return\n }\n const filename = route.filePath.split('/').pop() || 'file'\n res.writeHead(200, {\n 'content-type': contentTypeFor(filename),\n 'content-length': Buffer.byteLength(content),\n // RFC 5987 filename* so non-ASCII names survive; plain filename for the rest.\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n // Agent-authored content must never render on this origin.\n 'x-content-type-options': 'nosniff',\n })\n res.end(content)\n return\n }\n if (route.produced) {\n await handleProducedFiles(req, res, route.id, route.producedFileId)\n return\n }\n if (route.permissionId) {\n // REST counterpart of the WS permission_decision command, for controllers\n // without a socket (e.g. answering a job's AskUserQuestion from a webhook).\n if (req.method !== 'POST') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as ResolvePermissionRequest\n if (body?.behavior !== 'allow' && body?.behavior !== 'deny') {\n json(res, 400, { error: \"behavior must be 'allow' or 'deny'\" })\n return\n }\n if (!runner) {\n json(res, 409, { error: 'session is parked (it has no pending permission requests)' })\n return\n }\n if (!runner.resolvePermission(route.permissionId, body)) {\n json(res, 404, { error: 'permission request not found (already resolved or expired)' })\n return\n }\n json(res, 200, { resolved: true })\n return\n }\n if (req.method === 'GET') {\n json(res, 200, { session: runner?.info() ?? parked!.info })\n return\n }\n if (req.method === 'PATCH') {\n // Rename. A parked session has no runner to carry the change and its\n // snapshot belongs to the park store, so it is refused rather than lied to.\n if (!runner) {\n json(res, 409, { error: 'session is parked (wake it before renaming)' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as UpdateSessionRequest\n if (body?.title !== undefined) {\n if (body.title !== null && typeof body.title !== 'string') {\n json(res, 400, { error: 'title must be a string or null' })\n return\n }\n const title = typeof body.title === 'string' ? body.title.trim() : ''\n runner.setTitle(title || undefined)\n }\n json(res, 200, { session: runner.info() })\n return\n }\n if (req.method === 'DELETE') {\n registry.remove(route.id)\n // Fail anything still bridged: the session is gone, so no answer can land.\n bridge.remove(route.id)\n // And drop any parked state, so a late execution result can't wake a session\n // the client just ended.\n await parking.discard(route.id)\n // The session is gone; so is anything it was holding for it.\n attachmentStore.drop(route.id)\n producedFiles.drop(route.id)\n json(res, 200, {\n session: runner?.info() ?? { ...parked!.info, status: 'closed' as const },\n })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n const server = createServer((req, res) => {\n handleRequest(req, res).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : 'internal error'\n if (!res.headersSent) json(res, error instanceof SyntaxError ? 400 : 500, { error: message })\n else res.end()\n })\n })\n\n server.on('upgrade', (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n void (async () => {\n const pathname = new URL(req.url ?? '/', 'http://internal').pathname\n if (pathname === basePath + '/queue/ws') {\n if (!queue) {\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n if (!(await authenticate(req)).ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n queueSockets.add(ws)\n ws.on('close', () => queueSockets.delete(ws))\n void queue\n .stats()\n .then((stats) =>\n sendQueueFrame(ws, { type: 'queue_attached', protocolVersion: PROTOCOL_VERSION, stats }),\n )\n .catch(() => {})\n })\n return\n }\n const route = parseRoute(req.url ?? '/')\n if (!route?.ws || !route.id) {\n socket.destroy()\n return\n }\n if (!(await authenticate(req)).ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Attaching to a parked session wakes it: the client wants to drive it, and\n // its whole event log comes back with it, so `afterSeq` still lines up.\n const runner = await parking.ensureLive(route.id).catch(() => undefined)\n if (!runner) {\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n attachClient(ws, runner, req)\n })\n })().catch(() => socket.destroy())\n })\n\n const attachClient = (ws: WebSocket, runner: Runner, req: IncomingMessage): void => {\n const url = new URL(req.url ?? '/', 'http://internal')\n const afterSeq = Number(url.searchParams.get('afterSeq') ?? '0') || 0\n\n const send = (frame: ServerFrame): void => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame))\n }\n\n send({\n type: 'attached',\n protocolVersion: PROTOCOL_VERSION,\n session: runner.info(),\n replayingFrom: afterSeq,\n })\n const unsubscribe = runner.subscribe((event) => send({ type: 'event', event }), afterSeq)\n // Register for bridged tool calls: this client can be asked to execute them\n // in its own sandbox (see BridgeHub).\n const detachBridge = bridge.attach(runner.id, send)\n\n ws.on('message', (data: Buffer) => {\n let frame: ClientFrame\n try {\n frame = JSON.parse(data.toString('utf8')) as ClientFrame\n } catch {\n send({ type: 'protocol_error', message: 'invalid JSON frame' })\n return\n }\n handleCommand(frame, runner).catch((error: unknown) => {\n send({\n type: 'protocol_error',\n message: error instanceof Error ? error.message : 'command failed',\n })\n })\n })\n ws.on('close', () => {\n unsubscribe()\n detachBridge()\n // Nobody watching any more: a session waiting on a deferred execution can\n // give its runner back (after a grace period, so a reconnect costs nothing).\n parking.onDetach(runner.id)\n })\n }\n\n const handleCommand = async (frame: ClientFrame, runner: Runner): Promise<void> => {\n switch (frame.type) {\n case 'user_message': {\n if (!frame.attachmentIds?.length) {\n runner.sendMessage(frame.text)\n return\n }\n // The bytes live server-side; this is where a reference becomes content.\n // A missing id throws rather than sending a message that lost its picture.\n const resolved = attachmentStore.resolve(runner.id, frame.attachmentIds)\n if (!resolved.ok) {\n throw new Error(`unknown attachment(s): ${resolved.missing.join(', ')}`)\n }\n runner.sendMessage(frame.text, resolved.attachments)\n return\n }\n case 'permission_decision':\n if (frame.behavior === 'allow') {\n runner.resolvePermission(frame.requestId, {\n behavior: 'allow',\n updatedInput: frame.updatedInput,\n })\n } else {\n runner.resolvePermission(frame.requestId, {\n behavior: 'deny',\n message: frame.message,\n interrupt: frame.interrupt,\n })\n }\n return\n case 'interrupt':\n await runner.interrupt()\n return\n case 'set_permission_mode':\n if (frame.mode === 'bypassPermissions' && options.disableBypassPermissions) {\n throw new Error('bypassPermissions is disabled on this server (disableBypassPermissions)')\n }\n await runner.setPermissionMode(frame.mode)\n return\n case 'set_model':\n await runner.setModel(frame.model)\n return\n case 'tool_call_result':\n // Untrusted client input by contract — fine for the user's own data,\n // never a source for server-authoritative state. Unknown or already\n // settled ids are ignored rather than erroring: a late answer racing a\n // timeout is expected, not a client bug.\n bridge.resolve(runner.id, frame.executionId, { output: frame.output, logs: frame.logs })\n return\n case 'tool_call_error':\n bridge.resolve(runner.id, frame.executionId, {\n reason: frame.reason,\n error: frame.error,\n logs: frame.logs,\n })\n return\n case 'close':\n runner.close('client')\n return\n default:\n throw new Error(`unknown command: ${(frame as { type?: string }).type}`)\n }\n }\n\n return {\n server,\n registry,\n queue,\n bridge,\n parking,\n listen: async (port, host) => {\n // Before the first request: every lookup on the request path reads the\n // in-memory mirror, so it has to be populated before anything can hit it.\n await refreshStored()\n // Re-index and re-arm anything a durable store carried across a restart.\n await parking.hydrate()\n return new Promise((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n // After the bind and after refreshStored(), so stored profiles are\n // probed too; advisory, so it must never delay or wedge the listen.\n preflightCredentials()\n const address = server.address()\n resolve({ port: typeof address === 'object' && address ? address.port : port })\n })\n })\n },\n close: () =>\n new Promise((resolve) => {\n queue?.close()\n parking.close()\n registry.closeAll()\n for (const ws of queueSockets) ws.close()\n queueSockets.clear()\n wss.close()\n server.close(() => resolve())\n server.closeAllConnections()\n }),\n }\n}\n","import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport type { ProfileInfo } from '@workerdeck/protocol'\n\n/**\n * Where dashboard-managed profiles live. The seam exists for the same reason\n * `QueueAdapter` does: a single-host deployment wants the bundled file store and\n * no configuration, while an operator with a database wants their own.\n *\n * Profiles declared in `createWorkerServer({ profiles })` never enter a store —\n * they are code, and stay immutable. The store holds only what the management\n * routes created, and the two sets are unioned by name.\n *\n * A store holds NO credentials: `ProviderConfig.apiKeyEnv` is a variable name and\n * a Claude profile's `configDir` is a path. Both are resolved by the server's own\n * environment at session time, which is what keeps a stored profile safe to write\n * to disk and safe to serve from `GET /profiles`.\n */\nexport type ProfileStore = {\n /** Every stored profile. Called once at `listen()` and after each mutation. */\n list(): ProfileInfo[] | Promise<ProfileInfo[]>\n /** Create or replace by `profile.name`. */\n save(profile: ProfileInfo): void | Promise<void>\n /** Remove by name. Removing something absent is not an error. */\n delete(name: string): void | Promise<void>\n}\n\n/** Non-durable store for tests and ephemeral deployments. */\nexport function createMemoryProfileStore(seed: ProfileInfo[] = []): ProfileStore {\n const profiles = new Map(seed.map((p) => [p.name, p]))\n return {\n list: () => [...profiles.values()],\n save: (profile) => void profiles.set(profile.name, profile),\n delete: (name) => void profiles.delete(name),\n }\n}\n\n/**\n * JSON-file store: one array of profiles at `path` (default\n * `<cwd>/.workerdeck/profiles.json`). Writes go through a temp file and a\n * rename so a crash mid-write cannot truncate the operator's profile list.\n *\n * Single-process by design, exactly like the bundled queue adapter — two servers\n * sharing one file would race. That is what the seam is for.\n */\nexport function createFileProfileStore(path = join(process.cwd(), '.workerdeck', 'profiles.json')): ProfileStore {\n const read = (): Map<string, ProfileInfo> => {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown\n if (!Array.isArray(parsed)) return new Map()\n const profiles = parsed as ProfileInfo[]\n return new Map(profiles.filter((p) => p && typeof p.name === 'string').map((p) => [p.name, p]))\n } catch {\n // Absent or unparseable: start empty rather than refusing to boot. A\n // corrupt file is replaced on the next write, not silently merged into.\n return new Map()\n }\n }\n const write = (profiles: Map<string, ProfileInfo>): void => {\n mkdirSync(dirname(path), { recursive: true })\n const temp = `${path}.${process.pid}.tmp`\n writeFileSync(temp, JSON.stringify([...profiles.values()], null, 2))\n renameSync(temp, path)\n }\n return {\n list: () => [...read().values()],\n save: (profile) => {\n const profiles = read()\n profiles.set(profile.name, profile)\n write(profiles)\n },\n delete: (name) => {\n const profiles = read()\n if (profiles.delete(name)) write(profiles)\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["#maxFileBytes","#maxSessionBytes","#bySession","#bySession","#options","#sessions","#options","#chains","#emit","#send","#deliver","#options","#bridge","#sessions","#options","#configs","#track","#forget","#park","#closed","#detachTimers","#owners","#settled","#queue","#storeOps","#resume","#clearTimer","#timers","#resuming","#rebuild","#records","resolvePath","profiles"],"sources":["../src/host-files.ts","../src/host-file-search.ts","../src/attachments.ts","../src/produced-files.ts","../src/registry.ts","../src/notifications.ts","../src/bridge.ts","../src/parking.ts","../src/session-store.ts","../src/server.ts","../src/profile-store.ts"],"sourcesContent":["import type { Dirent } from 'node:fs'\nimport {\n closeSync,\n constants,\n fstatSync,\n ftruncateSync,\n lstatSync,\n openSync,\n readFileSync,\n realpathSync,\n writeFileSync,\n} from 'node:fs'\nimport { basename, dirname, isAbsolute, join, relative, sep } from 'node:path'\n\n/**\n * Containment core for the operator's host-filesystem routes (`/v1/fs/*`).\n *\n * The routes are operator privilege — gated by the auth key, outside the agent\n * permission flow — but the trees they expose are written BY the agent, so every\n * requested path is adversarial: a session can plant `root/notes -> ~/.ssh` and\n * wait for the operator's phone to browse into it. That is why `cwdAllowed` in\n * server.ts (resolve + prefix compare) is not reused here: it vets an\n * operator-typed cwd, where a lexical check is enough; here the *route* a path\n * takes matters, so containment is decided only on the realpath'd form.\n *\n * Disclosure policy: every refusal that consulted the filesystem is a uniform\n * `404 'not found'` — outside every root, escaping via symlink, dangling link,\n * and genuinely absent are indistinguishable. Anything finer would let a planted\n * link turn this API into an existence probe for paths outside the roots (403\n * iff `~/.ssh/id_rsa` exists). 403 is reserved for verdicts that reveal nothing\n * beyond the roots: malformed requests, and in-root targets of the wrong kind.\n *\n * TOCTOU: resolve-then-open is a race by construction and no check at this\n * layer closes it — between a resolve and the caller's open, the agent can swap\n * a verified component for a symlink. What resolve guarantees is that the\n * *returned* path was canonical and contained at resolve time. Callers must\n * open exactly `outcome.path` (never the requested string), preferably through\n * {@link readContained}/{@link writeContained}, which pin the final component\n * with O_NOFOLLOW, defuse fifo/device swaps with O_NONBLOCK + fstat-before-io,\n * and truncate only after the fd is proven to be a regular file. A *parent*\n * directory swapped inside the window can still redirect the open — closing\n * that needs openat2(RESOLVE_BENEATH), which Node does not expose. The window\n * is microseconds wide and the exposure is accepted and documented rather than\n * pretended away.\n */\n\nexport type HostFileRoot = {\n /** The operator's spelling, kept for display (`GET /v1/fs/roots`). */\n readonly configured: string\n /** What containment is checked against — and the prefix `ResolveOutcome.path`\n * is guaranteed to carry, so relative display paths are computed from this. */\n readonly canonical: string\n}\n\nexport type HostFileRoots = { readonly roots: readonly HostFileRoot[] }\n\n/**\n * Built once at startup from operator config. Roots are canonicalized here\n * because resolution produces realpath'd targets: a root that is itself a\n * symlink (`/tmp` -> `/private/tmp` on macOS) would otherwise contain nothing.\n * A misdeclared root throws rather than silently guarding the wrong tree —\n * same stance as profile config dirs in server.ts. An empty list is legal and\n * refuses everything; \"no roots means allow all\" is `cwdAllowed`'s contract,\n * never this module's.\n */\nexport function createHostFileRoots(roots: string[]): HostFileRoots {\n return {\n roots: roots.map((configured) => {\n if (invalidRequest(configured)) {\n throw new Error(\n `createHostFileRoots: root must be an absolute path: ${JSON.stringify(configured)}`,\n )\n }\n let canonical: string\n try {\n canonical = realpathSync(configured)\n } catch {\n throw new Error(`createHostFileRoots: root does not exist: ${configured}`)\n }\n if (!lstatSync(canonical).isDirectory()) {\n throw new Error(`createHostFileRoots: root is not a directory: ${configured}`)\n }\n return { configured, canonical }\n }),\n }\n}\n\nexport type ResolveOutcome =\n | { ok: true; path: string; root: string; kind: 'file' | 'dir' }\n | { ok: false; status: 403 | 404; error: string }\n\ntype Refusal = { ok: false; status: 403 | 404; error: string }\n\nfunction refuse(status: 403 | 404, error: string): Refusal {\n return { ok: false, status, error }\n}\n\n/** The uniform filesystem refusal — see the disclosure policy in the header.\n * The string is deliberately constant: a distinct message is as much an oracle\n * as a distinct status. */\nfunction notFound(): Refusal {\n return refuse(404, 'not found')\n}\n\n/** NUL is rejected before any fs call — Node throws a TypeError on NUL paths,\n * and that must surface as a refusal, not a 500. Relative paths are refused\n * outright rather than resolved against a cwd this API never promised. */\nfunction invalidRequest(requested: string): boolean {\n return requested.length === 0 || requested.includes('\\0') || !isAbsolute(requested)\n}\n\n/** Both sides are realpath output, so this is a pure lexical question — but a\n * bare prefix check gets the boundary wrong (`/x/app` would swallow\n * `/x/application`). `relative` answers it exactly: inside iff the walk from\n * root to candidate is empty or never has to leave through `..`. */\nfunction contained(rootCanonical: string, candidate: string): boolean {\n const rel = relative(rootCanonical, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nfunction rootContaining(roots: HostFileRoots, canonical: string): HostFileRoot | undefined {\n return roots.roots.find((root) => contained(root.canonical, canonical))\n}\n\n/**\n * For read/list: the target must exist. realpath is handed the request whole —\n * no lexical `..` collapsing first, because `root/link/..` is lexically `root`\n * but physically the link target's parent, and only the physical answer is the\n * true one. Symlinks that canonicalize *inside* a root are followed and served:\n * containment is a property of the canonical target, not of the route to it —\n * the operator granted the whole subtree, so nothing new becomes reachable.\n */\nexport function resolveExisting(roots: HostFileRoots, requested: string): ResolveOutcome {\n if (invalidRequest(requested)) return refuse(403, 'invalid path')\n let canonical: string\n try {\n canonical = realpathSync(requested)\n } catch {\n // Absent, unreadable, looped, name-too-long — uniformly absent.\n return notFound()\n }\n const root = rootContaining(roots, canonical)\n if (!root) return notFound()\n let target\n try {\n // realpath output cannot name a symlink, so lstat === stat here modulo a\n // race; lstat anyway, so a swap inside the window classifies as neither\n // file nor dir and refuses.\n target = lstatSync(canonical)\n } catch {\n return notFound()\n }\n if (target.isFile()) return { ok: true, path: canonical, root: root.canonical, kind: 'file' }\n if (target.isDirectory()) return { ok: true, path: canonical, root: root.canonical, kind: 'dir' }\n // In-root fifo/device/socket: a 403 here reveals nothing beyond the roots\n // (the operator can list the entry anyway), and reading one would hang the\n // request or stream zeros forever.\n return refuse(403, 'not a regular file or directory')\n}\n\n/**\n * For write: the target may not exist, so realpath cannot be asked directly.\n * An existing target reuses read semantics — writing *through* a symlink that\n * canonicalizes inside a root is allowed (`root/link -> root/real.txt` edits\n * real.txt), same reasoning as {@link resolveExisting}. A missing target\n * canonicalizes its immediate parent and re-checks: only the final component\n * may be new, and anything already sitting there — in practice a dangling\n * symlink — is refused, because open(2) with O_CREAT follows it and would\n * create the file wherever it points. That refusal is `not found`, not 403: a\n * link to an existing outside file already answers 404 via the exists branch,\n * so a distinct status for the dangling case would hand back exactly the\n * existence bit the uniform 404 exists to withhold.\n */\nexport function resolveForWrite(roots: HostFileRoots, requested: string): ResolveOutcome {\n if (invalidRequest(requested)) return refuse(403, 'invalid path')\n try {\n const canonical = realpathSync(requested)\n const root = rootContaining(roots, canonical)\n if (!root) return notFound()\n const target = lstatSync(canonical)\n if (target.isDirectory()) return refuse(403, 'is a directory')\n if (!target.isFile()) return refuse(403, 'not a regular file')\n return { ok: true, path: canonical, root: root.canonical, kind: 'file' }\n } catch {\n // Target absent or unresolvable — fall through and try it as a create.\n }\n const base = basename(requested)\n if (base === '' || base === '.' || base === '..') return refuse(403, 'invalid path')\n let parent: string\n try {\n parent = realpathSync(dirname(requested))\n } catch {\n return notFound()\n }\n const root = rootContaining(roots, parent)\n if (!root) return notFound()\n try {\n // Parent exists but is not a directory (`root/file.txt/x`): no such\n // location, same answer as a missing parent.\n if (!lstatSync(parent).isDirectory()) return notFound()\n } catch {\n return notFound()\n }\n const path = join(parent, base)\n // basename() cannot smuggle a separator, so this re-check is redundant by\n // construction — kept because this is the line an escape would have to cross.\n if (!contained(root.canonical, path)) return notFound()\n try {\n lstatSync(path)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ok: true, path, root: root.canonical, kind: 'file' }\n }\n return notFound()\n }\n // Something IS at the final component even though realpath(requested) failed:\n // a dangling symlink, or a concurrent create. Refuse both — see the header.\n return notFound()\n}\n\nexport type HostEntryKind = 'file' | 'dir' | 'symlink' | 'other'\n\n/** lstat semantics on purpose: a listing shows a symlink AS a symlink — the\n * server never follows one while rendering a directory. Following happens only\n * when the entry is itself requested, through {@link resolveExisting}, which\n * refuses it if it escapes. `readdir(withFileTypes)` already answers without\n * following, so this is classification, not I/O. */\nexport function entryKind(entry: Dirent): HostEntryKind {\n if (entry.isSymbolicLink()) return 'symlink'\n if (entry.isFile()) return 'file'\n if (entry.isDirectory()) return 'dir'\n return 'other'\n}\n\n// win32 lacks O_NOFOLLOW/O_NONBLOCK (undefined at runtime despite the typing);\n// there they contribute 0 and the fstat gates below stand alone.\nconst O_NOFOLLOW: number = constants.O_NOFOLLOW ?? 0\nconst O_NONBLOCK: number = constants.O_NONBLOCK ?? 0\n\nexport type ReadOutcome = { ok: true; data: Buffer } | Refusal\n\n/**\n * The open half of the resolve→open discipline; pass `ResolveOutcome.path`,\n * never the requested string. O_NOFOLLOW turns a final component swapped for a\n * symlink inside the race window into ELOOP instead of a follow; O_NONBLOCK\n * makes a swapped-in fifo open instantly instead of parking the request until a\n * writer appears (it is inert for regular files); the fstat gate refuses\n * anything that is not a plain file before a byte is read — `/dev/zero` would\n * otherwise be an unbounded read.\n */\nexport function readContained(path: string): ReadOutcome {\n let fd: number\n try {\n fd = openSync(path, constants.O_RDONLY | O_NOFOLLOW | O_NONBLOCK)\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? notFound() : refuse(403, 'refused')\n }\n try {\n if (!fstatSync(fd).isFile()) return refuse(403, 'not a regular file')\n return { ok: true, data: readFileSync(fd) }\n } catch {\n return refuse(403, 'refused')\n } finally {\n closeSync(fd)\n }\n}\n\nexport type WriteOutcome = { ok: true } | Refusal\n\n/**\n * O_CREAT|O_NOFOLLOW refuses (ELOOP) a symlink planted at the final component\n * after resolve — the exact swap that would land the write at the link's\n * target. Truncation happens via ftruncate only AFTER the fd is proven to be a\n * regular file, so a swapped-in device or fifo is never truncated or written;\n * O_NONBLOCK turns the reader-less-fifo open from a hang into ENXIO.\n */\nexport function writeContained(path: string, data: string | Uint8Array): WriteOutcome {\n let fd: number\n try {\n fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | O_NOFOLLOW | O_NONBLOCK, 0o644)\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? notFound() : refuse(403, 'refused')\n }\n try {\n if (!fstatSync(fd).isFile()) return refuse(403, 'not a regular file')\n ftruncateSync(fd)\n writeFileSync(fd, data)\n return { ok: true }\n } catch {\n return refuse(403, 'refused')\n } finally {\n closeSync(fd)\n }\n}\n","import { readdirSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { entryKind } from './host-files.ts'\n\n/**\n * The recursive half of the host-file routes: what `@file` autocomplete needs and\n * `/fs/list` deliberately isn't. Listing answers \"what is in this directory\"; this\n * answers \"which file in this tree did you mean\", which is a different query and a\n * different cost model.\n *\n * Kept out of `host-files.ts` on purpose. That module is the audited containment\n * core; this one walks *inside* an already-resolved, already-contained directory\n * and never resolves a path of its own. Its one security-relevant rule is that it\n * does not follow symlinks — see the walk below.\n */\n\n/**\n * Directories a source tree keeps that nobody types `@` looking for, and that are\n * usually most of the entries on disk. Skipping them is what makes the walk cheap\n * enough to run per keystroke; the operator can replace the list via\n * `hostFiles.ignore`.\n */\nexport const DEFAULT_IGNORED_DIRS = [\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n '.next',\n '.nuxt',\n '.svelte-kit',\n '.turbo',\n '.cache',\n 'dist',\n 'build',\n 'out',\n 'target',\n '.venv',\n 'venv',\n '__pycache__',\n '.pytest_cache',\n '.gradle',\n 'Pods',\n 'DerivedData',\n // Swift Package Manager's build directory. Its object files carry the source\n // names, so without this a Swift project's `@` search answers with\n // `Transcript.o` before `Transcript.swift`.\n '.build',\n]\n\nexport type FoundFile = {\n /** Absolute path, for a follow-up read. */\n path: string\n /** Path relative to the searched directory — what a picker shows and inserts. */\n relative: string\n}\n\nexport type SearchResult = {\n matches: FoundFile[]\n /** More matched (or more of the tree existed) than was returned. */\n truncated: boolean\n}\n\nexport type SearchOptions = {\n /** Fuzzy needle. Empty returns the shallowest files, which is what a bare `@` wants. */\n query?: string\n /** Max results returned. */\n limit?: number\n /** Directory names not to descend into. Defaults to {@link DEFAULT_IGNORED_DIRS}. */\n ignore?: readonly string[]\n /** Hard bound on entries examined, so a walk can't be turned into a DoS by\n * pointing it at a huge tree. Reaching it truncates rather than erroring. */\n maxScanned?: number\n}\n\n/**\n * Breadth-first so shallow files rank first before scoring even runs — for a bare\n * `@` that ordering *is* the ranking, and for a query it breaks ties the way a\n * person expects (`src/index.ts` over `src/a/b/c/index.ts`).\n *\n * Symlinks are skipped outright, as files and as directories. As directories it is\n * the difference between a bounded walk and an unbounded one (a cycle, or a link\n * to `/`); as files it keeps this function's output within the tree it was handed,\n * so nothing it offers can be a path that `resolveExisting` would later refuse.\n * A tree that genuinely lives behind symlinks is not autocompletable — an accepted\n * cost for not having to re-derive containment here.\n */\nexport function searchFiles(base: string, options: SearchOptions = {}): SearchResult {\n const limit = options.limit ?? 50\n const maxScanned = options.maxScanned ?? 20_000\n const ignore = new Set(options.ignore ?? DEFAULT_IGNORED_DIRS)\n const needle = (options.query ?? '').toLowerCase()\n\n const found: { file: FoundFile; score: number; depth: number }[] = []\n const queue: { dir: string; depth: number }[] = [{ dir: base, depth: 0 }]\n let scanned = 0\n let exhausted = true\n\n while (queue.length > 0) {\n const { dir, depth } = queue.shift()!\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n // Unreadable directory — skip it rather than failing the whole search.\n continue\n }\n for (const entry of entries) {\n if (++scanned > maxScanned) {\n exhausted = false\n queue.length = 0\n break\n }\n const kind = entryKind(entry)\n if (kind === 'dir') {\n if (!ignore.has(entry.name)) queue.push({ dir: join(dir, entry.name), depth: depth + 1 })\n continue\n }\n if (kind !== 'file') continue\n const path = join(dir, entry.name)\n const rel = relative(base, path)\n const score = scoreMatch(rel, entry.name, needle)\n if (score !== null) found.push({ file: { path, relative: rel }, score, depth })\n }\n }\n\n found.sort(\n (a, b) =>\n b.score - a.score ||\n a.depth - b.depth ||\n a.file.relative.length - b.file.relative.length ||\n a.file.relative.localeCompare(b.file.relative),\n )\n return {\n matches: found.slice(0, limit).map((f) => f.file),\n truncated: !exhausted || found.length > limit,\n }\n}\n\n/**\n * Subsequence matching, like every `@`-picker worth using: `seslist` finds\n * `SessionListView.swift`. Returns null for no match.\n *\n * Scored so the two things people actually mean win — a hit in the filename beats\n * one buried in the directory path, and characters typed consecutively beat the\n * same characters scattered — rather than trying to be a ranking engine.\n */\nfunction scoreMatch(relativePath: string, name: string, needle: string): number | null {\n if (needle === '') return 0\n const inName = subsequenceScore(name.toLowerCase(), needle)\n if (inName !== null) return inName + 1000\n return subsequenceScore(relativePath.toLowerCase(), needle)\n}\n\nfunction subsequenceScore(haystack: string, needle: string): number | null {\n let score = 0\n let from = 0\n let previous = -2\n for (const char of needle) {\n const at = haystack.indexOf(char, from)\n if (at === -1) return null\n if (at === previous + 1) score += 8\n if (at === 0) score += 4\n from = at + 1\n previous = at\n }\n // Shorter haystacks matched the same needle more tightly.\n return score - haystack.length / 100\n}\n","import { randomUUID } from 'node:crypto'\nimport { attachmentKind, normalizeMediaType, type AttachmentInput } from '@workerdeck/core'\nimport type { MessageAttachment } from '@workerdeck/protocol'\n\nexport type AttachmentStoreOptions = {\n /** Largest single upload. Default 10 MiB. */\n maxFileBytes?: number\n /** Ceiling on everything one session is holding. Default 64 MiB. */\n maxSessionBytes?: number\n}\n\nexport type AttachmentRejection =\n | { code: 'too_large'; message: string }\n | { code: 'session_full'; message: string }\n | { code: 'unsupported_type'; message: string }\n | { code: 'empty'; message: string }\n\nexport type PutResult =\n | { ok: true; attachment: MessageAttachment }\n | { ok: false; error: AttachmentRejection }\n\nconst DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024\nconst DEFAULT_MAX_SESSION_BYTES = 64 * 1024 * 1024\n\n/**\n * Per-session hold for files the user attached to a message.\n *\n * In memory, and deliberately so. An attachment is only *needed* for the instant\n * between the upload and the message that names it; everything after that is\n * convenience (a client re-rendering a thumbnail after a reattach). That is the\n * same bargain `GET /sessions/:id/files` makes — the session's lifetime, no\n * durability tier — and it keeps the gateway from accumulating a photo library\n * on disk that nobody asked it to look after.\n *\n * Both caps are enforced here rather than at the route, so a host embedding the\n * server cannot forget one: a single file that is too big is a 413, and so is a\n * session whose total would go over.\n */\nexport class AttachmentStore {\n #bySession = new Map<string, Map<string, AttachmentInput>>()\n #maxFileBytes: number\n #maxSessionBytes: number\n\n constructor(options: AttachmentStoreOptions = {}) {\n this.#maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES\n this.#maxSessionBytes = options.maxSessionBytes ?? DEFAULT_MAX_SESSION_BYTES\n }\n\n get maxFileBytes(): number {\n return this.#maxFileBytes\n }\n\n put(sessionId: string, name: string, mediaType: string, body: Buffer): PutResult {\n if (body.length === 0) {\n return { ok: false, error: { code: 'empty', message: 'attachment is empty' } }\n }\n if (body.length > this.#maxFileBytes) {\n return {\n ok: false,\n error: {\n code: 'too_large',\n message: `attachment is larger than the ${this.#maxFileBytes}-byte limit`,\n },\n }\n }\n const type = normalizeMediaType(mediaType)\n if (!attachmentKind(type)) {\n return {\n ok: false,\n error: { code: 'unsupported_type', message: `unsupported media type: ${type}` },\n }\n }\n const held = this.#bySession.get(sessionId) ?? new Map<string, AttachmentInput>()\n const heldBytes = [...held.values()].reduce((sum, a) => sum + a.bytes, 0)\n if (heldBytes + body.length > this.#maxSessionBytes) {\n return {\n ok: false,\n error: {\n code: 'session_full',\n message: `session is already holding ${heldBytes} bytes of attachments (limit ${this.#maxSessionBytes})`,\n },\n }\n }\n const attachment: AttachmentInput = {\n id: randomUUID(),\n name: safeName(name),\n mediaType: type,\n bytes: body.length,\n data: body.toString('base64'),\n }\n held.set(attachment.id, attachment)\n this.#bySession.set(sessionId, held)\n return { ok: true, attachment: ref(attachment) }\n }\n\n /** The stored record, bytes included — for the download route and for the send\n * path that turns ids into content blocks. */\n get(sessionId: string, id: string): AttachmentInput | undefined {\n return this.#bySession.get(sessionId)?.get(id)\n }\n\n /**\n * Resolve the ids a `user_message` named, in the order given.\n *\n * Missing ids are reported rather than skipped: a message that quietly lost its\n * picture reads as the model ignoring it, which is a far worse failure than a\n * command that errors.\n */\n resolve(sessionId: string, ids: readonly string[]): { ok: true; attachments: AttachmentInput[] } | { ok: false; missing: string[] } {\n const held = this.#bySession.get(sessionId)\n const attachments: AttachmentInput[] = []\n const missing: string[] = []\n for (const id of ids) {\n const found = held?.get(id)\n if (found) attachments.push(found)\n else missing.push(id)\n }\n return missing.length ? { ok: false, missing } : { ok: true, attachments }\n }\n\n drop(sessionId: string): void {\n this.#bySession.delete(sessionId)\n }\n}\n\nfunction ref(attachment: AttachmentInput): MessageAttachment {\n return {\n id: attachment.id,\n name: attachment.name,\n mediaType: attachment.mediaType,\n bytes: attachment.bytes,\n }\n}\n\n/**\n * A display name, not a path. The name is echoed back to clients and put in front\n * of the model in the text-attachment envelope, so directory separators, control\n * characters and unbounded length all come off here.\n */\nfunction safeName(name: string): string {\n const leaf = name.split(/[/\\\\]/).pop() ?? ''\n // Control characters, plus the two delimiters of the text-attachment envelope.\n // The control range is exactly what makes this worth doing: the name is\n // client-supplied and ends up both in a response header and in front of a model.\n // oxlint-disable-next-line no-control-regex\n const cleaned = leaf.replace(/[\\u0000-\\u001f\\u007f\"<>]/g, '').trim()\n if (cleaned === '' || cleaned === '.' || cleaned === '..') return 'attachment'\n return cleaned.length > 120 ? cleaned.slice(0, 120) : cleaned\n}\n","import type { Runner } from '@workerdeck/core'\n\n/** One host file an engine reported writing, as the store holds it. */\nexport type ProducedFile = {\n fileId: string\n /** Absolute host path, exactly as the runner reported it. */\n path: string\n mediaType?: string\n /** Size when the runner announced it — advisory, and re-read at serve time. */\n bytes?: number\n sessionId: string\n}\n\n/**\n * The paths this gateway will serve from `GET /sessions/:id/produced/:fileId`.\n *\n * **This is the whole access-control model, so it is worth being precise about\n * what it is.** The store is an allowlist built from one source and one only:\n * `file_produced` events, which a runner emits about a file its own engine just\n * wrote. It is not a directory grant. Nothing else can add to it — not a\n * request, not a config, and in particular not the agent, whose own path claims\n * go through `/fs/*` and that route's root allowlist.\n *\n * That is why the route needs neither `hostFiles.roots` nor `maxFileBytes`:\n * \"somewhere under a root the operator declared\" is a guess about which paths\n * are safe, while \"the exact path this session's runner reported producing\" is\n * a fact about one file. A 2 MB generated PNG is the common case, and making\n * the operator raise a byte cap to see their own picture was the bug this\n * replaces.\n *\n * Lifetime is the session's, like `AttachmentStore`'s: in memory, dropped when\n * the session is removed. The bytes are never held here — only the path, so a\n * gateway serving a long session accumulates a few hundred bytes per picture\n * rather than the pictures.\n */\nexport class ProducedFileStore {\n #bySession = new Map<string, Map<string, ProducedFile>>()\n\n /**\n * Register a runner's produced files for its lifetime.\n *\n * Subscribes from seq 0, which is the opposite of what `SessionNotifier` wants\n * and correct for the same reason: registration is idempotent (a `fileId` is\n * derived from its path, so re-registering overwrites with itself), and a\n * session rebuilt from a park must re-learn every file it produced before the\n * park — otherwise a client's transcript keeps rendering image cards whose\n * bytes have quietly become unreachable.\n */\n watch(runner: Runner): void {\n runner.subscribe((event) => {\n if (event.type !== 'file_produced') return\n const held = this.#bySession.get(runner.id) ?? new Map<string, ProducedFile>()\n held.set(event.fileId, {\n fileId: event.fileId,\n path: event.path,\n ...(event.mediaType ? { mediaType: event.mediaType } : {}),\n ...(event.bytes !== undefined ? { bytes: event.bytes } : {}),\n sessionId: runner.id,\n })\n this.#bySession.set(runner.id, held)\n }, 0)\n }\n\n get(sessionId: string, fileId: string): ProducedFile | undefined {\n return this.#bySession.get(sessionId)?.get(fileId)\n }\n\n /** Everything one session has produced, newest registration last. */\n list(sessionId: string): ProducedFile[] {\n return [...(this.#bySession.get(sessionId)?.values() ?? [])]\n }\n\n drop(sessionId: string): void {\n this.#bySession.delete(sessionId)\n }\n}\n","import { SessionRunner, type Runner, type SessionRunnerConfig } from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\nexport type SessionRegistryOptions = {\n /**\n * Called once per runner as it enters the table, before it starts — the one\n * seam every path goes through (create, prepare, adopt, and the rebuild of a\n * parked session), which is what a watcher that must not miss a session needs.\n */\n onRegister?: (runner: Runner) => void\n}\n\n/** In-memory session table. Terminal sessions stay listed until removed or the process exits. */\nexport class SessionRegistry {\n #sessions = new Map<string, Runner>()\n #options: SessionRegistryOptions\n\n constructor(options: SessionRegistryOptions = {}) {\n this.#options = options\n }\n\n create(config: SessionRunnerConfig): Runner {\n return this.adopt(new SessionRunner(config))\n }\n\n /** Build and list a Claude-engine runner without starting it, so watchers can\n * subscribe first. Call `start()` once they have. */\n prepare(config: SessionRunnerConfig): Runner {\n return this.register(new SessionRunner(config))\n }\n\n /** Register an already-built runner (a non-Claude engine) and start it. */\n adopt(runner: Runner): Runner {\n this.register(runner)\n void runner.start()\n return runner\n }\n\n /** List a runner without starting it — for a rehydrated session, whose watchers\n * must be subscribed before it comes back up. */\n register(runner: Runner): Runner {\n // Re-registering the same runner is normal — `prepare()` lists it and the\n // caller registers what it returned — so the hook fires on the *runner*, not\n // on the call. A different object under a known id (the rebuild of a parked\n // session) is a new runner and does fire.\n const existing = this.#sessions.get(runner.id)\n this.#sessions.set(runner.id, runner)\n if (existing !== runner) this.#options.onRegister?.(runner)\n return runner\n }\n\n get(id: string): Runner | undefined {\n return this.#sessions.get(id)\n }\n\n list(): SessionInfo[] {\n return [...this.#sessions.values()].map((r) => r.info())\n }\n\n remove(id: string): boolean {\n const runner = this.#sessions.get(id)\n if (!runner) return false\n runner.close('server')\n return this.#sessions.delete(id)\n }\n\n /** Drop a runner WITHOUT closing it: the session isn't ending, it parked and\n * lives on in its snapshot. Closing here would tell every client it was over. */\n evict(id: string): boolean {\n return this.#sessions.delete(id)\n }\n\n closeAll(): void {\n for (const runner of this.#sessions.values()) runner.close('server')\n }\n}\n","import type { SessionNotification, SessionWebhookConfig } from '@workerdeck/protocol'\nimport type { Runner } from '@workerdeck/core'\n\nexport type SessionNotificationOptions = {\n /** POST target for every notification. */\n webhook?: SessionWebhookConfig\n /** Local observer, invoked for every notification whether or not a webhook is\n * configured — the in-process seam a host (or the CLI's APNs forwarder) hooks.\n * Unfiltered: `webhook.events` narrows POST deliveries, not this. */\n onNotification?: (notification: SessionNotification) => void\n /** Delivery attempts per notification (exponential backoff). Default 3. */\n attempts?: number\n /** Initial backoff between attempts. Default 500ms. */\n retryDelayMs?: number\n}\n\n/**\n * Turns session events into the handful of notifications a human away from the\n * screen cares about, and delivers them to a webhook and/or a local observer.\n *\n * This is the *primitive*, deliberately transport-agnostic: the server stays\n * credential-free and knows nothing about APNs, Slack or email. Turning a\n * notification into a push is a forwarder's job (the turnkey CLI's), and one that\n * needs credentials, so it does not live here.\n *\n * Delivery is best-effort and ordered per session, mirroring the job queue's\n * webhook behaviour — a consumer that missed one can always attach to the session\n * WS with `afterSeq` and see the truth.\n */\nexport class SessionNotifier {\n readonly #options: SessionNotificationOptions\n /** Per-session delivery chain, so a session's notifications arrive in order. */\n readonly #chains = new Map<string, Promise<void>>()\n\n constructor(options: SessionNotificationOptions) {\n this.#options = options\n }\n\n /** True when nothing is listening — lets the caller skip subscribing at all. */\n get idle(): boolean {\n return !this.#options.webhook && !this.#options.onNotification\n }\n\n /**\n * Subscribe to a runner for its lifetime.\n *\n * `afterSeq` defaults to whatever the runner has already emitted, which is what\n * makes this safe on a *rehydrated* session: `subscribe` replays the log from\n * `afterSeq`, so subscribing at 0 to a session rebuilt from a park would\n * re-announce every permission request it ever made.\n */\n watch(runner: Runner, afterSeq = runner.info().lastSeq): void {\n if (this.idle) return\n runner.subscribe((event) => {\n switch (event.type) {\n case 'permission_requested':\n this.#emit(runner, event.seq, event.ts, {\n type: 'permission_requested',\n preview: event.request.title ?? event.request.toolName,\n request: event.request,\n })\n return\n case 'turn_result':\n this.#emit(runner, event.seq, event.ts, {\n type: 'turn_completed',\n preview: event.isError ? event.errors?.join('\\n') : event.result,\n result: {\n isError: event.isError,\n durationMs: event.durationMs,\n numTurns: event.numTurns,\n totalCostUsd: event.totalCostUsd,\n },\n })\n return\n case 'session_error':\n this.#emit(runner, event.seq, event.ts, {\n type: 'session_error',\n preview: event.message,\n })\n return\n case 'session_closed':\n this.#emit(runner, event.seq, event.ts, {\n type: 'session_closed',\n reason: event.reason,\n })\n return\n default:\n return\n }\n }, afterSeq)\n }\n\n #emit(\n runner: Runner,\n seq: number,\n ts: number,\n body: Omit<SessionNotification, 'sessionId' | 'session' | 'seq' | 'ts'>,\n ): void {\n // A microtask late, deliberately. Listeners run *inside* the emit, before the\n // runner has applied what the event means — `session_closed` is delivered\n // while the status still says 'starting', `session_error` before 'failed'.\n // The event supplies seq/ts, so identity and ordering are unaffected; only the\n // snapshot moves, and it moves to the truth.\n queueMicrotask(() => this.#send(runner, seq, ts, body))\n }\n\n #send(\n runner: Runner,\n seq: number,\n ts: number,\n body: Omit<SessionNotification, 'sessionId' | 'session' | 'seq' | 'ts'>,\n ): void {\n const webhook = this.#options.webhook\n const wanted = !webhook?.events || webhook.events.includes(body.type)\n if (!webhook && !this.#options.onNotification) return\n\n // `info()` is read here, not at delivery time: the snapshot must describe the\n // session as the event left it, not as it is after three retries.\n const notification: SessionNotification = {\n ...body,\n sessionId: runner.id,\n session: runner.info(),\n seq,\n ts,\n }\n\n try {\n this.#options.onNotification?.(notification)\n } catch {\n // An observer that throws must not take the session down with it.\n }\n\n if (!webhook || !wanted) return\n const previous = this.#chains.get(runner.id) ?? Promise.resolve()\n const next = previous.then(() => this.#deliver(webhook, notification))\n this.#chains.set(runner.id, next)\n // Drop the chain once it drains, so a long-lived server doesn't accumulate one\n // resolved promise per session it ever ran.\n void next.then(() => {\n if (this.#chains.get(runner.id) === next) this.#chains.delete(runner.id)\n })\n }\n\n /**\n * Best-effort POST with exponential backoff. Deliberately a near-copy of the\n * queue's job-webhook delivery rather than a shared helper: the two channels\n * have different payloads and different consumers, and coupling them would mean\n * a change to job deliveries silently changing session deliveries.\n */\n async #deliver(webhook: SessionWebhookConfig, notification: SessionNotification): Promise<void> {\n const attempts = this.#options.attempts ?? 3\n const baseDelay = this.#options.retryDelayMs ?? 500\n for (let attempt = 0; attempt < attempts; attempt++) {\n try {\n const res = await fetch(webhook.url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...webhook.headers },\n body: JSON.stringify(notification),\n })\n if (res.ok) return\n } catch {\n // network error — retry below\n }\n if (attempt < attempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** attempt))\n }\n }\n }\n}\n","import { BrowserBridgeExecutor, type BridgeAnswer, type ToolExecutionResult } from '@workerdeck/core'\nimport type { ServerFrame, ToolCallRequestFrame } from '@workerdeck/protocol'\n\ntype SessionBridge = {\n /** Every client currently attached to this session, in attach order. */\n sockets: Array<(frame: ServerFrame) => void>\n executor: BrowserBridgeExecutor\n}\n\nexport type BridgeHubOptions = {\n /** How long a bridged call may stay unanswered before it fails. Default 60000. */\n timeoutMs?: number\n /** Called when a bridged execution reaches a terminal result — the host feeds\n * it back into the runner's loop. */\n onResult?: (sessionId: string, executionId: string, result: ToolExecutionResult) => void\n}\n\n/**\n * Routes tool executions between a session and the browser tabs attached to it.\n *\n * A session may have several clients attached (dashboard plus embedded panel);\n * the bridge asks the **first attached** one, which is the closest thing to \"the\n * client driving this session\". If none is attached, dispatch fails fast rather\n * than hanging — an autonomous job simply never bridges, it uses the server\n * executor instead.\n */\nexport class BridgeHub {\n #sessions = new Map<string, SessionBridge>()\n #options: BridgeHubOptions\n\n constructor(options: BridgeHubOptions = {}) {\n this.#options = options\n }\n\n /** The executor to hand a runner for this session. Created on first use and\n * reused, so results routed back always reach the same pending table. */\n executorFor(sessionId: string): BrowserBridgeExecutor {\n return this.#bridge(sessionId).executor\n }\n\n /** How many clients are watching this session. Parking consults it: a session\n * someone is watching stays live. */\n attachedCount(sessionId: string): number {\n return this.#sessions.get(sessionId)?.sockets.length ?? 0\n }\n\n /** Register an attached client. Returns a detach function. */\n attach(sessionId: string, send: (frame: ServerFrame) => void): () => void {\n const bridge = this.#bridge(sessionId)\n bridge.sockets.push(send)\n return () => {\n const index = bridge.sockets.indexOf(send)\n if (index >= 0) bridge.sockets.splice(index, 1)\n }\n }\n\n /**\n * Deliver a client's answer to a bridged call. Returns false when the id is\n * unknown or already settled — late and duplicate answers are ignored.\n */\n resolve(sessionId: string, executionId: string, answer: BridgeAnswer): boolean {\n return this.#sessions.get(sessionId)?.executor.resolve(executionId, answer) ?? false\n }\n\n /** Drop a session's bridge, failing anything still in flight. */\n remove(sessionId: string): void {\n const bridge = this.#sessions.get(sessionId)\n if (!bridge) return\n bridge.executor.registry.cancelAll('session_closed', 'the session was closed')\n this.#sessions.delete(sessionId)\n }\n\n #bridge(sessionId: string): SessionBridge {\n const existing = this.#sessions.get(sessionId)\n if (existing) return existing\n const bridge: SessionBridge = {\n sockets: [],\n executor: new BrowserBridgeExecutor({\n timeoutMs: this.#options.timeoutMs,\n send: (frame: ToolCallRequestFrame) => {\n const target = bridge.sockets[0]\n if (!target) return false\n target(frame)\n return true\n },\n cancel: (executionId, reason) => {\n for (const send of bridge.sockets) send({ type: 'tool_call_canceled', executionId, reason })\n },\n onResult: (executionId, result) => {\n this.#options.onResult?.(sessionId, executionId, result)\n },\n }),\n }\n this.#sessions.set(sessionId, bridge)\n return bridge\n }\n}\n","import type {\n ParkedExecution,\n Runner,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\nimport type { SessionRegistry } from './registry.ts'\nimport type { ParkedSessionRecord, SessionStore } from './session-store.ts'\n\nexport type SessionParkOptions = {\n registry: SessionRegistry\n store: SessionStore\n /** Rebuild a parked session's runner. The snapshot rides in on\n * `config.restore`, so the engine adopts the id, event log, and history. */\n rebuild: (record: ParkedSessionRecord) => Promise<Runner>\n /** How many clients are attached to this session. A watched session stays live:\n * parking would pull the runner out from under the socket. */\n attachedCount: (sessionId: string) => number\n /** Wait this long after the last client detaches before parking, so a reconnect\n * (a wifi blip, a page reload) doesn't cost a teardown. Default 2000. */\n parkDelayMs?: number\n /** Grace given at {@link SessionParkManager.hydrate} to an execution whose\n * deadline passed while the server was down. Its result could not have been\n * delivered during the outage, so failing it the instant the process is back\n * would throw away an answer that is very likely seconds behind. Extends a\n * deadline, never shortens one. Default 60000. */\n expiredGraceMs?: number\n /** Veto + accounting hook, called before the teardown: the job queue frees the\n * run's concurrency slot here, and refuses (false) when the run is finalizing. */\n onParking?: (sessionId: string, executionId: string) => boolean\n /** The session is live again under a NEW runner object — anything holding the\n * old reference must rebind. */\n onResumed?: (sessionId: string, runner: Runner) => void\n /** Park/resume failures. These are not session errors — the session is intact,\n * the host's storage or engine assembly isn't. */\n onError?: (error: unknown, context: { sessionId: string; phase: 'park' | 'resume' }) => void\n}\n\n/**\n * Deferred execution's other half: parking a session that is waiting on work no\n * process in this server is doing.\n *\n * The runner announces the moment with `status_changed: 'parked'` — emitted only\n * once every dispatch of the batch has been handed over, so the snapshot can never\n * miss a call that was still being dispatched. From there this class snapshots,\n * evicts, and persists; delivering a result rebuilds the runner under the same id\n * and hands the result to it. The session's identity, event log, and seq numbering\n * survive intact, so a client reattaching with `afterSeq` sees one unbroken stream.\n */\nexport class SessionParkManager {\n #options: SessionParkOptions\n /** executionId → sessionId, for routing a result to its session. Kept in memory\n * across the park; rebuilt from the store by {@link hydrate}. */\n #owners = new Map<string, string>()\n /** Executions already settled, kept until their session ends so a late or\n * duplicate delivery answers \"already settled\" instead of \"never heard of it\". */\n #settled = new Map<string, string>()\n #timers = new Map<string, ReturnType<typeof setTimeout>>()\n /** One resume per session, ever: two results arriving together must not build\n * two runners under the same id (the second would orphan the first, leaking the\n * MCP connection the park existed to release). */\n #resuming = new Map<string, Promise<Runner | undefined>>()\n #detachTimers = new Map<string, ReturnType<typeof setTimeout>>()\n /** The config each live session was built from — what a rebuild needs, and the\n * one thing a runner doesn't carry on its public surface. */\n #configs = new Map<string, SessionRunnerConfig>()\n /**\n * In-flight store work per session, so operations on one record run in order.\n *\n * Load-bearing with any store whose writes are real I/O. `#park` must evict the\n * runner *before* the save completes (an attach between `park()` and `evict()`\n * would bind a client to an inert runner), which leaves a window where the\n * session is in neither the registry nor the store. A delivery arriving inside it\n * would read past the write: `store.get` misses, the result is answered 404, the\n * execution is filed as settled with its watchdog cleared — and then the record\n * lands on disk with nothing left alive that could ever wake it. A `discard`\n * inside the same window would delete nothing and leave the save to resurrect a\n * session the caller was told was closed.\n */\n #storeOps = new Map<string, Promise<void>>()\n #closed = false\n\n constructor(options: SessionParkOptions) {\n this.#options = options\n }\n\n /** Record the config a session was created with. Only sessions the host\n * remembers can be parked — there is no way to rebuild the others. */\n remember(sessionId: string, config: SessionRunnerConfig): void {\n this.#configs.set(sessionId, config)\n }\n\n /** Adopt the store's contents (a durable store after a restart): re-index the\n * executions and re-arm their watchdogs, no deadline sooner than the grace\n * window — nothing could have been delivered while the process was down. */\n async hydrate(): Promise<void> {\n const floor = Date.now() + (this.#options.expiredGraceMs ?? 60_000)\n for (const record of await this.#options.store.list()) {\n for (const execution of record.executions) this.#track(record.id, execution, floor)\n }\n }\n\n /**\n * Follow a session's lifecycle: index its deferred executions, park it when the\n * engine says the turn has come to rest on them, and clean up when it ends.\n * `afterSeq` skips a rehydrated runner's replayed history (re-arming a watchdog\n * from an event whose deadline already passed would fail the execution instantly).\n */\n watch(runner: Runner, afterSeq = 0): () => void {\n return runner.subscribe((event) => {\n switch (event.type) {\n case 'execution_dispatched':\n if (!event.deferred) return\n this.#track(runner.id, {\n executionId: event.executionId,\n toolName: event.toolName,\n expiresAt: event.expiresAt,\n })\n return\n case 'execution_result':\n case 'execution_failed':\n this.#forget(event.executionId)\n // One call of a multi-call park settling leaves the session parked on\n // the rest — it has to go back down, and no new status_changed will say so.\n if (runner.info().status === 'parked') void this.#park(runner)\n return\n case 'status_changed':\n if (event.status === 'parked') void this.#park(runner)\n return\n case 'session_closed':\n void this.discard(runner.id)\n return\n default:\n return\n }\n }, afterSeq)\n }\n\n /** A client detached: park the session if that was the last one watching. */\n onDetach(sessionId: string): void {\n if (this.#closed) return\n const runner = this.#options.registry.get(sessionId)\n if (!runner || runner.info().status !== 'parked') return\n clearTimeout(this.#detachTimers.get(sessionId))\n const timer = setTimeout(() => {\n this.#detachTimers.delete(sessionId)\n void this.#park(runner)\n }, this.#options.parkDelayMs ?? 2000)\n timer.unref?.()\n this.#detachTimers.set(sessionId, timer)\n }\n\n /** Which session this execution belongs to — still waiting, or already settled. */\n sessionFor(executionId: string): string | undefined {\n return this.#owners.get(executionId) ?? this.#settled.get(executionId)\n }\n\n /** The parked session's record, for the read paths (GET, list, attach). */\n get(id: string): Promise<ParkedSessionRecord | null> {\n return this.#queue(id, () => this.#options.store.get(id))\n }\n\n /** Every parked session's info, to merge into `GET {basePath}/sessions`. */\n async listInfo(): Promise<SessionInfo[]> {\n // A park mid-save belongs in the listing: it is neither in the registry nor\n // readable yet, and a caller that saw it there a moment ago must not see it\n // vanish. Waiting on the writes in flight is what keeps the two views joined.\n await Promise.all(this.#storeOps.values())\n return (await this.#options.store.list()).map((record) => record.info)\n }\n\n /** The live runner for a session, rehydrating a parked one on demand. Undefined\n * when the session is neither live nor parked. */\n async ensureLive(id: string): Promise<Runner | undefined> {\n const live = this.#options.registry.get(id)\n if (live) return live\n return this.#resume(id)\n }\n\n /**\n * Deliver a deferred execution's result. Rehydrates the session if needed and\n * folds the result into its agent loop.\n *\n * Undefined = no session is waiting on that id. `applied: false` = it was already\n * settled: a duplicate delivery, or one racing the watchdog. Both are expected,\n * neither is an error.\n */\n async submitResult(\n executionId: string,\n result: ToolExecutionResult,\n ): Promise<{ applied: boolean; sessionId: string } | undefined> {\n const sessionId = this.#owners.get(executionId)\n if (sessionId === undefined) {\n // Already answered — by an earlier delivery, or by the watchdog it raced.\n // Waking the session to be told again would be worse than useless.\n const settled = this.#settled.get(executionId)\n return settled === undefined ? undefined : { applied: false, sessionId: settled }\n }\n const runner = await this.ensureLive(sessionId)\n if (!runner) {\n this.#forget(executionId)\n return undefined\n }\n // Clear the watchdog first: settling re-enters the agent loop, and a timeout\n // firing behind it would try to fail a call that no longer exists.\n this.#clearTimer(executionId)\n const applied = runner.settleExecution?.(executionId, result) ?? false\n if (applied) this.#forget(executionId)\n return { applied, sessionId }\n }\n\n /** Drop a parked session for good: the run is over (closed, canceled, killed). */\n async discard(sessionId: string): Promise<void> {\n clearTimeout(this.#detachTimers.get(sessionId))\n this.#detachTimers.delete(sessionId)\n this.#configs.delete(sessionId)\n for (const [executionId, owner] of this.#owners) {\n if (owner === sessionId) this.#forget(executionId)\n }\n for (const [executionId, owner] of this.#settled) {\n if (owner === sessionId) this.#settled.delete(executionId)\n }\n await this.#queue(sessionId, () => this.#options.store.delete(sessionId))\n }\n\n close(): void {\n this.#closed = true\n for (const timer of this.#timers.values()) clearTimeout(timer)\n for (const timer of this.#detachTimers.values()) clearTimeout(timer)\n this.#timers.clear()\n this.#detachTimers.clear()\n }\n\n async #park(runner: Runner): Promise<void> {\n if (this.#closed || !runner.park) return\n const id = runner.id\n if (this.#options.registry.get(id) !== runner) return\n if (runner.info().status !== 'parked') return\n // Someone is watching: keep it live and reconsider when they leave.\n if (this.#options.attachedCount(id) > 0) return\n const executions = [...this.#owners].filter(([, owner]) => owner === id).map(([e]) => e)\n if (executions.length === 0) return\n const config = this.#configs.get(id)\n if (!config) return\n if (this.#options.onParking && !this.#options.onParking(id, executions[0]!)) return\n // park() → evict in one tick: an attach landing between them would bind a\n // client to a runner that is already inert.\n const snapshot = runner.park()\n if (!snapshot) return\n const info = { ...runner.info(), status: 'parked' as const }\n this.#options.registry.evict(id)\n const record: ParkedSessionRecord = {\n id,\n info,\n profile: info.profile,\n config,\n snapshot,\n executions: snapshot.parked,\n parkedAt: Date.now(),\n }\n for (const execution of snapshot.parked) this.#track(id, execution)\n try {\n await this.#queue(id, () => this.#options.store.save(record))\n } catch (error) {\n // The runner is already inert and evicted; without the record the session\n // cannot come back. Nothing to roll back to — surface it loudly.\n this.#options.onError?.(error, { sessionId: id, phase: 'park' })\n }\n }\n\n async #resume(id: string): Promise<Runner | undefined> {\n const inFlight = this.#resuming.get(id)\n if (inFlight) return inFlight\n const attempt = this.#rebuild(id)\n this.#resuming.set(id, attempt)\n try {\n return await attempt\n } finally {\n this.#resuming.delete(id)\n }\n }\n\n async #rebuild(id: string): Promise<Runner | undefined> {\n const record = await this.#queue(id, () => this.#options.store.get(id))\n if (!record) return undefined\n let runner: Runner\n try {\n runner = await this.#options.rebuild(record)\n } catch (error) {\n // Keep the record: the failure may be transient (an MCP server down), and\n // the next delivery — or a retried one — gets another chance.\n this.#options.onError?.(error, { sessionId: id, phase: 'resume' })\n throw error\n }\n if (runner.id !== id) {\n // The factory ignored `restore` and built a fresh session: it has none of\n // this one's history and would strand the execution we are waking for.\n runner.close('error')\n const error = new Error(\n `rebuilt session has id '${runner.id}', expected '${id}' — the engine factory must ` +\n 'forward EngineRunnerContext.restore to the runner config',\n )\n this.#options.onError?.(error, { sessionId: id, phase: 'resume' })\n throw error\n }\n // Registered and subscribed before it starts, so nothing it emits on the way\n // back up is missed by the queue or the watchers.\n this.#options.registry.register(runner)\n this.remember(id, record.config)\n this.watch(runner, record.snapshot.seq)\n this.#options.onResumed?.(id, runner)\n await this.#queue(id, () => this.#options.store.delete(id))\n void runner.start()\n return runner\n }\n\n /** Run a store operation after whatever is already in flight for this session.\n * The chain is per session and drops itself once idle; a failed operation never\n * poisons the ones behind it (each caller handles its own). */\n #queue<T>(sessionId: string, op: () => Promise<T>): Promise<T> {\n const previous = this.#storeOps.get(sessionId) ?? Promise.resolve()\n const result = previous.then(op)\n const settled = result.then(\n () => {},\n () => {},\n )\n this.#storeOps.set(sessionId, settled)\n void settled.then(() => {\n if (this.#storeOps.get(sessionId) === settled) this.#storeOps.delete(sessionId)\n })\n return result\n }\n\n #track(sessionId: string, execution: ParkedExecution, notBefore = 0): void {\n this.#owners.set(execution.executionId, sessionId)\n if (execution.expiresAt === undefined || this.#timers.has(execution.executionId)) return\n const expiresAt = Math.max(execution.expiresAt, notBefore)\n const timer = setTimeout(\n () => {\n this.#timers.delete(execution.executionId)\n // A result that never came is ordinary tool output, not a session error:\n // the agent gets told and adapts, exactly like a sandbox failure.\n void this.submitResult(execution.executionId, {\n status: 'failed',\n reason: 'timeout',\n error: `deferred execution '${execution.toolName}' produced no result before its deadline`,\n }).catch((error: unknown) => {\n this.#options.onError?.(error, { sessionId, phase: 'resume' })\n })\n },\n Math.max(0, expiresAt - Date.now()),\n )\n timer.unref?.()\n this.#timers.set(execution.executionId, timer)\n }\n\n #forget(executionId: string): void {\n this.#clearTimer(executionId)\n const owner = this.#owners.get(executionId)\n if (owner !== undefined) this.#settled.set(executionId, owner)\n this.#owners.delete(executionId)\n }\n\n #clearTimer(executionId: string): void {\n const timer = this.#timers.get(executionId)\n if (timer === undefined) return\n clearTimeout(timer)\n this.#timers.delete(executionId)\n }\n}\n","import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { ParkedExecution, RunnerSnapshot, SessionRunnerConfig } from '@workerdeck/core'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\n/**\n * A session with its live runner torn down, waiting on deferred executions.\n *\n * Everything needed to bring it back: the wire-visible info (so it still lists and\n * reads over REST while parked), the config to rebuild the runner, the engine's\n * snapshot, and what it is waiting for.\n */\nexport type ParkedSessionRecord = {\n id: string\n /** Session info as of the park, with `status: 'parked'`. */\n info: SessionInfo\n profile?: string\n /** The config the session was created with (profile defaults already applied). */\n config: SessionRunnerConfig\n snapshot: RunnerSnapshot\n executions: ParkedExecution[]\n parkedAt: number\n}\n\n/**\n * Where parked sessions live. Two implementations ship: {@link MemorySessionStore}\n * (a park survives a disconnect, not a restart) and {@link createFileSessionStore}\n * (it survives both, on one host); a redis/sqlite/table store implements the same\n * four operations.\n *\n * Two things to know before writing one: the record holds the session's whole\n * transcript and tool I/O, and `config` may carry host-injected values (env, hooks,\n * injected functions) that a JSON round-trip silently drops or, worse, persists.\n * {@link toDurableRecord} is the filter the bundled file store applies — reuse it.\n */\nexport interface SessionStore {\n save(record: ParkedSessionRecord): Promise<void>\n get(id: string): Promise<ParkedSessionRecord | null>\n list(): Promise<ParkedSessionRecord[]>\n delete(id: string): Promise<boolean>\n}\n\n/** Single-process, no persistence: parks survive a client disconnect, not a restart. */\nexport class MemorySessionStore implements SessionStore {\n #records = new Map<string, ParkedSessionRecord>()\n\n save(record: ParkedSessionRecord): Promise<void> {\n this.#records.set(record.id, record)\n return Promise.resolve()\n }\n\n get(id: string): Promise<ParkedSessionRecord | null> {\n return Promise.resolve(this.#records.get(id) ?? null)\n }\n\n list(): Promise<ParkedSessionRecord[]> {\n return Promise.resolve([...this.#records.values()])\n }\n\n delete(id: string): Promise<boolean> {\n return Promise.resolve(this.#records.delete(id))\n }\n}\n\n/**\n * Config fields that must not be written to durable storage: two are functions\n * (JSON drops them silently), `extraOptions` is SDK `Options` and may hold hooks\n * and callbacks, and `env` is a credential-bearing map — the same rule\n * `profile-store.ts` follows, for the same reason.\n *\n * Dropping them costs a rehydrated session nothing: all four are consumed by the\n * Claude engine alone, and the Claude engine cannot park (the CLI owns its process\n * state — `buildRunner` refuses a `restore` for it). A provider session's\n * credentials are resolved by `createEngineRunner` from the operator's environment\n * on every build, wake included.\n */\nconst EPHEMERAL_CONFIG_KEYS = ['queryFn', 'historyFn', 'extraOptions', 'env'] as const\n\n/** The record as it may be persisted: same session, config narrowed to what is\n * safe and meaningful to keep (see {@link EPHEMERAL_CONFIG_KEYS}). */\nexport function toDurableRecord(record: ParkedSessionRecord): ParkedSessionRecord {\n const config: SessionRunnerConfig = { ...record.config }\n for (const key of EPHEMERAL_CONFIG_KEYS) delete config[key]\n return { ...record, config }\n}\n\n/** Bump when the on-disk shape changes incompatibly; records written by another\n * version are ignored rather than half-read into a broken session. */\nconst FORMAT_VERSION = 1\n\nexport type FileSessionStoreOptions = {\n /** Directory holding one JSON file per parked session.\n * Default `<cwd>/.workerdeck/parked`. */\n dir?: string\n /** A record that could not be read or written. Losing one is losing a session's\n * way back, so this is worth logging — the store itself stays quiet and skips it. */\n onError?: (error: unknown, context: { path: string; op: 'save' | 'read' | 'delete' }) => void\n}\n\n/**\n * Durable single-host store: one JSON file per parked session under `dir`, written\n * through a temp file and a rename so a crash mid-write cannot truncate a session.\n * `hydrate()` at `listen()` picks them up, re-indexes their executions, and re-arms\n * the watchdogs, so a restart no longer loses parked work.\n *\n * Know what is on that disk: **the record holds the session's entire transcript** —\n * prompts, model output, and tool I/O — in plaintext. Put it somewhere with the same\n * protection as the SDK's own transcripts (`~/.claude/projects`), not in a directory\n * that gets served, synced, or backed up somewhere looser.\n *\n * Single-process by design, exactly like the bundled queue adapter and profile\n * store: two servers sharing one directory would both hydrate the same records and\n * race to rebuild them. That is what the seam is for.\n *\n * Nothing here reaps: a record leaves only when its session wakes or is deleted.\n * An execution dispatched without a deadline (a `DeferredExecutor` with no\n * `timeoutMs`) has no watchdog to end the wait, so its record — and its transcript\n * — stays until `DELETE /sessions/:id`. Give deferred calls a deadline, or sweep.\n */\nexport function createFileSessionStore(options: FileSessionStoreOptions = {}): SessionStore {\n const dir = options.dir ?? join(process.cwd(), '.workerdeck', 'parked')\n // Encoded, not interpolated: an id is a runner-assigned string, and a '/' in one\n // would otherwise write outside `dir`.\n const fileFor = (id: string): string => join(dir, `${encodeURIComponent(id)}.json`)\n\n const read = async (path: string): Promise<ParkedSessionRecord | null> => {\n let raw: string\n try {\n raw = await readFile(path, 'utf8')\n } catch (error) {\n // Absent is the ordinary case: nothing parked under that id. Anything else\n // (EACCES, EIO) is a session we cannot reach, and reporting it as \"nothing\n // is parked\" is how a restart guard ends up saying the coast is clear.\n if (isMissing(error)) return null\n options.onError?.(error, { path, op: 'read' })\n return null\n }\n try {\n return parseRecord(JSON.parse(raw))\n } catch (error) {\n options.onError?.(error, { path, op: 'read' })\n return null\n }\n }\n\n return {\n save: async (record) => {\n const path = fileFor(record.id)\n let payload: string\n try {\n payload = JSON.stringify({ version: FORMAT_VERSION, record: toDurableRecord(record) })\n } catch (error) {\n options.onError?.(error, { path, op: 'save' })\n throw new Error(\n `parked session '${record.id}' is not JSON-serializable — a host-injected value ` +\n `reached its config or snapshot: ${String(error)}`,\n )\n }\n try {\n // Transcripts, so owner-only: the docs say protect this like\n // `~/.claude/projects`, and the default 0755/0644 wouldn't.\n await mkdir(dir, { recursive: true, mode: 0o700 })\n const temp = `${path}.${process.pid}.tmp`\n await writeFile(temp, payload, { mode: 0o600 })\n await rename(temp, path)\n } catch (error) {\n options.onError?.(error, { path, op: 'save' })\n throw error\n }\n },\n\n get: (id) => read(fileFor(id)),\n\n list: async () => {\n let names: string[]\n try {\n names = await readdir(dir)\n } catch (error) {\n if (isMissing(error)) return [] // No directory yet: nothing has parked here.\n // An unreadable directory is not an empty one. `hydrate()` runs inside\n // `listen()`, so throwing here refuses the boot instead of coming up as a\n // server that has quietly forgotten every parked session.\n options.onError?.(error, { path: dir, op: 'read' })\n throw error\n }\n const records = await Promise.all(\n names\n .filter((name) => name.endsWith('.json'))\n .map(async (name) => {\n const record = await read(join(dir, name))\n if (!record) return null\n // A record only answers `get`/`delete` under the name its id encodes to.\n // One that got here some other way (a copy, an ops rename) would list\n // forever and be unreachable — better to say so than to serve a ghost.\n if (`${encodeURIComponent(record.id)}.json` === name) return record\n options.onError?.(\n new Error(`parked record '${record.id}' is stored as '${name}' and cannot be read back by id`),\n { path: join(dir, name), op: 'read' },\n )\n return null\n }),\n )\n return records.filter((record): record is ParkedSessionRecord => record !== null)\n },\n\n delete: async (id) => {\n const path = fileFor(id)\n try {\n await rm(path)\n return true\n } catch (error) {\n // Missing is not an error — a discard racing a wake-up hits this.\n if (isMissing(error)) return false\n options.onError?.(error, { path, op: 'delete' })\n return false\n }\n },\n }\n}\n\nconst isMissing = (error: unknown): boolean => (error as NodeJS.ErrnoException).code === 'ENOENT'\n\n/** Shape-check a parsed file. A record missing any of these could not be rebuilt,\n * and half-restoring one is worse than skipping it. */\nfunction parseRecord(value: unknown): ParkedSessionRecord | null {\n if (!value || typeof value !== 'object') return null\n const envelope = value as { version?: unknown; record?: unknown }\n if (envelope.version !== FORMAT_VERSION) return null\n const record = envelope.record as Partial<ParkedSessionRecord> | null\n if (!record || typeof record !== 'object') return null\n if (typeof record.id !== 'string' || typeof record.parkedAt !== 'number') return null\n if (!record.info || !record.config || !record.snapshot) return null\n if (!Array.isArray(record.executions)) return null\n return record as ParkedSessionRecord\n}\n","import { createHash } from 'node:crypto'\nimport {\n createReadStream,\n existsSync,\n lstatSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n type Dirent,\n} from 'node:fs'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { homedir } from 'node:os'\nimport type { Duplex } from 'node:stream'\nimport { basename, join, resolve as resolvePath, sep } from 'node:path'\nimport { WebSocketServer, type WebSocket } from 'ws'\nimport { attachmentKind, checkClaudeAuth, getEngineAdapter } from '@workerdeck/core'\nimport type {\n ClaudeAuthProbe,\n EngineAdapter,\n EngineAvailability,\n Runner,\n RunnerSnapshot,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport { JobQueue, type QueueAdapter } from '@workerdeck/queue'\nimport {\n ENGINE_CAPABILITIES,\n PROTOCOL_VERSION,\n supportsPermissionMode,\n type ClientFrame,\n type CreateJobRequest,\n type CreateSessionRequest,\n type JobEvent,\n type PermissionMode,\n type ProfileEngine,\n type ProfileConfigSnapshot,\n type ProfileInfo,\n type QueueServerFrame,\n type McpServerActionRequest,\n type ResolvePermissionRequest,\n type UpdateSessionRequest,\n type SdkSessionSummary,\n type ServerFrame,\n type SessionInfo,\n type SubmitExecutionResultRequest,\n type UpdateProfileRequest,\n type WriteHostFileRequest,\n} from '@workerdeck/protocol'\nimport { searchFiles } from './host-file-search.ts'\nimport {\n createHostFileRoots,\n entryKind,\n readContained,\n resolveExisting,\n resolveForWrite,\n writeContained,\n} from './host-files.ts'\nimport { AttachmentStore } from './attachments.ts'\nimport { ProducedFileStore } from './produced-files.ts'\nimport { SessionRegistry } from './registry.ts'\nimport { SessionNotifier, type SessionNotificationOptions } from './notifications.ts'\nimport { BridgeHub, type BridgeHubOptions } from './bridge.ts'\nimport { SessionParkManager } from './parking.ts'\nimport { MemorySessionStore, type SessionStore } from './session-store.ts'\nimport type { ProfileStore } from './profile-store.ts'\n\nexport type SdkSessionLister = (options: {\n dir?: string\n limit?: number\n offset?: number\n}) => Promise<SdkSessionSummary[]>\n\n/**\n * Return a principal (any truthy value) to accept the request, or null/undefined to\n * reject with 401. The host app supplies this — the worker has no auth story of its\n * own. A principal object may carry `allowedProfiles: string[]` to restrict which\n * profiles the caller can create sessions/jobs under (and see in GET /profiles) —\n * without it the caller may use every declared profile. It may also carry\n * `canManageProfiles: true` to allow creating/editing/deleting managed profiles\n * (requires the `profileStore` option); anything else means no.\n */\nexport type Authenticator = (\n req: IncomingMessage,\n) => unknown | Promise<unknown>\n\nexport type WorkerServerOptions = {\n /** Required unless `allowUnauthenticated: true` — the worker must never be exposed bare. */\n authenticate?: Authenticator\n /** Explicit opt-in to run without auth (local dev only). */\n allowUnauthenticated?: boolean\n /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */\n allowedCwdRoots?: string[]\n /**\n * The host filesystem routes (`{basePath}/fs/*`) — browse and read the\n * operator's real project tree, and optionally write to it.\n *\n * **Reading follows {@link allowedCwdRoots} and needs no grant of its own.**\n * A caller holding the auth key can already start a session in any allowed root\n * and have the agent read whatever is in it, so serving those same trees over\n * `/fs` adds no authority — it only removes the absurdity of going through a\n * language model to `cat` a file. Set `roots` here only to *narrow* that (or to\n * expose a tree sessions may not run in).\n *\n * With neither set the routes 404. That is not the same as inheriting\n * `allowedCwdRoots`' permissive \"unset means anywhere\": no cwd policy means\n * there is nothing to inherit, and \"anywhere\" is a statement about paths the\n * operator types at a keyboard, not one about what a phone may read.\n *\n * **Writing is a separate opt-in**, because it is the one part that is not\n * already implied. An agent's writes go through the permission flow; a `PUT` to\n * `/fs/write` does not. These routes are operator-privileged by design — the\n * caller is the operator — but that is a reason to make the bypass deliberate,\n * not a reason to skip the switch.\n *\n * Containment is *not* `cwdAllowed`, whichever roots are in play: these routes\n * walk paths the agent may have authored, so a symlink can escape a lexical\n * prefix check. See `host-files.ts` — canonicalize, then re-check.\n */\n hostFiles?: {\n /** Absolute paths. Unset inherits {@link allowedCwdRoots}; an explicit empty\n * array disables the routes (a policy, not an absence). */\n roots?: string[]\n /** Enable `PUT {basePath}/fs/write`. Default false — read-only. */\n write?: boolean\n /** Refuse reads above this (413) rather than streaming a gigabyte to a phone.\n * Default 1 MiB. Writes are bounded by {@link maxBodyBytes} instead. */\n maxFileBytes?: number\n /** Cap on entries returned per directory (the response says `truncated`).\n * Default 5000. */\n maxEntries?: number\n /** Directory names `GET /fs/find` will not descend into. Defaults to\n * `DEFAULT_IGNORED_DIRS` (`.git`, `node_modules`, build output…) — the thing\n * that keeps a per-keystroke search cheap on a real source tree. */\n ignore?: string[]\n }\n /**\n * Message attachments (`{basePath}/sessions/:id/attachments`) — the photos and\n * files a client sends alongside a message. Always on; these knobs only size it.\n *\n * There is no grant to make here the way `hostFiles.write` is one: an upload\n * lands in the session's own in-memory hold and reaches the model as message\n * content, which is exactly what typing does. What it *can* do is cost memory,\n * so both caps default low enough that a phone camera roll cannot fill the\n * gateway.\n */\n attachments?: {\n /** Largest single upload; over it is a 413. Default 10 MiB. */\n maxFileBytes?: number\n /** Ceiling on what one session holds at once. Default 64 MiB. */\n maxSessionBytes?: number\n }\n /**\n * Named Claude Code config directories sessions can run under (each becomes the\n * session's CLAUDE_CONFIG_DIR — settings, memory, skills, and the credentials the\n * SDK resolves from it). Declared here at startup; the API only reads them\n * (GET {basePath}/profiles). With more than one declared, every session/job create\n * must name its profile; with exactly one it is implicit. Unset: a 'default'\n * profile is auto-created from $CLAUDE_CONFIG_DIR or ~/.claude when that directory\n * exists. Pass [] to run without profiles (no env pinning at all).\n */\n profiles?: ProfileInfo[]\n /**\n * Persistence for dashboard-managed profiles, which mounts the profile\n * management routes (`POST /profiles`, `PATCH`/`DELETE /profiles/:name`).\n * Without it the profile set is startup config and the API stays read-only.\n *\n * Profiles declared in `profiles` are never stored and never editable over\n * HTTP — they are code. The two sets are unioned by name, declared winning.\n * Callers still need `canManageProfiles` on their principal.\n */\n profileStore?: ProfileStore\n /**\n * Config-dir roots a *managed* Claude profile's `configDir` must resolve inside\n * (mirrors {@link allowedCwdRoots}). Unset — the default — means the management\n * routes create provider profiles only: naming a config directory is choosing\n * which credential store a session runs on, so it stays operator-bounded.\n * Declared profiles are unaffected.\n */\n allowedConfigDirRoots?: string[]\n /** Map/patch the incoming CreateSessionRequest into the runner config (inject queryFn,\n * env, tool policy, per-skill constraints...). Defaults to identity.\n *\n * What this hook injects is **not** durable: a session rebuilt from a parked\n * record is built from the stored config, and a durable store persists neither\n * `env` nor injected functions (see `toDurableRecord` in session-store.ts). That costs the\n * Claude engine nothing, since it cannot park — but a provider host that\n * resolves credentials into `config.env` here for its `createEngineRunner` to\n * read back loses them on the wake. Resolve them in the factory instead. */\n buildRunnerConfig?: (req: CreateSessionRequest) => SessionRunnerConfig\n /** URL prefix for all routes. Default '/v1'. */\n basePath?: string\n /**\n * Handle requests that fall outside `basePath` instead of 404ing them. The\n * turnkey CLI serves the dashboard through this, which is the whole reason it\n * exists: a browser cannot put a header on a WebSocket handshake, so the only\n * credential a tab can present on a session attach is a cookie — and a cookie\n * only rides requests to the origin that set it. Serving the app and the API\n * from one origin is therefore not a convenience, it is what makes an\n * authenticated dashboard possible without a stamping proxy in front.\n *\n * Upgrades are not routed here: anything outside `basePath` is still refused.\n */\n fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n /**\n * Browser origins allowed to call this API cross-origin — for a dashboard\n * served somewhere other than this gateway.\n *\n * Off unless configured, and even then it is *sharing policy, not a\n * credential*: preflights are answered before auth (browsers strip\n * credentials from them, so they would otherwise 401), but every real request\n * still goes through `authenticate`, and an allowlisted page that does not\n * hold the key gets nothing.\n *\n * Two rules the implementation must keep: **exact origins only**, no\n * wildcards or suffix matching; and `Access-Control-Allow-Credentials` is\n * **never** sent, which is what keeps an ambient cookie from becoming\n * cross-origin authority. WebSocket upgrades are exempt from CORS entirely\n * and are unaffected by this — their credential is whatever the host's\n * `authenticate` accepts on the handshake.\n */\n cors?: { origins: string[] }\n /** Max JSON body size in bytes. Default 1 MiB. */\n maxBodyBytes?: number\n /**\n * Server-wide bypass policy: refuse `permissionMode: 'bypassPermissions'` on\n * session/job creation (403), and strip the `allowDangerouslySkipPermissions`\n * pre-authorization from requests (so clients that ask for the capability by\n * default keep working — their later switch attempt fails with the CLI's own\n * visible error instead). Mirrors Claude Code's\n * `permissions.disableBypassPermissionsMode` setting, enforced at the gateway.\n */\n disableBypassPermissions?: boolean\n /**\n * Fail closed on subscription credentials: if a session initializes with\n * `apiKeySource: 'oauth'` (a claude.ai login rather than an API key / Bedrock / Vertex),\n * it is terminated with a session_error. Recommended for services and any\n * unattended/scheduled use — Anthropic's terms require API-key auth for those.\n * Off by default: single-user personal deployments may legitimately run on the\n * operator's own subscription; the server then logs a one-time notice instead.\n */\n requireApiKey?: boolean\n /**\n * Launch-time credential sanity check: once `listen()` binds, each Claude\n * profile's session environment — exactly what `buildRunnerConfig` would hand\n * a session, host hook included — is probed with the SDK-bundled CLI's\n * `claude auth status`, concurrently and fire-and-forget, and a profile that\n * reports logged-out gets one console warning. Warn, never fail: the operator\n * may be about to log in, and a probe that cannot run at all (missing binary,\n * a CLI without `auth status`, unparseable output) stays silent — \"couldn't\n * check\" is not \"not logged in\". No credential material is read or logged.\n * Off by default (this is a library; tests must spawn nothing) — the turnkey\n * CLI turns it on. Pass an object to inject the probe (tests) or a timeout.\n */\n checkCredentials?: boolean | { probe?: ClaudeAuthProbe; timeoutMs?: number }\n /** Injectable lister for GET /sdk-sessions (tests) — honored for the CLAUDE\n * engine only, like the injectable claude auth probe (it predates the adapter\n * layer). Defaults to the claude adapter's lister (the SDK's on-disk session\n * store); other engines always answer through their adapter's\n * `listSessions`. */\n listSdkSessions?: SdkSessionLister\n /** Enable the job queue (`/jobs` + `/queue` routes). Jobs run as ordinary registry\n * sessions — attachable over the sessions WS — governed by these limits. */\n queue?: QueueServerOptions\n /**\n * Out-of-band notification for interactive sessions: the four moments a person\n * away from the screen needs (permission requested, turn done, error, closed)\n * POSTed to a webhook and/or handed to a local observer. Off unless configured.\n *\n * Server-wide, unlike the queue's per-job webhook — the point is to hear about\n * sessions you neither created nor are attached to, which is the situation a\n * mobile client is in permanently (iOS will not hold a WebSocket open in the\n * background). Every registry session qualifies, job runs included, so a job\n * carrying its own webhook is reported on both channels.\n *\n * This is the primitive, and it stays transport-agnostic on purpose: the OSS\n * server holds no push credentials. Turning a notification into an APNs push is\n * a forwarder's job — see the turnkey CLI.\n */\n notifications?: SessionNotificationOptions\n /** Browser-bridged tool execution: how long a bridged call may go unanswered\n * before it fails (default 60000), and where terminal results are delivered.\n * The hub is always available on the returned server as `bridge`. */\n bridge?: BridgeHubOptions\n /**\n * Deferred execution: a session that parks on an execution nothing here is\n * running has its state persisted and its runner torn down, and comes back when\n * the result is POSTed to `{basePath}/executions/:executionId/result`.\n *\n * On by default with an in-memory store, so a park survives a disconnect but not\n * a restart; pass `store: createFileSessionStore()` (or your own) to change that\n * — read its doc first, the record holds the whole transcript.\n */\n parking?: {\n store?: SessionStore\n /** Grace after the last client detaches before parking. Default 2000. */\n parkDelayMs?: number\n /** Grace given on boot to an execution whose deadline passed while the server\n * was down (durable stores only — nothing else survives a restart). Default 60000. */\n expiredGraceMs?: number\n /** Park/resume failures — storage or engine-assembly problems, not session errors. */\n onError?: (error: unknown, context: { sessionId: string; phase: 'park' | 'resume' }) => void\n }\n /**\n * Build a runner for a `provider` profile (the model-agnostic engine).\n * Required if any such profile is declared — the server refuses to start\n * otherwise, rather than failing at create time.\n *\n * Kept as a host hook so the server package neither imports a model SDK nor\n * decides how provider credentials are resolved: the factory reads them from\n * the operator's environment, exactly like the Claude credential chain.\n * `claude` and `codex` profiles never come through here — those engines ship\n * as in-repo adapters (`@workerdeck/core`'s `getEngineAdapter`).\n *\n * May be async: assembly that has to await — a per-session MCP connect, a\n * credential lookup — belongs here, with `AiSdkRunnerConfig.onClose` as the\n * disposer. A rejection fails the create — the session POST answers 500 with\n * the message, a job goes straight to `failed`.\n */\n createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>\n /**\n * Adapter overrides, keyed by engine — **for tests only** (the server\n * integration suite injects a fake codex engine so `pnpm test` spawns no\n * binary). Not a public extension point: third engines belong in core as\n * adapters, or behind `createEngineRunner` as provider profiles.\n */\n engines?: Partial<Record<ProfileEngine, EngineAdapter>>\n}\n\nexport type EngineRunnerContext = {\n /** The session config, with profile defaults already applied. */\n config: SessionRunnerConfig\n /** The profile that selected this engine. */\n profile: ProfileInfo\n /** Bridge hub, for handing the runner a browser-backed ToolExecutor\n * (`bridge.executorFor(sessionId)`). */\n bridge: BridgeHub\n /**\n * Set when rebuilding a session that parked on a deferred execution. Forward it\n * as `restore` on the engine config (`createEngineSession({ config: { ...config,\n * restore } })`) — the engine then adopts the session's id, event log, seq\n * numbering, history, and scratch filesystem instead of starting fresh.\n */\n restore?: RunnerSnapshot\n}\n\nexport type QueueServerOptions = {\n /** Concurrent job sessions. Default 1. */\n maxConcurrency?: number\n /** Token cap per job session (input+output+cache tokens); exceeding it kills the run. */\n sessionTokenLimit?: number\n /** Global job-token budget per UTC day; queued jobs are held once exhausted. */\n dailyTokenLimit?: number\n /** Wall-clock cap per job run — the watchdog against stuck CLIs. */\n maxJobDurationMs?: number\n /** Grace between interrupting a killed run and force-closing it. Default 5000. */\n killGraceMs?: number\n /** Expire terminal jobs after `maxAgeMs` (the in-memory adapter otherwise grows\n * unboundedly). */\n retention?: { maxAgeMs: number; sweepIntervalMs?: number }\n /** Queue backend. Defaults to the bundled in-memory adapter (single process,\n * no persistence) — redis/bullmq/pubsub adapters implement the same interface. */\n adapter?: QueueAdapter\n /** Webhook delivery attempts per event (default 3, exponential backoff). */\n webhookAttempts?: number\n webhookRetryDelayMs?: number\n /** Local observer for job lifecycle events (in addition to per-job webhooks). */\n onEvent?: (event: JobEvent) => void\n}\n\nexport type WorkerServer = {\n server: Server\n registry: SessionRegistry\n /** The job queue, when `queue` options were provided. */\n queue?: JobQueue\n /** Routes tool executions to attached browser clients. `bridge.executorFor(id)`\n * is the `ToolExecutor` to hand a runner that should execute in the tab. */\n bridge: BridgeHub\n /** Parked sessions: the store, the execution index, and the rehydration path.\n * Deliver a deferred result with `parking.submitResult(...)` in-process, or POST\n * it to `{basePath}/executions/:executionId/result`. */\n parking: SessionParkManager\n listen: (port: number, host?: string) => Promise<{ port: number }>\n close: () => Promise<void>\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(payload),\n })\n res.end(payload)\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n maxBytes: number,\n): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > maxBytes) throw new Error('request body too large')\n chunks.push(chunk as Buffer)\n }\n if (size === 0) return {}\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>\n}\n\n/** Body as bytes, refusing anything over `maxBytes`. Attachments are the one\n * thing this server takes that isn't JSON. */\nasync function readRawBody(req: IncomingMessage, maxBytes: number): Promise<Buffer> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > maxBytes) throw new Error('request body too large')\n chunks.push(chunk as Buffer)\n }\n return Buffer.concat(chunks)\n}\n\n/**\n * Curated, view-only snapshot of a profile's config dir for GET /profiles/:name.\n * Best-effort: a missing or unparseable settings.json just omits the settings block.\n * Env var VALUES are never read into the response — names only.\n *\n * Provider profiles have no config dir, so the snapshot is empty for them: their\n * configuration is the `provider` block already on ProfileInfo.\n */\nfunction readProfileConfig(profile: ProfileInfo): ProfileConfigSnapshot {\n const dir = profile.configDir\n if (!dir) return { hasUserMemory: false, skills: [], agents: [], commands: [] }\n const listDirs = (path: string): string[] => {\n try {\n return readdirSync(path, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort()\n } catch {\n return []\n }\n }\n const listMd = (path: string): string[] => {\n try {\n return readdirSync(path)\n .filter((file) => file.endsWith('.md'))\n .map((file) => file.slice(0, -3))\n .sort()\n } catch {\n return []\n }\n }\n const snapshot: ProfileConfigSnapshot = {\n hasUserMemory: existsSync(join(dir, 'CLAUDE.md')),\n skills: listDirs(join(dir, 'skills')),\n agents: listMd(join(dir, 'agents')),\n commands: listMd(join(dir, 'commands')),\n }\n try {\n const raw = JSON.parse(readFileSync(join(dir, 'settings.json'), 'utf8')) as Record<\n string,\n unknown\n >\n const permissions = (raw.permissions ?? {}) as Record<string, unknown>\n const count = (rules: unknown): number => (Array.isArray(rules) ? rules.length : 0)\n snapshot.settings = {\n model: typeof raw.model === 'string' ? raw.model : undefined,\n defaultPermissionMode:\n typeof permissions.defaultMode === 'string' ? permissions.defaultMode : undefined,\n permissionRules: {\n allow: count(permissions.allow),\n ask: count(permissions.ask),\n deny: count(permissions.deny),\n },\n envKeys:\n raw.env && typeof raw.env === 'object' ? Object.keys(raw.env).sort() : undefined,\n hooks:\n raw.hooks && typeof raw.hooks === 'object' ? Object.keys(raw.hooks).sort() : undefined,\n }\n } catch {\n // settings.json absent or unparseable — snapshot ships without the block\n }\n return snapshot\n}\n\n/** Conservative content types for VFS downloads: text formats the agent actually\n * produces; anything unrecognized ships as plain text (the VFS is string-backed). */\nconst CONTENT_TYPES: Record<string, string> = {\n json: 'application/json; charset=utf-8',\n md: 'text/markdown; charset=utf-8',\n html: 'text/html; charset=utf-8',\n csv: 'text/csv; charset=utf-8',\n xml: 'application/xml; charset=utf-8',\n svg: 'image/svg+xml; charset=utf-8',\n}\n\n/** sha256 hex — the currency of the conditional-write protocol on `/fs/write`. */\nfunction hashBytes(bytes: Buffer): string {\n return createHash('sha256').update(bytes).digest('hex')\n}\n\n/**\n * The file's text, or null if it isn't text. Decoding never fails in Node — invalid\n * bytes become U+FFFD — so the only honest test is a round trip: if re-encoding the\n * decoded string reproduces the original bytes, nothing was lost and the client can\n * safely edit and send it back. Anything else ships base64, which an editor can\n * refuse to open rather than silently corrupt on save.\n */\nfunction asUtf8(bytes: Buffer): string | null {\n const text = bytes.toString('utf8')\n return Buffer.from(text, 'utf8').equals(bytes) ? text : null\n}\n\nfunction contentTypeFor(filename: string): string {\n const ext = filename.includes('.') ? filename.split('.').pop()!.toLowerCase() : ''\n return CONTENT_TYPES[ext] ?? 'text/plain; charset=utf-8'\n}\n\n/** A profile runs the model-agnostic engine rather than Claude Code. `engine` is\n * optional so profiles written before provider support keep meaning 'claude'. */\nfunction isProviderProfile(profile: ProfileInfo): boolean {\n return profile.engine === 'provider'\n}\n\n/** The engine a profile runs, absent meaning 'claude' (pre-provider profiles). */\nfunction engineOf(profile: ProfileInfo | undefined): ProfileEngine {\n return profile?.engine ?? 'claude'\n}\n\n/** Where the CLI's own resolution lands for a given environment: an explicit\n * CLAUDE_CONFIG_DIR, else ~/.claude. */\nfunction cliConfigDir(env: Record<string, string | undefined>): string {\n return env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')\n}\n\n/** Auto-created profile when none are declared: the operator's own config dir. */\nfunction detectDefaultProfiles(): ProfileInfo[] {\n const dir = cliConfigDir(process.env)\n return existsSync(dir) ? [{ name: 'default', configDir: dir }] : []\n}\n\n/** Compare config dirs by what they name on disk: declared paths arrive with\n * trailing slashes or symlinked prefixes (`/var` vs `/private/var` on macOS); a\n * path that doesn't exist falls back to plain normalization. */\nfunction canonicalDir(path: string): string {\n try {\n return realpathSync(path)\n } catch {\n return resolvePath(path)\n }\n}\n\n/**\n * The env a Claude session under `profile` is spawned with, starting from\n * `base` (the host hook's env, else the server's own). The pin is skipped when\n * `base` would already land the CLI in the profile's dir, and that skip is\n * load-bearing, not an optimisation: CLAUDE_CONFIG_DIR *set at all* switches\n * the CLI's credential source to `<dir>/.credentials.json` — on macOS a\n * claude.ai login lives in the login Keychain, consulted only while the\n * variable is UNSET, so pinning even the CLI's own default `~/.claude` turns a\n * working login into \"Not logged in\". When `base` names a *different* dir than\n * the profile, the pin stands: the profile must win over hook- or operator-set\n * env, or sessions under two profiles quietly collapse into one identity.\n */\nfunction claudeSessionEnv(\n profile: ProfileInfo,\n base: Record<string, string | undefined>,\n): Record<string, string | undefined> {\n return canonicalDir(profile.configDir!) === canonicalDir(cliConfigDir(base))\n ? base\n : { ...base, CLAUDE_CONFIG_DIR: profile.configDir! }\n}\n\nfunction cwdAllowed(cwd: string, roots: string[] | undefined): boolean {\n if (!roots || roots.length === 0) return true\n const resolved = resolvePath(cwd)\n return roots.some((root) => {\n const r = resolvePath(root)\n return resolved === r || resolved.startsWith(r + sep)\n })\n}\n\nexport function createWorkerServer(options: WorkerServerOptions = {}): WorkerServer {\n if (!options.authenticate && !options.allowUnauthenticated) {\n throw new Error(\n 'createWorkerServer: provide `authenticate` or explicitly set `allowUnauthenticated: true`',\n )\n }\n const basePath = options.basePath ?? '/v1'\n const fallback = options.fallback\n // Exact origins only — a `Set` because the check runs on every request, and\n // exactness is the whole guarantee (no wildcards, no suffix matching).\n const corsOrigins =\n options.cors?.origins.length ? new Set(options.cors.origins) : undefined\n const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024\n /** The engine's adapter, honoring the test-only `engines` override. */\n const adapterFor = (engine: ProfileEngine | undefined): EngineAdapter =>\n options.engines?.[engine ?? 'claude'] ?? getEngineAdapter(engine)\n const hostBuildRunnerConfig =\n options.buildRunnerConfig ?? ((req: CreateSessionRequest): SessionRunnerConfig => req)\n\n // Profiles: declared at startup, or a single 'default' auto-created from the\n // operator's own config dir. Misdeclared dirs fail fast — the CLI would otherwise\n // silently start from an empty config (and a different credential chain).\n const declared = options.profiles ?? detectDefaultProfiles()\n const declaredByName = new Map(declared.map((p) => [p.name, p]))\n if (declaredByName.size !== declared.length) {\n throw new Error('createWorkerServer: duplicate profile names in `profiles`')\n }\n\n /**\n * Everything wrong with a profile that the server can tell without running it.\n * Shared by startup (where it throws) and the management routes (where it 400s),\n * so a profile created over HTTP can never be one startup would have refused.\n */\n const validateProfile = (p: ProfileInfo): string | null => {\n if (isProviderProfile(p)) {\n // Provider profiles have no config dir; they need an engine factory to\n // build a runner at all, so refuse up front rather than at create time.\n if (!p.provider?.id) return `provider profile '${p.name}' is missing provider.id`\n if (!options.createEngineRunner) {\n return (\n `profile '${p.name}' uses engine 'provider' but no ` +\n '`createEngineRunner` was provided to build one'\n )\n }\n } else if (p.engine === 'codex') {\n // The codexHome pin mirrors the claude configDir rule: a declared dir\n // must exist. Unset is fine — the binary's own ~/.codex.\n if (p.codexHome && !existsSync(p.codexHome)) {\n return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`\n }\n // No instructions surface exists (codex reads AGENTS.md from the cwd);\n // refusing beats silently dropping what the operator wrote.\n if (p.session?.instructions) {\n return (\n `profile '${p.name}' declares session.instructions, which the codex engine cannot ` +\n 'deliver — put instructions in the target repo’s AGENTS.md instead'\n )\n }\n } else if (!p.configDir || !existsSync(p.configDir)) {\n return `profile '${p.name}' configDir does not exist: ${p.configDir}`\n }\n if (options.disableBypassPermissions && p.defaults?.permissionMode === 'bypassPermissions') {\n return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`\n }\n // A default the profile's own engine can't run is misconfiguration: catch it\n // here rather than on every create under that profile.\n const fallbackMode = p.defaults?.permissionMode\n if (fallbackMode && !supportsPermissionMode(p.engine, fallbackMode)) {\n return (\n `profile '${p.name}' defaults to permission mode '${fallbackMode}', which engine ` +\n `'${engineOf(p)}' does not support (supported: ` +\n `${adapterFor(engineOf(p)).capabilities.permissionModes.join(', ')})`\n )\n }\n return null\n }\n\n for (const p of options.profiles ?? []) {\n const invalid = validateProfile(p)\n if (invalid) throw new Error(`createWorkerServer: ${invalid}`)\n }\n\n /**\n * Store-managed profiles, mirrored in memory so every lookup on the request path\n * stays synchronous. Loaded once at `listen()` and refreshed after each mutation\n * — single-process, exactly like the bundled queue adapter.\n */\n const stored = new Map<string, ProfileInfo>()\n const refreshStored = async (): Promise<void> => {\n if (!options.profileStore) return\n stored.clear()\n for (const p of await options.profileStore.list()) stored.set(p.name, p)\n }\n\n /** Response-only marker so a UI knows which rows it may edit. Declared profiles\n * are code; only store-backed ones can be changed over the API. */\n const withManagedFlag = (p: ProfileInfo): ProfileInfo =>\n declaredByName.has(p.name) ? p : { ...p, managed: true }\n\n /**\n * Response shape for a profile: the managed marker, the engine's capability\n * record, its static model catalog (correct from the first request — no\n * warm-up session, no process spawned), the availability verdict when one\n * has been probed, and the learned default model (the one thing a static\n * catalog cannot know: a claude profile's default is the operator's CLI\n * config, so it stays absent until a session on the profile reports it).\n * Read-only decoration — never persisted.\n */\n const forResponse = (p: ProfileInfo): ProfileInfo => {\n const adapter = adapterFor(p.engine)\n const base: ProfileInfo = {\n ...withManagedFlag(p),\n capabilities: adapter.capabilities,\n }\n // Provider model ids are operator-declared (provider.models); an empty\n // catalog must not shadow them with an empty picker.\n if (adapter.catalog.models.length > 0) base.models = adapter.catalog.models\n const defaultModel = profileDefaultModels.get(p.name)\n if (defaultModel) base.defaultModel = defaultModel\n const probed = availability.get(p.name)?.verdict\n if (probed && probed.available !== 'unknown') {\n base.available = probed.available\n if (probed.available === false) base.unavailableReason = probed.reason\n }\n return base\n }\n\n /** Declared profiles first: a name collision means the code wins, and the stored\n * one is unreachable rather than silently overriding server options. */\n const allProfiles = (): ProfileInfo[] => [\n ...declared,\n ...[...stored.values()].filter((p) => !declaredByName.has(p.name)),\n ]\n const profileFor = (name: string): ProfileInfo | undefined =>\n declaredByName.get(name) ?? stored.get(name)\n\n type Refusal = { status: number; error: string }\n\n /** Profile management is doubly opt-in: the operator wires a store, and the host\n * marks the principal. Neither on its own is enough. */\n const manageGuard = (auth: { canManageProfiles?: boolean }): Refusal | null => {\n if (!options.profileStore) {\n return { status: 404, error: 'profile management is not enabled on this server' }\n }\n if (!auth.canManageProfiles) {\n return { status: 403, error: 'not allowed to manage profiles' }\n }\n return null\n }\n\n /** Startup-declared profiles are code. Editing one over HTTP would make the\n * server options lie about what is actually running. */\n const declaredGuard = (profile: ProfileInfo): Refusal | null =>\n declaredByName.has(profile.name)\n ? {\n status: 403,\n error:\n `profile '${profile.name}' is declared in server options and cannot be changed ` +\n 'over the API — edit the `profiles` option instead',\n }\n : null\n\n /**\n * A managed Claude profile names a config directory, and that directory is a\n * credential store. Bound it to operator-declared roots; unset roots means the\n * management routes create provider profiles only.\n */\n const configDirGuard = (profile: ProfileInfo): Refusal | null => {\n if (isProviderProfile(profile)) return null\n const roots = options.allowedConfigDirRoots\n if (!roots || roots.length === 0) {\n return {\n status: 403,\n error:\n 'managed Claude profiles are disabled: set `allowedConfigDirRoots` to the ' +\n 'directories they may point at',\n }\n }\n return profile.configDir && cwdAllowed(profile.configDir, roots)\n ? null\n : { status: 403, error: 'configDir is outside the allowed roots' }\n }\n\n /** Validate, persist, and re-read: shared by create and update so a PATCH can\n * never leave behind a profile a POST would have refused. */\n const saveManagedProfile = async (res: ServerResponse, incoming: ProfileInfo): Promise<void> => {\n // `managed` is server-computed on every response; never persist a client's copy.\n const { managed: _clientClaim, ...profile } = incoming\n const refused = configDirGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const invalid = validateProfile(profile)\n if (invalid) {\n json(res, 400, { error: invalid })\n return\n }\n await options.profileStore!.save(profile)\n await refreshStored()\n json(res, 200, { profile: withManagedFlag(profile) })\n }\n\n /** Enforce the server's bypass policy on a create request. Returns a 403 message\n * for an explicit bypass-mode request; strips the pre-authorization capability\n * silently (see the option's doc for why). */\n const applyBypassPolicy = (req: CreateSessionRequest): string | null => {\n if (!options.disableBypassPermissions) return null\n if (req.permissionMode === 'bypassPermissions') {\n return 'bypassPermissions is disabled on this server (disableBypassPermissions)'\n }\n delete req.allowDangerouslySkipPermissions\n return null\n }\n\n /** Reject a permission mode the resolved profile's engine has no meaning for.\n * The create form already filters what it offers, but the API is the boundary:\n * a provider session asked for 'plan' should be told so, not silently coerced\n * into 'default' by whatever assembles its runner. Returns an error message. */\n const checkPermissionMode = (\n mode: PermissionMode | undefined,\n profile: ProfileInfo | undefined,\n ): string | null => {\n if (mode === undefined || supportsPermissionMode(profile?.engine, mode)) return null\n return (\n `permission mode '${mode}' is not supported by profile '${profile!.name}' ` +\n `(engine '${engineOf(profile)}') — supported: ` +\n adapterFor(profile?.engine).capabilities.permissionModes.join(', ')\n )\n }\n\n /**\n * Refuse the request fields the resolved profile's engine cannot honor —\n * read off its capability record, so the create form's filtering and the\n * API boundary can never disagree. Refusing beats coercing: a caller who\n * asked for something the engine has no meaning for should be told, not\n * left wondering where the option went. Also enforces the provider grant\n * rules (capabilities narrow, never widen; MCP servers are the profile's to\n * declare — MCP tools are authoritative, server-side, with server\n * credentials, so honoring a client-supplied server would let a caller\n * point an authoritative tool anywhere it liked).\n */\n const checkEngineGrants = (\n req: CreateSessionRequest,\n profile: ProfileInfo | undefined,\n ): string | null => {\n const engine = engineOf(profile)\n const caps = adapterFor(profile?.engine).capabilities\n const name = profile?.name ?? 'default'\n if (!caps.sessionMcpServers && req.mcpServers && Object.keys(req.mcpServers).length > 0) {\n return (\n `profile '${name}' runs the ${engine} engine, whose MCP servers are declared ` +\n 'outside the session request — a request cannot add its own'\n )\n }\n if (!caps.budgets && (req.maxTurns !== undefined || req.maxBudgetUsd !== undefined)) {\n return `the ${engine} engine does not honor maxTurns/maxBudgetUsd`\n }\n if (!caps.settingSources && req.settingSources !== undefined) {\n return `the ${engine} engine does not load settingSources`\n }\n if (!caps.resume && req.resume !== undefined) {\n return `the ${engine} engine cannot resume a session`\n }\n if (req.forkSession && engine !== 'claude') {\n return `the ${engine} engine cannot fork a resumed session`\n }\n if (\n req.reasoningEffort !== undefined &&\n (!caps.reasoningEfforts || caps.reasoningEfforts.length === 0)\n ) {\n return `the ${engine} engine does not take a reasoningEffort`\n }\n if (!profile || !isProviderProfile(profile)) return null\n const granted = profile.session?.capabilities\n if (!req.capabilities || !granted) return null\n const ungranted = req.capabilities.filter((c) => !granted.includes(c))\n if (ungranted.length === 0) return null\n return (\n `profile '${profile.name}' does not grant: ${ungranted.join(', ')} ` +\n `(granted: ${granted.join(', ') || 'none'}) — a request may narrow capabilities, not widen them`\n )\n }\n\n /** Drop request fields that are meaningless (not wrong) for the engine —\n * today just `questionBehavior` where no approval channel exists, so job\n * webhooks never grow phantom permission_requested expectations. */\n const stripInertFields = (req: CreateSessionRequest, profile: ProfileInfo | undefined): void => {\n if (!adapterFor(profile?.engine).capabilities.interactiveApprovals) {\n delete req.questionBehavior\n }\n }\n\n /** Profile-aware config hook: fill the profile's defaults into unset request fields,\n * run the host hook, then pin CLAUDE_CONFIG_DIR — the profile wins even when the\n * host hook set its own env (see `claudeSessionEnv` for the one case the pin is\n * skipped, and why). Handed to the queue too, so jobs inherit profiles. */\n const buildRunnerConfig = (req: CreateSessionRequest): SessionRunnerConfig => {\n const profile = req.profile !== undefined ? profileFor(req.profile) : undefined\n if (!profile) return hostBuildRunnerConfig(req)\n const config = hostBuildRunnerConfig({\n ...req,\n model: req.model ?? profile.defaults?.model ?? profile.provider?.model,\n permissionMode: req.permissionMode ?? profile.defaults?.permissionMode,\n })\n // Only claude profiles have a config dir to pin. Provider credentials come\n // from the operator's environment through the engine factory; a codex\n // profile's CODEX_HOME pin is applied by its adapter (the runner builds the\n // child env, because CodexOptions.env replaces rather than merges).\n if (engineOf(profile) !== 'claude') return config\n const base = config.env ?? process.env\n const env = claudeSessionEnv(profile, base)\n // A skipped pin returns `base` itself — leave the config alone so an unset\n // `env` stays unset (the SDK then spawns on process.env, unmaterialized).\n return env === base ? config : { ...config, env }\n }\n\n /** Build a runner for a session, choosing the engine from its profile. Async\n * because the engine factory may be: a provider session can need an awaited\n * assembly step (per-session MCP connect) before it has a runner at all.\n *\n * `restore` rebuilds a parked session rather than creating a new one — same id,\n * same log, mid-task. */\n const buildRunner = async (\n config: SessionRunnerConfig,\n restore?: RunnerSnapshot,\n ): Promise<Runner> => {\n const name = config.profile\n const profile = name !== undefined ? profileFor(name) : undefined\n if (name !== undefined && !profile) {\n // Only reachable on a resume: profiles can be deleted between park and\n // wake-up, and the session cannot be rebuilt without the one it ran on.\n throw new Error(`unknown profile: ${name}`)\n }\n if (profile && isProviderProfile(profile)) {\n // Guaranteed present: startup refuses provider profiles without a factory.\n return options.createEngineRunner!({ config, profile, bridge, restore })\n }\n // claude and codex ship as in-repo adapters; each refuses `restore` itself\n // (neither engine can rebuild a parked session — the binary owns its state).\n return adapterFor(profile?.engine).createRunner({ config, profile, restore })\n }\n\n const createRunner = async (config: SessionRunnerConfig): Promise<Runner> => {\n const runner = registry.register(await buildRunner(config))\n // Watchers first, then start: a session must not emit anything before the\n // things that persist and account for it are listening.\n parking.remember(runner.id, config)\n parking.watch(runner)\n void runner.start()\n return runner\n }\n\n /** Resolve a request's profile: required when several are declared, implicit with\n * exactly one, scoped by the principal's allowedProfiles. Returns the resolved\n * profile (undefined when the server declares none) or a response-ready error. */\n const resolveProfile = (\n name: unknown,\n allowedProfiles: string[] | undefined,\n ): { ok: true; profile?: ProfileInfo } | { ok: false; status: number; error: string } => {\n if (name !== undefined && typeof name !== 'string') {\n return { ok: false, status: 400, error: 'profile must be a string' }\n }\n const profiles = allProfiles()\n if (profiles.length === 0) {\n return name !== undefined\n ? { ok: false, status: 400, error: 'no profiles are configured on this server' }\n : { ok: true }\n }\n const effective = name ?? (profiles.length === 1 ? profiles[0]!.name : undefined)\n if (effective === undefined) {\n const available = profiles.map((p) => p.name).join(', ')\n return { ok: false, status: 400, error: `profile is required (available: ${available})` }\n }\n const profile = profileFor(effective)\n if (!profile) return { ok: false, status: 400, error: `unknown profile: ${effective}` }\n if (allowedProfiles && !allowedProfiles.includes(profile.name)) {\n return { ok: false, status: 403, error: `profile not allowed: ${profile.name}` }\n }\n return { ok: true, profile }\n }\n\n // Notifications ride the registry hook rather than the create paths, because the\n // session that most needs to reach a phone may be one that parked and was\n // rebuilt — and that path never goes near `createRunner`.\n const notifier = new SessionNotifier(options.notifications ?? {})\n /**\n * What each claude profile's *default* model resolves to, learned from the\n * `capabilities` events of sessions that ran on it. The model *list* is the\n * adapter's static catalog now; the default is the one thing a catalog\n * cannot know (it is the operator's CLI config), so it alone is still\n * learned — and still absent on a cold server, the accepted regression.\n */\n const profileDefaultModels = new Map<string, string>()\n const producedFiles = new ProducedFileStore()\n const registry = new SessionRegistry({\n onRegister: (runner) => {\n notifier.watch(runner)\n // Same hook, opposite replay choice: from 0, because a rebuilt session's\n // earlier pictures must stay fetchable (see ProducedFileStore.watch).\n producedFiles.watch(runner)\n const profile = runner.info().profile\n if (!profile) return\n runner.subscribe((event) => {\n if (event.type !== 'capabilities' || !event.defaultModel) return\n profileDefaultModels.set(profile, event.defaultModel)\n })\n },\n })\n const attachmentStore = new AttachmentStore(options.attachments)\n const bridge = new BridgeHub({\n ...options.bridge,\n onResult: (sessionId, executionId, result) => {\n // A runner that executes out-of-band (the model-agnostic engine bridging\n // to a browser tab) gets the result fed straight back into its loop —\n // operators don't wire this themselves. The host callback still fires,\n // for observability.\n registry.get(sessionId)?.settleExecution?.(executionId, result)\n options.bridge?.onResult?.(sessionId, executionId, result)\n },\n })\n const parking = new SessionParkManager({\n registry,\n store: options.parking?.store ?? new MemorySessionStore(),\n parkDelayMs: options.parking?.parkDelayMs,\n expiredGraceMs: options.parking?.expiredGraceMs,\n onError: options.parking?.onError,\n rebuild: (record) => buildRunner(record.config, record.snapshot),\n attachedCount: (sessionId) => bridge.attachedCount(sessionId),\n // Accounting is the queue's: it frees the run's slot and stops its clock, and\n // refuses the park outright when the run is already finalizing.\n onParking: (sessionId, executionId) => queue?.onSessionParking(sessionId, executionId) ?? true,\n onResumed: (sessionId, runner) => queue?.onSessionResumed(sessionId, runner),\n })\n const wss = new WebSocketServer({ noServer: true })\n /** Profiles (by name; '' = none) whose oauth notice has been logged. */\n const subscriptionNoticeShown = new Set<string>()\n\n // Live queue watchers (`{basePath}/queue/ws`): every job event is fanned out, and\n // lifecycle changes push refreshed stats so dashboards stay current without polling.\n const queueSockets = new Set<WebSocket>()\n const sendQueueFrame = (ws: WebSocket, frame: QueueServerFrame): void => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame))\n }\n const broadcastJobEvent = (event: JobEvent): void => {\n if (queueSockets.size === 0) return\n for (const ws of queueSockets) sendQueueFrame(ws, { type: 'job_event', event })\n if (event.type !== 'job_progress') {\n void queue\n ?.stats()\n .then((stats) => {\n for (const ws of queueSockets) sendQueueFrame(ws, { type: 'queue_stats', stats })\n })\n .catch(() => {})\n }\n }\n\n const queue = options.queue\n ? new JobQueue({\n ...options.queue,\n onEvent: (event) => {\n try {\n options.queue?.onEvent?.(event)\n } finally {\n broadcastJobEvent(event)\n }\n },\n // Job sessions are ordinary registry sessions (attachable/watchable) and go\n // through the same config hook, engine selection, and auth-provenance\n // watcher as client sessions.\n createRunner: async (config) => {\n const runner = await createRunner(config)\n watchAuthSource(runner)\n return runner\n },\n buildRunnerConfig,\n // A run that ends while parked (canceled, killed) leaves a snapshot behind\n // that nothing will ever wake.\n discardSession: (sessionId) => parking.discard(sessionId),\n })\n : undefined\n\n // Watch each session's init handshake for its auth provenance ('oauth' = claude.ai\n // subscription). The listener is a no-op after the first init; not worth unsubscribing.\n const watchAuthSource = (runner: Runner): void => {\n let seen = false\n runner.subscribe((event) => {\n if (seen || event.type !== 'system_init') return\n seen = true\n if (event.apiKeySource !== 'oauth') return\n if (options.requireApiKey) {\n runner.fail(\n 'This server requires API-key auth (requireApiKey), but the session initialized ' +\n \"with claude.ai subscription credentials (apiKeySource 'oauth'). Set \" +\n 'ANTHROPIC_API_KEY (or Bedrock/Vertex auth) in the server environment.',\n )\n } else {\n // Per profile, not global: distinct profiles are distinct accounts, and each\n // operator deserves the notice once.\n const profileName = runner.info().profile ?? ''\n if (subscriptionNoticeShown.has(profileName)) return\n subscriptionNoticeShown.add(profileName)\n const scope = profileName ? `Sessions under profile '${profileName}'` : 'Sessions'\n console.warn(\n `[workerdeck] ${scope} are using claude.ai subscription credentials ` +\n \"(apiKeySource 'oauth'), not an API key. That is only appropriate for personal, \" +\n 'single-user use of your own account. Unattended/scheduled or multi-user use ' +\n \"requires an API key under Anthropic's terms — set ANTHROPIC_API_KEY in the \" +\n 'server environment, or set requireApiKey: true to fail closed.',\n )\n }\n })\n }\n\n /**\n * Availability, per profile: the adapter's probe run over the env the real\n * assembly path produces (so anything the host hook injects — a\n * CLAUDE_CODE_OAUTH_TOKEN, say — counts as logged in). Cached, and served on\n * `GET /profiles` as `available`/`unavailableReason`.\n *\n * Gated on `checkCredentials` like the old claude-only preflight (this is a\n * library; `pnpm test` must spawn nothing unless a test injects fake\n * adapters or probes). 'unknown' stays out of the cache's answers: a probe\n * that couldn't run is not evidence of a missing login. **Display-only**\n * downstream — session create against an unavailable profile still proceeds\n * and fails with the engine's own error, because the probe can be stale in\n * both directions and refusing on it would turn a probe bug into an outage.\n */\n const availability = new Map<string, { verdict: EngineAvailability; at: number }>()\n const AVAILABILITY_TTL_MS = 60_000\n /** Profiles already warned about on the console, so re-probes don't spam. */\n const availabilityWarned = new Set<string>()\n\n const sessionEnvFor = (profile: ProfileInfo): Record<string, string | undefined> => {\n try {\n return buildRunnerConfig({ cwd: process.cwd(), profile: profile.name }).env ?? process.env\n } catch {\n // A host hook may choke on a probe-shaped request; fall back to the\n // profile pin alone, applied exactly as the real path applies it.\n return engineOf(profile) === 'claude' ? claudeSessionEnv(profile, process.env) : process.env\n }\n }\n\n const probeProfile = (profile: ProfileInfo): void => {\n if (!options.checkCredentials) return\n const conf = options.checkCredentials === true ? {} : options.checkCredentials\n // Mark in-flight immediately so concurrent GET /profiles don't re-spawn.\n availability.set(profile.name, {\n verdict: availability.get(profile.name)?.verdict ?? { available: 'unknown' },\n at: Date.now(),\n })\n const adapter = adapterFor(profile.engine)\n // The injectable claude probe predates the adapter layer and is honored\n // for claude profiles (existing tests and hosts wire it).\n const claudeProbe: ClaudeAuthProbe | undefined =\n engineOf(profile) !== 'claude'\n ? undefined\n : (conf.probe ??\n (conf.timeoutMs !== undefined\n ? (env) => checkClaudeAuth(env, { timeoutMs: conf.timeoutMs })\n : undefined))\n const run: Promise<EngineAvailability> =\n claudeProbe\n ? claudeProbe(sessionEnvFor(profile)).then(\n (status): EngineAvailability =>\n status === 'logged_in'\n ? { available: true }\n : status === 'logged_out'\n ? { available: false, reason: 'no usable Claude credentials for this profile' }\n : { available: 'unknown' },\n )\n : adapter.checkAvailability(profile, sessionEnvFor(profile))\n void run\n .then((verdict) => {\n availability.set(profile.name, { verdict, at: Date.now() })\n if (verdict.available === false && !availabilityWarned.has(profile.name)) {\n availabilityWarned.add(profile.name)\n console.warn(\n `[workerdeck] Profile '${profile.name}' is unavailable: ${verdict.reason} ` +\n '(`checkCredentials: false` disables this check)',\n )\n }\n if (verdict.available === true) availabilityWarned.delete(profile.name)\n })\n .catch(() => {\n // a probe that breaks is 'unknown', and unknown stays silent\n })\n }\n\n /** Launch-time sweep, concurrent and fire-and-forget. */\n const preflightCredentials = (): void => {\n for (const profile of allProfiles()) probeProfile(profile)\n }\n\n /** Lazy re-probe on reads, so an operator who just ran `codex login` (or\n * exported a key) sees the profile go green without a restart. Serves the\n * cached verdict now; the refreshed one lands on the next request. */\n const refreshAvailability = (profiles: ProfileInfo[]): void => {\n if (!options.checkCredentials) return\n const now = Date.now()\n for (const profile of profiles) {\n const cached = availability.get(profile.name)\n if (!cached || now - cached.at > AVAILABILITY_TTL_MS) probeProfile(profile)\n }\n }\n\n type AuthContext = { ok: boolean; allowedProfiles?: string[]; canManageProfiles?: boolean }\n const authenticate = async (req: IncomingMessage): Promise<AuthContext> => {\n if (!options.authenticate) return { ok: true }\n const principal = await options.authenticate(req)\n if (principal === null || principal === undefined || principal === false) return { ok: false }\n const allowed = (principal as { allowedProfiles?: unknown }).allowedProfiles\n return {\n ok: true,\n allowedProfiles:\n Array.isArray(allowed) && allowed.every((p) => typeof p === 'string')\n ? (allowed as string[])\n : undefined,\n // Opt-in, and only ever true when the host says so: an unauthenticated dev\n // server (no `authenticate`) returns early above and manages nothing.\n canManageProfiles:\n (principal as { canManageProfiles?: unknown }).canManageProfiles === true,\n }\n }\n\n // Route pattern: {basePath}/sessions[/:id[/ws | /permissions/:requestId |\n // /files[/<path>] | /attachments[/:attachmentId] | /mcp[/:serverName] |\n // /produced[/:fileId]]]\n const parseRoute = (\n url: string,\n ): {\n id?: string\n ws?: boolean\n permissionId?: string\n files?: boolean\n filePath?: string\n attachments?: boolean\n attachmentId?: string\n mcp?: boolean\n mcpServer?: string\n produced?: boolean\n producedFileId?: string\n } | null => {\n const pathname = new URL(url, 'http://internal').pathname\n if (!pathname.startsWith(basePath + '/sessions')) return null\n const rest = pathname.slice((basePath + '/sessions').length)\n if (rest === '' || rest === '/') return {}\n const parts = rest.replace(/^\\//, '').split('/')\n if (parts.length === 1) return { id: decodeURIComponent(parts[0]!) }\n if (parts.length === 2 && parts[1] === 'ws') {\n return { id: decodeURIComponent(parts[0]!), ws: true }\n }\n if (parts.length === 3 && parts[1] === 'permissions') {\n return { id: decodeURIComponent(parts[0]!), permissionId: decodeURIComponent(parts[2]!) }\n }\n if (parts.length <= 3 && parts[1] === 'attachments') {\n return {\n id: decodeURIComponent(parts[0]!),\n attachments: true,\n attachmentId: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length <= 3 && parts[1] === 'produced') {\n return {\n id: decodeURIComponent(parts[0]!),\n produced: true,\n producedFileId: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length <= 3 && parts[1] === 'mcp') {\n // Server names are opaque and may contain ':' (plugin:gtm:gtm) — one segment,\n // decoded whole.\n return {\n id: decodeURIComponent(parts[0]!),\n mcp: true,\n mcpServer: parts[2] === undefined ? undefined : decodeURIComponent(parts[2]),\n }\n }\n if (parts.length >= 2 && parts[1] === 'files') {\n // The remainder is a VFS path — slashes are its separators, so segments\n // are decoded individually and rejoined.\n const filePath = parts.slice(2).map(decodeURIComponent).join('/')\n return {\n id: decodeURIComponent(parts[0]!),\n files: true,\n filePath: filePath === '' ? undefined : '/' + filePath,\n }\n }\n return null\n }\n\n // Host filesystem: built once at startup so a misdeclared root fails here rather\n // than on the first request from a phone. Null = the routes do not exist.\n //\n // Reading inherits the cwd policy — a caller who can start a session in a root\n // can already read it through the agent — so `hostFiles.roots` is a narrowing,\n // not the enabling grant. `??` and not `||`: an explicit `roots: []` is an\n // operator turning the routes off, which must not fall through to the cwd roots.\n const hostFileRootPaths = options.hostFiles?.roots ?? options.allowedCwdRoots\n const hostFiles = hostFileRootPaths?.length ? createHostFileRoots(hostFileRootPaths) : null\n const hostFilesWritable = options.hostFiles?.write === true\n const maxHostFileBytes = options.hostFiles?.maxFileBytes ?? 1024 * 1024\n const maxHostDirEntries = options.hostFiles?.maxEntries ?? 5000\n\n /**\n * `{basePath}/sessions/:id/attachments` — the files a client sends with a message.\n *\n * `POST ?name=<name>` takes the raw bytes as the body and the media type from\n * the `content-type` header; there is no multipart parsing here on purpose, so\n * a phone and a browser both upload with one plain request and this file stays\n * dependency-free. `GET /:attachmentId` hands the bytes back for thumbnails.\n *\n * The download always answers `content-disposition: attachment` and `nosniff`,\n * the same as `/files`: an upload is client-supplied content served from the\n * gateway's own origin, and it must never render as a document there. (An\n * `<img src>` is unaffected — disposition does not apply to subresources.)\n */\n const handleAttachments = async (\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n session: SessionInfo,\n attachmentId?: string,\n ): Promise<void> => {\n if (req.method === 'POST' && attachmentId === undefined) {\n const url = new URL(req.url ?? '/', 'http://internal')\n const mediaType = req.headers['content-type']\n if (!mediaType) {\n json(res, 400, { error: 'content-type header is required' })\n return\n }\n // The engine's capability record names the kinds its sendMessage can\n // deliver ('document' is the record's 'pdf'). Refusing here keeps the\n // contract at the door: an upload that succeeds is one the message can use.\n const accepted = (session.capabilities ?? ENGINE_CAPABILITIES[session.engine ?? 'claude'])\n .attachments\n const kind = attachmentKind(mediaType)\n if (kind && !accepted.includes(kind === 'document' ? 'pdf' : kind)) {\n json(res, 415, {\n error: `the ${session.engine ?? 'claude'} engine does not accept ${kind} attachments`,\n })\n return\n }\n let body: Buffer\n try {\n body = await readRawBody(req, attachmentStore.maxFileBytes)\n } catch {\n json(res, 413, { error: 'attachment is larger than the limit' })\n return\n }\n const result = attachmentStore.put(\n sessionId,\n url.searchParams.get('name') ?? 'attachment',\n mediaType,\n body,\n )\n if (!result.ok) {\n const status =\n result.error.code === 'unsupported_type'\n ? 415\n : result.error.code === 'empty'\n ? 400\n : 413\n json(res, status, { error: result.error.message })\n return\n }\n json(res, 201, { attachment: result.attachment })\n return\n }\n if (req.method === 'GET' && attachmentId !== undefined) {\n const found = attachmentStore.get(sessionId, attachmentId)\n if (!found) {\n json(res, 404, { error: 'attachment not found' })\n return\n }\n const bytes = Buffer.from(found.data, 'base64')\n res.writeHead(200, {\n 'content-type': found.mediaType,\n 'content-length': bytes.length,\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(found.name)}`,\n 'x-content-type-options': 'nosniff',\n })\n res.end(bytes)\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `{basePath}/sessions/:id/mcp` — the session's MCP servers, and the three\n * things the CLI's own `/mcp` screen can do to one (reconnect, enable, disable).\n *\n * Every answer goes through `mcpStatusInfo`, which is where the servers' `env`\n * and `headers` are dropped: reading this route must not be a way to read the\n * operator's API tokens.\n */\n const handleMcp = async (\n req: IncomingMessage,\n res: ServerResponse,\n runner: Runner,\n serverName?: string,\n ): Promise<void> => {\n const listServers = async (): Promise<boolean> => {\n const servers = await runner.mcpServers?.()\n if (!servers) {\n json(res, 501, { error: 'this session does not report MCP servers' })\n return false\n }\n json(res, 200, { servers })\n return true\n }\n if (req.method === 'GET' && serverName === undefined) {\n await listServers()\n return\n }\n if (req.method === 'POST' && serverName !== undefined) {\n const body = (await readJsonBody(req, maxBodyBytes)) as McpServerActionRequest\n if (body?.action !== 'reconnect' && body?.action !== 'enable' && body?.action !== 'disable') {\n json(res, 400, { error: \"action must be 'reconnect', 'enable' or 'disable'\" })\n return\n }\n // Checked before dispatching, because the calls below are optionally\n // chained: an engine that lists its servers but cannot act on one (codex)\n // would otherwise no-op and then answer 200 with the unchanged list —\n // a button that reports success having done nothing, which is worse than\n // an error. Clients hide these controls off\n // `ENGINE_CAPABILITIES[engine].mcpServerActions`; this is the door.\n const canAct =\n body.action === 'reconnect'\n ? typeof runner.reconnectMcpServer === 'function'\n : typeof runner.setMcpServerEnabled === 'function'\n if (!canAct) {\n json(res, 501, {\n error: `this session's engine cannot ${body.action} an MCP server`,\n })\n return\n }\n try {\n if (body.action === 'reconnect') await runner.reconnectMcpServer?.(serverName)\n else await runner.setMcpServerEnabled?.(serverName, body.action === 'enable')\n } catch (error) {\n // The CLI's own message (\"No MCP server found named x\") is the useful one.\n json(res, 400, { error: error instanceof Error ? error.message : 'MCP action failed' })\n return\n }\n await listServers()\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `{basePath}/sessions/:id/produced[/:fileId]` — files this session's ENGINE\n * wrote on the host (codex's generated images), listed and served.\n *\n * The one route here with no root allowlist and no byte cap, and the comment\n * on {@link ProducedFileStore} is the argument for why that is right rather\n * than lax: the allowlist is the exact set of paths this session's own runner\n * announced producing. It is emphatically NOT a hole in `/fs/*` — a path the\n * *agent* named is not a produced file and never enters this store.\n *\n * Everything else matches the attachment download: `nosniff` and an attachment\n * disposition, because these bytes are model-authored and must not render as a\n * document on the gateway's origin. (`<img src>` is unaffected — disposition\n * does not apply to subresources, which is the whole point.)\n */\n const handleProducedFiles = async (\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n fileId?: string,\n ): Promise<void> => {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (fileId === undefined) {\n json(res, 200, {\n files: producedFiles.list(sessionId).map(({ fileId: id, path, mediaType, bytes }) => ({\n fileId: id,\n path,\n ...(mediaType ? { mediaType } : {}),\n ...(bytes !== undefined ? { bytes } : {}),\n })),\n })\n return\n }\n const found = producedFiles.get(sessionId, fileId)\n if (!found) {\n json(res, 404, { error: 'no such produced file' })\n return\n }\n // Re-checked at serve time, not trusted from the announcement: the engine\n // reported this path when it wrote the file, and the file may since have\n // been moved, replaced, or turned into a directory. `statSync` follows\n // symlinks deliberately — a link the engine itself created is part of what\n // it produced, and there is no containment root here for a realpath check\n // to compare against.\n let stat\n try {\n stat = statSync(found.path)\n } catch {\n json(res, 404, { error: 'produced file is no longer on disk' })\n return\n }\n if (!stat.isFile()) {\n json(res, 404, { error: 'produced file is not a regular file' })\n return\n }\n const filename = basename(found.path) || 'file'\n res.writeHead(200, {\n 'content-type': found.mediaType ?? contentTypeFor(filename),\n 'content-length': stat.size,\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n 'x-content-type-options': 'nosniff',\n })\n // Streamed rather than read whole: a generated PNG is a couple of megabytes\n // and there is no cap on this route, so buffering would put the file size\n // straight into the gateway's heap.\n await new Promise<void>((done) => {\n const stream = createReadStream(found.path)\n stream.on('error', () => {\n // Headers are already out; the only honest signal left is a truncated\n // response, which is what destroying the socket produces.\n res.destroy()\n done()\n })\n stream.on('close', () => done())\n stream.pipe(res)\n })\n }\n\n /**\n * `{basePath}/fs/*` — the operator's real tree. Authorized by the auth key alone\n * and deliberately outside the agent permission flow: the caller is the operator.\n *\n * Every path in here goes through `host-files.ts` first, which canonicalizes and\n * *then* re-checks containment. The naive prefix compare `cwdAllowed` does would\n * be wrong at this door — the agent writes into these trees, and a symlink it\n * created is a path the operator never typed.\n */\n const handleHostFiles = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n ): Promise<void> => {\n if (!hostFiles) {\n json(res, 404, { error: 'host file access is not configured on this server' })\n return\n }\n const route = pathname.slice((basePath + '/fs/').length)\n const url = new URL(req.url ?? '/', 'http://internal')\n const requested = url.searchParams.get('path')\n\n if (route === 'roots') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n // The canonical spelling, not the operator's: every other route answers in\n // canonical paths, and a client that round-trips a root it was given must\n // land on the same tree.\n json(res, 200, {\n roots: hostFiles.roots.map(({ canonical }) => ({\n path: canonical,\n name: basename(canonical) || canonical,\n })),\n canWrite: hostFilesWritable,\n })\n return\n }\n\n if (route === 'find') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!requested) {\n json(res, 400, { error: 'path is required' })\n return\n }\n const resolved = resolveExisting(hostFiles, requested)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n if (resolved.kind !== 'dir') {\n json(res, 400, { error: 'not a directory' })\n return\n }\n // Clamped, not validated: this runs per keystroke from a phone, and a\n // client asking for 10,000 matches is a client that made a typo.\n const asked = Number(url.searchParams.get('limit') ?? '')\n const limit = Number.isFinite(asked) && asked > 0 ? Math.min(asked, 200) : 50\n const result = searchFiles(resolved.path, {\n query: url.searchParams.get('q') ?? '',\n limit,\n ignore: options.hostFiles?.ignore,\n })\n json(res, 200, { base: resolved.path, ...result })\n return\n }\n\n if (route === 'list' || route === 'read') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!requested) {\n json(res, 400, { error: 'path is required' })\n return\n }\n const resolved = resolveExisting(hostFiles, requested)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n if (route === 'list') {\n if (resolved.kind !== 'dir') {\n json(res, 400, { error: 'not a directory' })\n return\n }\n let names: Dirent[]\n try {\n names = readdirSync(resolved.path, { withFileTypes: true })\n } catch {\n json(res, 403, { error: 'directory is not readable' })\n return\n }\n const truncated = names.length > maxHostDirEntries\n const entries = names.slice(0, maxHostDirEntries).map((entry) => {\n const path = join(resolved.path, entry.name)\n const type = entryKind(entry)\n // Size/mtime for regular files only, and via lstat — a listing must\n // never stat *through* a link, or a directory holding a link to a fifo\n // becomes an unlistable directory.\n let bytes: number | undefined\n let modifiedAt: number | undefined\n if (type === 'file') {\n try {\n const s = lstatSync(path)\n bytes = s.size\n modifiedAt = s.mtimeMs\n } catch {\n // Raced with a delete, or unreadable — the entry still lists.\n }\n }\n return { name: entry.name, path, type, bytes, modifiedAt }\n })\n entries.sort((a, b) => {\n const rank = (t: string): number => (t === 'dir' ? 0 : 1)\n return rank(a.type) - rank(b.type) || a.name.localeCompare(b.name)\n })\n json(res, 200, { path: resolved.path, entries, ...(truncated ? { truncated } : {}) })\n return\n }\n\n if (resolved.kind !== 'file') {\n json(res, 400, { error: 'not a regular file' })\n return\n }\n // Cheap pre-check so a gigabyte is refused rather than buffered. Advisory\n // only — the authoritative cap is on the bytes actually read, since the\n // file can grow between the stat and the open.\n let modifiedAt = 0\n try {\n const stats = lstatSync(resolved.path)\n if (stats.size > maxHostFileBytes) {\n json(res, 413, { error: `file is larger than ${maxHostFileBytes} bytes` })\n return\n }\n modifiedAt = stats.mtimeMs\n } catch {\n json(res, 404, { error: 'not found' })\n return\n }\n // Opens the canonical path with O_NOFOLLOW and gates on fstat, so a\n // component swapped for a symlink or a fifo after the resolve is refused\n // rather than followed or blocked on.\n const read = readContained(resolved.path)\n if (!read.ok) {\n json(res, read.status, { error: read.error })\n return\n }\n if (read.data.length > maxHostFileBytes) {\n json(res, 413, { error: `file is larger than ${maxHostFileBytes} bytes` })\n return\n }\n const text = asUtf8(read.data)\n json(res, 200, {\n path: resolved.path,\n content: text ?? read.data.toString('base64'),\n encoding: text === null ? 'base64' : 'utf8',\n bytes: read.data.length,\n hash: hashBytes(read.data),\n modifiedAt,\n })\n return\n }\n\n if (route === 'write') {\n if (req.method !== 'PUT') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (!hostFilesWritable) {\n json(res, 403, { error: 'host file writes are not enabled on this server' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as WriteHostFileRequest\n if (!body.path || typeof body.path !== 'string') {\n json(res, 400, { error: 'path is required' })\n return\n }\n if (typeof body.content !== 'string') {\n json(res, 400, { error: 'content is required' })\n return\n }\n if (body.encoding !== undefined && body.encoding !== 'utf8' && body.encoding !== 'base64') {\n json(res, 400, { error: \"encoding must be 'utf8' or 'base64'\" })\n return\n }\n const resolved = resolveForWrite(hostFiles, body.path)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const next = Buffer.from(body.content, body.encoding ?? 'utf8')\n if (next.length > maxHostFileBytes) {\n json(res, 413, { error: `content is larger than ${maxHostFileBytes} bytes` })\n return\n }\n // The agent is editing this same tree. Every write is conditional: either it\n // creates a file that does not exist, or it names the hash it is replacing.\n // There is no unconditional overwrite to reach for from a phone.\n //\n // Existence is decided by the read, not by a stat: `readContained` answers\n // 404 only for ENOENT, so anything else — an unreadable file, a swapped-in\n // device — refuses here instead of being mistaken for \"not there yet\" and\n // then clobbered as a create.\n const current = readContained(resolved.path)\n if (!current.ok && current.status !== 404) {\n json(res, current.status, { error: current.error })\n return\n }\n const existing = current.ok ? current.data : null\n if (existing && !body.expectedHash) {\n json(res, 409, { error: 'file exists — pass expectedHash to overwrite it' })\n return\n }\n if (existing && hashBytes(existing) !== body.expectedHash) {\n json(res, 409, { error: 'file changed on disk since it was read' })\n return\n }\n if (!existing && body.expectedHash) {\n json(res, 409, { error: 'file no longer exists' })\n return\n }\n const written = writeContained(resolved.path, next)\n if (!written.ok) {\n json(res, written.status, { error: written.error })\n return\n }\n let writtenAt = 0\n try {\n writtenAt = lstatSync(resolved.path).mtimeMs\n } catch {\n // The write landed; a stat that loses a race with a delete is not a\n // reason to report failure.\n }\n json(res, 200, {\n path: resolved.path,\n bytes: next.length,\n hash: hashBytes(next),\n modifiedAt: writtenAt,\n })\n return\n }\n\n json(res, 404, { error: 'not found' })\n }\n\n /**\n * `GET /sdk-sessions`, engine-aware: `?profile=` names whose on-disk store to\n * list, and the profile's engine adapter answers (for codex, over a\n * short-lived `thread/list` child — no live session involved). Absent\n * `profile`, the choice is implicit when the server declares exactly one\n * profile (the resolveProfile rule); with several, the Claude engine's\n * global store is listed — the pre-engine-aware behavior every existing\n * caller already gets, kept because old clients cannot answer a new 400.\n * The injectable `listSdkSessions` option predates the adapter layer and is\n * honored for the claude engine only (existing tests and hosts wire it),\n * exactly like the injectable claude auth probe.\n */\n const handleSdkSessions = async (\n req: IncomingMessage,\n res: ServerResponse,\n auth: AuthContext,\n ): Promise<void> => {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const url = new URL(req.url ?? '/', 'http://internal')\n const dir = url.searchParams.get('dir') ?? undefined\n const roots = options.allowedCwdRoots\n const limit = Number(url.searchParams.get('limit') ?? '') || undefined\n const offset = Number(url.searchParams.get('offset') ?? '') || undefined\n const requested = url.searchParams.get('profile') ?? undefined\n let profile: ProfileInfo | undefined\n if (requested !== undefined) {\n const resolved = resolveProfile(requested, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n profile = resolved.profile\n } else {\n // Implicit only when unambiguous AND permitted — a caller scoped away\n // from the server's one profile falls back to the legacy listing rather\n // than being handed a store it may not create sessions in.\n const all = allProfiles()\n if (all.length === 1 && (!auth.allowedProfiles || auth.allowedProfiles.includes(all[0]!.name))) {\n profile = all[0]\n }\n }\n const adapter = adapterFor(profile?.engine)\n if (!adapter.capabilities.listSessions) {\n json(res, 400, {\n error:\n `profile '${profile?.name ?? 'default'}' runs the ${engineOf(profile)} engine, ` +\n 'which has no browsable session store',\n })\n return\n }\n const lister: SdkSessionLister =\n engineOf(profile) === 'claude' && options.listSdkSessions\n ? options.listSdkSessions\n : (params) => {\n if (!adapter.listSessions) {\n throw new Error(`the ${engineOf(profile)} engine does not implement session listing`)\n }\n return adapter.listSessions({\n ...params,\n profile,\n env: profile ? sessionEnvFor(profile) : process.env,\n })\n }\n try {\n if (roots && roots.length > 0) {\n if (dir) {\n if (!cwdAllowed(dir, roots)) {\n json(res, 403, { error: 'dir is outside the allowed roots' })\n return\n }\n } else {\n // A bare listing spans ALL projects on the host, which is wider than the\n // cwd policy. Rather than refuse — a client with no directory to name (the\n // iOS session list) has no other way to ask — list them and drop the ones\n // outside the roots.\n //\n // Filtering, not fanning out over the roots: `dir` selects one project\n // directory and its worktrees, not everything beneath it, so asking for\n // `/Users/me/projects` finds nothing when the sessions belong to\n // `/Users/me/projects/some-app`. Pagination is applied after the filter for\n // the same reason, which is why the underlying call takes neither bound.\n json(res, 200, { sdkSessions: withinRoots(await lister({}), roots, limit, offset) })\n return\n }\n }\n json(res, 200, { sdkSessions: await lister({ dir, limit, offset }) })\n } catch (error) {\n // The engine's own message (binary missing, store unreadable) is the\n // useful one; listing is read-only, so surfacing it verbatim is safe.\n json(res, 500, { error: error instanceof Error ? error.message : 'failed to list sessions' })\n }\n }\n\n /** The sessions whose `cwd` is inside the roots, newest first, then paged. A\n * summary with no `cwd` cannot be shown to be inside them, so it is dropped. */\n const withinRoots = (\n sessions: SdkSessionSummary[],\n roots: string[],\n limit?: number,\n offset = 0,\n ): SdkSessionSummary[] => {\n const allowed = sessions\n .filter((s) => s.cwd !== undefined && cwdAllowed(s.cwd, roots))\n .sort((a, b) => b.lastModified - a.lastModified)\n return limit === undefined ? allowed.slice(offset) : allowed.slice(offset, offset + limit)\n }\n\n const handleJobs = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n ): Promise<void> => {\n if (!queue) {\n json(res, 404, { error: 'job queue not configured' })\n return\n }\n if (pathname === basePath + '/queue') {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n json(res, 200, { stats: await queue.stats() })\n return\n }\n const rest = pathname.slice((basePath + '/jobs').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n json(res, 200, { jobs: await queue.list() })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, maxBodyBytes)) as CreateJobRequest\n if (!body.session || typeof body.session !== 'object') {\n json(res, 400, { error: 'session is required' })\n return\n }\n if (!body.session.cwd || typeof body.session.cwd !== 'string') {\n json(res, 400, { error: 'session.cwd is required' })\n return\n }\n if (!body.session.prompt || typeof body.session.prompt !== 'string') {\n json(res, 400, { error: 'session.prompt is required' })\n return\n }\n if (!cwdAllowed(body.session.cwd, options.allowedCwdRoots)) {\n json(res, 403, { error: 'cwd is outside the allowed roots' })\n return\n }\n const refused = applyBypassPolicy(body.session)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = resolveProfile(body.session.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const badRequest =\n checkPermissionMode(body.session.permissionMode, resolved.profile) ??\n checkEngineGrants(body.session, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n stripInertFields(body.session, resolved.profile)\n // Normalize to the resolved name so an implicit single profile still lands\n // on JobInfo.profile and reaches the runner config at claim time.\n body.session.profile = resolved.profile?.name\n try {\n json(res, 201, { job: await queue.submit(body) })\n } catch (error) {\n json(res, 400, { error: error instanceof Error ? error.message : 'invalid job' })\n }\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const id = decodeURIComponent(rest)\n if (id.includes('/')) {\n json(res, 404, { error: 'not found' })\n return\n }\n if (req.method === 'GET') {\n const job = await queue.get(id)\n if (job) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n if (req.method === 'DELETE') {\n const job = await queue.cancel(id)\n if (job) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n /**\n * `POST {basePath}/executions/:executionId/result` — a deferred executor\n * delivering its outcome. Wakes the parked session, applies the result to its\n * agent loop, and lets the run continue.\n *\n * Scoped like every other session route: a principal restricted to certain\n * profiles cannot settle an execution belonging to a session outside them —\n * a result is trusted tool input, and injecting one into another tenant's loop\n * would be a way to steer it.\n */\n const handleExecutionResult = async (\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n ): Promise<void> => {\n const rest = pathname.slice((basePath + '/executions/').length).split('/')\n if (rest.length !== 2 || rest[1] !== 'result' || !rest[0]) {\n json(res, 404, { error: 'not found' })\n return\n }\n if (req.method !== 'POST') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const executionId = decodeURIComponent(rest[0])\n const body = (await readJsonBody(req, maxBodyBytes)) as SubmitExecutionResultRequest\n let result: ToolExecutionResult\n if (body?.status === 'ok') {\n if (!body.output || typeof body.output !== 'object') {\n json(res, 400, { error: \"output is required for status 'ok'\" })\n return\n }\n result = { status: 'ok', output: body.output.value, logs: body.logs }\n } else if (body?.status === 'failed') {\n if (typeof body.reason !== 'string' || typeof body.error !== 'string') {\n json(res, 400, { error: \"reason and error are required for status 'failed'\" })\n return\n }\n result = { status: 'failed', reason: body.reason, error: body.error, logs: body.logs }\n } else {\n json(res, 400, { error: \"status must be 'ok' or 'failed'\" })\n return\n }\n if (auth.allowedProfiles) {\n const owner = parking.sessionFor(executionId)\n const profile =\n owner === undefined\n ? undefined\n : (registry.get(owner)?.info().profile ?? (await parking.get(owner))?.profile)\n // Indistinguishable from an unknown id on purpose: whether an execution\n // exists elsewhere is not this caller's business.\n if (owner === undefined || (profile !== undefined && !auth.allowedProfiles.includes(profile))) {\n json(res, 404, { error: 'execution not found' })\n return\n }\n }\n const applied = await parking.submitResult(executionId, result)\n if (!applied) {\n json(res, 404, { error: 'execution not found (unknown id, or its session has ended)' })\n return\n }\n json(res, 200, applied)\n }\n\n const handleRequest = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const pathname = new URL(req.url ?? '/', 'http://internal').pathname\n\n // CORS, when the host configured it. Resource-sharing policy, not a\n // credential: every route stays behind `authenticate`, and an allowlisted\n // page still has to present the key. `Access-Control-Allow-Credentials` is\n // never sent — that is what keeps the cookie transport same-origin-only, so\n // opening this up cannot turn an ambient cookie into cross-origin authority.\n const origin = req.headers.origin\n const originAllowed =\n typeof origin === 'string' && corsOrigins !== undefined && corsOrigins.has(origin)\n if (originAllowed) {\n res.setHeader('access-control-allow-origin', origin)\n res.setHeader('vary', 'origin')\n }\n // Preflights arrive without credentials (browsers strip them), so they must\n // be answered *before* `authenticate` or every cross-origin call 401s on the\n // OPTIONS. Answering one grants nothing: no body, no side effect.\n if (req.method === 'OPTIONS' && req.headers['access-control-request-method'] !== undefined) {\n if (!originAllowed) {\n res.writeHead(403)\n res.end()\n return\n }\n res.setHeader('access-control-allow-methods', 'GET, HEAD, POST, PATCH, PUT, DELETE')\n res.setHeader('access-control-allow-headers', 'authorization, content-type, x-workerdeck-key')\n res.setHeader('access-control-max-age', '600')\n // Chrome's Private Network Access: a public page reaching a private\n // address (a tailnet's 100.64/10, a LAN) preflights for this explicitly.\n if (req.headers['access-control-request-private-network'] === 'true') {\n res.setHeader('access-control-allow-private-network', 'true')\n }\n res.writeHead(204)\n res.end()\n return\n }\n\n // Everything outside basePath belongs to the host, if it wants it. Checked\n // first so the fallback owns a total, contiguous namespace rather than\n // whatever the route table happens to leave over.\n if (fallback && pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n await fallback(req, res)\n return\n }\n if (\n pathname === basePath + '/jobs' ||\n pathname.startsWith(basePath + '/jobs/') ||\n pathname === basePath + '/queue'\n ) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleJobs(req, res, pathname, auth)\n return\n }\n if (pathname === basePath + '/profiles' || pathname.startsWith(basePath + '/profiles/')) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n const rest = pathname.slice((basePath + '/profiles').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n const visible = auth.allowedProfiles\n ? allProfiles().filter((p) => auth.allowedProfiles!.includes(p.name))\n : allProfiles()\n // Stale-while-revalidate: answer from the cache, re-probe anything\n // older than the TTL so the next read reflects a fresh login.\n refreshAvailability(visible)\n json(res, 200, {\n profiles: visible.map(forResponse),\n canManage: manageGuard(auth) === null,\n })\n return\n }\n if (req.method === 'POST') {\n const refused = manageGuard(auth)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as ProfileInfo\n if (!body.name || typeof body.name !== 'string') {\n json(res, 400, { error: 'name is required' })\n return\n }\n if (profileFor(body.name)) {\n json(res, 409, { error: `profile already exists: ${body.name}` })\n return\n }\n await saveManagedProfile(res, body)\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const name = decodeURIComponent(rest)\n const profile = name.includes('/') ? undefined : profileFor(name)\n if (!profile) {\n json(res, 404, { error: 'profile not found' })\n return\n }\n if (auth.allowedProfiles && !auth.allowedProfiles.includes(profile.name)) {\n json(res, 403, { error: `profile not allowed: ${profile.name}` })\n return\n }\n if (req.method === 'GET') {\n json(res, 200, { profile: withManagedFlag(profile), config: readProfileConfig(profile) })\n return\n }\n if (req.method === 'PATCH' || req.method === 'DELETE') {\n const refused = manageGuard(auth) ?? declaredGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n if (req.method === 'DELETE') {\n await options.profileStore!.delete(profile.name)\n await refreshStored()\n res.writeHead(204).end()\n return\n }\n const patch = (await readJsonBody(req, maxBodyBytes)) as UpdateProfileRequest\n // `name` is the route, not the body — a rename would orphan every session\n // and job already pinned to the old one.\n await saveManagedProfile(res, { ...profile, ...patch, name: profile.name })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n if (pathname.startsWith(basePath + '/executions/')) {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleExecutionResult(req, res, pathname, auth)\n return\n }\n if (pathname === basePath + '/sdk-sessions') {\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleSdkSessions(req, res, auth)\n return\n }\n if (pathname.startsWith(basePath + '/fs/')) {\n // Authenticated before the 404-when-unconfigured answer, so an unauthenticated\n // caller cannot learn whether this server exposes a filesystem at all.\n if (!(await authenticate(req)).ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleHostFiles(req, res, pathname)\n return\n }\n const route = parseRoute(req.url ?? '/')\n if (!route || route.ws) {\n json(res, 404, { error: 'not found' })\n return\n }\n const auth = await authenticate(req)\n if (!auth.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n\n if (!route.id) {\n if (req.method === 'GET') {\n // Parked sessions are live sessions that happen to have no runner right\n // now — leaving them out would read as \"gone\".\n json(res, 200, { sessions: [...registry.list(), ...(await parking.listInfo())] })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, maxBodyBytes)) as CreateSessionRequest\n if (!body.cwd || typeof body.cwd !== 'string') {\n json(res, 400, { error: 'cwd is required' })\n return\n }\n if (!cwdAllowed(body.cwd, options.allowedCwdRoots)) {\n json(res, 403, { error: 'cwd is outside the allowed roots' })\n return\n }\n const refused = applyBypassPolicy(body)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = resolveProfile(body.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const badRequest =\n checkPermissionMode(body.permissionMode, resolved.profile) ??\n checkEngineGrants(body, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n stripInertFields(body, resolved.profile)\n // Resolved name (even when implicit) so SessionInfo.profile is always set.\n body.profile = resolved.profile?.name\n const runner = await createRunner(buildRunnerConfig(body))\n watchAuthSource(runner)\n json(res, 201, { session: runner.info() })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n return\n }\n\n const runner = registry.get(route.id)\n // A parked session has no runner but is very much alive: it reads, lists, and\n // serves its files from the snapshot, and only waking it needs a rebuild.\n const parked = runner ? null : await parking.get(route.id)\n if (!runner && !parked) {\n json(res, 404, { error: 'session not found' })\n return\n }\n if (route.attachments) {\n await handleAttachments(req, res, route.id, runner?.info() ?? parked!.info, route.attachmentId)\n return\n }\n if (route.mcp) {\n if (!runner) {\n json(res, 409, { error: 'session is parked (wake it before asking about MCP)' })\n return\n }\n await handleMcp(req, res, runner, route.mcpServer)\n return\n }\n if (route.files) {\n // Deliverables live in the session's in-memory VFS — downloadable while\n // the session lives (durability is a persistence-tier concern, not ours).\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const snapshotFiles = parked?.snapshot.vfs\n const vfs = runner?.vfs ?? (snapshotFiles && {\n list: () => Object.keys(snapshotFiles).sort(),\n read: (path: string) => snapshotFiles[path],\n })\n if (!vfs) {\n json(res, 404, { error: 'session has no file store' })\n return\n }\n if (route.filePath === undefined) {\n const files = vfs.list().map((path) => ({ path, bytes: vfs.read(path)?.length ?? 0 }))\n json(res, 200, { files })\n return\n }\n const content = vfs.read(route.filePath)\n if (content === undefined) {\n json(res, 404, { error: `no such file: ${route.filePath}` })\n return\n }\n const filename = route.filePath.split('/').pop() || 'file'\n res.writeHead(200, {\n 'content-type': contentTypeFor(filename),\n 'content-length': Buffer.byteLength(content),\n // RFC 5987 filename* so non-ASCII names survive; plain filename for the rest.\n 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n // Agent-authored content must never render on this origin.\n 'x-content-type-options': 'nosniff',\n })\n res.end(content)\n return\n }\n if (route.produced) {\n await handleProducedFiles(req, res, route.id, route.producedFileId)\n return\n }\n if (route.permissionId) {\n // REST counterpart of the WS permission_decision command, for controllers\n // without a socket (e.g. answering a job's AskUserQuestion from a webhook).\n if (req.method !== 'POST') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as ResolvePermissionRequest\n if (body?.behavior !== 'allow' && body?.behavior !== 'deny') {\n json(res, 400, { error: \"behavior must be 'allow' or 'deny'\" })\n return\n }\n if (!runner) {\n json(res, 409, { error: 'session is parked (it has no pending permission requests)' })\n return\n }\n if (!runner.resolvePermission(route.permissionId, body)) {\n json(res, 404, { error: 'permission request not found (already resolved or expired)' })\n return\n }\n json(res, 200, { resolved: true })\n return\n }\n if (req.method === 'GET') {\n json(res, 200, { session: runner?.info() ?? parked!.info })\n return\n }\n if (req.method === 'PATCH') {\n // Rename. A parked session has no runner to carry the change and its\n // snapshot belongs to the park store, so it is refused rather than lied to.\n if (!runner) {\n json(res, 409, { error: 'session is parked (wake it before renaming)' })\n return\n }\n const body = (await readJsonBody(req, maxBodyBytes)) as UpdateSessionRequest\n if (body?.title !== undefined) {\n if (body.title !== null && typeof body.title !== 'string') {\n json(res, 400, { error: 'title must be a string or null' })\n return\n }\n const title = typeof body.title === 'string' ? body.title.trim() : ''\n runner.setTitle(title || undefined)\n }\n json(res, 200, { session: runner.info() })\n return\n }\n if (req.method === 'DELETE') {\n registry.remove(route.id)\n // Fail anything still bridged: the session is gone, so no answer can land.\n bridge.remove(route.id)\n // And drop any parked state, so a late execution result can't wake a session\n // the client just ended.\n await parking.discard(route.id)\n // The session is gone; so is anything it was holding for it.\n attachmentStore.drop(route.id)\n producedFiles.drop(route.id)\n json(res, 200, {\n session: runner?.info() ?? { ...parked!.info, status: 'closed' as const },\n })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n }\n\n const server = createServer((req, res) => {\n handleRequest(req, res).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : 'internal error'\n if (!res.headersSent) json(res, error instanceof SyntaxError ? 400 : 500, { error: message })\n else res.end()\n })\n })\n\n server.on('upgrade', (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n void (async () => {\n const pathname = new URL(req.url ?? '/', 'http://internal').pathname\n if (pathname === basePath + '/queue/ws') {\n if (!queue) {\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n if (!(await authenticate(req)).ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n queueSockets.add(ws)\n ws.on('close', () => queueSockets.delete(ws))\n void queue\n .stats()\n .then((stats) =>\n sendQueueFrame(ws, { type: 'queue_attached', protocolVersion: PROTOCOL_VERSION, stats }),\n )\n .catch(() => {})\n })\n return\n }\n const route = parseRoute(req.url ?? '/')\n if (!route?.ws || !route.id) {\n socket.destroy()\n return\n }\n if (!(await authenticate(req)).ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Attaching to a parked session wakes it: the client wants to drive it, and\n // its whole event log comes back with it, so `afterSeq` still lines up.\n const runner = await parking.ensureLive(route.id).catch(() => undefined)\n if (!runner) {\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n attachClient(ws, runner, req)\n })\n })().catch(() => socket.destroy())\n })\n\n const attachClient = (ws: WebSocket, runner: Runner, req: IncomingMessage): void => {\n const url = new URL(req.url ?? '/', 'http://internal')\n const afterSeq = Number(url.searchParams.get('afterSeq') ?? '0') || 0\n\n const send = (frame: ServerFrame): void => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame))\n }\n\n send({\n type: 'attached',\n protocolVersion: PROTOCOL_VERSION,\n session: runner.info(),\n replayingFrom: afterSeq,\n })\n const unsubscribe = runner.subscribe((event) => send({ type: 'event', event }), afterSeq)\n // Register for bridged tool calls: this client can be asked to execute them\n // in its own sandbox (see BridgeHub).\n const detachBridge = bridge.attach(runner.id, send)\n\n ws.on('message', (data: Buffer) => {\n let frame: ClientFrame\n try {\n frame = JSON.parse(data.toString('utf8')) as ClientFrame\n } catch {\n send({ type: 'protocol_error', message: 'invalid JSON frame' })\n return\n }\n handleCommand(frame, runner).catch((error: unknown) => {\n send({\n type: 'protocol_error',\n message: error instanceof Error ? error.message : 'command failed',\n })\n })\n })\n ws.on('close', () => {\n unsubscribe()\n detachBridge()\n // Nobody watching any more: a session waiting on a deferred execution can\n // give its runner back (after a grace period, so a reconnect costs nothing).\n parking.onDetach(runner.id)\n })\n }\n\n const handleCommand = async (frame: ClientFrame, runner: Runner): Promise<void> => {\n switch (frame.type) {\n case 'user_message': {\n if (!frame.attachmentIds?.length) {\n runner.sendMessage(frame.text)\n return\n }\n // The bytes live server-side; this is where a reference becomes content.\n // A missing id throws rather than sending a message that lost its picture.\n const resolved = attachmentStore.resolve(runner.id, frame.attachmentIds)\n if (!resolved.ok) {\n throw new Error(`unknown attachment(s): ${resolved.missing.join(', ')}`)\n }\n runner.sendMessage(frame.text, resolved.attachments)\n return\n }\n case 'permission_decision':\n if (frame.behavior === 'allow') {\n runner.resolvePermission(frame.requestId, {\n behavior: 'allow',\n updatedInput: frame.updatedInput,\n })\n } else {\n runner.resolvePermission(frame.requestId, {\n behavior: 'deny',\n message: frame.message,\n interrupt: frame.interrupt,\n })\n }\n return\n case 'interrupt':\n await runner.interrupt()\n return\n case 'set_permission_mode':\n if (frame.mode === 'bypassPermissions' && options.disableBypassPermissions) {\n throw new Error('bypassPermissions is disabled on this server (disableBypassPermissions)')\n }\n await runner.setPermissionMode(frame.mode)\n return\n case 'set_model':\n await runner.setModel(frame.model)\n return\n case 'tool_call_result':\n // Untrusted client input by contract — fine for the user's own data,\n // never a source for server-authoritative state. Unknown or already\n // settled ids are ignored rather than erroring: a late answer racing a\n // timeout is expected, not a client bug.\n bridge.resolve(runner.id, frame.executionId, { output: frame.output, logs: frame.logs })\n return\n case 'tool_call_error':\n bridge.resolve(runner.id, frame.executionId, {\n reason: frame.reason,\n error: frame.error,\n logs: frame.logs,\n })\n return\n case 'close':\n runner.close('client')\n return\n default:\n throw new Error(`unknown command: ${(frame as { type?: string }).type}`)\n }\n }\n\n return {\n server,\n registry,\n queue,\n bridge,\n parking,\n listen: async (port, host) => {\n // Before the first request: every lookup on the request path reads the\n // in-memory mirror, so it has to be populated before anything can hit it.\n await refreshStored()\n // Re-index and re-arm anything a durable store carried across a restart.\n await parking.hydrate()\n return new Promise((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n // After the bind and after refreshStored(), so stored profiles are\n // probed too; advisory, so it must never delay or wedge the listen.\n preflightCredentials()\n const address = server.address()\n resolve({ port: typeof address === 'object' && address ? address.port : port })\n })\n })\n },\n close: () =>\n new Promise((resolve) => {\n queue?.close()\n parking.close()\n registry.closeAll()\n for (const ws of queueSockets) ws.close()\n queueSockets.clear()\n wss.close()\n server.close(() => resolve())\n server.closeAllConnections()\n }),\n }\n}\n","import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport type { ProfileInfo } from '@workerdeck/protocol'\n\n/**\n * Where dashboard-managed profiles live. The seam exists for the same reason\n * `QueueAdapter` does: a single-host deployment wants the bundled file store and\n * no configuration, while an operator with a database wants their own.\n *\n * Profiles declared in `createWorkerServer({ profiles })` never enter a store —\n * they are code, and stay immutable. The store holds only what the management\n * routes created, and the two sets are unioned by name.\n *\n * A store holds NO credentials: `ProviderConfig.apiKeyEnv` is a variable name and\n * a Claude profile's `configDir` is a path. Both are resolved by the server's own\n * environment at session time, which is what keeps a stored profile safe to write\n * to disk and safe to serve from `GET /profiles`.\n */\nexport type ProfileStore = {\n /** Every stored profile. Called once at `listen()` and after each mutation. */\n list(): ProfileInfo[] | Promise<ProfileInfo[]>\n /** Create or replace by `profile.name`. */\n save(profile: ProfileInfo): void | Promise<void>\n /** Remove by name. Removing something absent is not an error. */\n delete(name: string): void | Promise<void>\n}\n\n/** Non-durable store for tests and ephemeral deployments. */\nexport function createMemoryProfileStore(seed: ProfileInfo[] = []): ProfileStore {\n const profiles = new Map(seed.map((p) => [p.name, p]))\n return {\n list: () => [...profiles.values()],\n save: (profile) => void profiles.set(profile.name, profile),\n delete: (name) => void profiles.delete(name),\n }\n}\n\n/**\n * JSON-file store: one array of profiles at `path` (default\n * `<cwd>/.workerdeck/profiles.json`). Writes go through a temp file and a\n * rename so a crash mid-write cannot truncate the operator's profile list.\n *\n * Single-process by design, exactly like the bundled queue adapter — two servers\n * sharing one file would race. That is what the seam is for.\n */\nexport function createFileProfileStore(path = join(process.cwd(), '.workerdeck', 'profiles.json')): ProfileStore {\n const read = (): Map<string, ProfileInfo> => {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown\n if (!Array.isArray(parsed)) return new Map()\n const profiles = parsed as ProfileInfo[]\n return new Map(profiles.filter((p) => p && typeof p.name === 'string').map((p) => [p.name, p]))\n } catch {\n // Absent or unparseable: start empty rather than refusing to boot. A\n // corrupt file is replaced on the next write, not silently merged into.\n return new Map()\n }\n }\n const write = (profiles: Map<string, ProfileInfo>): void => {\n mkdirSync(dirname(path), { recursive: true })\n const temp = `${path}.${process.pid}.tmp`\n writeFileSync(temp, JSON.stringify([...profiles.values()], null, 2))\n renameSync(temp, path)\n }\n return {\n list: () => [...read().values()],\n save: (profile) => {\n const profiles = read()\n profiles.set(profile.name, profile)\n write(profiles)\n },\n delete: (name) => {\n const profiles = read()\n if (profiles.delete(name)) write(profiles)\n },\n }\n}\n"],"mappings":"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"}