@workerdeck/server 0.16.0 → 0.18.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.
- package/README.md +10 -0
- package/build/index.d.mts +67 -7
- package/build/index.mjs +398 -14
- package/build/index.mjs.map +1 -1
- package/package.json +5 -5
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["resolvePath","#records","#maxFileBytes","#maxSessionBytes","#bySession","#verdicts","#warned","#opts","#options","#bridge","#sessions","#options","#chains","#emit","#send","#deliver","#options","#configs","#rememberDormant","#track","#forget","#park","#closed","#detachTimers","#owners","#settled","#queue","#storeOps","#resume","#clearTimer","#timers","#resuming","#rebuild","#bySession","#declared","#declaredByName","#stored","#opts","#profiles","#options","#sessions","profiles"],"sources":["../src/lib/http.ts","../src/lib/profile-env.ts","../src/lib/parse-route.ts","../src/routes/executions.ts","../src/services/host-files.ts","../src/services/host-file-search.ts","../src/routes/host-files.ts","../src/routes/jobs.ts","../src/routes/profiles.ts","../src/routes/sdk-sessions.ts","../src/services/session-store.ts","../src/routes/attachments.ts","../src/routes/mcp.ts","../src/routes/produced-files.ts","../src/routes/sessions.ts","../src/routes/ws.ts","../src/services/attachments.ts","../src/lib/scope.ts","../src/services/auth.ts","../src/services/availability.ts","../src/services/bridge.ts","../src/services/notifications.ts","../src/services/parking.ts","../src/services/produced-files.ts","../src/services/profiles.ts","../src/services/profile-usage.ts","../src/services/registry.ts","../src/services/session-factory.ts","../src/server.ts","../src/lib/sandboxed-profile.ts","../src/lib/provider-runner.ts","../src/services/profile-store.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport function 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\nexport async 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. */\nexport async 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/** 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`. */\nexport function 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 */\nexport function asUtf8(bytes: Buffer): string | null {\n const text = bytes.toString('utf8')\n return Buffer.from(text, 'utf8').equals(bytes) ? text : null\n}\n\nexport function 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 * Pure profile/environment/path rules: which engine a profile runs, where the\n * CLI's config resolution lands, the CLAUDE_CONFIG_DIR pin, and the cwd-roots\n * policy. No state, no I/O beyond reads of the filesystem the rules are about.\n */\nimport { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve as resolvePath, sep } from 'node:path'\nimport type { ProfileConfigSnapshot, ProfileEngine, ProfileInfo } from '@workerdeck/protocol'\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'. */\nexport function isProviderProfile(profile: ProfileInfo): boolean {\n return profile.engine === 'provider'\n}\n\n/** The engine a profile runs, absent meaning 'claude' (pre-provider profiles). */\nexport function 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. */\nexport function 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. */\nexport function 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. */\nexport function 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 */\nexport function 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\nexport function 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\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 */\nexport function 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","/** The `/sessions` route family, parsed. Pattern:\n * {basePath}/sessions[/:id[/ws | /permissions/:requestId | /files[/<path>] |\n * /attachments[/:attachmentId] | /mcp[/:serverName] | /produced[/:fileId]]]\n */\nexport type SessionRoute = {\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}\n\nexport function parseSessionRoute(basePath: string, url: string): SessionRoute | 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 * `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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ToolExecutionResult } from '@workerdeck/core'\nimport type { SubmitExecutionResultRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleExecutionResult(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, basePath, parking, registry } = ctx\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, ctx.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 || auth.scope || ctx.options.authorizeSession) {\n const owner = parking.sessionFor(executionId)\n const info =\n owner === undefined\n ? undefined\n : (registry.get(owner)?.info() ?? (await parking.get(owner))?.info)\n const profile = info?.profile\n // Indistinguishable from an unknown id on purpose: whether an execution\n // exists elsewhere is not this caller's business. Scope is checked as well\n // as the profile because a result is trusted tool input — settling another\n // scope's execution is a way to steer its loop, not merely to read it.\n const refused =\n owner === undefined ||\n (auth.allowedProfiles !== undefined &&\n profile !== undefined &&\n !auth.allowedProfiles.includes(profile)) ||\n (info !== undefined && !authSvc.canSee(auth, info))\n if (refused) {\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","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","/**\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 `services/host-files.ts` first, which\n * canonicalizes and *then* re-checks containment. The naive prefix compare\n * `cwdAllowed` does would be wrong at this door — the agent writes into these\n * trees, and a symlink it created is a path the operator never typed.\n */\nimport { lstatSync, readdirSync, type Dirent } from 'node:fs'\nimport { join } from 'node:path'\nimport { basename } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { WriteHostFileRequest } from '@workerdeck/protocol'\nimport { asUtf8, hashBytes, json, readJsonBody } from '../lib/http.ts'\nimport { searchFiles } from '../services/host-file-search.ts'\nimport {\n entryKind,\n readContained,\n resolveExisting,\n resolveForWrite,\n writeContained,\n} from '../services/host-files.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleHostFiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n): Promise<void> {\n const { basePath, hostFiles, hostFilesWritable, maxHostFileBytes, maxHostDirEntries } = ctx\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: ctx.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, ctx.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","/** `{basePath}/jobs` + `{basePath}/queue` — job submission, listing, cancel,\n * and the gateway-wide queue stats. Jobs run as ordinary registry sessions;\n * these routes are the queue's REST face. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { CreateJobRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleJobs(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, availability, basePath, factory, queue } = ctx\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 // Gateway-wide counters across every scope, with no id to filter on.\n if (!authSvc.isOperator(auth)) {\n json(res, 404, { error: 'not found' })\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 // Same rule as the sessions list, over the job's copy of the tags: the\n // queue must not be a side door into a session the caller cannot attach to.\n const jobs = await queue.list()\n json(res, 200, { jobs: jobs.filter((job) => authSvc.canSeeJob(auth, job)) })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, ctx.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.prompt || typeof body.session.prompt !== 'string') {\n json(res, 400, { error: 'session.prompt is required' })\n return\n }\n const refusedScope = factory.applyScope(body.session, auth)\n if (refusedScope) {\n json(res, refusedScope.status, { error: refusedScope.error })\n return\n }\n const refused = factory.applyBypassPolicy(body.session)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = factory.resolveProfile(body.session.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const unavailable = availability.checkAvailable(resolved.profile)\n if (unavailable) {\n json(res, unavailable.status, { error: unavailable.error })\n return\n }\n const refusedCwd = factory.checkCwd(body.session, resolved.profile)\n if (refusedCwd) {\n json(res, refusedCwd.status, { error: refusedCwd.error })\n return\n }\n const badRequest =\n factory.checkPermissionMode(body.session.permissionMode, resolved.profile) ??\n factory.checkEngineGrants(body.session, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n factory.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 && authSvc.canSeeJob(auth, job)) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n if (req.method === 'DELETE') {\n // Checked before the cancel, not after: a refused caller must not be able\n // to kill a run and then be told it does not exist.\n const existing = await queue.get(id)\n if (!existing || !authSvc.canSeeJob(auth, existing)) {\n json(res, 404, { error: 'job not found' })\n return\n }\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","/** `{basePath}/profiles[/:name]` — the profile list every create form renders\n * from, and (with a `profileStore`) the management routes. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ProfileInfo, UpdateProfileRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport { readProfileConfig } from '../lib/profile-env.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleProfiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, availability, basePath, profiles } = ctx\n const saveManaged = async (incoming: ProfileInfo): Promise<void> => {\n const saved = await profiles.saveManaged(incoming)\n if (!saved.ok) json(res, saved.status, { error: saved.error })\n else json(res, 200, { profile: saved.profile })\n }\n const rest = pathname.slice((basePath + '/profiles').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n const visible = auth.allowedProfiles\n ? profiles.all().filter((p) => auth.allowedProfiles!.includes(p.name))\n : profiles.all()\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 availability.refresh(visible)\n json(res, 200, {\n profiles: visible.map((p) => profiles.forResponse(p)),\n canManage: profiles.manageGuard(auth) === null,\n })\n return\n }\n if (req.method === 'POST') {\n const refused = profiles.manageGuard(auth)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const body = (await readJsonBody(req, ctx.maxBodyBytes)) as ProfileInfo\n if (!body.name || typeof body.name !== 'string') {\n json(res, 400, { error: 'name is required' })\n return\n }\n if (profiles.get(body.name)) {\n json(res, 409, { error: `profile already exists: ${body.name}` })\n return\n }\n await saveManaged(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 : profiles.get(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 // The config snapshot is the operator's own config directory read back —\n // skill, agent and command names, hook names, the *keys* of the env in\n // `settings.json`. A profile a scoped end user is allowed to *run* is\n // not thereby a directory they may inventory, so the snapshot is\n // withheld from a non-operator while the profile record itself (name,\n // engine, model catalog) still answers, because a create form needs it.\n json(res, 200, {\n // The same decoration the list route serves (`forResponse`): the\n // capability record, the model catalog, the availability verdict and\n // the plan usage. It had been the bare record, which made the detail\n // route answer *less* about a profile than the list it was opened\n // from — a client could only get the usage state by listing.\n profile: profiles.forResponse(profile),\n config: authSvc.isOperator(auth) ? readProfileConfig(profile) : undefined,\n })\n return\n }\n if (req.method === 'PATCH' || req.method === 'DELETE') {\n const refused = profiles.manageGuard(auth) ?? profiles.declaredGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n if (req.method === 'DELETE') {\n await ctx.options.profileStore!.delete(profile.name)\n await profiles.refreshStored()\n res.writeHead(204).end()\n return\n }\n const patch = (await readJsonBody(req, ctx.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 saveManaged({ ...profile, ...patch, name: profile.name })\n return\n }\n json(res, 405, { error: 'method not allowed' })\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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ProfileInfo, SdkSessionSummary } from '@workerdeck/protocol'\nimport { json } from '../lib/http.ts'\nimport { cwdAllowed, engineOf } from '../lib/profile-env.ts'\nimport type { SdkSessionLister } from '../options.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleSdkSessions(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n auth: AuthContext,\n): Promise<void> {\n const { adapterFor, factory, profiles } = ctx\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 = ctx.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 = factory.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 = profiles.all()\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' && ctx.listSdkSessions\n ? ctx.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 ? factory.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. */\nfunction 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","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 /** Absent on records written before dormant sessions existed — those are all parked. */\n kind?: 'parked'\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 * A live session, remembered so it can be brought back after a gateway restart.\n *\n * The counterpart to a park, and deliberately not the same mechanism. A park\n * preserves *mid-task* state, which means a `RunnerSnapshot` — and only the\n * provider engine can produce one, because the claude and codex engines run\n * behind a binary that owns its own process state. What those two have instead\n * is a session store of their own: the transcript is already on disk under an\n * engine session id, and `CreateSessionRequest.resume` is how you get it back.\n *\n * So this record holds no transcript at all. It holds the id (every client keys\n * its watermarks and routes on it), the engine session id to resume from, and\n * the config to rebuild with — and rehydration is an ordinary create with\n * `resume` set, done **lazily on first attach**, because eagerly respawning\n * every session at boot is a fork bomb wearing a feature's clothes.\n *\n * Written only for engines whose capability record says `resume`, and only once\n * an `sdkSessionId` exists: a record that would come back with an empty\n * transcript is worse than no record.\n */\nexport type DormantSessionRecord = {\n kind: 'dormant'\n id: string\n /** Session info as of the last save, with `status: 'idle'` — whatever it was\n * doing, it is not doing it now. */\n info: SessionInfo\n profile?: string\n /**\n * The config the session was built from, minus the ephemeral keys. Fed back\n * through the server's `buildRunnerConfig` on wake rather than used as-is, so\n * the profile's env pin and the host hook's injections are **re-derived**\n * instead of persisted (see {@link EPHEMERAL_CONFIG_KEYS}).\n */\n config: SessionRunnerConfig\n /** Where the transcript actually lives. Without one there is nothing to resume. */\n sdkSessionId: string\n savedAt: number\n}\n\n/** What a {@link SessionStore} holds: a session waiting on deferred work, or one\n * waiting to be asked for again. */\nexport type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord\n\nexport const isDormant = (record: StoredSessionRecord): record is DormantSessionRecord =>\n record.kind === 'dormant'\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: StoredSessionRecord): Promise<void>\n get(id: string): Promise<StoredSessionRecord | null>\n list(): Promise<StoredSessionRecord[]>\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, StoredSessionRecord>()\n\n save(record: StoredSessionRecord): Promise<void> {\n this.#records.set(record.id, record)\n return Promise.resolve()\n }\n\n get(id: string): Promise<StoredSessionRecord | null> {\n return Promise.resolve(this.#records.get(id) ?? null)\n }\n\n list(): Promise<StoredSessionRecord[]> {\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, but the reason differs by\n * record kind and both halves matter. A provider session's credentials are\n * resolved by `createEngineRunner` from the operator's environment on every\n * build, wake included — so a parked record never needed them. A **dormant**\n * record is a claude or codex session, which does consume `env` (the profile's\n * `CLAUDE_CONFIG_DIR` pin lives there), and that is precisely why waking one\n * feeds its config back through the server's `buildRunnerConfig` instead of\n * handing it to the engine as-is: the pin and the host hook's injections are\n * re-derived from the profile, never read back off disk. Persisting them would\n * be a credential map in a file *and* a stale one.\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<T extends StoredSessionRecord>(record: T): T {\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<StoredSessionRecord | 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 StoredSessionRecord => 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): StoredSessionRecord | 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<StoredSessionRecord> | null\n if (!record || typeof record !== 'object') return null\n if (typeof record.id !== 'string') return null\n if (!record.info || !record.config) return null\n // The discriminator is optional on the wire: every record written before\n // dormant sessions existed is a park, and those files must keep working\n // across the upgrade that introduced this.\n if (record.kind === 'dormant') {\n const dormant = record as Partial<DormantSessionRecord>\n if (typeof dormant.sdkSessionId !== 'string' || typeof dormant.savedAt !== 'number') return null\n return dormant as DormantSessionRecord\n }\n const parked = record as Partial<ParkedSessionRecord>\n if (typeof parked.parkedAt !== 'number' || !parked.snapshot) return null\n if (!Array.isArray(parked.executions)) return null\n return parked as ParkedSessionRecord\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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { attachmentKind } from '@workerdeck/core'\nimport { ENGINE_CAPABILITIES, type SessionInfo } from '@workerdeck/protocol'\nimport { json, readRawBody } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleAttachments(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n session: SessionInfo,\n attachmentId?: string,\n): Promise<void> {\n const { attachmentStore } = ctx\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 * `{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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Runner } from '@workerdeck/core'\nimport type { McpServerActionRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleMcp(\n ctx: ServerContext,\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, ctx.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 * `{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 `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 */\nimport { createReadStream, statSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { contentTypeFor, json } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleProducedFiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n fileId?: string,\n): Promise<void> {\n const { producedFiles } = ctx\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","/** `{basePath}/sessions[/:id[...]]` — the list, the create, and every\n * per-session subroute. One `canSee` gate covers all of them, byte-identical\n * to the unknown-id answer: whether a session exists elsewhere is not this\n * caller's business. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type {\n CreateSessionRequest,\n ResolvePermissionRequest,\n UpdateSessionRequest,\n} from '@workerdeck/protocol'\nimport { contentTypeFor, json, readJsonBody } from '../lib/http.ts'\nimport type { SessionRoute } from '../lib/parse-route.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport { isDormant } from '../services/session-store.ts'\nimport type { ServerContext } from '../context.ts'\nimport { handleAttachments } from './attachments.ts'\nimport { handleMcp } from './mcp.ts'\nimport { handleProducedFiles } from './produced-files.ts'\n\nexport async function handleSessions(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n route: SessionRoute,\n auth: AuthContext,\n): Promise<void> {\n const { attachmentStore, auth: authSvc, availability, bridge, factory, parking, producedFiles, registry } = ctx\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 const sessions = [...registry.list(), ...(await parking.listInfo())]\n json(res, 200, { sessions: sessions.filter((session) => authSvc.canSee(auth, session)) })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, ctx.maxBodyBytes)) as CreateSessionRequest\n const refusedScope = factory.applyScope(body, auth)\n if (refusedScope) {\n json(res, refusedScope.status, { error: refusedScope.error })\n return\n }\n const refused = factory.applyBypassPolicy(body)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = factory.resolveProfile(body.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const unavailable = availability.checkAvailable(resolved.profile)\n if (unavailable) {\n json(res, unavailable.status, { error: unavailable.error })\n return\n }\n const refusedCwd = factory.checkCwd(body, resolved.profile)\n if (refusedCwd) {\n json(res, refusedCwd.status, { error: refusedCwd.error })\n return\n }\n const badRequest =\n factory.checkPermissionMode(body.permissionMode, resolved.profile) ??\n factory.checkEngineGrants(body, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n factory.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 factory.createRunner(factory.buildRunnerConfig(body))\n factory.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 // One gate for every `/sessions/:id/*` subroute below — GET, PATCH, DELETE,\n // files, produced, attachments, mcp, and the permission decision that would\n // otherwise let another scope answer this session's approvals. Byte-identical\n // to the unknown-id answer above: whether a session exists elsewhere is not\n // this caller's business.\n if (!authSvc.canSee(auth, runner?.info() ?? parked!.info)) {\n json(res, 404, { error: 'session not found' })\n return\n }\n if (route.attachments) {\n await handleAttachments(\n ctx,\n req,\n res,\n route.id,\n runner?.info() ?? parked!.info,\n route.attachmentId,\n )\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(ctx, 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 // A dormant session has no VFS to serve: its files, if it made any, live\n // wherever the engine wrote them, not in a snapshot we kept.\n const snapshotFiles = parked && !isDormant(parked) ? parked.snapshot.vfs : undefined\n const vfs =\n runner?.vfs ??\n (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(ctx, 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, ctx.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, ctx.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 // A rename emits no event, so nothing else would re-save the dormant\n // record — and the wake rebuilds from it. Without this the new name\n // survives only until the next restart.\n parking.touch(runner)\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","/** The session WebSocket: attach (replay + live), and the client command\n * surface. One live attach per client socket; the bridge registration is what\n * lets this client execute bridged tool calls in its own sandbox. */\nimport type { IncomingMessage } from 'node:http'\nimport type { WebSocket } from 'ws'\nimport type { Runner } from '@workerdeck/core'\nimport { PROTOCOL_VERSION, type ClientFrame, type ServerFrame } from '@workerdeck/protocol'\nimport type { ServerContext } from '../context.ts'\n\nexport function attachClient(\n ctx: ServerContext,\n ws: WebSocket,\n runner: Runner,\n req: IncomingMessage,\n): void {\n const { bridge, parking } = ctx\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 // `coalesceReplay` is opt-in and this is the one caller: a client's reducer\n // is last-write-wins for the readings it drops, so all it ever sees is the\n // final value — whereas the in-process subscribers (parking above all) read\n // those same events as *transitions* and must keep every one. Without it a\n // long session replays every per-turn usage poll, and the client renders\n // each: the meters visibly count up through the session's whole history on\n // every attach.\n const unsubscribe = runner.subscribe((event) => send({ type: 'event', event }), afterSeq, {\n coalesceReplay: true,\n })\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(ctx, 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\nasync function handleCommand(ctx: ServerContext, frame: ClientFrame, runner: Runner): Promise<void> {\n const { attachmentStore, bridge } = ctx\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' && ctx.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","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","/**\n * The scope rules — opaque tags assigned at create, immutable after, and the\n * only intra-deployment scoping primitive there is. WorkerDeck stores and\n * enforces the tags; the embedder's `authorizeSession` decides what they mean.\n */\n\n/** Most tags one session (or one principal) may carry, and the longest a key or\n * value may be. Not a security property — a bound so an opaque map cannot become\n * an unbounded store that every list response then carries. */\nconst MAX_SCOPE_KEYS = 16\nconst MAX_SCOPE_LEN = 200\n\n/** A `Record<string, string>` or nothing. Duck-typed the same way\n * `allowedProfiles` is: a malformed value is ignored, never half-applied. */\nexport function readScope(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const entries = Object.entries(value as Record<string, unknown>)\n if (entries.some(([, v]) => typeof v !== 'string')) return undefined\n return Object.fromEntries(entries) as Record<string, string>\n}\n\n/** Validate a caller-supplied scope. Returns an error string, or null when it is\n * well-formed (including when it is absent). */\nexport function checkScope(value: unknown): string | null {\n if (value === undefined) return null\n const scope = readScope(value)\n if (!scope) return 'scope must be an object of string values'\n const entries = Object.entries(scope)\n if (entries.length > MAX_SCOPE_KEYS) return `scope may carry at most ${MAX_SCOPE_KEYS} keys`\n for (const [key, val] of entries) {\n if (key.length === 0) return 'scope keys must not be empty'\n if (key.length > MAX_SCOPE_LEN || val.length > MAX_SCOPE_LEN) {\n return `scope keys and values must be at most ${MAX_SCOPE_LEN} characters`\n }\n }\n return null\n}\n\n/** Key-order-independent equality — a host runner that rebuilt the record\n * rather than echoing the reference must still pass the build-time check. */\nexport function sameScope(\n a: Record<string, string> | undefined,\n b: Record<string, string> | undefined,\n): boolean {\n const left = Object.entries(a ?? {}).sort(([x], [y]) => (x < y ? -1 : 1))\n const right = Object.entries(b ?? {}).sort(([x], [y]) => (x < y ? -1 : 1))\n return (\n left.length === right.length &&\n left.every(([key, value], i) => right[i]![0] === key && right[i]![1] === value)\n )\n}\n\n/**\n * The default visibility rule, used whenever the host supplies no\n * `authorizeSession`: every key the principal pins must match the session's, and\n * an unset principal scope sees everything.\n *\n * The asymmetry is intended — a session may carry tags the principal says\n * nothing about (an app that tags `{space, user, conversation}` while the\n * principal only pins `{space, user}` still works), but a session missing a key\n * the principal pins is not this caller's.\n */\nexport function scopeMatches(\n principal: Record<string, string> | undefined,\n session: Record<string, string> | undefined,\n): boolean {\n if (!principal) return true\n return Object.entries(principal).every(([key, value]) => session?.[key] === value)\n}\n","/**\n * Request authentication and the visibility predicates. One `canSee` behind the\n * list filter, every `/sessions/:id/*` route, the WS attach, the execution-result\n * door and the job routes — three callers, one predicate, so they cannot drift\n * into three subtly different answers.\n */\nimport type { IncomingMessage } from 'node:http'\nimport type { JobInfo, SessionInfo } from '@workerdeck/protocol'\nimport { readScope, scopeMatches } from '../lib/scope.ts'\nimport type { WorkerServerOptions } from '../options.ts'\nimport type { SessionRegistry } from './registry.ts'\n\nexport type AuthContext = {\n ok: boolean\n allowedProfiles?: string[]\n canManageProfiles?: boolean\n /** Whatever the host's `authenticate` returned, handed back to\n * `authorizeSession` verbatim — the host wrote both, and should get its own\n * object rather than this parsed shape. */\n principal?: unknown\n /** Parsed off the principal, duck-typed exactly like `allowedProfiles`.\n * Undefined = unrestricted. */\n scope?: Record<string, string>\n /** `principal.operator`, when the host stated it. Undefined = infer (see\n * {@link AuthService.isOperator}). */\n operator?: boolean\n}\n\nexport type AuthService = ReturnType<typeof createAuthService>\n\nexport function createAuthService(deps: {\n options: Pick<WorkerServerOptions, 'authenticate' | 'authorizeSession'>\n refs: { registry?: SessionRegistry }\n}) {\n const { options, refs } = deps\n\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 const scope = readScope((principal as { scope?: unknown }).scope)\n return {\n ok: true,\n principal,\n // An empty object pins nothing, so it is unrestricted like an absent one —\n // an embedder must never read `{}` as \"sees no sessions\".\n scope: scope && Object.keys(scope).length > 0 ? scope : undefined,\n // Three-state on purpose: absent lets `isOperator` infer, and only an\n // actual boolean overrides the inference in either direction.\n operator:\n typeof (principal as { operator?: unknown }).operator === 'boolean'\n ? ((principal as { operator: boolean }).operator)\n : undefined,\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 /** May this caller see — and therefore drive — this session? */\n const canSee = (auth: AuthContext, session: SessionInfo): boolean => {\n if (!options.authorizeSession) return scopeMatches(auth.scope, session.scope)\n try {\n return options.authorizeSession(auth.principal, session) === true\n } catch {\n // A policy that threw has not said yes. Fail closed rather than 500 the\n // route — a list of a hundred rows must not become a page-wide error\n // because one row's tags surprised the host's rule.\n return false\n }\n }\n\n /**\n * The job flavour of {@link canSee}. Once the run has started, the live\n * session's info is the real subject and the host's rule decides on it. Before\n * that (queued) and after (finished, session gone) there is no session to\n * hand over, so the predicate gets a **stub** built from what the job records:\n * its scope, its profile, its cwd.\n *\n * A stub rather than a fallback to the default rule, which is what this did\n * first and was wrong: a host policy *narrower* than plain tag-match (tags\n * plus a role, say) would have had queued jobs admitted — and cancelable — by\n * a peer it rejects. The predicate must be the only rule wherever it exists.\n * A host reading fields a queued job cannot have (model, status detail) gets\n * `undefined` and should treat the id and the scope as the load-bearing ones.\n */\n const canSeeJob = (auth: AuthContext, job: JobInfo): boolean => {\n const live = job.sessionId ? refs.registry!.get(job.sessionId)?.info() : undefined\n if (live) return canSee(auth, live)\n if (!options.authorizeSession) return scopeMatches(auth.scope, job.scope)\n return canSee(auth, {\n id: job.sessionId ?? job.id,\n status:\n job.status === 'running'\n ? 'running'\n : job.status === 'parked'\n ? 'parked'\n : job.status === 'queued'\n ? 'starting'\n : 'closed',\n cwd: job.cwd,\n profile: job.profile,\n createdAt: job.createdAt,\n lastSeq: 0,\n pendingPermissionCount: 0,\n scope: job.scope,\n })\n }\n\n /**\n * Is this caller the operator, rather than someone embedded inside a scope?\n *\n * It decides the surfaces that answer about the **gateway** instead of about\n * one session — the host filesystem, the engine's own on-disk session store,\n * the queue and its firehose. There is nothing to filter on those and no\n * honest way to narrow them, so a non-operator is refused outright (404, like\n * every other miss).\n *\n * Two ways to be one, and the second exists because the first is not enough.\n * A principal carrying `scope` is an end user; a principal carrying neither\n * `scope` nor a policy is the operator — that is the unscoped default every\n * existing deployment relies on. But a host may write `authorizeSession` over\n * its *own* principal shape and never set `scope` at all, and reading that as\n * \"everyone is the operator\" is how a locked-down gateway ends up serving its\n * filesystem to end users. So **declaring a policy withdraws the default**,\n * and such a host marks its operator principals explicitly with\n * `operator: true` (`operator: false` forces the other way, at any time).\n */\n const isOperator = (auth: AuthContext): boolean =>\n auth.operator ?? (auth.scope === undefined && !options.authorizeSession)\n\n return { authenticate, canSee, canSeeJob, isOperator }\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 * (`requireAvailableProfile` is the one deliberate exception, and only on a\n * definite `false`.)\n */\nimport { checkClaudeAuth } from '@workerdeck/core'\nimport type { ClaudeAuthProbe, EngineAdapter, EngineAvailability } from '@workerdeck/core'\nimport type { ProfileEngine, ProfileInfo } from '@workerdeck/protocol'\nimport { engineOf } from '../lib/profile-env.ts'\nimport type { Refusal } from './profiles.ts'\n\nconst AVAILABILITY_TTL_MS = 60_000\n\nexport type AvailabilityTrackerOptions = {\n checkCredentials?: boolean | { probe?: ClaudeAuthProbe; timeoutMs?: number }\n requireAvailableProfile?: boolean\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n /** The env the real assembly path would spawn this profile's session with. */\n sessionEnvFor: (profile: ProfileInfo) => Record<string, string | undefined>\n}\n\nexport class AvailabilityTracker {\n readonly #verdicts = new Map<string, { verdict: EngineAvailability; at: number }>()\n /** Profiles already warned about on the console, so re-probes don't spam. */\n readonly #warned = new Set<string>()\n readonly #opts: AvailabilityTrackerOptions\n\n constructor(opts: AvailabilityTrackerOptions) {\n this.#opts = opts\n }\n\n /** The cached verdict, if any probe has answered. */\n get(name: string): EngineAvailability | undefined {\n return this.#verdicts.get(name)?.verdict\n }\n\n probe(profile: ProfileInfo): void {\n const { checkCredentials, adapterFor, sessionEnvFor } = this.#opts\n if (!checkCredentials) return\n const conf = checkCredentials === true ? {} : checkCredentials\n // Mark in-flight immediately so concurrent GET /profiles don't re-spawn.\n this.#verdicts.set(profile.name, {\n verdict: this.#verdicts.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 this.#verdicts.set(profile.name, { verdict, at: Date.now() })\n if (verdict.available === false && !this.#warned.has(profile.name)) {\n this.#warned.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) this.#warned.delete(profile.name)\n })\n .catch(() => {\n // a probe that breaks is 'unknown', and unknown stays silent\n })\n }\n\n /**\n * The create-time half of `requireAvailableProfile`. Only a definite `false`\n * refuses: an unprobed profile ('unknown', or probes turned off entirely) is\n * not evidence of anything and must not become a closed door.\n */\n checkAvailable(profile: ProfileInfo | undefined): Refusal | null {\n if (!this.#opts.requireAvailableProfile || !profile) return null\n const verdict = this.get(profile.name)\n if (!verdict || verdict.available !== false) return null\n return {\n status: 503,\n error: `profile '${profile.name}' is unavailable: ${verdict.reason ?? 'no usable credentials'}`,\n }\n }\n\n /** Launch-time sweep, concurrent and fire-and-forget. */\n preflight(profiles: ProfileInfo[]): void {\n for (const profile of profiles) this.probe(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 refresh(profiles: ProfileInfo[]): void {\n if (!this.#opts.checkCredentials) return\n const now = Date.now()\n for (const profile of profiles) {\n const cached = this.#verdicts.get(profile.name)\n if (!cached || now - cached.at > AVAILABILITY_TTL_MS) this.probe(profile)\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 { 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 type {\n ParkedExecution,\n Runner,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport { ENGINE_CAPABILITIES, type SessionInfo } from '@workerdeck/protocol'\nimport type { SessionRegistry } from './registry.ts'\nimport {\n isDormant,\n type DormantSessionRecord,\n type ParkedSessionRecord,\n type SessionStore,\n type StoredSessionRecord,\n} from './session-store.ts'\n\nexport type SessionParkOptions = {\n registry: SessionRegistry\n store: SessionStore\n /**\n * Rebuild a stored session's runner. For a park the snapshot rides in on\n * `config.restore`, so the engine adopts the id, event log, and history; for a\n * dormant record there is no snapshot and the engine is asked to resume its\n * own session under the stored id. Either way the runner it returns must carry\n * `record.id` — {@link SessionParkManager} refuses one that does not.\n */\n rebuild: (record: StoredSessionRecord) => 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/remember/resume failures. These are not session errors — the session\n * is intact, the host's storage or engine assembly isn't. */\n onError?: (\n error: unknown,\n context: { sessionId: string; phase: 'park' | 'remember' | 'resume' },\n ) => void\n}\n\n/**\n * Two ways a session outlives its runner, behind one door.\n *\n * **Parking** is deferred execution's other half: a session waiting on work no\n * process in this server is doing.\n *\n * **Dormancy** is the restart story for the engines that cannot park. Every live\n * claude or codex session leaves a small record naming its engine session id, so\n * a gateway that comes back up lists them and resumes one the first time someone\n * attaches. Both kinds live in the same store and come back through the same\n * `ensureLive`, which is why there is one class here and not two.\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 /**\n * Re-save a live session's dormant record because something outside the event\n * stream changed it.\n *\n * `#rememberDormant` is otherwise driven by `status_changed` and `system_init`\n * alone, and a rename (`PATCH /sessions/:id`) fires neither — so without this\n * a renamed session that is never touched again keeps its old title on disk\n * and comes back under it. Safe to call for anything: every gate in\n * `#rememberDormant` still applies, so a session that cannot be resumed, has\n * no engine session yet, or is no longer the registry's writes nothing.\n */\n touch(runner: Runner): void {\n void this.#rememberDormant(runner)\n }\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 *\n * Dormant records need nothing here, which is the point of them. They list\n * from the store (`listInfo`) and come back on first attach (`ensureLive`), so\n * a boot with fifty remembered sessions spawns nothing at all.\n */\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 if (isDormant(record)) continue\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 else void this.#rememberDormant(runner)\n return\n case 'system_init':\n // The first moment a resume is even possible: before the engine names\n // its session there is nothing to come back to.\n void this.#rememberDormant(runner)\n return\n case 'session_closed':\n // Not during shutdown. The registry closes every runner on the way\n // down and the reason it gives is the same 'server' a DELETE produces,\n // so the *only* thing separating \"this session is over\" from \"this\n // process is over\" is that `close()` ran first — which is exactly why\n // it does (`server.ts` closes parking before the registry). Without\n // this guard a graceful restart forgets every dormant session it was\n // supposed to preserve.\n if (this.#closed) return\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 stored session's record, for the read paths (GET, list, attach). */\n get(id: string): Promise<StoredSessionRecord | null> {\n return this.#queue(id, () => this.#options.store.get(id))\n }\n\n /** Every stored 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 const records = await this.#options.store.list()\n // A live session has a dormant record too — that is what makes it survive a\n // restart — so the registry wins and the record is skipped. Without this the\n // merged listing would show every running session twice.\n return records\n .filter((record) => this.#options.registry.get(record.id) === undefined)\n .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 /**\n * Write (or refresh) the dormant record that lets this session survive a\n * restart. Cheap and repeated on purpose — driven off `system_init` and every\n * non-park status change — because the alternative is a shutdown hook, and a\n * shutdown hook is exactly what a `kill -9`, an OOM or a pulled power cable\n * do not run.\n *\n * Four gates, each of which would otherwise produce a record that is worse\n * than none: the engine must be able to resume at all (a provider session\n * would come back with an empty transcript — it has `park()` instead), it must\n * have named its session, the host must remember the config to rebuild from,\n * and the runner must still be the registry's. That last one is what keeps a\n * park from being overwritten: `#park` evicts before it saves, so a late event\n * from an evicted runner finds itself a stranger here and writes nothing.\n */\n async #rememberDormant(runner: Runner): Promise<void> {\n if (this.#closed) return\n const info = runner.info()\n const sdkSessionId = info.sdkSessionId\n if (sdkSessionId === undefined) return\n const capabilities = info.capabilities ?? ENGINE_CAPABILITIES[info.engine ?? 'claude']\n if (!capabilities.resume) return\n const config = this.#configs.get(runner.id)\n if (!config) return\n if (this.#options.registry.get(runner.id) !== runner) return\n const record: DormantSessionRecord = {\n kind: 'dormant',\n id: runner.id,\n // Whatever it was doing, it is not doing it now: a record that came back\n // saying `running` would show a spinner over a session with no process.\n info: { ...info, status: 'idle' },\n profile: info.profile,\n // `#configs` holds the config the session was BUILT from, and a rename\n // never reaches it: `setTitle` replaces the runner's own `#config`. Since\n // a wake rebuilds from `record.config` and discards `record.info`, taking\n // the config's `meta` verbatim resurrected the pre-rename title — the\n // rename survived the listing (which reads `info`) and died on the wake.\n // `info.meta` IS the runner's live `#config.meta`, so this is the current\n // one by construction, not a copy that can drift.\n config: { ...config, meta: info.meta },\n sdkSessionId,\n savedAt: Date.now(),\n }\n try {\n await this.#queue(runner.id, () => this.#options.store.save(record))\n } catch (error) {\n // Losing this costs the session its way back after a restart, and nothing\n // else — the live session is untouched.\n this.#options.onError?.(error, { sessionId: runner.id, phase: 'remember' })\n }\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 kind: 'parked',\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` (or, for a dormant record, the id): it\n // built a fresh session with none of this one's history, which would\n // strand the execution we are waking for and orphan every client's\n // watermarks against an id that no longer exists.\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 (or, without a snapshot, the session id) ' +\n '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 // A park resumes mid-stream and must not re-arm watchdogs from its own\n // replayed events; a dormant session starts a fresh event log (the history\n // arrives as the engine's `replay: true` backfill), so there is no prior seq\n // to skip and nothing deferred in it to re-index.\n this.watch(runner, isDormant(record) ? 0 : record.snapshot.seq)\n this.#options.onResumed?.(id, runner)\n // A park's record *is* the session — consumed on wake. A dormant one is a\n // way back that the session still needs the next time the process dies, so\n // it stays and is refreshed in place; `listInfo` already hides it while the\n // registry has the session, and `discard` removes it when the session ends.\n if (!isDormant(record)) 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 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","/**\n * The profile directory: startup-declared profiles unioned with store-managed\n * ones, validation shared by startup (throws) and the management routes (400s),\n * and the response decoration (`forResponse`) every profile answer goes through.\n *\n * Declared profiles are code — never persisted, never editable over HTTP. The\n * store-managed set is mirrored in memory so every lookup on the request path\n * stays synchronous; `refreshStored()` reloads it after each mutation.\n */\nimport { existsSync } from 'node:fs'\nimport { supportsPermissionMode, type ProfileEngine, type ProfileInfo } from '@workerdeck/protocol'\nimport type { EngineAdapter, EngineAvailability } from '@workerdeck/core'\nimport { cwdAllowed, engineOf, isProviderProfile } from '../lib/profile-env.ts'\nimport type { ProfileStore } from './profile-store.ts'\n\nexport type Refusal = { status: number; error: string }\n\nexport type ProfileServiceOptions = {\n /** Startup-declared profiles (code). */\n declared: ProfileInfo[]\n /** Persistence for dashboard-managed profiles; absent = read-only API. */\n store?: ProfileStore\n /** Roots a managed Claude profile's configDir must resolve inside. */\n allowedConfigDirRoots?: string[]\n disableBypassPermissions?: boolean\n /** True when a `createEngineRunner` factory exists to build provider runners. */\n hasEngineRunnerFactory: boolean\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n /** Response-time decoration, injected as accessors because the state lives in\n * sibling services (learned default models, availability verdicts, plan usage). */\n decorate: {\n defaultModel: (name: string) => string | undefined\n availability: (name: string) => EngineAvailability | undefined\n usage: (name: string) => ProfileInfo['usage'] | undefined\n }\n}\n\nexport class ProfileService {\n readonly #declared: ProfileInfo[]\n readonly #declaredByName: Map<string, ProfileInfo>\n /** Store-managed profiles, mirrored in memory — see the module doc. */\n readonly #stored = new Map<string, ProfileInfo>()\n readonly #opts: ProfileServiceOptions\n\n constructor(opts: ProfileServiceOptions) {\n this.#opts = opts\n this.#declared = opts.declared\n this.#declaredByName = new Map(opts.declared.map((p) => [p.name, p]))\n if (this.#declaredByName.size !== opts.declared.length) {\n throw new Error('createWorkerServer: duplicate profile names in `profiles`')\n }\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 validate(p: ProfileInfo): string | null {\n const { adapterFor, disableBypassPermissions, hasEngineRunnerFactory } = this.#opts\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 (!hasEngineRunnerFactory) {\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 (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 /** Reload the in-memory mirror of the store — once at `listen()`, and after\n * each management-route mutation. Single-process, like the bundled queue. */\n async refreshStored(): Promise<void> {\n if (!this.#opts.store) return\n this.#stored.clear()\n for (const p of await this.#opts.store.list()) this.#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 withManagedFlag(p: ProfileInfo): ProfileInfo {\n return this.#declaredByName.has(p.name) ? p : { ...p, managed: true }\n }\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, 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 * and the plan usage learned from the profile's sessions' rate_limit events.\n * Read-only decoration — never persisted.\n */\n forResponse(p: ProfileInfo): ProfileInfo {\n const { adapterFor, decorate } = this.#opts\n const adapter = adapterFor(p.engine)\n const base: ProfileInfo = {\n ...this.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 = decorate.defaultModel(p.name)\n if (defaultModel) base.defaultModel = defaultModel\n const probed = decorate.availability(p.name)\n if (probed && probed.available !== 'unknown') {\n base.available = probed.available\n if (probed.available === false) base.unavailableReason = probed.reason\n }\n // Plan usage, learned like the default model and display-only like the\n // availability verdict. The 0%-after-elapsed-reset inference happens in\n // `usage()` per request, so it is computed against *this* moment's clock.\n const usage = decorate.usage(p.name)\n if (usage) base.usage = usage\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 all(): ProfileInfo[] {\n return [\n ...this.#declared,\n ...[...this.#stored.values()].filter((p) => !this.#declaredByName.has(p.name)),\n ]\n }\n\n get(name: string): ProfileInfo | undefined {\n return this.#declaredByName.get(name) ?? this.#stored.get(name)\n }\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 manageGuard(auth: { canManageProfiles?: boolean }): Refusal | null {\n if (!this.#opts.store) {\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 declaredGuard(profile: ProfileInfo): Refusal | null {\n return this.#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 /**\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 configDirGuard(profile: ProfileInfo): Refusal | null {\n if (isProviderProfile(profile)) return null\n const roots = this.#opts.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 and persist a managed profile. Shared by create and update so a\n * PATCH can never leave behind a profile a POST would have refused. Returns\n * the saved profile (managed-flagged) or a refusal. */\n async saveManaged(\n incoming: ProfileInfo,\n ): Promise<{ ok: true; profile: ProfileInfo } | ({ ok: false } & Refusal)> {\n // `managed` is server-computed on every response; never persist a client's copy.\n const { managed: _clientClaim, ...profile } = incoming\n const refused = this.configDirGuard(profile)\n if (refused) return { ok: false, ...refused }\n const invalid = this.validate(profile)\n if (invalid) return { ok: false, status: 400, error: invalid }\n await this.#opts.store!.save(profile)\n await this.refreshStored()\n return { ok: true, profile: this.withManagedFlag(profile) }\n }\n}\n","import type { Runner } from '@workerdeck/core'\nimport type { ProfileUsage, ProfileUsageWindow, RateLimitInfo } from '@workerdeck/protocol'\n\n/** A reading as held: what the engine said, and the event clock it said it at. */\ntype HeldWindow = { info: RateLimitInfo; updatedAt: number }\n\n/**\n * The gateway's single plan-usage state per profile, fed from every session's\n * `rate_limit` events and served on `GET /profiles` (`ProfileInfo.usage`).\n *\n * Why this exists at all: usage had only ever lived in session transcripts, so\n * a client attaching to a session that idled since yesterday replayed\n * yesterday's reading as if current — and a session opened today knew nothing\n * of what a sibling session on the same account spent an hour ago. The profile\n * is the account boundary (one config dir / codex home / provider key = one\n * plan), so the newest reading across all of a profile's sessions is the one\n * usage state that is ever worth showing. No history: last-write-wins per\n * window, exactly the reducer's rule on the client side.\n *\n * Last-write-wins goes by the **event's own clock**, not arrival order:\n * `watch()` subscribes from seq 0 (a replayed log is how a rebuilt session's\n * readings arrive at all), and a replayed yesterday-reading must not clobber\n * the fresher one another session on the same profile reported live. All\n * events are stamped by this gateway's clock at emit time, so the comparison\n * is sound across sessions.\n *\n * In-memory on purpose, like the learned default models and the availability\n * cache: display-only state may start empty after a restart (absent = unknown,\n * never 0%), and the first session to report refills it.\n */\nexport class ProfileUsageTracker {\n /** profile name → rateLimitType → newest reading. */\n #profiles = new Map<string, Map<string, HeldWindow>>()\n\n /** Follow a runner's `rate_limit` events for its lifetime. Sessions without a\n * profile have no account to attribute usage to and are skipped. */\n watch(runner: Runner): void {\n const profile = runner.info().profile\n if (!profile) return\n runner.subscribe((event) => {\n if (event.type !== 'rate_limit') return\n const type = event.info.rateLimitType\n if (!type) return\n let windows = this.#profiles.get(profile)\n if (!windows) this.#profiles.set(profile, (windows = new Map()))\n const held = windows.get(type)\n if (held && held.updatedAt > event.ts) return\n // Whole-reading replacement, mirroring the transcript reducer — a newer\n // event is the newer truth even where it carries fewer fields.\n windows.set(type, { info: event.info, updatedAt: event.ts })\n })\n }\n\n /**\n * The profile's windows as they should be served *now*. Undefined until any\n * session on the profile has reported (unknown, never 0%).\n *\n * The 0%-after-reset inference lives here — at serve time — and nowhere\n * else, because it is a function of the wall clock: a window whose own\n * `resetsAt` has passed with no newer reading has provably rolled, so the\n * pre-reset utilization is no longer merely stale but *wrong*. It cannot be\n * a producer's job (the producers only relay what the engine said, and the\n * whole problem is the engine's silence; a fabricated 0% event would be\n * replayed from transcripts forever as if reported) and must not be every\n * renderer's (N clients would each reimplement the clock math). The held\n * reading stays untouched, so a late fresh report still lands by ts, and the\n * served zero is labeled `inferredReset` — it is a floor, not a report: the\n * account may have been used outside this gateway since the reset.\n */\n usage(profile: string, now = Date.now()): ProfileUsage | undefined {\n const windows = this.#profiles.get(profile)\n if (!windows || windows.size === 0) return undefined\n const out: ProfileUsage = {}\n for (const [type, held] of windows) out[type] = serveWindow(held, now)\n return out\n }\n}\n\n/** `resetsAt` is epoch **seconds** (protocol contract); `now` is epoch ms. */\nfunction serveWindow(held: HeldWindow, now: number): ProfileUsageWindow {\n const resetsAt = held.info.resetsAt\n if (resetsAt !== undefined && resetsAt * 1000 <= now) {\n return {\n info: {\n // A rolled window is not rejecting anyone, whatever the last reading's\n // status said; `resetsAt`/`isUsingOverage` describe the previous window\n // and are dropped rather than served as facts about this one.\n status: 'allowed',\n rateLimitType: held.info.rateLimitType,\n utilization: 0,\n },\n updatedAt: held.updatedAt,\n inferredReset: true,\n }\n }\n return { info: held.info, updatedAt: held.updatedAt }\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","/**\n * The create pipeline: everything between a `CreateSessionRequest` arriving and\n * a `Runner` running — policy checks (bypass, permission mode, engine grants,\n * scope, cwd), profile resolution, config assembly (`buildRunnerConfig`), and\n * the one chokepoint that builds runners for create, dormant rebuild and parked\n * rebuild alike (`buildRunner`).\n *\n * Registry/parking/bridge are handed in as late-bound refs because construction\n * is mutually recursive with them (parking's rebuild callback calls\n * `buildRunner`; `createRunner` registers and watches). The refs are filled\n * during assembly, before the server accepts a request.\n */\nimport { supportsPermissionMode, type CreateSessionRequest, type PermissionMode, type ProfileEngine, type ProfileInfo } from '@workerdeck/protocol'\nimport type { EngineAdapter, Runner, RunnerSnapshot, SessionRunnerConfig } from '@workerdeck/core'\nimport { checkScope, sameScope } from '../lib/scope.ts'\nimport { claudeSessionEnv, cwdAllowed, engineOf, isProviderProfile } from '../lib/profile-env.ts'\nimport type { EngineRunnerContext } from '../options.ts'\nimport type { BridgeHub } from './bridge.ts'\nimport type { SessionParkManager } from './parking.ts'\nimport type { ProfileService } from './profiles.ts'\nimport type { SessionRegistry } from './registry.ts'\n\nexport type SessionFactoryDeps = {\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n profiles: ProfileService\n hostBuildRunnerConfig: (req: CreateSessionRequest) => SessionRunnerConfig\n createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>\n allowedCwdRoots?: string[]\n disableBypassPermissions?: boolean\n requireApiKey?: boolean\n /** Late-bound: filled during assembly, read at call time. */\n refs: {\n registry?: SessionRegistry\n parking?: SessionParkManager\n bridge?: BridgeHub\n }\n}\n\nexport type SessionFactory = ReturnType<typeof createSessionFactory>\n\nexport function createSessionFactory(deps: SessionFactoryDeps) {\n const { adapterFor, profiles, refs } = deps\n\n /** Profiles (by name; '' = none) whose oauth notice has been logged. */\n const subscriptionNoticeShown = new Set<string>()\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 (!deps.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 /**\n * Validate the request's scope and merge the principal's into it.\n *\n * A scoped principal's keys are *filled in* when the request omits them and\n * *refused* when the request disagrees: a caller inside a scope may narrow\n * itself with extra tags, never claim to be somewhere else. That makes an\n * embedder's stamping proxy defense in depth rather than the only line — a\n * request that slipped past it still cannot create a session in another\n * scope. An unscoped principal (the operator) may write any tags.\n */\n const applyScope = (\n req: CreateSessionRequest,\n auth: { scope?: Record<string, string> },\n ): { status: number; error: string } | null => {\n const invalid = checkScope(req.scope)\n if (invalid) return { status: 400, error: invalid }\n if (!auth.scope) return null\n const merged: Record<string, string> = { ...req.scope }\n for (const [key, value] of Object.entries(auth.scope)) {\n const claimed = merged[key]\n if (claimed !== undefined && claimed !== value) {\n return { status: 403, error: `scope '${key}' does not match the caller's` }\n }\n merged[key] = value\n }\n // Re-checked after the merge, so the advertised bound is the real ceiling\n // rather than one the principal's own keys can push past.\n const tooBig = checkScope(merged)\n if (tooBig) return { status: 400, error: tooBig }\n req.scope = merged\n return null\n }\n\n /**\n * `cwd`, required or not depending on the engine's capability record — the\n * record rather than the engine name, so a host engine that has no host\n * filesystem gets the same treatment without this file learning its name.\n *\n * When one *is* supplied it is validated even for an engine that will not read\n * it: a path the caller went out of their way to name should not be quietly\n * exempt from the operator's roots. And note what this check is not — for a\n * filesystem-less engine `allowedCwdRoots` guards nothing at all. The\n * boundary there is the capability wiring, not a path prefix.\n */\n const checkCwd = (\n req: CreateSessionRequest,\n profile: ProfileInfo | undefined,\n ): { status: number; error: string } | null => {\n if (req.cwd !== undefined && typeof req.cwd !== 'string') {\n return { status: 400, error: 'cwd must be a string' }\n }\n if (!req.cwd) {\n // Absent = true, so an engine record that predates the field keeps the old\n // always-required behaviour.\n return adapterFor(profile?.engine).capabilities.hostCwd === false\n ? null\n : { status: 400, error: 'cwd is required' }\n }\n return cwdAllowed(req.cwd, deps.allowedCwdRoots)\n ? null\n : { status: 403, error: 'cwd is outside the allowed roots' }\n }\n\n /**\n * Re-stamp the request's scope onto whatever the host's `buildRunnerConfig`\n * returned. The hook is host code and may rewrite the config wholesale; a\n * hook that dropped `scope` would silently *widen* a session's visibility,\n * which is the one direction a bug here must not go. Same posture as the\n * profile's env pin winning over the hook.\n */\n const withScope = (\n config: SessionRunnerConfig,\n scope: Record<string, string> | undefined,\n ): SessionRunnerConfig => (scope === undefined ? config : { ...config, scope })\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 ? profiles.get(req.profile) : undefined\n if (!profile) return withScope(deps.hostBuildRunnerConfig(req), req.scope)\n const config = withScope(\n deps.hostBuildRunnerConfig({\n ...req,\n model: req.model ?? profile.defaults?.model ?? profile.provider?.model,\n permissionMode: req.permissionMode ?? profile.defaults?.permissionMode,\n }),\n req.scope,\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 /** The env a probe should test: exactly what the real assembly path produces. */\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 /** 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 /** Adopt this id rather than minting one — rehydrating a dormant session. */\n id?: string,\n ): Promise<Runner> => {\n const name = config.profile\n const profile = name !== undefined ? profiles.get(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 const runner =\n profile && isProviderProfile(profile)\n ? // Guaranteed present: startup refuses provider profiles without a factory.\n await deps.createEngineRunner!({ config, profile, bridge: refs.bridge!, restore, id })\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 await adapterFor(profile?.engine).createRunner({ config, profile, restore, id })\n // The single chokepoint for create, dormant rebuild and parked rebuild, so\n // it is the one place worth checking that the runner reports the scope it\n // was built with. A host-supplied runner that forgot to echo it would be\n // invisible to every enforcement point below and visible to everyone.\n const reported = runner.info().scope\n if (!sameScope(reported, config.scope)) {\n throw new Error(\n `runner for session ${runner.id} reports scope ${JSON.stringify(reported)}, ` +\n `expected ${JSON.stringify(config.scope)} — echo config.scope from info()`,\n )\n }\n return runner\n }\n\n const createRunner = async (config: SessionRunnerConfig): Promise<Runner> => {\n const runner = refs.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 refs.parking!.remember(runner.id, config)\n refs.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 all = profiles.all()\n if (all.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 ?? (all.length === 1 ? all[0]!.name : undefined)\n if (effective === undefined) {\n const available = all.map((p) => p.name).join(', ')\n return { ok: false, status: 400, error: `profile is required (available: ${available})` }\n }\n const profile = profiles.get(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 // 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 (deps.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 return {\n applyBypassPolicy,\n checkPermissionMode,\n checkEngineGrants,\n stripInertFields,\n applyScope,\n checkCwd,\n buildRunnerConfig,\n sessionEnvFor,\n buildRunner,\n createRunner,\n resolveProfile,\n watchAuthSource,\n }\n}\n","/**\n * `createWorkerServer` — the assembly. Option types live in `options.ts`, the\n * shared-state record routes take in `context.ts`, per-route behaviour in\n * `routes/`, the stateful pieces in `services/`, and the pure rules in `lib/`.\n * This file only wires them together and dispatches requests.\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { WebSocketServer, type WebSocket } from 'ws'\nimport { getEngineAdapter } from '@workerdeck/core'\nimport type { EngineAdapter } from '@workerdeck/core'\nimport { JobQueue } from '@workerdeck/queue'\nimport {\n PROTOCOL_VERSION,\n type CreateSessionRequest,\n type JobEvent,\n type ProfileEngine,\n type QueueServerFrame,\n} from '@workerdeck/protocol'\nimport type { SessionRunnerConfig } from '@workerdeck/core'\nimport type { ServerContext } from './context.ts'\nimport { json } from './lib/http.ts'\nimport { detectDefaultProfiles } from './lib/profile-env.ts'\nimport { parseSessionRoute } from './lib/parse-route.ts'\nimport type { WorkerServer, WorkerServerOptions } from './options.ts'\nimport { handleExecutionResult } from './routes/executions.ts'\nimport { handleHostFiles } from './routes/host-files.ts'\nimport { handleJobs } from './routes/jobs.ts'\nimport { handleProfiles } from './routes/profiles.ts'\nimport { handleSdkSessions } from './routes/sdk-sessions.ts'\nimport { handleSessions } from './routes/sessions.ts'\nimport { attachClient } from './routes/ws.ts'\nimport { AttachmentStore } from './services/attachments.ts'\nimport { createAuthService } from './services/auth.ts'\nimport { AvailabilityTracker } from './services/availability.ts'\nimport { BridgeHub } from './services/bridge.ts'\nimport { createHostFileRoots } from './services/host-files.ts'\nimport { SessionNotifier } from './services/notifications.ts'\nimport { SessionParkManager } from './services/parking.ts'\nimport { ProducedFileStore } from './services/produced-files.ts'\nimport { ProfileService } from './services/profiles.ts'\nimport { ProfileUsageTracker } from './services/profile-usage.ts'\nimport { SessionRegistry } from './services/registry.ts'\nimport { createSessionFactory } from './services/session-factory.ts'\nimport { isDormant, MemorySessionStore } from './services/session-store.ts'\n\n// The public option types moved to options.ts; re-exported here so\n// `import { ... } from './server.ts'` keeps working for older in-repo callers.\nexport type {\n Authenticator,\n EngineRunnerContext,\n QueueServerOptions,\n SdkSessionLister,\n WorkerServer,\n WorkerServerOptions,\n} from './options.ts'\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\n // ---- Profiles, decorated at response time from the trackers below.\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 /** The single plan-usage state per profile, fed from every session's\n * `rate_limit` events and served by `forResponse` (see ProfileUsageTracker). */\n const profileUsage = new ProfileUsageTracker()\n const profiles = new ProfileService({\n // Declared at startup, or a single 'default' auto-created from the\n // operator's own config dir. Misdeclared dirs fail fast — the CLI would\n // otherwise silently start from an empty config (and a different\n // credential chain).\n declared: options.profiles ?? detectDefaultProfiles(),\n store: options.profileStore,\n allowedConfigDirRoots: options.allowedConfigDirRoots,\n disableBypassPermissions: options.disableBypassPermissions,\n hasEngineRunnerFactory: options.createEngineRunner !== undefined,\n adapterFor,\n decorate: {\n defaultModel: (name) => profileDefaultModels.get(name),\n availability: (name) => availability.get(name),\n usage: (name) => profileUsage.usage(name),\n },\n })\n for (const p of options.profiles ?? []) {\n const invalid = profiles.validate(p)\n if (invalid) throw new Error(`createWorkerServer: ${invalid}`)\n }\n\n // ---- The create pipeline. Registry/parking/bridge are late-bound refs,\n // filled just below — construction is mutually recursive (parking's rebuild\n // calls the factory's buildRunner).\n const refs: { registry?: SessionRegistry; parking?: SessionParkManager; bridge?: BridgeHub } = {}\n const factory = createSessionFactory({\n adapterFor,\n profiles,\n hostBuildRunnerConfig:\n options.buildRunnerConfig ?? ((req: CreateSessionRequest): SessionRunnerConfig => req),\n createEngineRunner: options.createEngineRunner,\n allowedCwdRoots: options.allowedCwdRoots,\n disableBypassPermissions: options.disableBypassPermissions,\n requireApiKey: options.requireApiKey,\n refs,\n })\n\n const availability = new AvailabilityTracker({\n checkCredentials: options.checkCredentials,\n requireAvailableProfile: options.requireAvailableProfile,\n adapterFor,\n sessionEnvFor: factory.sessionEnvFor,\n })\n\n // ---- The stateful services.\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 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 // Also from 0 — replay is guarded by the events' own timestamps there.\n profileUsage.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 // A park restores from its snapshot; a dormant record has none, so it is\n // rebuilt like an ordinary create — the config back through\n // `buildRunnerConfig` (so the profile's env pin and the host hook's\n // injections are re-derived rather than read off disk), `resume` pointed at\n // the engine's own session, and the WorkerDeck id carried over by hand,\n // because nothing in the config would otherwise preserve it.\n rebuild: (record) =>\n isDormant(record)\n ? factory.buildRunner(\n factory.buildRunnerConfig({\n ...record.config,\n // A wake is \"come back as you were\", never a new turn. `prompt` is\n // the *first* prompt and it was consumed by the original run, but\n // it persists in the record and `start()` sends it unconditionally\n // — so a session created with an opening prompt used to re-run it\n // on top of the thread the resume had just replayed, costing a\n // turn and doing unrequested work. The provider engine has always\n // had this guard for its own rehydration (`if (config.restore)` in\n // `AiSdkRunner.start`); claude and codex resume by `resume`, which\n // — unlike `restore` — is a public request field where\n // `createSession({ resume, prompt })` legitimately means \"continue\n // this thread, and here is the next thing\". So the suppression\n // belongs here, at the one call site that means rehydration, and\n // not in the runners.\n prompt: undefined,\n // Dropping the prompt would otherwise cost the session its name:\n // `#title()` falls back to deriving one from `prompt` whenever\n // `meta.title` is unset. `record.info.title` has already resolved\n // that precedence, so freezing it into `meta` keeps a derived name\n // through the wake and is a no-op when the session was renamed.\n meta: record.info.title\n ? { ...record.config.meta, title: record.info.title }\n : record.config.meta,\n resume: record.sdkSessionId,\n }),\n undefined,\n record.id,\n )\n : factory.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 refs.registry = registry\n refs.parking = parking\n refs.bridge = bridge\n\n const auth = createAuthService({ options, refs })\n\n const wss = new WebSocketServer({ noServer: true })\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 factory.createRunner(config)\n factory.watchAuthSource(runner)\n return runner\n },\n buildRunnerConfig: factory.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 // 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\n // ---- The record every route module reads.\n const ctx: ServerContext = {\n options,\n basePath,\n maxBodyBytes,\n adapterFor,\n listSdkSessions: options.listSdkSessions,\n profiles,\n availability,\n auth,\n factory,\n registry,\n parking,\n bridge,\n queue,\n attachmentStore,\n producedFiles,\n hostFiles,\n hostFilesWritable: options.hostFiles?.write === true,\n maxHostFileBytes: options.hostFiles?.maxFileBytes ?? 1024 * 1024,\n maxHostDirEntries: options.hostFiles?.maxEntries ?? 5000,\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 authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleJobs(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname === basePath + '/profiles' || pathname.startsWith(basePath + '/profiles/')) {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleProfiles(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname.startsWith(basePath + '/executions/')) {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleExecutionResult(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname === basePath + '/sdk-sessions') {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n // The engine's own on-disk store, which is the operator's and spans every\n // scope: there is no per-session id here to filter by.\n if (!auth.isOperator(authCtx)) {\n json(res, 404, { error: 'not found' })\n return\n }\n await handleSdkSessions(ctx, req, res, authCtx)\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 const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n // Operator privilege by design (see `hostFiles`), so a scoped principal is\n // simply not who these routes are for — and it answers the same 404 an\n // unconfigured gateway does.\n if (!auth.isOperator(authCtx)) {\n json(res, 404, { error: 'not found' })\n return\n }\n await handleHostFiles(ctx, req, res, pathname)\n return\n }\n const route = parseSessionRoute(basePath, req.url ?? '/')\n if (!route || route.ws) {\n json(res, 404, { error: 'not found' })\n return\n }\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleSessions(ctx, req, res, route, authCtx)\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 const queueAuth = await auth.authenticate(req)\n if (!queueAuth.ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Every job's events — prompts, progress previews, result text — are\n // fanned to every socket here. There is no per-socket filter yet, so a\n // scoped principal is refused the firehose outright rather than being\n // handed other scopes' runs. (Per-socket filtering is the later fix; a\n // 404 now is the honest version of not having built it.)\n if (!auth.isOperator(queueAuth)) {\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 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 = parseSessionRoute(basePath, req.url ?? '/')\n if (!route?.ws || !route.id) {\n socket.destroy()\n return\n }\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Scope is checked *before* the wake, off the record's stored info: waking\n // rebuilds the runner and reconnects its MCP servers, and doing that for a\n // caller who is about to get a 404 spends the session's resources on\n // someone with no claim to it.\n const known = registry.get(route.id)?.info() ?? (await parking.get(route.id))?.info\n if (known && !auth.canSee(authCtx, known)) {\n socket.write('HTTP/1.1 404 Not Found\\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 // Re-checked after the wake as well: the pre-check can only consult what\n // the registry and the store already know, and this is the socket that\n // can drive the session.\n if (!runner || !auth.canSee(authCtx, runner.info())) {\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(ctx, ws, runner, req)\n })\n })().catch(() => socket.destroy())\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 profiles.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 availability.preflight(profiles.all())\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 type { ProfileInfo, ProviderConfig, SessionCapability } from '@workerdeck/protocol'\n\n/**\n * A `provider` profile that grants a session nothing but the sandbox: the\n * QuickJS guest, the in-memory VFS, and the model.\n *\n * This adds no mechanism. `capabilities: []` and `mcpServers: []` already mean\n * what they mean, and `createToolContext` already withholds a tool whose backend\n * the host did not inject. What the helper buys is that the locked-down profile\n * is one call rather than three fields an operator has to get right together —\n * the failure mode being a profile that *looks* sandboxed and still grants\n * `deliver_file` because nobody wrote the empty array.\n *\n * What a session under it can do:\n * - run untrusted JavaScript in the WASM guest, under the interpreter's own\n * timeout and memory limits (`eval_script`),\n * - read and write the session's in-memory VFS, which is a map and not a\n * filesystem — no host path is reachable from it.\n *\n * What it cannot do: read or write a host path, spawn a process, reach the\n * network (`web_fetch`/`download`/`web_search` are capabilities, and none is\n * granted), deliver a file, or use an MCP server.\n *\n * Two things this helper does **not** do, because they are not a profile's to\n * decide. It does not authorize anyone — visibility is\n * `CreateSessionRequest.scope` plus the gateway's `authorizeSession`. And it\n * does not make the model's *input* trustworthy: content the loop reads is\n * attacker-influenced by default, and a sandbox bounds what a tool can reach,\n * not what a prompt can talk the model into asking for.\n *\n * @param name Profile name clients name in `CreateSessionRequest.profile`.\n * @param provider Which model to run (credentials stay in the operator's\n * environment and are resolved by the host's `createEngineRunner` — never\n * here, and never on the wire).\n */\nexport function sandboxedProviderProfile(\n name: string,\n provider: ProviderConfig,\n options: {\n description?: string\n /** Prepended to the session's system prompt. */\n instructions?: string\n /** Profile-level run defaults (model, permission mode) — see\n * {@link ProfileInfo.defaults}. */\n defaults?: ProfileInfo['defaults']\n /**\n * Capabilities to grant on top of the floor. Default `[]` — the floor is\n * nothing, and every entry here is a deliberate widening you are writing\n * down: `web_fetch` gives the loop egress (SSRF-guarded, but egress),\n * `download` and `web_search` reach whatever backends you injected, and\n * `deliver_file` lets it hand a file to the client.\n */\n capabilities?: SessionCapability[]\n /**\n * MCP servers, **by name**, whose tools sessions may use. Default `[]`.\n * MCP tools are authoritative — they run with the host's credentials and\n * are never bridged — so naming one here is a larger grant than any\n * capability above it.\n */\n mcpServers?: string[]\n } = {},\n): ProfileInfo {\n return {\n name,\n engine: 'provider',\n provider,\n description: options.description ?? 'Sandboxed: no host filesystem, no shell, no egress',\n defaults: options.defaults,\n session: {\n // Both arrays are load-bearing and neither may become `undefined`: absent\n // means \"no declaration\", which grants whatever the host happened to wire\n // — the opposite of what this profile promises. An explicit `[]` is the\n // floor; the options above raise it in the open.\n capabilities: options.capabilities ?? [],\n mcpServers: options.mcpServers ?? [],\n instructions: options.instructions,\n },\n }\n}\n","import {\n createEngineSession,\n type EngineSessionOptions,\n type HostToolDefinition,\n type LanguageModel,\n type McpConnection,\n type Runner,\n type ToolExecutor,\n type ToolSet,\n} from '@workerdeck/core'\nimport type { EngineRunnerContext } from '../options.ts'\n\nexport type ProviderRunnerOptions = {\n /**\n * The model to run. A function is called per turn with the session's\n * requested model id (undefined = the profile's default), which is what makes\n * the in-session model switcher work; a bare instance pins one model.\n */\n model: LanguageModel | ((modelId: string | undefined) => LanguageModel)\n /**\n * Where sandboxed tools (`eval_script` and any `sandboxed` entry in `tools`)\n * execute. This is a real architectural choice, not a default worth guessing\n * at, so it is required:\n *\n * - a {@link ToolExecutor} — an in-process guest (`new QuickJsExecutor(...)`\n * from `@workerdeck/core`), which is right when the data the loop reasons\n * over lives in this process. It is also the only option that works when no\n * client is attached, which is every unattended job.\n * - `'browser'` — the attached tab, resolved per call from the bridge. Right\n * when the data is *there* (a document the user is editing) and it should\n * not travel to the gateway at all. Note the trade: it hands an executor to\n * the party being sandboxed against, so its results are untrusted input.\n */\n executor: ToolExecutor | 'browser'\n /** Capability backends — the same shape {@link createEngineSession} takes.\n * Wiring one only offers it; the profile and request decide the grant. */\n capabilities?: EngineSessionOptions['capabilities']\n /** Host tools at explicit trust levels (`@workerdeck/core`'s `withHostTools`). */\n tools?: Record<string, HostToolDefinition>\n /** A live MCP connection from `connectMcpTools`. Prefer this over `mcpTools`:\n * it is what lets a profile's unhonoured `mcpServers` refuse the build, and\n * what makes `GET /sessions/:id/mcp` answer for this session. */\n mcp?: McpConnection\n /** A bare MCP tool set, for a host assembling one itself. */\n mcpTools?: ToolSet\n /** System-prompt addition, unless the profile declares its own. */\n instructions?: string\n /** Sandbox limits per execution. */\n executionLimits?: { timeoutMs?: number; memoryLimitBytes?: number }\n /** Scratch-filesystem seed for a new session. Ignored on a rehydration, so a\n * parked turn's files are never overwritten. */\n seedVfs?: Record<string, string>\n /** Release per-session resources: the MCP connection, an issued token, a\n * watcher. Runs on close **and on park** — parking releases the same things. */\n onClose?: () => void | Promise<void>\n}\n\n/**\n * Build a provider-engine runner from the server's `createEngineRunner` context.\n *\n * `createEngineRunner` is a blank sheet: it hands you a context and wants a\n * `Runner`, and four of the five things a correct one must do are invisible in\n * the types — forward `restore`, adopt `id`, seed the VFS only when *not*\n * restoring, and dispose per-session resources. Each is a runtime-only failure\n * (a woken session that starts empty, a refused rebuild, an overwritten\n * filesystem, a connection leaked per session), and each is handled here.\n *\n * ```ts\n * createEngineRunner: (ctx) =>\n * createProviderRunner(ctx, {\n * model: (id) => openai(id ?? 'gpt-5.6-luna'),\n * executor: quickjs,\n * capabilities: { webFetch: {} },\n * mcp,\n * onClose: () => mcp.close(),\n * }),\n * ```\n *\n * The hook itself stays open for anything this does not cover — this is the\n * 80% case, not a replacement for it.\n */\nexport async function createProviderRunner(\n ctx: EngineRunnerContext,\n options: ProviderRunnerOptions,\n): Promise<Runner> {\n const { config, profile, bridge, restore, id } = ctx\n const resolveModel = (modelId: string | undefined): LanguageModel =>\n typeof options.model === 'function' ? options.model(modelId) : options.model\n // The runner's id does not exist at assembly time, so a bridged executor has\n // to be resolved per call from the call's own sessionId.\n const executor: ToolExecutor =\n options.executor === 'browser'\n ? { dispatch: (call) => bridge.executorFor(call.sessionId).dispatch(call) }\n : options.executor\n\n return createEngineSession({\n config: {\n ...config,\n languageModel: resolveModel(config.model),\n // What makes a parked session come back as itself: same id, same event\n // log, same history, mid-task.\n restore,\n onClose: options.onClose,\n },\n // Set only when the gateway is rebuilding a session across a restart;\n // ignoring it strands every client's route and watermark.\n id,\n profile,\n resolveModel: (_profile, c) => resolveModel(c.model),\n selectExecutor: () => executor,\n backend: options.executor === 'browser' ? 'browser' : 'server',\n capabilities: options.capabilities,\n tools: options.tools,\n mcp: options.mcp,\n mcpTools: options.mcpTools,\n instructions: options.instructions,\n executionLimits: options.executionLimits,\n seedVfs: options.seedVfs,\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":["resolvePath","#records","#ttlMs","#byCwd","#resolve","#maxFileBytes","#maxSessionBytes","#bySession","#verdicts","#warned","#opts","#options","#bridge","#sessions","#options","#chains","#emit","#send","#deliver","#options","#configs","#rememberDormant","#persistLive","#track","#forget","#park","#closed","#detachTimers","#owners","#settled","#queue","#storeOps","#resume","#clearTimer","#timers","#resuming","#rebuild","#bySession","#declared","#declaredByName","#stored","#opts","#profiles","#options","#sessions","profiles"],"sources":["../src/lib/http.ts","../src/lib/profile-env.ts","../src/lib/parse-route.ts","../src/routes/executions.ts","../src/services/host-files.ts","../src/services/host-file-search.ts","../src/routes/host-files.ts","../src/routes/jobs.ts","../src/routes/profiles.ts","../src/routes/sdk-sessions.ts","../src/services/session-store.ts","../src/routes/attachments.ts","../src/routes/mcp.ts","../src/routes/produced-files.ts","../src/services/project-info.ts","../src/routes/project-icon.ts","../src/routes/tool-results.ts","../src/routes/sessions.ts","../src/routes/ws.ts","../src/services/attachments.ts","../src/lib/scope.ts","../src/services/auth.ts","../src/services/availability.ts","../src/services/bridge.ts","../src/services/notifications.ts","../src/services/parking.ts","../src/services/produced-files.ts","../src/services/profiles.ts","../src/services/profile-usage.ts","../src/services/registry.ts","../src/services/session-factory.ts","../src/server.ts","../src/lib/sandboxed-profile.ts","../src/lib/provider-runner.ts","../src/services/profile-store.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport function 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\nexport async 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. */\nexport async 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/** 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`. */\nexport function 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 */\nexport function asUtf8(bytes: Buffer): string | null {\n const text = bytes.toString('utf8')\n return Buffer.from(text, 'utf8').equals(bytes) ? text : null\n}\n\nexport function 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 * Pure profile/environment/path rules: which engine a profile runs, where the\n * CLI's config resolution lands, the CLAUDE_CONFIG_DIR pin, and the cwd-roots\n * policy. No state, no I/O beyond reads of the filesystem the rules are about.\n */\nimport { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve as resolvePath, sep } from 'node:path'\nimport type { ProfileConfigSnapshot, ProfileEngine, ProfileInfo } from '@workerdeck/protocol'\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'. */\nexport function isProviderProfile(profile: ProfileInfo): boolean {\n return profile.engine === 'provider'\n}\n\n/** The engine a profile runs, absent meaning 'claude' (pre-provider profiles). */\nexport function 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. */\nexport function 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. */\nexport function 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. */\nexport function 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 */\nexport function 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\nexport function 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\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 */\nexport function 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","/** The `/sessions` route family, parsed. Pattern:\n * {basePath}/sessions[/:id[/ws | /permissions/:requestId | /files[/<path>] |\n * /attachments[/:attachmentId] | /mcp[/:serverName] | /produced[/:fileId] |\n * /events/:seq/result | /project/icon]]\n */\nexport type SessionRoute = {\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 /** `/events/:seq/result` — the untruncated tool result in that event. */\n resultSeq?: number\n /** `/project/icon` — the bytes behind `SessionInfo.project.icon`. */\n projectIcon?: boolean\n}\n\nexport function parseSessionRoute(basePath: string, url: string): SessionRoute | 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] === 'project' && parts[2] === 'icon') {\n return { id: decodeURIComponent(parts[0]!), projectIcon: true }\n }\n if (parts.length === 4 && parts[1] === 'events' && parts[3] === 'result') {\n const seq = Number(parts[2])\n if (!Number.isInteger(seq) || seq < 0) return null\n return { id: decodeURIComponent(parts[0]!), resultSeq: seq }\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 * `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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ToolExecutionResult } from '@workerdeck/core'\nimport type { SubmitExecutionResultRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleExecutionResult(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, basePath, parking, registry } = ctx\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, ctx.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 || auth.scope || ctx.options.authorizeSession) {\n const owner = parking.sessionFor(executionId)\n const info =\n owner === undefined\n ? undefined\n : (registry.get(owner)?.info() ?? (await parking.get(owner))?.info)\n const profile = info?.profile\n // Indistinguishable from an unknown id on purpose: whether an execution\n // exists elsewhere is not this caller's business. Scope is checked as well\n // as the profile because a result is trusted tool input — settling another\n // scope's execution is a way to steer its loop, not merely to read it.\n const refused =\n owner === undefined ||\n (auth.allowedProfiles !== undefined &&\n profile !== undefined &&\n !auth.allowedProfiles.includes(profile)) ||\n (info !== undefined && !authSvc.canSee(auth, info))\n if (refused) {\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","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 `..`. Exported for\n * the project-icon resolver (`project-info.ts`), which makes the same claim\n * against a project root; both callers must hand it realpath output only. */\nexport function 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","/**\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 `services/host-files.ts` first, which\n * canonicalizes and *then* re-checks containment. The naive prefix compare\n * `cwdAllowed` does would be wrong at this door — the agent writes into these\n * trees, and a symlink it created is a path the operator never typed.\n */\nimport { lstatSync, readdirSync, type Dirent } from 'node:fs'\nimport { join } from 'node:path'\nimport { basename } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { WriteHostFileRequest } from '@workerdeck/protocol'\nimport { asUtf8, hashBytes, json, readJsonBody } from '../lib/http.ts'\nimport { searchFiles } from '../services/host-file-search.ts'\nimport {\n entryKind,\n readContained,\n resolveExisting,\n resolveForWrite,\n writeContained,\n} from '../services/host-files.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleHostFiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n): Promise<void> {\n const { basePath, hostFiles, hostFilesWritable, maxHostFileBytes, maxHostDirEntries } = ctx\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: ctx.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, ctx.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","/** `{basePath}/jobs` + `{basePath}/queue` — job submission, listing, cancel,\n * and the gateway-wide queue stats. Jobs run as ordinary registry sessions;\n * these routes are the queue's REST face. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { CreateJobRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleJobs(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, availability, basePath, factory, queue } = ctx\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 // Gateway-wide counters across every scope, with no id to filter on.\n if (!authSvc.isOperator(auth)) {\n json(res, 404, { error: 'not found' })\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 // Same rule as the sessions list, over the job's copy of the tags: the\n // queue must not be a side door into a session the caller cannot attach to.\n const jobs = await queue.list()\n json(res, 200, { jobs: jobs.filter((job) => authSvc.canSeeJob(auth, job)) })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, ctx.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.prompt || typeof body.session.prompt !== 'string') {\n json(res, 400, { error: 'session.prompt is required' })\n return\n }\n const refusedScope = factory.applyScope(body.session, auth)\n if (refusedScope) {\n json(res, refusedScope.status, { error: refusedScope.error })\n return\n }\n const refused = factory.applyBypassPolicy(body.session)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = factory.resolveProfile(body.session.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const unavailable = availability.checkAvailable(resolved.profile)\n if (unavailable) {\n json(res, unavailable.status, { error: unavailable.error })\n return\n }\n const refusedCwd = factory.checkCwd(body.session, resolved.profile)\n if (refusedCwd) {\n json(res, refusedCwd.status, { error: refusedCwd.error })\n return\n }\n const badRequest =\n factory.checkPermissionMode(body.session.permissionMode, resolved.profile) ??\n factory.checkEngineGrants(body.session, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n factory.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 && authSvc.canSeeJob(auth, job)) json(res, 200, { job })\n else json(res, 404, { error: 'job not found' })\n return\n }\n if (req.method === 'DELETE') {\n // Checked before the cancel, not after: a refused caller must not be able\n // to kill a run and then be told it does not exist.\n const existing = await queue.get(id)\n if (!existing || !authSvc.canSeeJob(auth, existing)) {\n json(res, 404, { error: 'job not found' })\n return\n }\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","/** `{basePath}/profiles[/:name]` — the profile list every create form renders\n * from, and (with a `profileStore`) the management routes. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ProfileInfo, UpdateProfileRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport { readProfileConfig } from '../lib/profile-env.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleProfiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n pathname: string,\n auth: AuthContext,\n): Promise<void> {\n const { auth: authSvc, availability, basePath, profiles } = ctx\n const saveManaged = async (incoming: ProfileInfo): Promise<void> => {\n const saved = await profiles.saveManaged(incoming)\n if (!saved.ok) json(res, saved.status, { error: saved.error })\n else json(res, 200, { profile: saved.profile })\n }\n const rest = pathname.slice((basePath + '/profiles').length).replace(/^\\//, '')\n if (rest === '') {\n if (req.method === 'GET') {\n const visible = auth.allowedProfiles\n ? profiles.all().filter((p) => auth.allowedProfiles!.includes(p.name))\n : profiles.all()\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 availability.refresh(visible)\n json(res, 200, {\n profiles: visible.map((p) => profiles.forResponse(p)),\n canManage: profiles.manageGuard(auth) === null,\n })\n return\n }\n if (req.method === 'POST') {\n const refused = profiles.manageGuard(auth)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n const body = (await readJsonBody(req, ctx.maxBodyBytes)) as ProfileInfo\n if (!body.name || typeof body.name !== 'string') {\n json(res, 400, { error: 'name is required' })\n return\n }\n if (profiles.get(body.name)) {\n json(res, 409, { error: `profile already exists: ${body.name}` })\n return\n }\n await saveManaged(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 : profiles.get(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 // The config snapshot is the operator's own config directory read back —\n // skill, agent and command names, hook names, the *keys* of the env in\n // `settings.json`. A profile a scoped end user is allowed to *run* is\n // not thereby a directory they may inventory, so the snapshot is\n // withheld from a non-operator while the profile record itself (name,\n // engine, model catalog) still answers, because a create form needs it.\n json(res, 200, {\n // The same decoration the list route serves (`forResponse`): the\n // capability record, the model catalog, the availability verdict and\n // the plan usage. It had been the bare record, which made the detail\n // route answer *less* about a profile than the list it was opened\n // from — a client could only get the usage state by listing.\n profile: profiles.forResponse(profile),\n config: authSvc.isOperator(auth) ? readProfileConfig(profile) : undefined,\n })\n return\n }\n if (req.method === 'PATCH' || req.method === 'DELETE') {\n const refused = profiles.manageGuard(auth) ?? profiles.declaredGuard(profile)\n if (refused) {\n json(res, refused.status, { error: refused.error })\n return\n }\n if (req.method === 'DELETE') {\n await ctx.options.profileStore!.delete(profile.name)\n await profiles.refreshStored()\n res.writeHead(204).end()\n return\n }\n const patch = (await readJsonBody(req, ctx.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 saveManaged({ ...profile, ...patch, name: profile.name })\n return\n }\n json(res, 405, { error: 'method not allowed' })\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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { ProfileInfo, SdkSessionSummary } from '@workerdeck/protocol'\nimport { json } from '../lib/http.ts'\nimport { cwdAllowed, engineOf } from '../lib/profile-env.ts'\nimport type { SdkSessionLister } from '../options.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleSdkSessions(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n auth: AuthContext,\n): Promise<void> {\n const { adapterFor, factory, profiles } = ctx\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 = ctx.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 = factory.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 = profiles.all()\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' && ctx.listSdkSessions\n ? ctx.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 ? factory.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. */\nfunction 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","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 captured whole: its wire-visible info, the config to rebuild the\n * runner, the engine's snapshot, and what it is waiting for.\n *\n * Two things are stored in this one shape, and the discriminator is `kind`:\n *\n * - **`parked`** — the live runner is torn down and the session is waiting on\n * deferred executions. The record *is* the session, so waking consumes it.\n * - **`live`** — the runner is up and this is a copy taken after a turn, so the\n * session survives a restart (`persistLive`). It is a way back that the\n * session still needs the *next* time the process dies, so waking **refreshes\n * it in place** and only `session_closed` removes it.\n *\n * That difference is the whole reason these are two kinds rather than one, and\n * it is a correctness difference, not bookkeeping: consuming a live record on\n * wake opens a window from the attach to the next turn in which the session\n * exists nowhere durable. A user who opens a session, reads it and types nothing\n * would lose it to a redeploy — silently, which is the failure class worth\n * spending a discriminator on.\n *\n * They share the shape so a store, and an older server, need no new code: every\n * other branch (rebuild from `config` + `snapshot`, serve `snapshot.vfs`, arm\n * `executions`, subscribe past `snapshot.seq`) is already right for both.\n */\nexport type ParkedSessionRecord = {\n /** Absent on records written before dormant sessions existed — those are all parked. */\n kind?: 'parked' | 'live'\n id: string\n /** Session info as of the write: `parked` for a park, `idle` for a live copy —\n * never `running`, which would come back as a spinner over no process. */\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 /** Empty for a live record: an idle session is waiting on nothing, so there is\n * nothing for `hydrate` to arm a watchdog for. */\n executions: ParkedExecution[]\n /** When it was written. Named for the park that came first. */\n parkedAt: number\n}\n\n/** A live record is refreshed in place on wake; a park is consumed by it. */\nexport const isLiveRecord = (record: StoredSessionRecord): boolean => record.kind === 'live'\n\n/**\n * A live session, remembered so it can be brought back after a gateway restart.\n *\n * The counterpart to a park, and deliberately not the same mechanism. A park\n * preserves *mid-task* state, which means a `RunnerSnapshot` — and only the\n * provider engine can produce one, because the claude and codex engines run\n * behind a binary that owns its own process state. What those two have instead\n * is a session store of their own: the transcript is already on disk under an\n * engine session id, and `CreateSessionRequest.resume` is how you get it back.\n *\n * So this record holds no transcript at all. It holds the id (every client keys\n * its watermarks and routes on it), the engine session id to resume from, and\n * the config to rebuild with — and rehydration is an ordinary create with\n * `resume` set, done **lazily on first attach**, because eagerly respawning\n * every session at boot is a fork bomb wearing a feature's clothes.\n *\n * Written only for engines whose capability record says `resume`, and only once\n * an `sdkSessionId` exists: a record that would come back with an empty\n * transcript is worse than no record.\n */\nexport type DormantSessionRecord = {\n kind: 'dormant'\n id: string\n /** Session info as of the last save, with `status: 'idle'` — whatever it was\n * doing, it is not doing it now. */\n info: SessionInfo\n profile?: string\n /**\n * The config the session was built from, minus the ephemeral keys. Fed back\n * through the server's `buildRunnerConfig` on wake rather than used as-is, so\n * the profile's env pin and the host hook's injections are **re-derived**\n * instead of persisted (see {@link EPHEMERAL_CONFIG_KEYS}).\n */\n config: SessionRunnerConfig\n /** Where the transcript actually lives. Without one there is nothing to resume. */\n sdkSessionId: string\n savedAt: number\n}\n\n/** What a {@link SessionStore} holds: a session waiting on deferred work, or one\n * waiting to be asked for again. */\nexport type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord\n\nexport const isDormant = (record: StoredSessionRecord): record is DormantSessionRecord =>\n record.kind === 'dormant'\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: StoredSessionRecord): Promise<void>\n get(id: string): Promise<StoredSessionRecord | null>\n list(): Promise<StoredSessionRecord[]>\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, StoredSessionRecord>()\n\n save(record: StoredSessionRecord): Promise<void> {\n this.#records.set(record.id, record)\n return Promise.resolve()\n }\n\n get(id: string): Promise<StoredSessionRecord | null> {\n return Promise.resolve(this.#records.get(id) ?? null)\n }\n\n list(): Promise<StoredSessionRecord[]> {\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, but the reason differs by\n * record kind and both halves matter. A provider session's credentials are\n * resolved by `createEngineRunner` from the operator's environment on every\n * build, wake included — so a parked record never needed them. A **dormant**\n * record is a claude or codex session, which does consume `env` (the profile's\n * `CLAUDE_CONFIG_DIR` pin lives there), and that is precisely why waking one\n * feeds its config back through the server's `buildRunnerConfig` instead of\n * handing it to the engine as-is: the pin and the host hook's injections are\n * re-derived from the profile, never read back off disk. Persisting them would\n * be a credential map in a file *and* a stale one.\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<T extends StoredSessionRecord>(record: T): T {\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<StoredSessionRecord | 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 StoredSessionRecord => 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): StoredSessionRecord | 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<StoredSessionRecord> | null\n if (!record || typeof record !== 'object') return null\n if (typeof record.id !== 'string') return null\n if (!record.info || !record.config) return null\n // The discriminator is optional on the wire: every record written before\n // dormant sessions existed is a park, and those files must keep working\n // across the upgrade that introduced this.\n //\n // A `live` record deliberately falls through to the parked arm and passes it —\n // same fields, same requirements. That is what makes a *downgrade* graceful:\n // an older server reads the file, lists the session idle, and restores it once\n // (then consumes it, having no concept of refreshing in place). Losing\n // write-through on a downgrade is the right cost; refusing to parse the file\n // would lose the session.\n if (record.kind === 'dormant') {\n const dormant = record as Partial<DormantSessionRecord>\n if (typeof dormant.sdkSessionId !== 'string' || typeof dormant.savedAt !== 'number') return null\n return dormant as DormantSessionRecord\n }\n const parked = record as Partial<ParkedSessionRecord>\n if (typeof parked.parkedAt !== 'number' || !parked.snapshot) return null\n if (!Array.isArray(parked.executions)) return null\n return parked as ParkedSessionRecord\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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { attachmentKind } from '@workerdeck/core'\nimport { ENGINE_CAPABILITIES, type SessionInfo } from '@workerdeck/protocol'\nimport { json, readRawBody } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleAttachments(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n session: SessionInfo,\n attachmentId?: string,\n): Promise<void> {\n const { attachmentStore } = ctx\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 * `{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 */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Runner } from '@workerdeck/core'\nimport type { McpServerActionRequest } from '@workerdeck/protocol'\nimport { json, readJsonBody } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleMcp(\n ctx: ServerContext,\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, ctx.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 * `{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 `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 */\nimport { createReadStream, statSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { contentTypeFor, json } from '../lib/http.ts'\nimport type { ServerContext } from '../context.ts'\n\nexport async function handleProducedFiles(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n sessionId: string,\n fileId?: string,\n): Promise<void> {\n const { producedFiles } = ctx\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","import { createHash } from 'node:crypto'\nimport { lstatSync, realpathSync } from 'node:fs'\nimport { basename, dirname, isAbsolute, join, resolve } from 'node:path'\nimport type { ProjectIcon, ProjectInfo, SessionInfo } from '@workerdeck/protocol'\nimport { contained, readContained } from './host-files.ts'\n\n/**\n * Project identity discovery: the `.workerdeck.json` ancestor walk behind\n * `SessionInfo.project`, and the read side of `GET /sessions/:id/project/icon`.\n *\n * The gateway resolves this — not each client — because the file lives on the\n * gateway's filesystem, which a phone or a remote browser cannot see. It is\n * stamped onto `SessionInfo` at **serve time** (`withProject`), never persisted:\n * a copy captured into a parking record would replay a stale name forever,\n * where a serve-time read picks up an edited file on every session at once\n * within the TTL. That is the profile tracker's 0%-after-reset placement\n * argument, applied to a filesystem fact instead of a clock.\n *\n * Every failure degrades to \"no project\" and never to an error: a session must\n * not fail, or even warn, because of a display declaration. A malformed,\n * oversized, or symlinked `.workerdeck.json` is *skipped and the walk\n * continues* — a broken file in `packages/ui` must not shadow the repo root's\n * valid one — and inside a valid file each field degrades on its own (junk\n * name → the root's basename, junk icon → no icon).\n *\n * The icon is the security surface, because its path comes out of a config\n * file the *agent* can write (the session cwd is the agent's working tree).\n * The rules are `host-files.ts`'s, not `cwdAllowed`'s (docs/GOTCHAS.md §Host\n * filesystem): the declared path is resolved against the project root and then\n * realpath'd **whole**, containment is decided on the canonical result only\n * (so `\"icon\": \"../../../../etc/key.png\"` and a planted `icon.png → ~/.ssh/…`\n * symlink both fail the same check), the open goes through `readContained`\n * (O_NOFOLLOW, fstat-before-io), and the media type comes from the *declared*\n * extension — png and svg only, by decision. A refused icon is\n * indistinguishable on the wire and on the route from a never-declared one:\n * `icon` absent, the route 404s. The one disclosure this feature accepts is\n * inherent to it: the walk reads ancestors of a vetted cwd, so a project file\n * an operator placed *above* their roots (`~/.workerdeck.json`) applies to\n * everything under it — nearest-wins from the cwd, exactly git's own\n * discovery, and the file is a display declaration by definition.\n *\n * Cache: per exact cwd string, TTL'd, with negative results cached at the same\n * price — `GET /sessions` polls at 1.2s while anything is working and the hit\n * path must be a Map lookup, never a walk. Keyed by cwd rather than by root\n * because the root is not known until the walk has run. Bounded by sweeping\n * expired entries once the map outgrows any plausible live session count.\n */\n\nconst PROJECT_FILE = '.workerdeck.json'\n/** A config file, not a document — anything bigger is skipped as malformed. */\nconst MAX_PROJECT_FILE_BYTES = 64 * 1024\n/** An icon is rendered at list-row size; half a megabyte is already generous,\n * and this route buffers (no stream), so the cap bounds gateway heap too. */\nexport const MAX_PROJECT_ICON_BYTES = 512 * 1024\n/** Display name clip — a list row's width, not a document's. */\nconst MAX_NAME_CHARS = 80\n/** lucide's naming: lowercase kebab-case. Shape-only — the gateway has no icon\n * catalog and must not grow one; an unknown-but-well-formed name ships and the\n * client falls back (a stale row, never withheld state). */\nconst GLYPH_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/\n\nconst DEFAULT_TTL_MS = 30_000\n/** Sweep threshold: above this many cached cwds, expired entries are evicted\n * on the next resolve so dead sessions' keys do not accumulate forever. */\nconst SWEEP_ABOVE = 256\n\ntype IconMediaType = 'image/png' | 'image/svg+xml'\n\n/** What the icon route serves from — held server-side only; the wire carries\n * the `ProjectIcon` address, never the path. */\nexport type ResolvedProjectIcon = { path: string; mediaType: IconMediaType; hash: string }\n\ntype Resolution = {\n project?: ProjectInfo\n icon?: ResolvedProjectIcon\n expiresAt: number\n}\n\nexport class ProjectInfoService {\n readonly #ttlMs: number\n readonly #byCwd = new Map<string, Resolution>()\n\n constructor(options: { ttlMs?: number } = {}) {\n this.#ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n }\n\n /**\n * The serve-time decoration: `info` with `project` stamped on, or the same\n * object untouched when there is nothing to add — the common case on a list\n * poll, so no allocation for it (replaySlice's same-object rule).\n */\n withProject(info: SessionInfo): SessionInfo {\n if (!info.cwd) return info\n const project = this.#resolve(info.cwd).project\n return project ? { ...info, project } : info\n }\n\n /** The icon route's read side: the canonical, contained icon file for this\n * session's cwd — resolved from the gateway's own cache, never from anything\n * the client named. Undefined = no project, no icon, or an icon refused. */\n iconFor(cwd: string): ResolvedProjectIcon | undefined {\n if (!cwd) return undefined\n return this.#resolve(cwd).icon\n }\n\n #resolve(cwd: string): Resolution {\n const now = Date.now()\n const held = this.#byCwd.get(cwd)\n if (held && held.expiresAt > now) return held\n if (this.#byCwd.size > SWEEP_ABOVE) {\n for (const [key, entry] of this.#byCwd) {\n if (entry.expiresAt <= now) this.#byCwd.delete(key)\n }\n }\n const fresh = { ...discover(cwd), expiresAt: now + this.#ttlMs }\n this.#byCwd.set(cwd, fresh)\n return fresh\n }\n}\n\n/** The ancestor walk: realpath the cwd (a lexical walk over `/tmp/x` would\n * miss the file at `/private/tmp/x`, and canonicalizing here is what makes\n * `root` — the grouping key — spell identically for every cwd inside one\n * project), then nearest `.workerdeck.json` wins, to the filesystem root. */\nfunction discover(cwd: string): Omit<Resolution, 'expiresAt'> {\n // A relative cwd would realpath against the gateway process's own cwd and\n // walk *its* ancestry — refused outright, like host-files' invalidRequest.\n if (!isAbsolute(cwd) || cwd.includes('\\0')) return {}\n let dir: string\n try {\n dir = realpathSync(cwd)\n } catch {\n return {} // cwd gone or unreadable — a session must still serve.\n }\n for (;;) {\n const found = tryLoad(join(dir, PROJECT_FILE), dir)\n if (found) return found\n const parent = dirname(dir)\n if (parent === dir) return {}\n dir = parent\n }\n}\n\n/** One directory's verdict: a project record, or undefined to keep walking —\n * which is the same answer for \"absent\" and for every malformed shape. */\nfunction tryLoad(file: string, root: string): Omit<Resolution, 'expiresAt'> | undefined {\n let stat\n try {\n stat = lstatSync(file)\n } catch {\n return undefined\n }\n // A symlinked project file is skipped, not followed — the agent writes this\n // tree, and `readContained`'s O_NOFOLLOW would refuse it below anyway; a\n // directory of that name is nothing either.\n if (!stat.isFile() || stat.size > MAX_PROJECT_FILE_BYTES) return undefined\n const read = readContained(file)\n if (!read.ok) return undefined\n let parsed: unknown\n try {\n parsed = JSON.parse(read.data.toString('utf8'))\n } catch {\n return undefined // Malformed JSON: cannot even tell it is ours — walk on.\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined\n const raw = parsed as { name?: unknown; icon?: unknown }\n // A parsed object IS the project marker, whatever its fields hold: `{}` is a\n // valid declaration of \"this directory is the root\", named by its basename.\n const name =\n typeof raw.name === 'string' && raw.name.trim()\n ? raw.name.trim().slice(0, MAX_NAME_CHARS)\n : basename(root)\n const icon = classifyIcon(raw.icon, root)\n return {\n project: { name, root, ...(icon ? { icon: icon.wire } : {}) },\n ...(icon?.resolved ? { icon: icon.resolved } : {}),\n }\n}\n\n/**\n * The one-string icon rule (documented on protocol's `ProjectInfo`): ends in\n * `.png`/`.svg` → repo-relative image path, else lucide-shaped glyph name,\n * else ignored. Total and collision-free — a glyph name contains no dot.\n */\nfunction classifyIcon(\n value: unknown,\n root: string,\n): { wire: ProjectIcon; resolved?: ResolvedProjectIcon } | undefined {\n if (typeof value !== 'string') return undefined\n const declared = value.trim()\n if (!declared || declared.includes('\\0') || declared.length > 512) return undefined\n const lower = declared.toLowerCase()\n const mediaType: IconMediaType | undefined = lower.endsWith('.png')\n ? 'image/png'\n : lower.endsWith('.svg')\n ? 'image/svg+xml'\n : undefined\n if (!mediaType) {\n if (!GLYPH_RE.test(declared) || declared.length > 64) return undefined\n return { wire: { type: 'glyph', name: declared } }\n }\n // An image path. Relative only — the file is checked into a repo that clones\n // onto other machines, so an absolute path is wrong by construction (and\n // refusing it early keeps the containment check from ever seeing one).\n // Backslashes are refused rather than translated: repo paths are posix.\n if (isAbsolute(declared) || declared.includes('\\\\')) return undefined\n let canonical: string\n try {\n canonical = realpathSync(resolve(root, declared))\n } catch {\n return undefined // Absent, dangling, looped — uniformly no icon.\n }\n // Containment on the canonical form only: this refuses `..` escapes and\n // planted symlinks by the same one check. `root` is realpath output by\n // construction (the walk starts from a realpath'd cwd).\n if (!contained(root, canonical)) return undefined\n let stat\n try {\n stat = lstatSync(canonical)\n } catch {\n return undefined\n }\n if (!stat.isFile() || stat.size === 0 || stat.size > MAX_PROJECT_ICON_BYTES) return undefined\n // Hashed at discovery because the wire carries it: the client's cross-session\n // cache key and the route's ETag. One bounded read per project per TTL.\n const read = readContained(canonical)\n if (!read.ok || read.data.length > MAX_PROJECT_ICON_BYTES) return undefined\n const hash = createHash('sha256').update(read.data).digest('hex')\n return {\n wire: { type: 'image', mediaType, hash },\n resolved: { path: canonical, mediaType, hash },\n }\n}\n","/**\n * `{basePath}/sessions/:id/project/icon` — the bytes behind a\n * `ProjectIcon.image` reference.\n *\n * Session-scoped on purpose, like `/events/:seq/result`: the caller's\n * `/sessions/:id/*` `canSee` gate runs before this does, which is the whole\n * authorization story — a scoped principal's miss is the uniform 404 before\n * any filesystem is consulted, and no second policy exists to drift. A\n * project-keyed route would have needed the project root in its URL, and a\n * route addressed by host paths is an existence oracle for the gateway's\n * filesystem.\n *\n * The request carries **no path and no client input at all** beyond the\n * session id: the file served is whatever the gateway's own discovery resolved\n * for this session's cwd (realpath-contained in the project root, png/svg,\n * size-capped — see `ProjectInfoService`). Re-read at serve time through\n * `readContained` and re-capped, because resolve-time guarantees hold at\n * resolve time only.\n *\n * \"No icon\" is one answer whatever the reason — no project, a glyph-only\n * project, or a declared icon the resolver refused (escaping, oversized,\n * malformed): distinguishing them would tell a caller *why* a path outside the\n * root was refused, which is the disclosure the uniform answer withholds.\n *\n * `ETag` is the icon's own content hash — the same value the wire's\n * `ProjectIcon.image.hash` carries — so a client that cached bytes by hash\n * revalidates for free and two sessions in one project share one cache entry.\n * `nosniff` + attachment disposition because the bytes come out of a repo\n * (cloned from anywhere, writable by the agent): an SVG must never render as a\n * document on the gateway's origin, and `<img src>` is unaffected by\n * disposition, which is the point (produced-files' rule).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { json } from '../lib/http.ts'\nimport { readContained } from '../services/host-files.ts'\nimport { MAX_PROJECT_ICON_BYTES, type ProjectInfoService } from '../services/project-info.ts'\n\nexport function handleProjectIcon(\n projects: ProjectInfoService,\n req: IncomingMessage,\n res: ServerResponse,\n cwd: string,\n): void {\n if (req.method !== 'GET') {\n json(res, 405, { error: 'method not allowed' })\n return\n }\n const icon = projects.iconFor(cwd)\n if (!icon) {\n json(res, 404, { error: 'no project icon' })\n return\n }\n const etag = `\"${icon.hash}\"`\n if (req.headers['if-none-match'] === etag) {\n res.writeHead(304, { etag })\n res.end()\n return\n }\n const read = readContained(icon.path)\n // Refused between resolve and open (swapped for a symlink, replaced by a\n // fifo, grown past the cap): the same single answer as \"no icon\".\n if (!read.ok || read.data.length > MAX_PROJECT_ICON_BYTES) {\n json(res, 404, { error: 'no project icon' })\n return\n }\n res.writeHead(200, {\n 'content-type': icon.mediaType,\n 'content-length': read.data.length,\n etag,\n // Short and private: the wire's hash tells a client exactly when to\n // refetch, so the browser cache only has to bridge one poll interval —\n // and a project icon swap should not take an hour to reach a dashboard.\n 'cache-control': 'private, max-age=300',\n 'content-disposition': 'attachment; filename=\"project-icon\"',\n 'x-content-type-options': 'nosniff',\n })\n res.end(read.data)\n}\n","/**\n * `{basePath}/sessions/:id/events/:seq/result?toolUseId=` — the whole of a tool\n * result whose replay delivered only its head.\n *\n * **No new store.** The bytes are already in the runner's event log, which is\n * the same log the replay walked past; a second copy of a 641 KB result kept\n * \"for fetching\" would be exactly the cost this feature exists to remove.\n * `Runner.eventAt` is the read side of it.\n *\n * `toolUseId` is **required and verified against the block**, and that is not\n * belt-and-braces. A woken dormant session has a fresh log with fresh seqs, so\n * a `sourceSeq` a client cached before a gateway restart can name a completely\n * different event — and without the check the reader is handed another tool's\n * output under the row they pressed, silently. That is the exact bug class this\n * feature exists to remove, so it is refused rather than guessed at.\n *\n * The scope gate is the caller's (`/sessions/:id/*` is checked before this runs),\n * which is the whole authorization story here: visibility is full control.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { imagePartRef, type SessionEvent, type ToolResultBlock } from '@workerdeck/protocol'\nimport { json } from '../lib/http.ts'\n\n/** How this route reaches the log: a live runner's `eventAt`, or a **parked**\n * snapshot's own events. A park keeps the log untruncated precisely so a\n * session read days later can still be read whole, so refusing one here would\n * break the case the design was careful to preserve. A *dormant* record holds no\n * log at all, so it resolves to `undefined` and 404s — the same answer `/files`\n * already gives it. */\nexport type EventLookup = ((seq: number) => SessionEvent | undefined) | undefined\n\nexport function handleToolResult(\n req: IncomingMessage,\n res: ServerResponse,\n lookup: EventLookup,\n seq: number,\n): 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 toolUseId = url.searchParams.get('toolUseId')\n if (!toolUseId) {\n json(res, 400, { error: 'toolUseId is required' })\n return\n }\n // An engine that never truncates need not offer the log either — see\n // `Runner.eventAt`. 501 rather than 404: the session exists and the row the\n // reader pressed is real; it is this gateway's engine that cannot answer.\n if (!lookup) {\n json(res, 501, { error: 'engine does not serve stored events' })\n return\n }\n const event = lookup(seq)\n if (!event || event.type !== 'user_message' || !Array.isArray(event.message.content)) {\n json(res, 404, { error: 'no such event' })\n return\n }\n const block = event.message.content.find(\n (candidate): candidate is ToolResultBlock =>\n candidate.type === 'tool_result' &&\n (candidate as ToolResultBlock).tool_use_id === toolUseId,\n )\n if (!block) {\n json(res, 404, { error: 'no such tool result in that event' })\n return\n }\n\n // `part=N` — one image part's bytes, which is the other half of the image-ref\n // rule: the replay delivered an address, this answers it. Raw bytes rather\n // than JSON+base64, because `readProducedFile` → Blob → object URL is the\n // shipped precedent, the platform decodes it, and JSON would double the\n // memory and put a decode on the client's main thread.\n const partParam = url.searchParams.get('part')\n if (partParam !== null) {\n const index = Number(partParam)\n const parts = block.content\n const part = Number.isInteger(index) && Array.isArray(parts) ? parts[index] : undefined\n // Verified against the STORED block, never trusted from the query: the\n // address was stamped from this array and must still name a base64 image.\n const ref = part ? imagePartRef(part, index) : undefined\n if (!ref) {\n json(res, 404, { error: 'no such image part in that tool result' })\n return\n }\n const source = (part as { source?: { data?: string } }).source\n const bytes = Buffer.from(source?.data ?? '', 'base64')\n res.writeHead(200, { 'content-type': ref.media_type, 'content-length': String(bytes.length) })\n res.end(bytes)\n return\n }\n\n // The JSON mode. `imageRefs=1` projects the whole block's image parts through\n // the same rule the replay used — without it a \"show everything\" press against\n // an image-bearing block ships every screenshot's base64 inside the JSON, and\n // `hydrateToolResult` keeps only the text. Default stays whole, so this is\n // byte-identical to what shipped before the flag existed.\n const content =\n url.searchParams.get('imageRefs') === '1' && Array.isArray(block.content)\n ? block.content.map((part, index) => imagePartRef(part, index) ?? part)\n : (block.content ?? '')\n json(res, 200, { seq, toolUseId, content, isError: block.is_error === true })\n}\n","/** `{basePath}/sessions[/:id[...]]` — the list, the create, and every\n * per-session subroute. One `canSee` gate covers all of them, byte-identical\n * to the unknown-id answer: whether a session exists elsewhere is not this\n * caller's business. */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type {\n CreateSessionRequest,\n ResolvePermissionRequest,\n UpdateSessionRequest,\n} from '@workerdeck/protocol'\nimport { contentTypeFor, json, readJsonBody } from '../lib/http.ts'\nimport type { SessionRoute } from '../lib/parse-route.ts'\nimport type { AuthContext } from '../services/auth.ts'\nimport { isDormant } from '../services/session-store.ts'\nimport type { ServerContext } from '../context.ts'\nimport { handleAttachments } from './attachments.ts'\nimport { handleMcp } from './mcp.ts'\nimport { handleProducedFiles } from './produced-files.ts'\nimport { handleProjectIcon } from './project-icon.ts'\nimport { handleToolResult } from './tool-results.ts'\n\nexport async function handleSessions(\n ctx: ServerContext,\n req: IncomingMessage,\n res: ServerResponse,\n route: SessionRoute,\n auth: AuthContext,\n): Promise<void> {\n const { attachmentStore, auth: authSvc, availability, bridge, factory, parking, producedFiles, projects, registry } = ctx\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 const sessions = [...registry.list(), ...(await parking.listInfo())]\n json(res, 200, {\n // Project identity is stamped at serve time, after the scope filter:\n // decorating a row nobody may see would spend walks on hidden sessions.\n sessions: sessions\n .filter((session) => authSvc.canSee(auth, session))\n .map((session) => projects.withProject(session)),\n })\n return\n }\n if (req.method === 'POST') {\n const body = (await readJsonBody(req, ctx.maxBodyBytes)) as CreateSessionRequest\n const refusedScope = factory.applyScope(body, auth)\n if (refusedScope) {\n json(res, refusedScope.status, { error: refusedScope.error })\n return\n }\n const refused = factory.applyBypassPolicy(body)\n if (refused) {\n json(res, 403, { error: refused })\n return\n }\n const resolved = factory.resolveProfile(body.profile, auth.allowedProfiles)\n if (!resolved.ok) {\n json(res, resolved.status, { error: resolved.error })\n return\n }\n const unavailable = availability.checkAvailable(resolved.profile)\n if (unavailable) {\n json(res, unavailable.status, { error: unavailable.error })\n return\n }\n const refusedCwd = factory.checkCwd(body, resolved.profile)\n if (refusedCwd) {\n json(res, refusedCwd.status, { error: refusedCwd.error })\n return\n }\n const badRequest =\n factory.checkPermissionMode(body.permissionMode, resolved.profile) ??\n factory.checkEngineGrants(body, resolved.profile)\n if (badRequest) {\n json(res, 400, { error: badRequest })\n return\n }\n factory.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 factory.createRunner(factory.buildRunnerConfig(body))\n factory.watchAuthSource(runner)\n json(res, 201, { session: projects.withProject(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 // One gate for every `/sessions/:id/*` subroute below — GET, PATCH, DELETE,\n // files, produced, attachments, mcp, and the permission decision that would\n // otherwise let another scope answer this session's approvals. Byte-identical\n // to the unknown-id answer above: whether a session exists elsewhere is not\n // this caller's business.\n if (!authSvc.canSee(auth, runner?.info() ?? parked!.info)) {\n json(res, 404, { error: 'session not found' })\n return\n }\n if (route.attachments) {\n await handleAttachments(\n ctx,\n req,\n res,\n route.id,\n runner?.info() ?? parked!.info,\n route.attachmentId,\n )\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(ctx, 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 // A dormant session has no VFS to serve: its files, if it made any, live\n // wherever the engine wrote them, not in a snapshot we kept.\n const snapshotFiles = parked && !isDormant(parked) ? parked.snapshot.vfs : undefined\n const vfs =\n runner?.vfs ??\n (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(ctx, req, res, route.id, route.producedFileId)\n return\n }\n if (route.projectIcon) {\n // The cwd is the gateway's own record of this session, never client input,\n // and the `canSee` gate above is the route's whole authorization story.\n handleProjectIcon(projects, req, res, (runner?.info() ?? parked!.info).cwd)\n return\n }\n if (route.resultSeq !== undefined) {\n const snapshot = parked && !isDormant(parked) ? parked.snapshot.events : undefined\n handleToolResult(\n req,\n res,\n runner?.eventAt?.bind(runner) ??\n (snapshot && ((seq: number) => snapshot.find((event) => event.seq === seq))),\n route.resultSeq,\n )\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, ctx.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: projects.withProject(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, ctx.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 // A rename emits no event, so nothing else would re-save the dormant\n // record — and the wake rebuilds from it. Without this the new name\n // survives only until the next restart.\n parking.touch(runner)\n }\n json(res, 200, { session: projects.withProject(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: projects.withProject(\n runner?.info() ?? { ...parked!.info, status: 'closed' as const },\n ),\n })\n return\n }\n json(res, 405, { error: 'method not allowed' })\n}\n","/** The session WebSocket: attach (replay + live), and the client command\n * surface. One live attach per client socket; the bridge registration is what\n * lets this client execute bridged tool calls in its own sandbox. */\nimport type { IncomingMessage } from 'node:http'\nimport type { WebSocket } from 'ws'\nimport type { Runner } from '@workerdeck/core'\nimport { PROTOCOL_VERSION, type ClientFrame, type ServerFrame } from '@workerdeck/protocol'\nimport type { ServerContext } from '../context.ts'\n\nexport function attachClient(\n ctx: ServerContext,\n ws: WebSocket,\n runner: Runner,\n req: IncomingMessage,\n): void {\n const { bridge, parking } = ctx\n const url = new URL(req.url ?? '/', 'http://internal')\n const afterSeq = Number(url.searchParams.get('afterSeq') ?? '0') || 0\n // Opt-in, from the query string, because only the attaching *renderer* knows\n // whether it can fetch the rest (see `Runner.subscribe`). A gateway that\n // truncated for everyone would hand an older client a head with no marker.\n const truncateResults = url.searchParams.get('truncateResults') === '1'\n // Its own flag rather than a widening of `truncateResults`, and the reason is\n // the family's no-bump argument itself: it rests on \"a client that never asked\n // cannot receive one\" holding *by construction*. A flag whose meaning grew\n // after it shipped is exactly the fact a later reader cannot recover, and a\n // caller that asked for text heads never asked to have its pixels swapped for\n // addresses it has no code to fetch.\n const imageRefs = url.searchParams.get('imageRefs') === '1'\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 // The attach snapshot is the session-level source (no event carries it),\n // so project identity is stamped here like everywhere a SessionInfo ships.\n session: ctx.projects.withProject(runner.info()),\n replayingFrom: afterSeq,\n })\n // `coalesceReplay` is opt-in and this is the one caller: a client's reducer\n // is last-write-wins for the readings it drops, so all it ever sees is the\n // final value — whereas the in-process subscribers (parking above all) read\n // those same events as *transitions* and must keep every one. Without it a\n // long session replays every per-turn usage poll, and the client renders\n // each: the meters visibly count up through the session's whole history on\n // every attach.\n const unsubscribe = runner.subscribe((event) => send({ type: 'event', event }), afterSeq, {\n coalesceReplay: true,\n truncateResults,\n imageRefs,\n })\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(ctx, 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\nasync function handleCommand(ctx: ServerContext, frame: ClientFrame, runner: Runner): Promise<void> {\n const { attachmentStore, bridge } = ctx\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' && ctx.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","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","/**\n * The scope rules — opaque tags assigned at create, immutable after, and the\n * only intra-deployment scoping primitive there is. WorkerDeck stores and\n * enforces the tags; the embedder's `authorizeSession` decides what they mean.\n */\n\n/** Most tags one session (or one principal) may carry, and the longest a key or\n * value may be. Not a security property — a bound so an opaque map cannot become\n * an unbounded store that every list response then carries. */\nconst MAX_SCOPE_KEYS = 16\nconst MAX_SCOPE_LEN = 200\n\n/** A `Record<string, string>` or nothing. Duck-typed the same way\n * `allowedProfiles` is: a malformed value is ignored, never half-applied. */\nexport function readScope(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const entries = Object.entries(value as Record<string, unknown>)\n if (entries.some(([, v]) => typeof v !== 'string')) return undefined\n return Object.fromEntries(entries) as Record<string, string>\n}\n\n/** Validate a caller-supplied scope. Returns an error string, or null when it is\n * well-formed (including when it is absent). */\nexport function checkScope(value: unknown): string | null {\n if (value === undefined) return null\n const scope = readScope(value)\n if (!scope) return 'scope must be an object of string values'\n const entries = Object.entries(scope)\n if (entries.length > MAX_SCOPE_KEYS) return `scope may carry at most ${MAX_SCOPE_KEYS} keys`\n for (const [key, val] of entries) {\n if (key.length === 0) return 'scope keys must not be empty'\n if (key.length > MAX_SCOPE_LEN || val.length > MAX_SCOPE_LEN) {\n return `scope keys and values must be at most ${MAX_SCOPE_LEN} characters`\n }\n }\n return null\n}\n\n/** Key-order-independent equality — a host runner that rebuilt the record\n * rather than echoing the reference must still pass the build-time check. */\nexport function sameScope(\n a: Record<string, string> | undefined,\n b: Record<string, string> | undefined,\n): boolean {\n const left = Object.entries(a ?? {}).sort(([x], [y]) => (x < y ? -1 : 1))\n const right = Object.entries(b ?? {}).sort(([x], [y]) => (x < y ? -1 : 1))\n return (\n left.length === right.length &&\n left.every(([key, value], i) => right[i]![0] === key && right[i]![1] === value)\n )\n}\n\n/**\n * The default visibility rule, used whenever the host supplies no\n * `authorizeSession`: every key the principal pins must match the session's, and\n * an unset principal scope sees everything.\n *\n * The asymmetry is intended — a session may carry tags the principal says\n * nothing about (an app that tags `{space, user, conversation}` while the\n * principal only pins `{space, user}` still works), but a session missing a key\n * the principal pins is not this caller's.\n */\nexport function scopeMatches(\n principal: Record<string, string> | undefined,\n session: Record<string, string> | undefined,\n): boolean {\n if (!principal) return true\n return Object.entries(principal).every(([key, value]) => session?.[key] === value)\n}\n","/**\n * Request authentication and the visibility predicates. One `canSee` behind the\n * list filter, every `/sessions/:id/*` route, the WS attach, the execution-result\n * door and the job routes — three callers, one predicate, so they cannot drift\n * into three subtly different answers.\n */\nimport type { IncomingMessage } from 'node:http'\nimport type { JobInfo, SessionInfo } from '@workerdeck/protocol'\nimport { readScope, scopeMatches } from '../lib/scope.ts'\nimport type { WorkerServerOptions } from '../options.ts'\nimport type { SessionRegistry } from './registry.ts'\n\nexport type AuthContext = {\n ok: boolean\n allowedProfiles?: string[]\n canManageProfiles?: boolean\n /** Whatever the host's `authenticate` returned, handed back to\n * `authorizeSession` verbatim — the host wrote both, and should get its own\n * object rather than this parsed shape. */\n principal?: unknown\n /** Parsed off the principal, duck-typed exactly like `allowedProfiles`.\n * Undefined = unrestricted. */\n scope?: Record<string, string>\n /** `principal.operator`, when the host stated it. Undefined = infer (see\n * {@link AuthService.isOperator}). */\n operator?: boolean\n}\n\nexport type AuthService = ReturnType<typeof createAuthService>\n\nexport function createAuthService(deps: {\n options: Pick<WorkerServerOptions, 'authenticate' | 'authorizeSession'>\n refs: { registry?: SessionRegistry }\n}) {\n const { options, refs } = deps\n\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 const scope = readScope((principal as { scope?: unknown }).scope)\n return {\n ok: true,\n principal,\n // An empty object pins nothing, so it is unrestricted like an absent one —\n // an embedder must never read `{}` as \"sees no sessions\".\n scope: scope && Object.keys(scope).length > 0 ? scope : undefined,\n // Three-state on purpose: absent lets `isOperator` infer, and only an\n // actual boolean overrides the inference in either direction.\n operator:\n typeof (principal as { operator?: unknown }).operator === 'boolean'\n ? ((principal as { operator: boolean }).operator)\n : undefined,\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 /** May this caller see — and therefore drive — this session? */\n const canSee = (auth: AuthContext, session: SessionInfo): boolean => {\n if (!options.authorizeSession) return scopeMatches(auth.scope, session.scope)\n try {\n return options.authorizeSession(auth.principal, session) === true\n } catch {\n // A policy that threw has not said yes. Fail closed rather than 500 the\n // route — a list of a hundred rows must not become a page-wide error\n // because one row's tags surprised the host's rule.\n return false\n }\n }\n\n /**\n * The job flavour of {@link canSee}. Once the run has started, the live\n * session's info is the real subject and the host's rule decides on it. Before\n * that (queued) and after (finished, session gone) there is no session to\n * hand over, so the predicate gets a **stub** built from what the job records:\n * its scope, its profile, its cwd.\n *\n * A stub rather than a fallback to the default rule, which is what this did\n * first and was wrong: a host policy *narrower* than plain tag-match (tags\n * plus a role, say) would have had queued jobs admitted — and cancelable — by\n * a peer it rejects. The predicate must be the only rule wherever it exists.\n * A host reading fields a queued job cannot have (model, status detail) gets\n * `undefined` and should treat the id and the scope as the load-bearing ones.\n */\n const canSeeJob = (auth: AuthContext, job: JobInfo): boolean => {\n const live = job.sessionId ? refs.registry!.get(job.sessionId)?.info() : undefined\n if (live) return canSee(auth, live)\n if (!options.authorizeSession) return scopeMatches(auth.scope, job.scope)\n return canSee(auth, {\n id: job.sessionId ?? job.id,\n status:\n job.status === 'running'\n ? 'running'\n : job.status === 'parked'\n ? 'parked'\n : job.status === 'queued'\n ? 'starting'\n : 'closed',\n cwd: job.cwd,\n profile: job.profile,\n createdAt: job.createdAt,\n lastSeq: 0,\n pendingPermissionCount: 0,\n scope: job.scope,\n })\n }\n\n /**\n * Is this caller the operator, rather than someone embedded inside a scope?\n *\n * It decides the surfaces that answer about the **gateway** instead of about\n * one session — the host filesystem, the engine's own on-disk session store,\n * the queue and its firehose. There is nothing to filter on those and no\n * honest way to narrow them, so a non-operator is refused outright (404, like\n * every other miss).\n *\n * Two ways to be one, and the second exists because the first is not enough.\n * A principal carrying `scope` is an end user; a principal carrying neither\n * `scope` nor a policy is the operator — that is the unscoped default every\n * existing deployment relies on. But a host may write `authorizeSession` over\n * its *own* principal shape and never set `scope` at all, and reading that as\n * \"everyone is the operator\" is how a locked-down gateway ends up serving its\n * filesystem to end users. So **declaring a policy withdraws the default**,\n * and such a host marks its operator principals explicitly with\n * `operator: true` (`operator: false` forces the other way, at any time).\n */\n const isOperator = (auth: AuthContext): boolean =>\n auth.operator ?? (auth.scope === undefined && !options.authorizeSession)\n\n return { authenticate, canSee, canSeeJob, isOperator }\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 * (`requireAvailableProfile` is the one deliberate exception, and only on a\n * definite `false`.)\n */\nimport { checkClaudeAuth } from '@workerdeck/core'\nimport type { ClaudeAuthProbe, EngineAdapter, EngineAvailability } from '@workerdeck/core'\nimport type { ProfileEngine, ProfileInfo } from '@workerdeck/protocol'\nimport { engineOf } from '../lib/profile-env.ts'\nimport type { Refusal } from './profiles.ts'\n\nconst AVAILABILITY_TTL_MS = 60_000\n\nexport type AvailabilityTrackerOptions = {\n checkCredentials?: boolean | { probe?: ClaudeAuthProbe; timeoutMs?: number }\n requireAvailableProfile?: boolean\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n /** The env the real assembly path would spawn this profile's session with. */\n sessionEnvFor: (profile: ProfileInfo) => Record<string, string | undefined>\n}\n\nexport class AvailabilityTracker {\n readonly #verdicts = new Map<string, { verdict: EngineAvailability; at: number }>()\n /** Profiles already warned about on the console, so re-probes don't spam. */\n readonly #warned = new Set<string>()\n readonly #opts: AvailabilityTrackerOptions\n\n constructor(opts: AvailabilityTrackerOptions) {\n this.#opts = opts\n }\n\n /** The cached verdict, if any probe has answered. */\n get(name: string): EngineAvailability | undefined {\n return this.#verdicts.get(name)?.verdict\n }\n\n probe(profile: ProfileInfo): void {\n const { checkCredentials, adapterFor, sessionEnvFor } = this.#opts\n if (!checkCredentials) return\n const conf = checkCredentials === true ? {} : checkCredentials\n // Mark in-flight immediately so concurrent GET /profiles don't re-spawn.\n this.#verdicts.set(profile.name, {\n verdict: this.#verdicts.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 this.#verdicts.set(profile.name, { verdict, at: Date.now() })\n if (verdict.available === false && !this.#warned.has(profile.name)) {\n this.#warned.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) this.#warned.delete(profile.name)\n })\n .catch(() => {\n // a probe that breaks is 'unknown', and unknown stays silent\n })\n }\n\n /**\n * The create-time half of `requireAvailableProfile`. Only a definite `false`\n * refuses: an unprobed profile ('unknown', or probes turned off entirely) is\n * not evidence of anything and must not become a closed door.\n */\n checkAvailable(profile: ProfileInfo | undefined): Refusal | null {\n if (!this.#opts.requireAvailableProfile || !profile) return null\n const verdict = this.get(profile.name)\n if (!verdict || verdict.available !== false) return null\n return {\n status: 503,\n error: `profile '${profile.name}' is unavailable: ${verdict.reason ?? 'no usable credentials'}`,\n }\n }\n\n /** Launch-time sweep, concurrent and fire-and-forget. */\n preflight(profiles: ProfileInfo[]): void {\n for (const profile of profiles) this.probe(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 refresh(profiles: ProfileInfo[]): void {\n if (!this.#opts.checkCredentials) return\n const now = Date.now()\n for (const profile of profiles) {\n const cached = this.#verdicts.get(profile.name)\n if (!cached || now - cached.at > AVAILABILITY_TTL_MS) this.probe(profile)\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 { SessionInfo, 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 /** Gateway wiring, not a host option: the serve-time `SessionInfo` decoration\n * (project identity today), so a webhook or push consumer reads the same\n * record every REST caller does. The assembly supplies it; identity when\n * absent. */\n decorateInfo?: (info: SessionInfo) => SessionInfo\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: (this.#options.decorateInfo ?? ((info) => info))(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 type {\n ParkedExecution,\n Runner,\n SessionRunnerConfig,\n ToolExecutionResult,\n} from '@workerdeck/core'\nimport { ENGINE_CAPABILITIES, type SessionInfo } from '@workerdeck/protocol'\nimport type { SessionRegistry } from './registry.ts'\nimport {\n isDormant,\n isLiveRecord,\n type DormantSessionRecord,\n type ParkedSessionRecord,\n type SessionStore,\n type StoredSessionRecord,\n} from './session-store.ts'\n\nexport type SessionParkOptions = {\n registry: SessionRegistry\n store: SessionStore\n /**\n * Rebuild a stored session's runner. For a park the snapshot rides in on\n * `config.restore`, so the engine adopts the id, event log, and history; for a\n * dormant record there is no snapshot and the engine is asked to resume its\n * own session under the stored id. Either way the runner it returns must carry\n * `record.id` — {@link SessionParkManager} refuses one that does not.\n */\n rebuild: (record: StoredSessionRecord) => 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 /**\n * Keep a live session's snapshot written through to the store, so it survives\n * a gateway restart. **Off by default**: a library must not start writing a\n * session's whole transcript to disk because someone upgraded.\n *\n * This is the restart story for the engine that cannot go dormant. Dormancy\n * works by remembering an *engine* session id to resume from, which the\n * provider engine does not have — there is no store behind it, the history\n * lives in the runner. What it has instead is `snapshot()`, so the record\n * carries the state itself and rehydration is the ordinary `restore` path.\n * Between the two, every engine survives a restart.\n *\n * Written at the end of a turn, never on a shutdown hook — a `kill -9`, an OOM\n * or a pulled power cable run no hook, and that is precisely the case this\n * exists for. Turn-end is also a natural rate limit: one write per turn, not\n * one per token.\n */\n persistLive?: boolean\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/remember/resume failures. These are not session errors — the session\n * is intact, the host's storage or engine assembly isn't. */\n onError?: (\n error: unknown,\n context: { sessionId: string; phase: 'park' | 'remember' | 'resume' },\n ) => void\n}\n\n/**\n * Two ways a session outlives its runner, behind one door.\n *\n * **Parking** is deferred execution's other half: a session waiting on work no\n * process in this server is doing.\n *\n * **Dormancy** is the restart story for the engines that cannot park. Every live\n * claude or codex session leaves a small record naming its engine session id, so\n * a gateway that comes back up lists them and resumes one the first time someone\n * attaches. Both kinds live in the same store and come back through the same\n * `ensureLive`, which is why there is one class here and not two.\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 /**\n * Re-save a live session's dormant record because something outside the event\n * stream changed it.\n *\n * `#rememberDormant` is otherwise driven by `status_changed` and `system_init`\n * alone, and a rename (`PATCH /sessions/:id`) fires neither — so without this\n * a renamed session that is never touched again keeps its old title on disk\n * and comes back under it. Safe to call for anything: every gate in\n * `#rememberDormant` still applies, so a session that cannot be resumed, has\n * no engine session yet, or is no longer the registry's writes nothing.\n */\n touch(runner: Runner): void {\n void this.#rememberDormant(runner)\n // Both, because a session has exactly one of the two record kinds and this\n // has no business knowing which: `#rememberDormant` no-ops for a provider\n // session (no `resume` capability) and `#persistLive` no-ops for a claude or\n // codex one (no `snapshot()`). Without this half, a renamed provider session\n // survives the listing and comes back under its old title — the same bug\n // this method was added for, one engine over.\n void this.#persistLive(runner)\n }\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 *\n * Dormant records need nothing here, which is the point of them. They list\n * from the store (`listInfo`) and come back on first attach (`ensureLive`), so\n * a boot with fifty remembered sessions spawns nothing at all.\n */\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 if (isDormant(record)) continue\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 else void this.#rememberDormant(runner)\n return\n case 'turn_result':\n // The write-through moment: a turn has ended, so the history is whole\n // and the snapshot is cheap to justify.\n void this.#persistLive(runner)\n return\n case 'permission_mode_changed':\n case 'model_changed':\n // Both are part of what \"the session is still there\" means, and both\n // can be flipped between turns — so neither is covered by turn_result\n // alone. Flipped *mid*-turn, `snapshot()` refuses (a turn is in\n // flight) and the turn's own write covers it a moment later.\n void this.#persistLive(runner)\n return\n case 'system_init':\n // The first moment a resume is even possible: before the engine names\n // its session there is nothing to come back to.\n void this.#rememberDormant(runner)\n return\n case 'session_closed':\n // Not during shutdown. The registry closes every runner on the way\n // down and the reason it gives is the same 'server' a DELETE produces,\n // so the *only* thing separating \"this session is over\" from \"this\n // process is over\" is that `close()` ran first — which is exactly why\n // it does (`server.ts` closes parking before the registry). Without\n // this guard a graceful restart forgets every dormant session it was\n // supposed to preserve.\n if (this.#closed) return\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 stored session's record, for the read paths (GET, list, attach). */\n get(id: string): Promise<StoredSessionRecord | null> {\n return this.#queue(id, () => this.#options.store.get(id))\n }\n\n /** Every stored 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 const records = await this.#options.store.list()\n // A live session has a dormant record too — that is what makes it survive a\n // restart — so the registry wins and the record is skipped. Without this the\n // merged listing would show every running session twice.\n return records\n .filter((record) => this.#options.registry.get(record.id) === undefined)\n .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 /**\n * Write (or refresh) the dormant record that lets this session survive a\n * restart. Cheap and repeated on purpose — driven off `system_init` and every\n * non-park status change — because the alternative is a shutdown hook, and a\n * shutdown hook is exactly what a `kill -9`, an OOM or a pulled power cable\n * do not run.\n *\n * Four gates, each of which would otherwise produce a record that is worse\n * than none: the engine must be able to resume at all (a provider session\n * would come back with an empty transcript — it has `park()` instead), it must\n * have named its session, the host must remember the config to rebuild from,\n * and the runner must still be the registry's. That last one is what keeps a\n * park from being overwritten: `#park` evicts before it saves, so a late event\n * from an evicted runner finds itself a stranger here and writes nothing.\n */\n async #rememberDormant(runner: Runner): Promise<void> {\n if (this.#closed) return\n const info = runner.info()\n const sdkSessionId = info.sdkSessionId\n if (sdkSessionId === undefined) return\n const capabilities = info.capabilities ?? ENGINE_CAPABILITIES[info.engine ?? 'claude']\n if (!capabilities.resume) return\n const config = this.#configs.get(runner.id)\n if (!config) return\n if (this.#options.registry.get(runner.id) !== runner) return\n const record: DormantSessionRecord = {\n kind: 'dormant',\n id: runner.id,\n // Whatever it was doing, it is not doing it now: a record that came back\n // saying `running` would show a spinner over a session with no process.\n info: { ...info, status: 'idle' },\n profile: info.profile,\n // `#configs` holds the config the session was BUILT from, and a rename\n // never reaches it: `setTitle` replaces the runner's own `#config`. Since\n // a wake rebuilds from `record.config` and discards `record.info`, taking\n // the config's `meta` verbatim resurrected the pre-rename title — the\n // rename survived the listing (which reads `info`) and died on the wake.\n // `info.meta` IS the runner's live `#config.meta`, so this is the current\n // one by construction, not a copy that can drift.\n config: { ...config, meta: info.meta },\n sdkSessionId,\n savedAt: Date.now(),\n }\n try {\n await this.#queue(runner.id, () => this.#options.store.save(record))\n } catch (error) {\n // Losing this costs the session its way back after a restart, and nothing\n // else — the live session is untouched.\n this.#options.onError?.(error, { sessionId: runner.id, phase: 'remember' })\n }\n }\n\n /**\n * Write a live session's snapshot through to the store, so a restart can\n * rebuild it. The counterpart to {@link #rememberDormant} for the engine that\n * has no engine-side session to resume from — same discipline, different\n * mechanism: that one remembers *where the transcript is*, this one carries it.\n *\n * The gates are the same four, plus the option and the engine's ability. The\n * `registry.get(runner.id) !== runner` check is doing the same work it does\n * there: a runner that has been evicted (parked, or replaced by a rebuild)\n * finds itself a stranger here and writes nothing, so a late event cannot\n * overwrite a park with a stale live record.\n *\n * **This must not run synchronously inside the event listener**, and that is\n * easy to lose. `turn_result` is emitted from inside the turn, *before* the\n * `finally` that clears the runner's abort controller — so a `snapshot()`\n * called straight from the listener would see a turn in flight and refuse,\n * every single time, silently. `#queue`'s microtask hop is what puts the call\n * after it. A refactor that \"simplifies\" this into a direct call produces a\n * write-through that never writes and nothing that says so.\n */\n async #persistLive(runner: Runner): Promise<void> {\n if (this.#closed || !this.#options.persistLive || !runner.snapshot) return\n const config = this.#configs.get(runner.id)\n if (!config) return\n if (this.#options.registry.get(runner.id) !== runner) return\n try {\n await this.#queue(runner.id, async () => {\n // Re-checked inside the queue: this runs a tick or more after the event,\n // and the session may have parked or been evicted in between — a park's\n // record must not be overwritten by the live copy queued behind it.\n if (this.#closed || this.#options.registry.get(runner.id) !== runner) return\n const snapshot = runner.snapshot?.()\n if (!snapshot) return\n const info = runner.info()\n const record: ParkedSessionRecord = {\n kind: 'live',\n id: runner.id,\n // Idle, never the live status: a record that came back saying\n // `running` would show a spinner over a session with no process, and\n // one saying `parked` would claim it waits on work it does not have.\n info: { ...info, status: 'idle' },\n profile: info.profile,\n // `info.meta` IS the runner's live `#config.meta`, and `#configs`\n // never sees a rename — the same reason `#rememberDormant` spells it\n // this way. Without it a renamed session comes back under its old\n // title, having listed correctly right up until the restart.\n config: { ...config, meta: info.meta },\n snapshot,\n // Whatever it is waiting on, which is empty for the ordinary idle\n // write and is *not* for the case that matters: a session parked on\n // deferred work stays live while a client is attached (`#park` defers\n // to the socket), so this record is its only durable one and `hydrate`\n // re-arms the watchdogs from exactly this list.\n executions: snapshot.parked,\n parkedAt: Date.now(),\n }\n await this.#options.store.save(record)\n })\n } catch (error) {\n // Losing this costs the session its way back after a restart, and nothing\n // else — the live session is untouched. Same phase as the dormant write:\n // they are the same operation for different engines.\n this.#options.onError?.(error, { sessionId: runner.id, phase: 'remember' })\n }\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 kind: 'parked',\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` (or, for a dormant record, the id): it\n // built a fresh session with none of this one's history, which would\n // strand the execution we are waking for and orphan every client's\n // watermarks against an id that no longer exists.\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 (or, without a snapshot, the session id) ' +\n '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 // A park resumes mid-stream and must not re-arm watchdogs from its own\n // replayed events; a dormant session starts a fresh event log (the history\n // arrives as the engine's `replay: true` backfill), so there is no prior seq\n // to skip and nothing deferred in it to re-index.\n this.watch(runner, isDormant(record) ? 0 : record.snapshot.seq)\n this.#options.onResumed?.(id, runner)\n // A park's record *is* the session — consumed on wake. A dormant one, and a\n // live one, are a way back that the session still needs the next time the\n // process dies, so they stay and are refreshed in place; `listInfo` already\n // hides them while the registry has the session, and `discard` removes them\n // when the session ends.\n //\n // Consuming a live record here would be the feature's worst bug rather than\n // a tidier lifecycle: it opens a window from this attach to the next turn in\n // which the session exists nowhere durable, so someone who opens a session,\n // reads it and types nothing loses it to a redeploy — with no error and no\n // trace.\n if (!isDormant(record) && !isLiveRecord(record)) {\n await this.#queue(id, () => this.#options.store.delete(id))\n }\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 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","/**\n * The profile directory: startup-declared profiles unioned with store-managed\n * ones, validation shared by startup (throws) and the management routes (400s),\n * and the response decoration (`forResponse`) every profile answer goes through.\n *\n * Declared profiles are code — never persisted, never editable over HTTP. The\n * store-managed set is mirrored in memory so every lookup on the request path\n * stays synchronous; `refreshStored()` reloads it after each mutation.\n */\nimport { existsSync } from 'node:fs'\nimport { supportsPermissionMode, type ProfileEngine, type ProfileInfo } from '@workerdeck/protocol'\nimport type { EngineAdapter, EngineAvailability } from '@workerdeck/core'\nimport { cwdAllowed, engineOf, isProviderProfile } from '../lib/profile-env.ts'\nimport type { ProfileStore } from './profile-store.ts'\n\nexport type Refusal = { status: number; error: string }\n\nexport type ProfileServiceOptions = {\n /** Startup-declared profiles (code). */\n declared: ProfileInfo[]\n /** Persistence for dashboard-managed profiles; absent = read-only API. */\n store?: ProfileStore\n /** Roots a managed Claude profile's configDir must resolve inside. */\n allowedConfigDirRoots?: string[]\n disableBypassPermissions?: boolean\n /** True when a `createEngineRunner` factory exists to build provider runners. */\n hasEngineRunnerFactory: boolean\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n /** Response-time decoration, injected as accessors because the state lives in\n * sibling services (learned default models, availability verdicts, plan usage). */\n decorate: {\n defaultModel: (name: string) => string | undefined\n availability: (name: string) => EngineAvailability | undefined\n usage: (name: string) => ProfileInfo['usage'] | undefined\n }\n}\n\nexport class ProfileService {\n readonly #declared: ProfileInfo[]\n readonly #declaredByName: Map<string, ProfileInfo>\n /** Store-managed profiles, mirrored in memory — see the module doc. */\n readonly #stored = new Map<string, ProfileInfo>()\n readonly #opts: ProfileServiceOptions\n\n constructor(opts: ProfileServiceOptions) {\n this.#opts = opts\n this.#declared = opts.declared\n this.#declaredByName = new Map(opts.declared.map((p) => [p.name, p]))\n if (this.#declaredByName.size !== opts.declared.length) {\n throw new Error('createWorkerServer: duplicate profile names in `profiles`')\n }\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 validate(p: ProfileInfo): string | null {\n const { adapterFor, disableBypassPermissions, hasEngineRunnerFactory } = this.#opts\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 (!hasEngineRunnerFactory) {\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 (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 /** Reload the in-memory mirror of the store — once at `listen()`, and after\n * each management-route mutation. Single-process, like the bundled queue. */\n async refreshStored(): Promise<void> {\n if (!this.#opts.store) return\n this.#stored.clear()\n for (const p of await this.#opts.store.list()) this.#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 withManagedFlag(p: ProfileInfo): ProfileInfo {\n return this.#declaredByName.has(p.name) ? p : { ...p, managed: true }\n }\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, 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 * and the plan usage learned from the profile's sessions' rate_limit events.\n * Read-only decoration — never persisted.\n */\n forResponse(p: ProfileInfo): ProfileInfo {\n const { adapterFor, decorate } = this.#opts\n const adapter = adapterFor(p.engine)\n const base: ProfileInfo = {\n ...this.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 = decorate.defaultModel(p.name)\n if (defaultModel) base.defaultModel = defaultModel\n const probed = decorate.availability(p.name)\n if (probed && probed.available !== 'unknown') {\n base.available = probed.available\n if (probed.available === false) base.unavailableReason = probed.reason\n }\n // Plan usage, learned like the default model and display-only like the\n // availability verdict. The 0%-after-elapsed-reset inference happens in\n // `usage()` per request, so it is computed against *this* moment's clock.\n const usage = decorate.usage(p.name)\n if (usage) base.usage = usage\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 all(): ProfileInfo[] {\n return [\n ...this.#declared,\n ...[...this.#stored.values()].filter((p) => !this.#declaredByName.has(p.name)),\n ]\n }\n\n get(name: string): ProfileInfo | undefined {\n return this.#declaredByName.get(name) ?? this.#stored.get(name)\n }\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 manageGuard(auth: { canManageProfiles?: boolean }): Refusal | null {\n if (!this.#opts.store) {\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 declaredGuard(profile: ProfileInfo): Refusal | null {\n return this.#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 /**\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 configDirGuard(profile: ProfileInfo): Refusal | null {\n if (isProviderProfile(profile)) return null\n const roots = this.#opts.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 and persist a managed profile. Shared by create and update so a\n * PATCH can never leave behind a profile a POST would have refused. Returns\n * the saved profile (managed-flagged) or a refusal. */\n async saveManaged(\n incoming: ProfileInfo,\n ): Promise<{ ok: true; profile: ProfileInfo } | ({ ok: false } & Refusal)> {\n // `managed` is server-computed on every response; never persist a client's copy.\n const { managed: _clientClaim, ...profile } = incoming\n const refused = this.configDirGuard(profile)\n if (refused) return { ok: false, ...refused }\n const invalid = this.validate(profile)\n if (invalid) return { ok: false, status: 400, error: invalid }\n await this.#opts.store!.save(profile)\n await this.refreshStored()\n return { ok: true, profile: this.withManagedFlag(profile) }\n }\n}\n","import type { Runner } from '@workerdeck/core'\nimport type { ProfileUsage, ProfileUsageWindow, RateLimitInfo } from '@workerdeck/protocol'\n\n/** A reading as held: what the engine said, and the event clock it said it at. */\ntype HeldWindow = { info: RateLimitInfo; updatedAt: number }\n\n/**\n * The gateway's single plan-usage state per profile, fed from every session's\n * `rate_limit` events and served on `GET /profiles` (`ProfileInfo.usage`).\n *\n * Why this exists at all: usage had only ever lived in session transcripts, so\n * a client attaching to a session that idled since yesterday replayed\n * yesterday's reading as if current — and a session opened today knew nothing\n * of what a sibling session on the same account spent an hour ago. The profile\n * is the account boundary (one config dir / codex home / provider key = one\n * plan), so the newest reading across all of a profile's sessions is the one\n * usage state that is ever worth showing. No history: last-write-wins per\n * window, exactly the reducer's rule on the client side.\n *\n * Last-write-wins goes by the **event's own clock**, not arrival order:\n * `watch()` subscribes from seq 0 (a replayed log is how a rebuilt session's\n * readings arrive at all), and a replayed yesterday-reading must not clobber\n * the fresher one another session on the same profile reported live. All\n * events are stamped by this gateway's clock at emit time, so the comparison\n * is sound across sessions.\n *\n * In-memory on purpose, like the learned default models and the availability\n * cache: display-only state may start empty after a restart (absent = unknown,\n * never 0%), and the first session to report refills it.\n */\nexport class ProfileUsageTracker {\n /** profile name → rateLimitType → newest reading. */\n #profiles = new Map<string, Map<string, HeldWindow>>()\n\n /** Follow a runner's `rate_limit` events for its lifetime. Sessions without a\n * profile have no account to attribute usage to and are skipped. */\n watch(runner: Runner): void {\n const profile = runner.info().profile\n if (!profile) return\n runner.subscribe((event) => {\n if (event.type !== 'rate_limit') return\n const type = event.info.rateLimitType\n if (!type) return\n let windows = this.#profiles.get(profile)\n if (!windows) this.#profiles.set(profile, (windows = new Map()))\n const held = windows.get(type)\n if (held && held.updatedAt > event.ts) return\n // Whole-reading replacement, mirroring the transcript reducer — a newer\n // event is the newer truth even where it carries fewer fields.\n windows.set(type, { info: event.info, updatedAt: event.ts })\n })\n }\n\n /**\n * The profile's windows as they should be served *now*. Undefined until any\n * session on the profile has reported (unknown, never 0%).\n *\n * The 0%-after-reset inference lives here — at serve time — and nowhere\n * else, because it is a function of the wall clock: a window whose own\n * `resetsAt` has passed with no newer reading has provably rolled, so the\n * pre-reset utilization is no longer merely stale but *wrong*. It cannot be\n * a producer's job (the producers only relay what the engine said, and the\n * whole problem is the engine's silence; a fabricated 0% event would be\n * replayed from transcripts forever as if reported) and must not be every\n * renderer's (N clients would each reimplement the clock math). The held\n * reading stays untouched, so a late fresh report still lands by ts, and the\n * served zero is labeled `inferredReset` — it is a floor, not a report: the\n * account may have been used outside this gateway since the reset.\n */\n usage(profile: string, now = Date.now()): ProfileUsage | undefined {\n const windows = this.#profiles.get(profile)\n if (!windows || windows.size === 0) return undefined\n const out: ProfileUsage = {}\n for (const [type, held] of windows) out[type] = serveWindow(held, now)\n return out\n }\n}\n\n/** `resetsAt` is epoch **seconds** (protocol contract); `now` is epoch ms. */\nfunction serveWindow(held: HeldWindow, now: number): ProfileUsageWindow {\n const resetsAt = held.info.resetsAt\n if (resetsAt !== undefined && resetsAt * 1000 <= now) {\n return {\n info: {\n // A rolled window is not rejecting anyone, whatever the last reading's\n // status said; `resetsAt`/`isUsingOverage` describe the previous window\n // and are dropped rather than served as facts about this one.\n status: 'allowed',\n rateLimitType: held.info.rateLimitType,\n utilization: 0,\n },\n updatedAt: held.updatedAt,\n inferredReset: true,\n }\n }\n return { info: held.info, updatedAt: held.updatedAt }\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","/**\n * The create pipeline: everything between a `CreateSessionRequest` arriving and\n * a `Runner` running — policy checks (bypass, permission mode, engine grants,\n * scope, cwd), profile resolution, config assembly (`buildRunnerConfig`), and\n * the one chokepoint that builds runners for create, dormant rebuild and parked\n * rebuild alike (`buildRunner`).\n *\n * Registry/parking/bridge are handed in as late-bound refs because construction\n * is mutually recursive with them (parking's rebuild callback calls\n * `buildRunner`; `createRunner` registers and watches). The refs are filled\n * during assembly, before the server accepts a request.\n */\nimport { supportsPermissionMode, type CreateSessionRequest, type PermissionMode, type ProfileEngine, type ProfileInfo } from '@workerdeck/protocol'\nimport type { EngineAdapter, Runner, RunnerSnapshot, SessionRunnerConfig } from '@workerdeck/core'\nimport { checkScope, sameScope } from '../lib/scope.ts'\nimport { claudeSessionEnv, cwdAllowed, engineOf, isProviderProfile } from '../lib/profile-env.ts'\nimport type { EngineRunnerContext } from '../options.ts'\nimport type { BridgeHub } from './bridge.ts'\nimport type { SessionParkManager } from './parking.ts'\nimport type { ProfileService } from './profiles.ts'\nimport type { SessionRegistry } from './registry.ts'\n\nexport type SessionFactoryDeps = {\n adapterFor: (engine: ProfileEngine | undefined) => EngineAdapter\n profiles: ProfileService\n hostBuildRunnerConfig: (req: CreateSessionRequest) => SessionRunnerConfig\n createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>\n allowedCwdRoots?: string[]\n disableBypassPermissions?: boolean\n requireApiKey?: boolean\n /** Late-bound: filled during assembly, read at call time. */\n refs: {\n registry?: SessionRegistry\n parking?: SessionParkManager\n bridge?: BridgeHub\n }\n}\n\nexport type SessionFactory = ReturnType<typeof createSessionFactory>\n\nexport function createSessionFactory(deps: SessionFactoryDeps) {\n const { adapterFor, profiles, refs } = deps\n\n /** Profiles (by name; '' = none) whose oauth notice has been logged. */\n const subscriptionNoticeShown = new Set<string>()\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 (!deps.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 /**\n * Validate the request's scope and merge the principal's into it.\n *\n * A scoped principal's keys are *filled in* when the request omits them and\n * *refused* when the request disagrees: a caller inside a scope may narrow\n * itself with extra tags, never claim to be somewhere else. That makes an\n * embedder's stamping proxy defense in depth rather than the only line — a\n * request that slipped past it still cannot create a session in another\n * scope. An unscoped principal (the operator) may write any tags.\n */\n const applyScope = (\n req: CreateSessionRequest,\n auth: { scope?: Record<string, string> },\n ): { status: number; error: string } | null => {\n const invalid = checkScope(req.scope)\n if (invalid) return { status: 400, error: invalid }\n if (!auth.scope) return null\n const merged: Record<string, string> = { ...req.scope }\n for (const [key, value] of Object.entries(auth.scope)) {\n const claimed = merged[key]\n if (claimed !== undefined && claimed !== value) {\n return { status: 403, error: `scope '${key}' does not match the caller's` }\n }\n merged[key] = value\n }\n // Re-checked after the merge, so the advertised bound is the real ceiling\n // rather than one the principal's own keys can push past.\n const tooBig = checkScope(merged)\n if (tooBig) return { status: 400, error: tooBig }\n req.scope = merged\n return null\n }\n\n /**\n * `cwd`, required or not depending on the engine's capability record — the\n * record rather than the engine name, so a host engine that has no host\n * filesystem gets the same treatment without this file learning its name.\n *\n * When one *is* supplied it is validated even for an engine that will not read\n * it: a path the caller went out of their way to name should not be quietly\n * exempt from the operator's roots. And note what this check is not — for a\n * filesystem-less engine `allowedCwdRoots` guards nothing at all. The\n * boundary there is the capability wiring, not a path prefix.\n */\n const checkCwd = (\n req: CreateSessionRequest,\n profile: ProfileInfo | undefined,\n ): { status: number; error: string } | null => {\n if (req.cwd !== undefined && typeof req.cwd !== 'string') {\n return { status: 400, error: 'cwd must be a string' }\n }\n if (!req.cwd) {\n // Absent = true, so an engine record that predates the field keeps the old\n // always-required behaviour.\n return adapterFor(profile?.engine).capabilities.hostCwd === false\n ? null\n : { status: 400, error: 'cwd is required' }\n }\n return cwdAllowed(req.cwd, deps.allowedCwdRoots)\n ? null\n : { status: 403, error: 'cwd is outside the allowed roots' }\n }\n\n /**\n * Re-stamp the request's scope onto whatever the host's `buildRunnerConfig`\n * returned. The hook is host code and may rewrite the config wholesale; a\n * hook that dropped `scope` would silently *widen* a session's visibility,\n * which is the one direction a bug here must not go. Same posture as the\n * profile's env pin winning over the hook.\n */\n const withScope = (\n config: SessionRunnerConfig,\n scope: Record<string, string> | undefined,\n ): SessionRunnerConfig => (scope === undefined ? config : { ...config, scope })\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 ? profiles.get(req.profile) : undefined\n if (!profile) return withScope(deps.hostBuildRunnerConfig(req), req.scope)\n const config = withScope(\n deps.hostBuildRunnerConfig({\n ...req,\n model: req.model ?? profile.defaults?.model ?? profile.provider?.model,\n permissionMode: req.permissionMode ?? profile.defaults?.permissionMode,\n }),\n req.scope,\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 /** The env a probe should test: exactly what the real assembly path produces. */\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 /** 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 /** Adopt this id rather than minting one — rehydrating a dormant session. */\n id?: string,\n ): Promise<Runner> => {\n const name = config.profile\n const profile = name !== undefined ? profiles.get(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 const runner =\n profile && isProviderProfile(profile)\n ? // Guaranteed present: startup refuses provider profiles without a factory.\n await deps.createEngineRunner!({ config, profile, bridge: refs.bridge!, restore, id })\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 await adapterFor(profile?.engine).createRunner({ config, profile, restore, id })\n // The single chokepoint for create, dormant rebuild and parked rebuild, so\n // it is the one place worth checking that the runner reports the scope it\n // was built with. A host-supplied runner that forgot to echo it would be\n // invisible to every enforcement point below and visible to everyone.\n const reported = runner.info().scope\n if (!sameScope(reported, config.scope)) {\n throw new Error(\n `runner for session ${runner.id} reports scope ${JSON.stringify(reported)}, ` +\n `expected ${JSON.stringify(config.scope)} — echo config.scope from info()`,\n )\n }\n return runner\n }\n\n const createRunner = async (config: SessionRunnerConfig): Promise<Runner> => {\n const runner = refs.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 refs.parking!.remember(runner.id, config)\n refs.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 all = profiles.all()\n if (all.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 ?? (all.length === 1 ? all[0]!.name : undefined)\n if (effective === undefined) {\n const available = all.map((p) => p.name).join(', ')\n return { ok: false, status: 400, error: `profile is required (available: ${available})` }\n }\n const profile = profiles.get(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 // 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 (deps.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 return {\n applyBypassPolicy,\n checkPermissionMode,\n checkEngineGrants,\n stripInertFields,\n applyScope,\n checkCwd,\n buildRunnerConfig,\n sessionEnvFor,\n buildRunner,\n createRunner,\n resolveProfile,\n watchAuthSource,\n }\n}\n","/**\n * `createWorkerServer` — the assembly. Option types live in `options.ts`, the\n * shared-state record routes take in `context.ts`, per-route behaviour in\n * `routes/`, the stateful pieces in `services/`, and the pure rules in `lib/`.\n * This file only wires them together and dispatches requests.\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { WebSocketServer, type WebSocket } from 'ws'\nimport { getEngineAdapter } from '@workerdeck/core'\nimport type { EngineAdapter } from '@workerdeck/core'\nimport { JobQueue } from '@workerdeck/queue'\nimport {\n PROTOCOL_VERSION,\n type CreateSessionRequest,\n type JobEvent,\n type ProfileEngine,\n type QueueServerFrame,\n} from '@workerdeck/protocol'\nimport type { SessionRunnerConfig } from '@workerdeck/core'\nimport type { ServerContext } from './context.ts'\nimport { json } from './lib/http.ts'\nimport { detectDefaultProfiles } from './lib/profile-env.ts'\nimport { parseSessionRoute } from './lib/parse-route.ts'\nimport type { WorkerServer, WorkerServerOptions } from './options.ts'\nimport { handleExecutionResult } from './routes/executions.ts'\nimport { handleHostFiles } from './routes/host-files.ts'\nimport { handleJobs } from './routes/jobs.ts'\nimport { handleProfiles } from './routes/profiles.ts'\nimport { handleSdkSessions } from './routes/sdk-sessions.ts'\nimport { handleSessions } from './routes/sessions.ts'\nimport { attachClient } from './routes/ws.ts'\nimport { AttachmentStore } from './services/attachments.ts'\nimport { createAuthService } from './services/auth.ts'\nimport { AvailabilityTracker } from './services/availability.ts'\nimport { BridgeHub } from './services/bridge.ts'\nimport { createHostFileRoots } from './services/host-files.ts'\nimport { SessionNotifier } from './services/notifications.ts'\nimport { SessionParkManager } from './services/parking.ts'\nimport { ProducedFileStore } from './services/produced-files.ts'\nimport { ProfileService } from './services/profiles.ts'\nimport { ProfileUsageTracker } from './services/profile-usage.ts'\nimport { ProjectInfoService } from './services/project-info.ts'\nimport { SessionRegistry } from './services/registry.ts'\nimport { createSessionFactory } from './services/session-factory.ts'\nimport { isDormant, MemorySessionStore } from './services/session-store.ts'\n\n// The public option types moved to options.ts; re-exported here so\n// `import { ... } from './server.ts'` keeps working for older in-repo callers.\nexport type {\n Authenticator,\n EngineRunnerContext,\n QueueServerOptions,\n SdkSessionLister,\n WorkerServer,\n WorkerServerOptions,\n} from './options.ts'\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\n // ---- Profiles, decorated at response time from the trackers below.\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 /** The single plan-usage state per profile, fed from every session's\n * `rate_limit` events and served by `forResponse` (see ProfileUsageTracker). */\n const profileUsage = new ProfileUsageTracker()\n const profiles = new ProfileService({\n // Declared at startup, or a single 'default' auto-created from the\n // operator's own config dir. Misdeclared dirs fail fast — the CLI would\n // otherwise silently start from an empty config (and a different\n // credential chain).\n declared: options.profiles ?? detectDefaultProfiles(),\n store: options.profileStore,\n allowedConfigDirRoots: options.allowedConfigDirRoots,\n disableBypassPermissions: options.disableBypassPermissions,\n hasEngineRunnerFactory: options.createEngineRunner !== undefined,\n adapterFor,\n decorate: {\n defaultModel: (name) => profileDefaultModels.get(name),\n availability: (name) => availability.get(name),\n usage: (name) => profileUsage.usage(name),\n },\n })\n for (const p of options.profiles ?? []) {\n const invalid = profiles.validate(p)\n if (invalid) throw new Error(`createWorkerServer: ${invalid}`)\n }\n\n // ---- The create pipeline. Registry/parking/bridge are late-bound refs,\n // filled just below — construction is mutually recursive (parking's rebuild\n // calls the factory's buildRunner).\n const refs: { registry?: SessionRegistry; parking?: SessionParkManager; bridge?: BridgeHub } = {}\n const factory = createSessionFactory({\n adapterFor,\n profiles,\n hostBuildRunnerConfig:\n options.buildRunnerConfig ?? ((req: CreateSessionRequest): SessionRunnerConfig => req),\n createEngineRunner: options.createEngineRunner,\n allowedCwdRoots: options.allowedCwdRoots,\n disableBypassPermissions: options.disableBypassPermissions,\n requireApiKey: options.requireApiKey,\n refs,\n })\n\n const availability = new AvailabilityTracker({\n checkCredentials: options.checkCredentials,\n requireAvailableProfile: options.requireAvailableProfile,\n adapterFor,\n sessionEnvFor: factory.sessionEnvFor,\n })\n\n // ---- The stateful services.\n // Project identity (`SessionInfo.project`), resolved from `.workerdeck.json`\n // at serve time and never persisted — see ProjectInfoService.\n const projects = new ProjectInfoService()\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({\n ...options.notifications,\n // A push consumer reads the same decorated record every REST caller does.\n decorateInfo: (info) => projects.withProject(info),\n })\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 // Also from 0 — replay is guarded by the events' own timestamps there.\n profileUsage.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 persistLive: options.parking?.persistLive,\n onError: options.parking?.onError,\n // A park restores from its snapshot; a dormant record has none, so it is\n // rebuilt like an ordinary create — the config back through\n // `buildRunnerConfig` (so the profile's env pin and the host hook's\n // injections are re-derived rather than read off disk), `resume` pointed at\n // the engine's own session, and the WorkerDeck id carried over by hand,\n // because nothing in the config would otherwise preserve it.\n rebuild: (record) =>\n isDormant(record)\n ? factory.buildRunner(\n factory.buildRunnerConfig({\n ...record.config,\n // A wake is \"come back as you were\", never a new turn. `prompt` is\n // the *first* prompt and it was consumed by the original run, but\n // it persists in the record and `start()` sends it unconditionally\n // — so a session created with an opening prompt used to re-run it\n // on top of the thread the resume had just replayed, costing a\n // turn and doing unrequested work. The provider engine has always\n // had this guard for its own rehydration (`if (config.restore)` in\n // `AiSdkRunner.start`); claude and codex resume by `resume`, which\n // — unlike `restore` — is a public request field where\n // `createSession({ resume, prompt })` legitimately means \"continue\n // this thread, and here is the next thing\". So the suppression\n // belongs here, at the one call site that means rehydration, and\n // not in the runners.\n prompt: undefined,\n // Dropping the prompt would otherwise cost the session its name:\n // `#title()` falls back to deriving one from `prompt` whenever\n // `meta.title` is unset. `record.info.title` has already resolved\n // that precedence, so freezing it into `meta` keeps a derived name\n // through the wake and is a no-op when the session was renamed.\n meta: record.info.title\n ? { ...record.config.meta, title: record.info.title }\n : record.config.meta,\n resume: record.sdkSessionId,\n }),\n undefined,\n record.id,\n )\n : factory.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 refs.registry = registry\n refs.parking = parking\n refs.bridge = bridge\n\n const auth = createAuthService({ options, refs })\n\n const wss = new WebSocketServer({ noServer: true })\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 factory.createRunner(config)\n factory.watchAuthSource(runner)\n return runner\n },\n buildRunnerConfig: factory.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 // 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\n // ---- The record every route module reads.\n const ctx: ServerContext = {\n options,\n basePath,\n maxBodyBytes,\n adapterFor,\n listSdkSessions: options.listSdkSessions,\n profiles,\n availability,\n auth,\n factory,\n registry,\n parking,\n bridge,\n projects,\n queue,\n attachmentStore,\n producedFiles,\n hostFiles,\n hostFilesWritable: options.hostFiles?.write === true,\n maxHostFileBytes: options.hostFiles?.maxFileBytes ?? 1024 * 1024,\n maxHostDirEntries: options.hostFiles?.maxEntries ?? 5000,\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 authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleJobs(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname === basePath + '/profiles' || pathname.startsWith(basePath + '/profiles/')) {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleProfiles(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname.startsWith(basePath + '/executions/')) {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleExecutionResult(ctx, req, res, pathname, authCtx)\n return\n }\n if (pathname === basePath + '/sdk-sessions') {\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n // The engine's own on-disk store, which is the operator's and spans every\n // scope: there is no per-session id here to filter by.\n if (!auth.isOperator(authCtx)) {\n json(res, 404, { error: 'not found' })\n return\n }\n await handleSdkSessions(ctx, req, res, authCtx)\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 const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n // Operator privilege by design (see `hostFiles`), so a scoped principal is\n // simply not who these routes are for — and it answers the same 404 an\n // unconfigured gateway does.\n if (!auth.isOperator(authCtx)) {\n json(res, 404, { error: 'not found' })\n return\n }\n await handleHostFiles(ctx, req, res, pathname)\n return\n }\n const route = parseSessionRoute(basePath, req.url ?? '/')\n if (!route || route.ws) {\n json(res, 404, { error: 'not found' })\n return\n }\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n json(res, 401, { error: 'unauthorized' })\n return\n }\n await handleSessions(ctx, req, res, route, authCtx)\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 const queueAuth = await auth.authenticate(req)\n if (!queueAuth.ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Every job's events — prompts, progress previews, result text — are\n // fanned to every socket here. There is no per-socket filter yet, so a\n // scoped principal is refused the firehose outright rather than being\n // handed other scopes' runs. (Per-socket filtering is the later fix; a\n // 404 now is the honest version of not having built it.)\n if (!auth.isOperator(queueAuth)) {\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 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 = parseSessionRoute(basePath, req.url ?? '/')\n if (!route?.ws || !route.id) {\n socket.destroy()\n return\n }\n const authCtx = await auth.authenticate(req)\n if (!authCtx.ok) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n // Scope is checked *before* the wake, off the record's stored info: waking\n // rebuilds the runner and reconnects its MCP servers, and doing that for a\n // caller who is about to get a 404 spends the session's resources on\n // someone with no claim to it.\n const known = registry.get(route.id)?.info() ?? (await parking.get(route.id))?.info\n if (known && !auth.canSee(authCtx, known)) {\n socket.write('HTTP/1.1 404 Not Found\\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 // Re-checked after the wake as well: the pre-check can only consult what\n // the registry and the store already know, and this is the socket that\n // can drive the session.\n if (!runner || !auth.canSee(authCtx, runner.info())) {\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(ctx, ws, runner, req)\n })\n })().catch(() => socket.destroy())\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 profiles.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 availability.preflight(profiles.all())\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 type { ProfileInfo, ProviderConfig, SessionCapability } from '@workerdeck/protocol'\n\n/**\n * A `provider` profile that grants a session nothing but the sandbox: the\n * QuickJS guest, the in-memory VFS, and the model.\n *\n * This adds no mechanism. `capabilities: []` and `mcpServers: []` already mean\n * what they mean, and `createToolContext` already withholds a tool whose backend\n * the host did not inject. What the helper buys is that the locked-down profile\n * is one call rather than three fields an operator has to get right together —\n * the failure mode being a profile that *looks* sandboxed and still grants\n * `deliver_file` because nobody wrote the empty array.\n *\n * What a session under it can do:\n * - run untrusted JavaScript in the WASM guest, under the interpreter's own\n * timeout and memory limits (`eval_script`),\n * - read and write the session's in-memory VFS, which is a map and not a\n * filesystem — no host path is reachable from it.\n *\n * What it cannot do: read or write a host path, spawn a process, reach the\n * network (`web_fetch`/`download`/`web_search` are capabilities, and none is\n * granted), deliver a file, or use an MCP server.\n *\n * Two things this helper does **not** do, because they are not a profile's to\n * decide. It does not authorize anyone — visibility is\n * `CreateSessionRequest.scope` plus the gateway's `authorizeSession`. And it\n * does not make the model's *input* trustworthy: content the loop reads is\n * attacker-influenced by default, and a sandbox bounds what a tool can reach,\n * not what a prompt can talk the model into asking for.\n *\n * @param name Profile name clients name in `CreateSessionRequest.profile`.\n * @param provider Which model to run (credentials stay in the operator's\n * environment and are resolved by the host's `createEngineRunner` — never\n * here, and never on the wire).\n */\nexport function sandboxedProviderProfile(\n name: string,\n provider: ProviderConfig,\n options: {\n description?: string\n /** Prepended to the session's system prompt. */\n instructions?: string\n /** Profile-level run defaults (model, permission mode) — see\n * {@link ProfileInfo.defaults}. */\n defaults?: ProfileInfo['defaults']\n /**\n * Capabilities to grant on top of the floor. Default `[]` — the floor is\n * nothing, and every entry here is a deliberate widening you are writing\n * down: `web_fetch` gives the loop egress (SSRF-guarded, but egress),\n * `download` and `web_search` reach whatever backends you injected, and\n * `deliver_file` lets it hand a file to the client.\n */\n capabilities?: SessionCapability[]\n /**\n * MCP servers, **by name**, whose tools sessions may use. Default `[]`.\n * MCP tools are authoritative — they run with the host's credentials and\n * are never bridged — so naming one here is a larger grant than any\n * capability above it.\n */\n mcpServers?: string[]\n } = {},\n): ProfileInfo {\n return {\n name,\n engine: 'provider',\n provider,\n description: options.description ?? 'Sandboxed: no host filesystem, no shell, no egress',\n defaults: options.defaults,\n session: {\n // Both arrays are load-bearing and neither may become `undefined`: absent\n // means \"no declaration\", which grants whatever the host happened to wire\n // — the opposite of what this profile promises. An explicit `[]` is the\n // floor; the options above raise it in the open.\n capabilities: options.capabilities ?? [],\n mcpServers: options.mcpServers ?? [],\n instructions: options.instructions,\n },\n }\n}\n","import {\n createEngineSession,\n type EngineSessionOptions,\n type HostToolDefinition,\n type LanguageModel,\n type McpConnection,\n type Runner,\n type ToolExecutor,\n type ToolSet,\n} from '@workerdeck/core'\nimport type { EngineRunnerContext } from '../options.ts'\n\nexport type ProviderRunnerOptions = {\n /**\n * The model to run. A function is called per turn with the session's\n * requested model id (undefined = the profile's default), which is what makes\n * the in-session model switcher work; a bare instance pins one model.\n */\n model: LanguageModel | ((modelId: string | undefined) => LanguageModel)\n /**\n * Where sandboxed tools (`eval_script` and any `sandboxed` entry in `tools`)\n * execute. This is a real architectural choice, not a default worth guessing\n * at, so it is required:\n *\n * - a {@link ToolExecutor} — an in-process guest (`new QuickJsExecutor(...)`\n * from `@workerdeck/core`), which is right when the data the loop reasons\n * over lives in this process. It is also the only option that works when no\n * client is attached, which is every unattended job.\n * - `'browser'` — the attached tab, resolved per call from the bridge. Right\n * when the data is *there* (a document the user is editing) and it should\n * not travel to the gateway at all. Note the trade: it hands an executor to\n * the party being sandboxed against, so its results are untrusted input.\n */\n executor: ToolExecutor | 'browser'\n /** Capability backends — the same shape {@link createEngineSession} takes.\n * Wiring one only offers it; the profile and request decide the grant. */\n capabilities?: EngineSessionOptions['capabilities']\n /** Host tools at explicit trust levels (`@workerdeck/core`'s `withHostTools`). */\n tools?: Record<string, HostToolDefinition>\n /** A live MCP connection from `connectMcpTools`. Prefer this over `mcpTools`:\n * it is what lets a profile's unhonoured `mcpServers` refuse the build, and\n * what makes `GET /sessions/:id/mcp` answer for this session. */\n mcp?: McpConnection\n /** A bare MCP tool set, for a host assembling one itself. */\n mcpTools?: ToolSet\n /** System-prompt addition, unless the profile declares its own. */\n instructions?: string\n /** Sandbox limits per execution. */\n executionLimits?: { timeoutMs?: number; memoryLimitBytes?: number }\n /** Scratch-filesystem seed for a new session. Ignored on a rehydration, so a\n * parked turn's files are never overwritten. */\n seedVfs?: Record<string, string>\n /** Release per-session resources: the MCP connection, an issued token, a\n * watcher. Runs on close **and on park** — parking releases the same things. */\n onClose?: () => void | Promise<void>\n}\n\n/**\n * Build a provider-engine runner from the server's `createEngineRunner` context.\n *\n * `createEngineRunner` is a blank sheet: it hands you a context and wants a\n * `Runner`, and four of the five things a correct one must do are invisible in\n * the types — forward `restore`, adopt `id`, seed the VFS only when *not*\n * restoring, and dispose per-session resources. Each is a runtime-only failure\n * (a woken session that starts empty, a refused rebuild, an overwritten\n * filesystem, a connection leaked per session), and each is handled here.\n *\n * ```ts\n * createEngineRunner: (ctx) =>\n * createProviderRunner(ctx, {\n * model: (id) => openai(id ?? 'gpt-5.6-luna'),\n * executor: quickjs,\n * capabilities: { webFetch: {} },\n * mcp,\n * onClose: () => mcp.close(),\n * }),\n * ```\n *\n * The hook itself stays open for anything this does not cover — this is the\n * 80% case, not a replacement for it.\n */\nexport async function createProviderRunner(\n ctx: EngineRunnerContext,\n options: ProviderRunnerOptions,\n): Promise<Runner> {\n const { config, profile, bridge, restore, id } = ctx\n const resolveModel = (modelId: string | undefined): LanguageModel =>\n typeof options.model === 'function' ? options.model(modelId) : options.model\n // The runner's id does not exist at assembly time, so a bridged executor has\n // to be resolved per call from the call's own sessionId.\n const executor: ToolExecutor =\n options.executor === 'browser'\n ? { dispatch: (call) => bridge.executorFor(call.sessionId).dispatch(call) }\n : options.executor\n\n return createEngineSession({\n config: {\n ...config,\n languageModel: resolveModel(config.model),\n // What makes a parked session come back as itself: same id, same event\n // log, same history, mid-task.\n restore,\n onClose: options.onClose,\n },\n // Set only when the gateway is rebuilding a session across a restart;\n // ignoring it strands every client's route and watermark.\n id,\n profile,\n resolveModel: (_profile, c) => resolveModel(c.model),\n selectExecutor: () => executor,\n backend: options.executor === 'browser' ? 'browser' : 'server',\n capabilities: options.capabilities,\n tools: options.tools,\n mcp: options.mcp,\n mcpTools: options.mcpTools,\n instructions: options.instructions,\n executionLimits: options.executionLimits,\n seedVfs: options.seedVfs,\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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"}
|