theokit 0.72.0 → 0.73.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/dist/{actions-virtual-module-WP47EKD7.js → actions-virtual-module-OHENNMQG.js} +4 -4
- package/dist/{actions-virtual-module-6LND2WHL.js → actions-virtual-module-QCDKQ6QD.js} +4 -4
- package/dist/adapters/agent-mount.js +2 -2
- package/dist/adapters/security-headers.d.ts +27 -11
- package/dist/adapters/security-headers.js.map +1 -1
- package/dist/{agent-KUP3XA2S.js → agent-OWJ5A7W5.js} +2 -2
- package/dist/{app-typed-client-IMRSVARP.js → app-typed-client-2PTK2SJH.js} +4 -4
- package/dist/{app-typed-client-6VIHSYJH.js → app-typed-client-ILLQYTXP.js} +4 -4
- package/dist/{aws-lambda-7AZNJGWU.js → aws-lambda-OP2L5RKZ.js} +3 -3
- package/dist/{build-4J24BAEC.js → build-CVP2XU5M.js} +5 -5
- package/dist/{bun-NKVHOFSJ.js → bun-CZXHPK2W.js} +4 -4
- package/dist/{chunk-KOG5K26V.js → chunk-2Z7TANA4.js} +2 -2
- package/dist/{chunk-IVGQMV3S.js → chunk-6U67SO5U.js} +2 -2
- package/dist/{chunk-V5SSTT3C.js → chunk-C43AHOHI.js} +1 -1
- package/dist/chunk-C43AHOHI.js.map +1 -0
- package/dist/{chunk-YWWMYEAC.js → chunk-CLSYYO5F.js} +2 -2
- package/dist/{chunk-BZ6DFSKG.js → chunk-D3M7LPHY.js} +20 -14
- package/dist/{chunk-BZ6DFSKG.js.map → chunk-D3M7LPHY.js.map} +1 -1
- package/dist/{chunk-JMN4COMK.js → chunk-D7STTNQN.js} +3 -3
- package/dist/{chunk-EZHDAVAF.js → chunk-EDBQM6RB.js} +27 -4
- package/dist/chunk-EDBQM6RB.js.map +1 -0
- package/dist/{chunk-QD5X56I3.js → chunk-FNJVSFEC.js} +1 -1
- package/dist/chunk-FNJVSFEC.js.map +1 -0
- package/dist/{chunk-LJNMSKFL.js → chunk-HDAILYVM.js} +2 -2
- package/dist/{chunk-RE2WE7CD.js → chunk-LOOOB46A.js} +2 -2
- package/dist/{chunk-7CHPOMP5.js → chunk-M3DT6EMK.js} +3 -3
- package/dist/chunk-M3DT6EMK.js.map +1 -0
- package/dist/{chunk-PEL3MJLM.js → chunk-MX63AWEK.js} +1 -1
- package/dist/{chunk-PEL3MJLM.js.map → chunk-MX63AWEK.js.map} +1 -1
- package/dist/{chunk-IB5VIXLR.js → chunk-R36YNWBU.js} +2 -2
- package/dist/{chunk-C5CAL3DX.js → chunk-SEGD3JUC.js} +1 -1
- package/dist/chunk-SEGD3JUC.js.map +1 -0
- package/dist/{chunk-4D4YVXHR.js → chunk-SYIQV4UH.js} +29 -2
- package/dist/{chunk-4D4YVXHR.js.map → chunk-SYIQV4UH.js.map} +1 -1
- package/dist/{chunk-RDG5GMDW.js → chunk-WRHT5NAJ.js} +20 -14
- package/dist/{chunk-RDG5GMDW.js.map → chunk-WRHT5NAJ.js.map} +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/client/index.d.ts +18 -1
- package/dist/client/index.js +15 -1
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-4M4XVNUH.js → cloudflare-2SVRRVV5.js} +3 -3
- package/dist/{deno-deploy-IVACZ62D.js → deno-deploy-OK4QFJIZ.js} +4 -4
- package/dist/{dev-JZVHEYN4.js → dev-B2IZEVAD.js} +6 -6
- package/dist/{index-inRdhi59.d.ts → index-CmkWlIDO.d.ts} +3 -1
- package/dist/index.js +5 -5
- package/dist/{internal-api-BNDRCEPZ.js → internal-api-5Y3MLZH4.js} +4 -4
- package/dist/{internal-api-3IZVNNGB.js → internal-api-6XNLCNI5.js} +4 -4
- package/dist/{mcp-HCXHATU3.js → mcp-GJQIREYC.js} +2 -2
- package/dist/{netlify-3HEWV375.js → netlify-IAC53FVZ.js} +3 -3
- package/dist/{observability-bootstrap-FP6LMMJE.js → observability-bootstrap-DOWRMADT.js} +2 -2
- package/dist/{preview-FSUPVADO.js → preview-XBP622HM.js} +3 -3
- package/dist/{registry-SDBODRU6.js → registry-L6JG75LY.js} +7 -7
- package/dist/server/http/index.d.ts +1 -1
- package/dist/server/http/index.js +5 -3
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +5 -3
- package/dist/server/index.js.map +1 -1
- package/dist/{server-boundary-WUKORSW2.js → server-boundary-E5GFVPLB.js} +4 -4
- package/dist/{server-boundary-P52I6YMH.js → server-boundary-IFIYNGTA.js} +4 -4
- package/dist/{start-LCA67RES.js → start-JGNBTTG2.js} +7 -7
- package/dist/{vercel-5PMYEEKF.js → vercel-EHAOKPF7.js} +3 -3
- package/dist/vite-plugin/index.js +5 -5
- package/dist/{vite-plugin-WFA7NPYK.js → vite-plugin-54XIRMKT.js} +6 -6
- package/package.json +4 -4
- package/dist/chunk-7CHPOMP5.js.map +0 -1
- package/dist/chunk-C5CAL3DX.js.map +0 -1
- package/dist/chunk-EZHDAVAF.js.map +0 -1
- package/dist/chunk-QD5X56I3.js.map +0 -1
- package/dist/chunk-V5SSTT3C.js.map +0 -1
- /package/dist/{actions-virtual-module-WP47EKD7.js.map → actions-virtual-module-OHENNMQG.js.map} +0 -0
- /package/dist/{actions-virtual-module-6LND2WHL.js.map → actions-virtual-module-QCDKQ6QD.js.map} +0 -0
- /package/dist/{agent-KUP3XA2S.js.map → agent-OWJ5A7W5.js.map} +0 -0
- /package/dist/{app-typed-client-IMRSVARP.js.map → app-typed-client-2PTK2SJH.js.map} +0 -0
- /package/dist/{app-typed-client-6VIHSYJH.js.map → app-typed-client-ILLQYTXP.js.map} +0 -0
- /package/dist/{aws-lambda-7AZNJGWU.js.map → aws-lambda-OP2L5RKZ.js.map} +0 -0
- /package/dist/{build-4J24BAEC.js.map → build-CVP2XU5M.js.map} +0 -0
- /package/dist/{bun-NKVHOFSJ.js.map → bun-CZXHPK2W.js.map} +0 -0
- /package/dist/{chunk-KOG5K26V.js.map → chunk-2Z7TANA4.js.map} +0 -0
- /package/dist/{chunk-IVGQMV3S.js.map → chunk-6U67SO5U.js.map} +0 -0
- /package/dist/{chunk-YWWMYEAC.js.map → chunk-CLSYYO5F.js.map} +0 -0
- /package/dist/{chunk-JMN4COMK.js.map → chunk-D7STTNQN.js.map} +0 -0
- /package/dist/{chunk-LJNMSKFL.js.map → chunk-HDAILYVM.js.map} +0 -0
- /package/dist/{chunk-RE2WE7CD.js.map → chunk-LOOOB46A.js.map} +0 -0
- /package/dist/{chunk-IB5VIXLR.js.map → chunk-R36YNWBU.js.map} +0 -0
- /package/dist/{cloudflare-4M4XVNUH.js.map → cloudflare-2SVRRVV5.js.map} +0 -0
- /package/dist/{deno-deploy-IVACZ62D.js.map → deno-deploy-OK4QFJIZ.js.map} +0 -0
- /package/dist/{dev-JZVHEYN4.js.map → dev-B2IZEVAD.js.map} +0 -0
- /package/dist/{internal-api-BNDRCEPZ.js.map → internal-api-5Y3MLZH4.js.map} +0 -0
- /package/dist/{internal-api-3IZVNNGB.js.map → internal-api-6XNLCNI5.js.map} +0 -0
- /package/dist/{mcp-HCXHATU3.js.map → mcp-GJQIREYC.js.map} +0 -0
- /package/dist/{netlify-3HEWV375.js.map → netlify-IAC53FVZ.js.map} +0 -0
- /package/dist/{observability-bootstrap-FP6LMMJE.js.map → observability-bootstrap-DOWRMADT.js.map} +0 -0
- /package/dist/{preview-FSUPVADO.js.map → preview-XBP622HM.js.map} +0 -0
- /package/dist/{registry-SDBODRU6.js.map → registry-L6JG75LY.js.map} +0 -0
- /package/dist/{server-boundary-WUKORSW2.js.map → server-boundary-E5GFVPLB.js.map} +0 -0
- /package/dist/{server-boundary-P52I6YMH.js.map → server-boundary-IFIYNGTA.js.map} +0 -0
- /package/dist/{start-LCA67RES.js.map → start-JGNBTTG2.js.map} +0 -0
- /package/dist/{vercel-5PMYEEKF.js.map → vercel-EHAOKPF7.js.map} +0 -0
- /package/dist/{vite-plugin-WFA7NPYK.js.map → vite-plugin-54XIRMKT.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/contracts/client-safe-error.ts","../src/server/http/send-response.ts","../src/server/http/node-request.ts","../src/server/http/controller-dispatch.ts","../src/server/security/csrf-warn-dispatch.ts","../src/server/observability/audit-log.ts","../src/server/security/csrf.ts"],"sourcesContent":["import type { TheoErrorEnvelope } from './error-envelope.js'\n\n/**\n * What an error is allowed to tell the caller.\n *\n * Most error codes describe something the caller did and can fix, so their message is the useful\n * part of the response. An *internal* failure is the opposite: its message describes the server —\n * a connection string, an upstream host, a stack of internal names — and the caller can act on\n * none of it. In production it is redacted; in development it is exactly what makes the framework\n * debuggable, so it stays.\n *\n * This lives in one place because it was previously stated in two and missing from a third. The\n * Node runner redacted, the Web error builder redacted, and an exception escaping a Web handler\n * took a hand-built path that did neither — same route, same failure, more disclosure depending\n * on which transport served it. That is the \"one contract, three transports\" rule in\n * `docs/program/three-target-parity.md` being broken by duplication rather than by design.\n */\n\n/** Both spellings the codebase uses for \"this is our fault, and the detail is ours too\". */\nconst INTERNAL_CODES: ReadonlySet<string> = new Set(['INTERNAL_ERROR', 'INTERNAL_SERVER_ERROR'])\n\nconst GENERIC_INTERNAL_MESSAGE = 'Internal server error'\n\nfunction redacts(code: string): boolean {\n return INTERNAL_CODES.has(code) && process.env.NODE_ENV === 'production'\n}\n\n/** The message this code may carry to the caller. */\nexport function clientSafeErrorMessage(code: string, message: string): string {\n return redacts(code) ? GENERIC_INTERNAL_MESSAGE : message\n}\n\n/**\n * The envelope this code may carry to the caller.\n *\n * When it redacts, `cause`, `meta` and `ext` go with the message rather than being filtered\n * field by field: they exist to describe the failure, and the whole point is that this failure is\n * not describable to the caller. Keeping the code is what lets a client branch on it.\n */\nexport function clientSafeErrorEnvelope(envelope: TheoErrorEnvelope): TheoErrorEnvelope {\n if (!redacts(envelope.code)) return envelope\n return { code: envelope.code, message: GENERIC_INTERNAL_MESSAGE }\n}\n","import type { ServerResponse } from 'node:http'\n\nimport { clientSafeErrorMessage } from '../../core/contracts/client-safe-error.js'\nimport type { TheoTransformer } from '../transformer.js'\n\n/**\n * Canonical HTTP response helpers (T5.1 extraction).\n *\n * Moved out of execute.ts so request-pipeline stages (execute-stages.ts,\n * handle-request-error.ts, etc.) can depend on these helpers without\n * creating a cycle through execute.ts.\n *\n * Public surface re-exported from execute.ts for backward compat — every\n * existing caller of `sendError` / `sendJson` continues to work via the\n * `theokit/server` barrel.\n */\n\nexport function sendJson(\n res: ServerResponse,\n data: unknown,\n status = 200,\n transformer?: TheoTransformer,\n): void {\n // T1.2 — transformer-aware serialization. Default (no transformer) uses\n // JSON.stringify direct for backward compat.\n const body = transformer ? transformer.serialize(data) : JSON.stringify(data)\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n}\n\n/** Render anything that would end the log line as a visible escape, so one call logs one line. */\nfunction oneLine(value: string): string {\n return value.replace(/[\\r\\n]/g, '\\\\n')\n}\n\nexport interface SendErrorOptions {\n custom404Html?: string\n custom500Html?: string\n}\n\n/**\n * Canonical error response.\n *\n * T6.3 (PV-17): the positional 7-param signature is preserved for backward\n * compat. New call sites should use the options-bag form:\n *\n * sendError(res, { code, message, status, issues?, requestId?, options? })\n *\n * Both shapes resolve to the same implementation.\n */\nexport interface SendErrorInput {\n code: string\n message: string\n status: number\n issues?: unknown[]\n requestId?: string\n options?: SendErrorOptions\n}\n\nexport function sendError(res: ServerResponse, input: SendErrorInput): void\n/* eslint-disable-next-line max-params -- T6.3: positional overload preserved\n for backward compat (callers across cli/server still use positional). The\n options-bag overload above is the recommended path. */\nexport function sendError(\n res: ServerResponse,\n code: string,\n message: string,\n status: number,\n issues?: unknown[],\n requestId?: string,\n options?: SendErrorOptions,\n): void\n/* eslint-disable-next-line max-params -- delegates to two surface overloads above; the parameter\n count mirrors the back-compat contract, not internal complexity. The `complexity` half of this\n suppression went away when the redaction rule stopped being restated inline. */\nexport function sendError(\n res: ServerResponse,\n codeOrInput: string | SendErrorInput,\n message?: string,\n status?: number,\n issues?: unknown[],\n requestId?: string,\n options?: SendErrorOptions,\n): void {\n let code: string\n if (typeof codeOrInput === 'string') {\n code = codeOrInput\n message = message ?? ''\n status = status ?? 500\n } else {\n code = codeOrInput.code\n message = codeOrInput.message\n status = codeOrInput.status\n issues = codeOrInput.issues\n requestId = codeOrInput.requestId\n options = codeOrInput.options\n }\n const errorMessage = clientSafeErrorMessage(code, message)\n\n if (code === 'INTERNAL_ERROR') {\n // One log entry per call, whatever the message contains. An exception message can be built\n // from request data and can therefore carry a newline; unescaped, that lets a caller append\n // whatever lines it likes to the log — including a plausible entry attributed to something\n // else (CodeQL `js/log-injection`).\n console.error(`[${oneLine(requestId ?? 'no-id')}] ${oneLine(message)}`)\n }\n\n if (status === 404 && options?.custom404Html) {\n const body = options.custom404Html\n res.writeHead(404, {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n return\n }\n if (status === 500 && options?.custom500Html) {\n const body = options.custom500Html\n res.writeHead(500, {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n return\n }\n\n sendJson(\n res,\n {\n error: {\n code,\n message: errorMessage,\n ...(requestId ? { requestId } : {}),\n ...(issues ? { issues } : {}),\n },\n },\n status,\n )\n}\n\n/**\n * T5a.2 Phase G slice 4/N — Web-Standards response helpers.\n *\n * Mirror of `sendJson` + `sendError` for the Web `Request`/`Response`\n * shape. Returns a native `Response` directly instead of mutating a\n * `ServerResponse`.\n *\n * v1.0 § Phase G.\n *\n * **Difference vs IncomingMessage path:**\n * - No `Content-Length` set explicitly — the runtime computes it from\n * the body when needed. CF Workers / Bun / Deno all do this; setting\n * it manually risks conflict if the body is a stream rather than a\n * fixed string.\n * - Custom 404/500 HTML options preserved (same opts shape).\n * - `requestId` flows into the response body's error envelope AND\n * surfaces as `x-request-id` header (parity with handleWebRequestError\n * Phase G slice 3/N).\n */\nexport function buildJsonResponse(\n data: unknown,\n status = 200,\n transformer?: TheoTransformer,\n): Response {\n const body = transformer ? transformer.serialize(data) : JSON.stringify(data)\n return new Response(body, {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport function buildErrorResponse(input: SendErrorInput): Response {\n const { code, message, status, issues, requestId, options } = input\n const errorMessage = clientSafeErrorMessage(code, message)\n\n if (code === 'INTERNAL_ERROR') {\n // One log entry per call, whatever the message contains. An exception message can be built\n // from request data and can therefore carry a newline; unescaped, that lets a caller append\n // whatever lines it likes to the log — including a plausible entry attributed to something\n // else (CodeQL `js/log-injection`).\n console.error(`[${oneLine(requestId ?? 'no-id')}] ${oneLine(message)}`)\n }\n\n const headers: Record<string, string> = {}\n if (requestId !== undefined) headers['x-request-id'] = requestId\n\n if (status === 404 && options?.custom404Html !== undefined) {\n headers['content-type'] = 'text/html; charset=utf-8'\n return new Response(options.custom404Html, { status: 404, headers })\n }\n if (status === 500 && options?.custom500Html !== undefined) {\n headers['content-type'] = 'text/html; charset=utf-8'\n return new Response(options.custom500Html, { status: 500, headers })\n }\n\n headers['content-type'] = 'application/json'\n const body = JSON.stringify({\n error: {\n code,\n message: errorMessage,\n ...(requestId ? { requestId } : {}),\n ...(issues ? { issues } : {}),\n },\n })\n return new Response(body, { status, headers })\n}\n","/**\n * Pure Node `IncomingMessage` → Web `Request` converters.\n *\n * Kept free of any dependency on `web-handler.js` / `execute*.js` so the\n * executor (`execute.ts`) can build the handler-facing Web `Request` without\n * pulling the Web dispatch pipeline into its import graph (ADR-0028 R3a — the\n * Node adapter is the ONLY place IncomingMessage ↔ Request conversion lives;\n * these are the primitive converters it and the executor share).\n */\nimport type { IncomingMessage } from 'node:http'\nimport { Readable } from 'node:stream'\n\n/** Pick the first usable string from Node's `string | string[] | undefined` headers. */\nfunction pickHeaderString(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n for (const v of value) if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n\n/** Web Request requires an absolute URL; synthesize one from the Host header. */\nfunction synthesizeAbsoluteUrl(req: IncomingMessage): string {\n const host = pickHeaderString(req.headers.host) ?? 'localhost'\n return `http://${host}${req.url ?? '/'}`\n}\n\n/**\n * Collapse Node's `string | string[]` headers into a Web `Headers`. Repeated\n * headers are comma-joined (not `.append`ed) because `Headers.append` creates\n * multi-value entries that behave differently on `.get()` (EC-1).\n */\nfunction nodeHeadersToWeb(req: IncomingMessage): Headers {\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n headers.set(key, value.join(', '))\n } else {\n headers.set(key, value)\n }\n }\n return headers\n}\n\n/**\n * Build a Web `Request` from a Node `IncomingMessage`, body included. The Web\n * Request spec requires an absolute URL; we synthesize one from the Host header\n * (fallback `localhost` for test doubles).\n *\n * For methods with a body (POST/PUT/PATCH/DELETE), the Node Readable stream is\n * wrapped as a Web ReadableStream via `Readable.toWeb()` so downstream consumers\n * can call `request.json()` / `request.formData()` / `request.text()` natively.\n */\nexport function incomingMessageToWebRequest(req: IncomingMessage): Request {\n const url = synthesizeAbsoluteUrl(req)\n const headers = nodeHeadersToWeb(req)\n\n const method = (req.method ?? 'GET').toUpperCase()\n const hasBody = method !== 'GET' && method !== 'HEAD'\n\n if (!hasBody) {\n return new Request(url, { method, headers })\n }\n\n // Drain Node's Readable into a Web ReadableStream. `Readable.toWeb` is\n // available in Node 18+ (theokit's engines.node floor is 22+, so safe).\n const webStream = Readable.toWeb(req) as ReadableStream\n return new Request(url, {\n method,\n headers,\n body: webStream,\n // EC-2: Node 18+ requires `duplex: 'half'` when body is a stream.\n // The `RequestInit` type omits it (Web spec gap); cast accordingly.\n ...({ duplex: 'half' } as { duplex: 'half' }),\n })\n}\n\n/**\n * A Node request that has NOT been converted yet — method now, body only if someone claims it.\n *\n * theokit#400. `incomingMessageToWebRequest` drains the Node stream, and a stream drains once. A\n * dispatcher that converts in order to decide whether it owns a path has already spent the body on\n * every path it does not own: the next branch attaches to a readable that has already ended, waits\n * for an `'end'` that cannot fire twice, and the request hangs with no status at all.\n *\n * The fix is an ordering one, so the type encodes the ordering: a router reads `method` (free) and\n * calls `toRequest()` only after it has decided the request is its own. Passing the source instead\n * of a `Request` is what makes \"did you convert before deciding?\" answerable by reading a signature.\n *\n * `toRequest()` memoizes, because a second conversion of the same `IncomingMessage` yields a\n * Request whose body is an empty closed stream — a silent truncation, which is worse than the hang\n * it would replace.\n */\nexport interface WebRequestSource {\n /** Uppercase HTTP method. Available without touching the body. */\n readonly method: string\n /** Convert on demand. Idempotent: repeated calls return the same `Request`. */\n toRequest: () => Request\n}\n\n/** Wrap `req` as a {@link WebRequestSource} — the conversion is deferred and memoized. */\nexport function createWebRequestSource(req: IncomingMessage): WebRequestSource {\n let converted: Request | undefined\n return {\n method: (req.method ?? 'GET').toUpperCase(),\n toRequest: () => (converted ??= incomingMessageToWebRequest(req)),\n }\n}\n\n/**\n * Build the Web `Request` handed to a route handler as `ctx.request` in the\n * Node server path (dev + `theokit start`). Method + absolute URL + headers\n * only — NO body.\n *\n * Why no body: the Node executor parses the request body BEFORE the handler\n * runs and exposes the parsed value as `ctx.body` (the typed, documented body\n * API). By the time the handler is called the Node stream is already drained,\n * so re-wrapping it would yield an empty/closed stream. Handlers read the body\n * via `ctx.body`; `ctx.request` is for the Web-standard header/cookie/URL/method\n * surface (e.g. `createSessionManagerWeb.getSession(ctx.request)`).\n *\n * Per ADR-0028 R3a, handlers see a Web `Request` in every runtime — this closes\n * the gap where the Node path leaked the raw `IncomingMessage` (whose `.headers`\n * is a plain object, so `.headers.get(...)` threw for Web-standard consumers).\n */\nexport function incomingMessageToHandlerRequest(req: IncomingMessage): Request {\n return new Request(synthesizeAbsoluteUrl(req), {\n method: (req.method ?? 'GET').toUpperCase(),\n headers: nodeHeadersToWeb(req),\n })\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Controller files are walked from the developer's `serverDir/controllers`\n * (a build-time config path), never from HTTP input. No injection vector.\n */\nimport { readdirSync, type Dirent } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { join } from 'node:path'\n\nimport {\n CONTROLLER_PREFIX,\n createDecoratorHandler,\n getMeta,\n isControllerClass,\n Reflector,\n type ServeAgent,\n} from '@theokit/http'\n\nimport type { PluginContext } from '../plugin-types.js'\nimport type { PluginRunner } from '../plugins/plugin-runner.js'\nimport { dispatchCsrfWarn } from '../security/csrf-warn-dispatch.js'\nimport { enforceCsrf, type DisallowedConfig } from '../security/csrf.js'\n\nimport { incomingMessageToWebRequest } from './node-request.js'\nimport { sendError } from './send-response.js'\n\n/** A decorator controller constructor (`@Controller` class). */\ntype ControllerClass = new (...args: never[]) => object\n\n/** Loads a controller module by absolute path. In dev this is Vite's `ssrLoadModule`\n * (the Task 1.1 swc transform has already compiled the parameter decorators); tests\n * inject `@theokit/http`'s `loadControllerWithSwc`. */\nexport type ControllerModuleLoader = (absPath: string) => Promise<Record<string, unknown>>\n\n/** A built controller route table exposed as a pure Web-Standard handler. */\ninterface ControllerDispatcher {\n /** `null` = no controller route matched — the host owns the miss (404 / fall-through). */\n dispatch(request: Request): Promise<Response | null>\n /** Non-executing route probe — true when a controller route owns `method` + `pathname`. */\n matches(method: string, pathname: string): boolean\n /** True when the controller owning `pathname` declared `theokit:csrf-exempt`. */\n isCsrfExempt(pathname: string): boolean\n}\n\n// State-mutating methods get CSRF, mirroring the file-route pipeline (execute.ts).\nconst CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/**\n * The plugin lifecycle a controller route runs, or a do-nothing stand-in when there is none.\n *\n * A null object rather than an optional runner threaded through four call sites: with `undefined`\n * every stage reads `if (runner && ctx)` and the dispatcher's branch count carries a question that\n * was already answered once. Here the question is answered when the object is built, and the\n * stages below say what they do instead of re-deciding whether to do it.\n *\n * `shortCircuited` from the inert one is always `false`, which is the truth: no hook answered\n * because there were no hooks.\n */\ninterface ControllerLifecycle {\n onRequest(): Promise<boolean>\n preHandler(): Promise<boolean>\n onError(error: unknown): Promise<void>\n onResponse(): Promise<void>\n}\n\nconst INERT_LIFECYCLE: ControllerLifecycle = {\n onRequest: () => Promise.resolve(false),\n preHandler: () => Promise.resolve(false),\n onError: () => Promise.resolve(),\n onResponse: () => Promise.resolve(),\n}\n\n/**\n * Build the lifecycle for a request a controller OWNS (usetheokit/theokit#607).\n *\n * @param runner - absent for an app that declares no plugins.\n * @param owned - `dispatcher.matches(...)`, the non-executing probe. False means this dispatcher\n * is about to decline the path, and a hook fired here would run on a request the host is going\n * to serve some other way — which is how a hook ends up firing twice for one request.\n */\nfunction controllerLifecycle(\n runner: PluginRunner | undefined,\n owned: boolean,\n ctx: PluginContext,\n): ControllerLifecycle {\n if (runner === undefined || !owned) return INERT_LIFECYCLE\n return {\n async onRequest() {\n runner.applyDecorations(ctx.ctx)\n return (await runner.runOnRequest(ctx)).shortCircuited\n },\n async preHandler() {\n return (await runner.runPreHandler(ctx)).shortCircuited\n },\n async onError(error: unknown) {\n await runner.runOnError(ctx, error)\n },\n async onResponse() {\n await runner.runOnResponse(ctx)\n },\n }\n}\n\n/**\n * A controller declaring that it authenticates by other means, so the CSRF gate has nothing to add.\n *\n * The case this exists for is a webhook. Stripe, GitHub and every other sender authenticate with an\n * HMAC over the request body — stronger than a header, and entirely unrelated to one. None of them\n * will ever send `X-Theo-Action`, so without this a webhook endpoint answers 403 to every real\n * delivery and the only escape is `csrf: 'warn'` for the whole application (theokit#535).\n *\n * DELIBERATELY separate from `theokit:public`. They answer different questions — \"may an\n * unauthenticated caller reach this?\" and \"does this route authenticate by other means?\" — and a\n * route can want the first without the second. Conflating them would lift the gate off every public\n * route in the ecosystem as a side effect of a webhook fix.\n *\n * Declared on the CONTROLLER, not the method: the granularity a webhook needs, and it avoids a\n * second path matcher alongside `handle.matches` that could drift from it.\n */\nconst CSRF_EXEMPT_METADATA = 'theokit:csrf-exempt'\n\n/**\n * Segment-wise, so `api/hooks` never matches `api/hooks-admin`.\n *\n * Split-and-filter rather than a trimming regex: `/^\\/+|\\/+$/` is quadratic on a pathname of\n * repeated slashes, and a pathname is attacker-supplied (sonarjs/slow-regex). Splitting is linear\n * and `filter(Boolean)` drops the empty segments the leading and trailing slashes produce, which is\n * all the trim was for.\n */\nfunction segments(value: string): string[] {\n return value.split('/').filter(Boolean)\n}\n\nfunction pathOwnedByPrefix(pathname: string, prefix: string): boolean {\n const want = segments(prefix)\n const got = segments(pathname)\n if (want.length === 0 || got.length < want.length) return false\n return want.every((segment, i) => got[i] === segment)\n}\n\n/** Recursively collect `*.controller.ts` files under `dir` (absolute paths). */\n/**\n * Every `*.controller.ts` under `dir`, recursively. Exported for theokit#123: the build emitter\n * must find exactly the same set the dev dispatcher does, and two independent walks would be two\n * definitions of \"a controller\" that drift.\n */\nexport function findControllerFiles(dir: string): string[] {\n const found: string[] = []\n const walk = (current: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(current, { withFileTypes: true })\n } catch {\n return // dir doesn't exist — no controllers\n }\n for (const entry of entries) {\n const full = join(current, entry.name)\n if (entry.isDirectory()) walk(full)\n // theokit#123 — `.mjs` alongside `.ts`. Dev walks the SOURCE tree; production walks the\n // COMPILED tree under `dist/controllers`, where the same files exist as `*.controller.mjs`.\n // One walk for both keeps a single definition of \"a controller file\"; two would drift, and a\n // file that counts in dev and not in production is exactly the dev/prod split this fixes.\n else if (entry.name.endsWith('.controller.ts') || entry.name.endsWith('.controller.mjs'))\n found.push(full)\n }\n }\n walk(dir)\n return found\n}\n\n/** A discovered controller: its source file + the loaded `@Controller` class. */\ninterface ControllerModule {\n filePath: string\n cls: ControllerClass\n /**\n * The module's full export namespace — theokit#124.\n *\n * Kept alongside the class because it is the ONLY place a `@Body(schema)` regains a name. The\n * schema on `WalkResult.bodySchema` is a runtime `z.ZodType` with no source identifier, but it is\n * the very object this module exported, so matching it back by reference identity recovers the\n * exported name the typed-client codegen needs to write `z.infer<typeof ...>`.\n *\n * Unused by the dispatch path, which needs only the class.\n */\n exports: Readonly<Record<string, unknown>>\n}\n\n/**\n * Load every `@Controller` class under `controllersDir` via the injected loader,\n * keeping each class paired with its source file (needed by the typed-client\n * codegen to emit `import type { X } from '<file>'`). Non-controller exports are\n * ignored (`isControllerClass` — reused from @theokit/http).\n */\nexport async function scanControllerModules(\n controllersDir: string,\n loadModule: ControllerModuleLoader,\n): Promise<ControllerModule[]> {\n const files = findControllerFiles(controllersDir)\n const modules: ControllerModule[] = []\n for (const filePath of files) {\n const mod = await loadModule(filePath)\n for (const exported of Object.values(mod)) {\n if (typeof exported === 'function' && isControllerClass(exported)) {\n modules.push({ filePath, cls: exported as ControllerClass, exports: mod })\n }\n }\n }\n return modules\n}\n\n/** Load every `@Controller` class under `controllersDir` (classes only). */\nasync function scanControllers(\n controllersDir: string,\n loadModule: ControllerModuleLoader,\n): Promise<ControllerClass[]> {\n const modules = await scanControllerModules(controllersDir, loadModule)\n return modules.map((m) => m.cls)\n}\n\n/**\n * Scan `controllersDir` and build a Web-Standard dispatcher over the decorator\n * controllers found. Returns `null` when the directory has no controllers, so\n * the host can skip the controller path entirely (zero cost for routes-only apps).\n *\n * Dispatch REUSES @theokit/http's `createDecoratorHandler` (match + `@Param`\n * binding + `@Body` validation + Response building) — never re-implemented (ADR-1).\n */\nexport async function createControllerDispatcher(opts: {\n controllersDir: string\n loadModule: ControllerModuleLoader\n /** M47 — serves `@Expose`-bound agent routes (theo supplies a `mountAgent`-backed impl). */\n serveAgent?: ServeAgent\n}): Promise<ControllerDispatcher | null> {\n const classes = await scanControllers(opts.controllersDir, opts.loadModule)\n if (classes.length === 0) return null\n // No `undeclaredRoutes` pass-through, deliberately (usetheokit/theokit#576). The handler defaults\n // to `'deny'`, and a theokit app cannot ship an undeclared controller anyway — `theokit build`\n // refuses one (#514). What this closes is `theokit dev`, which never runs that gate: an undeclared\n // route now answers 403 there, at the first request, instead of being served until the build\n // catches it. An escape here would only let an app defer a failure it cannot ship past.\n const handle = createDecoratorHandler({ controllers: classes, serveAgent: opts.serveAgent })\n const reflector = new Reflector()\n\n // Read once at construction: the metadata cannot change between requests, and re-walking every\n // class per request would put a reflection pass on the hot path for a value that never moves.\n const exemptPrefixes = classes\n // `Reflector`, not `Reflect.getMetadata`: the global is only typed where `reflect-metadata`\n // has been imported, and this module does not import it — the dts build fails on it (TS2339).\n // `@SetMetadata` writes through the same store, so the reader is the framework's own.\n .filter((cls) => reflector.getByKey<boolean>(CSRF_EXEMPT_METADATA, cls) === true)\n .map((cls) => {\n const meta = getMeta<{ prefix?: string }>(CONTROLLER_PREFIX, cls)\n return meta?.prefix ?? ''\n })\n .filter((prefix) => prefix !== '')\n\n return {\n dispatch: (request) => handle(request),\n matches: (method, pathname) => handle.matches(method, pathname),\n isCsrfExempt: (pathname) => exemptPrefixes.some((p) => pathOwnedByPrefix(pathname, p)),\n }\n}\n\n/**\n * Write a controller's Web `Response` to a Node response, byte for byte.\n *\n * The body used to be read with `await response.text()`, under a comment asserting that controllers\n * never stream. The comment was true of this path and hid the real problem: `text()` decodes as\n * UTF-8, so every byte >= 0x80 became `U+FFFD`. A payload of all 256 byte values arrived as 512\n * bytes; a 55 296-byte MPEG from `@theokit/plugin-voice` arrived as 76 790 bytes beginning\n * `ef bf bd`. Status 200, correct content-type, plausible length — invisible until someone opens\n * the file. File routes were never affected: `executeRoute` pumps the stream, so this was a silent\n * divergence between two paths meant to be at parity.\n *\n * `arrayBuffer()` fixes it without changing anything else. This path stays BUFFERED, exactly as\n * before — a controller returning a streamed body still has it collected here, so a plugin that\n * promises progressive delivery does not get it through a controller. That is a real limitation and\n * a separate change: making it stream alters when bytes reach the client, which is behaviour beyond\n * the corruption this repairs.\n */\nasync function writeControllerResponse(res: ServerResponse, response: Response): Promise<void> {\n const headersBag: Record<string, string> = {}\n for (const [k, v] of response.headers) {\n if (k.toLowerCase() !== 'set-cookie') headersBag[k] = v\n }\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) res.setHeader('Set-Cookie', setCookies)\n res.writeHead(response.status, headersBag)\n // `Buffer.from(ArrayBuffer)` views the bytes as they are. Any string in between is a decode, and\n // a decode of arbitrary bytes is a loss.\n const body = response.body ? Buffer.from(await response.arrayBuffer()) : undefined\n res.end(body !== undefined && body.length > 0 ? body : undefined)\n}\n\n/**\n * The `api-middleware` fall-through in one call: scan `controllersDir`, build the\n * dispatcher, and serve the request. Builds a body-ful Web `Request` (`@Body`\n * needs the body; the raw stream is undrained at a route miss) and enforces CSRF\n * with the SAME gate file routes use (parity). Returns `true` when a controller\n * handled it (or CSRF blocked it), `false` when there are no controllers OR none\n * matched (the host continues to its own 404). Built per-miss so controller edits\n * reflect via HMR.\n */\nexport async function dispatchControllerRequest(args: {\n controllersDir: string\n loadModule: ControllerModuleLoader\n req: IncomingMessage\n res: ServerResponse\n csrfMode: 'off' | 'warn' | 'strict'\n disallowed?: DisallowedConfig\n requestId: string\n /** M47 — serves `@Expose`-bound agent routes (mountAgent-backed); omit for routes-only apps. */\n serveAgent?: ServeAgent\n /**\n * usetheokit/theokit#607 — the plugin lifecycle a controller route runs.\n *\n * This parameter did not exist, so neither caller could pass one, so a `@Controller` route ran\n * NO hook in either surface: `theokit start` gave it nothing at all, and `theokit dev` gave it\n * only the `onRequest` its middleware happened to fire before matching. An adopter's identity\n * plugin was therefore dead while the boot log reported it registered, and a rate limiter written\n * as a `preHandler` enforced nothing while reading exactly like protection.\n *\n * Omit it for an app with no plugins — the path then costs one `undefined` check.\n */\n pluginRunner?: PluginRunner\n}): Promise<boolean> {\n const { req, res, csrfMode, disallowed, requestId, pluginRunner } = args\n const dispatcher = await createControllerDispatcher({\n controllersDir: args.controllersDir,\n loadModule: args.loadModule,\n serveAgent: args.serveAgent,\n })\n if (!dispatcher) return false\n\n const method = (req.method ?? 'GET').toUpperCase()\n const webRequest = incomingMessageToWebRequest(req)\n const pathname = new URL(webRequest.url).pathname\n\n /**\n * Whether a controller route owns this path, decided WITHOUT executing anything.\n *\n * The same non-executing probe the CSRF gate below already used, hoisted because the plugin\n * lifecycle needs the identical answer. Every hook is gated on it: this function runs for every\n * unmatched `/api/*` url, so firing a hook before knowing the path is ours would run the\n * lifecycle on requests this dispatcher is about to decline — and the host runs its own for\n * those (`api-middleware.ts`, the \"nobody owns it\" arm).\n */\n const owned = dispatcher.matches(method, pathname)\n const lifecycle = controllerLifecycle(pluginRunner, owned, {\n request: webRequest,\n response: res,\n ctx: {},\n requestId,\n })\n\n // #607 — onRequest BEFORE the CSRF gate, mirroring `executeRoute`: a hook that establishes\n // identity must have run before anything decides whether to refuse the caller.\n if (await lifecycle.onRequest()) return true\n\n // CSRF parity: enforce ONLY when a protected-method controller route actually\n // owns this path (probe with the non-executing matcher — never double-dispatch,\n // which would run the handler + its side effects). An unrouted path falls\n // through to the host's own 404, not a 403.\n if (CSRF_PROTECTED_METHODS.has(method) && owned && !dispatcher.isCsrfExempt(pathname)) {\n const decision = enforceCsrf(\n req,\n csrfMode,\n { warn: dispatchCsrfWarn, path: req.url },\n disallowed,\n )\n if (!decision.allow) {\n sendError(\n res,\n 'CSRF_INVALID',\n decision.reason ?? 'CSRF check failed',\n 403,\n undefined,\n requestId,\n )\n return true\n }\n }\n\n // #607 — preHandler after the CSRF gate and immediately before the handler, the position it\n // holds in `execute.ts` for a file route. A hook that answers here stops the pipeline.\n if (await lifecycle.preHandler()) return true\n\n let response: Response | null\n try {\n response = await dispatcher.dispatch(webRequest)\n } catch (err) {\n // Fail loud: the hook observes the failure and the error keeps rising to the host, which owns\n // the 500 envelope. Swallowing it here would give plugins a view the caller does not have.\n await lifecycle.onError(err)\n throw err\n }\n if (response === null) return false\n await writeControllerResponse(res, response)\n\n // After the response is written, as `executeRoute` and `serveThroughPluginLifecycle` both do.\n await lifecycle.onResponse()\n return true\n}\n","/**\n * Canonical CSRF warn dispatcher (T3.3 of architecture-review-remediation-plan).\n *\n * Consolidates the duplicated `warn: (payload) => { warnOnce(...) }` closure\n * that previously appeared in both `http/execute.ts` and\n * `http/action-execute.ts`. Resolves PV-10 (DRY).\n *\n * `warnOnce` dedupes by `event:method:path` so a request loop with 1000 POSTs\n * doesn't flood logs with identical warnings. Apps grep for `event\":\"csrf.warn\"`\n * (stable event shape — see [[enforcement-cutover.md]]).\n */\nimport { warnOnce } from '../observability/logger.js'\n\ninterface CsrfWarnPayload {\n event: string\n method: string\n path?: string\n reason: string\n code?: string\n docsUrl?: string\n warnOnce?: boolean\n}\n\n/**\n * Build the warn callback that `enforceCsrf` invokes for soft-mode warnings.\n * Returned function is suitable for the `warn` field of `enforceCsrf`'s options.\n */\nexport function dispatchCsrfWarn(payload: CsrfWarnPayload): void {\n const key = `${payload.event}:${payload.method}:${payload.path ?? ''}`\n warnOnce(key, payload as unknown as Record<string, unknown>)\n}\n","/**\n * T4.1 — Audit logging interface + default JSON stdout sink.\n *\n * Per ADR D4: define the interface; ship a zero-dep default; reserve\n * adapter shapes for Postgres, File, OpenTelemetry, Sentry as follow-up\n * packages. Persistence has heavy deps (`pg`, `better-sqlite3`); we\n * keep core dep-free and let users opt in.\n *\n * Compatibility:\n * - Node / Bun / Deno / Vercel — console.log is sync, captured.\n * - Edge runtimes (CF Workers, Vercel Edge) — console.log is captured\n * but may be rate-limited by the platform. For high-volume edge audit,\n * implement a custom sink writing to a queue / HTTP endpoint.\n */\n\nexport interface AuditEvent {\n /** Domain-qualified verb. Convention: `<domain>.<verb>` (e.g. csrf.warn, session.rotated). */\n action: string\n /** Who triggered the event. Anonymous = no auth at time of event. */\n actor?: { type: 'user' | 'system' | 'anonymous'; id?: string }\n /** What was operated on (optional). */\n resource?: { type: string; id?: string }\n /** Arbitrary event-specific metadata. JSON-serializable. */\n metadata?: Record<string, unknown>\n /** ISO 8601 timestamp. If absent, sink fills in `new Date().toISOString()`. */\n timestamp?: string\n /** Optional trace id (populated by middleware from `x-trace-id`). */\n traceId?: string\n}\n\nexport interface AuditLogger {\n log(event: AuditEvent): void | Promise<void>\n}\n\n/**\n * Default sink: one JSON line per event to stdout. Sync. Never throws.\n *\n * EC: circular refs / BigInt values fall back to a placeholder line so\n * the event is still observable (action + traceId) without crashing the\n * request lifecycle.\n */\nexport class JsonStdoutSink implements AuditLogger {\n log(event: AuditEvent): void {\n const enriched = {\n level: 'audit' as const,\n ...event,\n timestamp: event.timestamp ?? new Date().toISOString(),\n }\n try {\n // eslint-disable-next-line no-console -- JsonStdoutSink IS the audit output\n console.log(JSON.stringify(enriched, jsonReplacer))\n } catch {\n // eslint-disable-next-line no-console -- fallback when payload won't serialize\n console.log(\n `{\"level\":\"audit\",\"action\":${JSON.stringify(event.action)},\"timestamp\":${JSON.stringify(enriched.timestamp)},\"note\":\"payload could not be serialized\"}`,\n )\n }\n }\n}\n\n/**\n * Replacer that walks BigInt → string. Circular ref handling is via the\n * outer try/catch (JSON.stringify throws TypeError on cycles; we drop to\n * the fallback line). We don't implement custom cycle-breaking walker\n * because the audit payload is meant to be JSON — if user metadata has\n * a cycle, the right answer is to fix the caller, not silently lose\n * the structure.\n */\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') return value.toString()\n return value\n}\n\n/**\n * No-op logger. Returned when `config.audit` is unset. Zero overhead;\n * framework wiring sites null-check before calling.\n */\nexport function createNoOpLogger(): AuditLogger {\n return {\n log() {\n // intentionally empty\n },\n }\n}\n\n/**\n * T4.2 — Safe-emit wrapper. Used by framework wiring sites (csrf.ts,\n * rate-limit.ts, session.ts) so a logger throw NEVER propagates into\n * the request handler.\n */\nexport function safeAudit(logger: AuditLogger | undefined, event: AuditEvent): void {\n if (!logger) return\n try {\n const r = logger.log(event)\n // Discard the Promise — async sinks are fire-and-forget by design.\n if (r && typeof r.then === 'function') {\n r.catch(() => {\n // swallow async sink failures — audit must never crash the request\n })\n }\n } catch {\n // swallow sync sink failures — audit must never crash the request\n }\n}\n","import type { IncomingMessage } from 'node:http'\n\nimport type { AuditLogger } from '../observability/audit-log.js'\nimport { safeAudit } from '../observability/audit-log.js'\n\n/**\n * CSRF enforcement mode.\n *\n * - `off` — skip CSRF entirely. Use only when you have another defense\n * (e.g. you don't ship session cookies, all auth is bearer).\n * - `warn` — log a structured warning when the check would fail, but\n * still serve the request. Default for 0.2.0. Migration mode.\n * - `strict` — reject failing requests with 403 + code `CSRF_INVALID`.\n * Will become the default in 0.3.0.\n */\nexport type CsrfMode = 'off' | 'warn' | 'strict'\n\n/**\n * Per-request structured logger surface. Only `warn` is used by enforceCsrf;\n * we don't require a full Logger here so callers can pass a mock or the\n * console directly.\n */\nexport interface CsrfLogger {\n warn: (payload: CsrfWarnPayload) => void\n /** Optional path the request was destined for — used for log correlation. */\n path?: string\n}\n\n/**\n * T5.1 — Rails-inspired per-route escalation.\n *\n * `routes` accepts string (exact match) or RegExp entries. When a request\n * path matches AND the request would otherwise emit a warning, the\n * `behavior` field decides what happens:\n *\n * - `'warn'` → normal warn dispatch (no-op vs default)\n * - `'raise'` → escalate to 403 regardless of global `csrf` mode\n *\n * `'raise'` never downgrades: when global mode is `'off'`, validation is\n * skipped entirely and disallowed dispatch never runs.\n */\nexport interface DisallowedConfig {\n routes: (string | RegExp)[]\n behavior: 'warn' | 'raise'\n}\n\n/**\n * Test whether `path` matches any of the supplied patterns. String\n * patterns are EXACT (trailing slash matters — use RegExp for tolerance).\n *\n * EC-5: when a RegExp carries the `/g` flag, `.test()` mutates\n * `lastIndex` and the next invocation may miss. We reset `lastIndex`\n * before each test so the matcher is a pure function.\n */\nexport function matchDisallowed(path: string, patterns: readonly (string | RegExp)[]): boolean {\n for (const p of patterns) {\n if (typeof p === 'string') {\n if (path === p) return true\n } else if (p instanceof RegExp) {\n p.lastIndex = 0\n if (p.test(path)) return true\n }\n // Neither string nor RegExp: ignore silently (defensive — the public\n // type forbids it but runtime data may slip past).\n }\n return false\n}\n\n/**\n * T2.2 — Stable cutover identifier shipped with every csrf.warn payload.\n *\n * Convention borrowed from Vite's `deprecations.ts:74` — a `code` plus a\n * `docsUrl` lets users (a) grep their logs for a single stable identifier\n * to find every csrf.warn line, and (b) click through directly to the\n * migration guide. Strings are exported constants so the analyzer (T2.3)\n * and migration guide can reference the same source of truth.\n */\nexport const CSRF_WARN_CODE = 'CSRF_STRICT_CUTOVER' as const\nexport const CSRF_WARN_DOCS_URL = 'https://theokit.dev/upgrade/csrf-strict-cutover' as const\n\n/**\n * Pick a header value, choosing the first entry when an array (Node sets\n * arrays for headers that legitimately appear multiple times). Returns\n * `''` when the header is absent or empty — callers treat `''` as \"skip\".\n */\nfunction pickHeader(value: string | string[] | undefined): string {\n if (typeof value === 'string') return value\n if (Array.isArray(value) && value.length > 0) return value[0]\n return ''\n}\n\nexport interface CsrfWarnPayload {\n event: 'csrf.warn'\n method: string\n path: string | undefined\n reason: string\n /**\n * Stable identifier for the 0.2 → 0.3 CSRF strict cutover. Always\n * `'CSRF_STRICT_CUTOVER'`. Grep-able from prod logs.\n */\n code: string\n /**\n * Link to the section of the migration guide explaining how to clear\n * this specific warning class.\n */\n docsUrl: string\n}\n\n/**\n * T5a.2 Phase B (slice 1/6): pure header-only CSRF check extracted from\n * `validateCsrf(req: IncomingMessage)` so it can be re-used by the Web-\n * Standards `validateCsrfRequest(request: Request)` sibling. Per the T5a.2\n * plan v1.0 § Phase B, header-only leaves are first to migrate. This is\n * the dual-signature pattern (anti-pattern #2 avoidance): IncomingMessage\n * consumers unchanged; new Request consumers go through the same logic\n * via the shared helper.\n *\n * Pure logic — accepts pre-extracted header values as strings or null.\n */\nfunction isCsrfValidFromHeaders(opts: {\n csrfActionHeader: string | null\n origin: string | null\n host: string | null\n}): { valid: true } | { valid: false; reason: string } {\n // 1. Custom header must be present (primary defense — simple form posts\n // cannot set custom headers, browsers gate via CORS preflight)\n if (opts.csrfActionHeader !== '1') {\n return { valid: false, reason: 'Missing X-Theo-Action header' }\n }\n\n // 2. Origin matching (secondary defense)\n if (opts.origin === null || opts.origin === '') {\n // Browsers omit Origin for same-origin requests — treat as valid\n return { valid: true }\n }\n\n if (opts.host === null || opts.host === '') {\n return { valid: true }\n }\n\n try {\n const originHost = new URL(opts.origin).host\n if (originHost !== opts.host) {\n return { valid: false, reason: `Origin ${opts.origin} does not match host ${opts.host}` }\n }\n } catch {\n return { valid: false, reason: `Invalid origin: ${opts.origin}` }\n }\n\n return { valid: true }\n}\n\nexport function validateCsrf(\n req: IncomingMessage,\n): { valid: true } | { valid: false; reason: string } {\n // IncomingMessage adapter — normalize Node header shape to the pure\n // helper's input shape (string|null).\n const action = req.headers['x-theo-action']\n const origin = req.headers.origin\n const host = req.headers.host\n\n // RFC 6454: Origin is single-valued. A caller that synthesizes an\n // IncomingMessage — an adapter, a shim, a proxy library — can hand us an\n // array, and choosing one of two conflicting origins is a decision the\n // request never authorized. The disagreement IS the rejection.\n //\n // `node:http` itself joins a repeated Origin with `, ` rather than\n // producing an array, and that string already fails to parse as a URL\n // below. This branch covers the shape the type allows and `pickHeader`\n // used to resolve silently.\n if (Array.isArray(origin)) {\n return { valid: false, reason: 'Multiple Origin headers (RFC 6454 violation)' }\n }\n\n return isCsrfValidFromHeaders({\n csrfActionHeader: typeof action === 'string' ? action : null,\n origin: origin !== undefined ? origin || null : null,\n host: host !== undefined ? pickHeader(host) || null : null,\n })\n}\n\n/**\n * T5a.2 Phase B (slice 1/6) — Web-Standards-shaped CSRF validator.\n *\n * Mirror of `validateCsrf(req: IncomingMessage)` for the Web `Request`\n * shape. Consumes `request.headers.get(name)` (native Web `Headers` API)\n * instead of `req.headers[name]` (Node `IncomingMessage` indexer). Same\n * CSRF policy + same return shape — the difference is only the input\n * extraction.\n *\n * Used by `executeWebRequest` (T5a.2 Phase A) to enforce CSRF on the\n * Web-Standards request handler entry-point.\n */\nexport function validateCsrfRequest(\n request: Request,\n): { valid: true } | { valid: false; reason: string } {\n return isCsrfValidFromHeaders({\n csrfActionHeader: request.headers.get('x-theo-action'),\n origin: request.headers.get('origin'),\n host: request.headers.get('host'),\n })\n}\n\n/**\n * Enforce CSRF policy with mode-aware behavior. Wrapper over `validateCsrf`\n * that turns the boolean valid/invalid into a request-level allow decision,\n * gated by mode + structured warning in warn mode.\n *\n * Phase 5 — CSRF warn-first (EC-1).\n */\n/**\n * Dispatch the csrf.warn payload to both the structured logger and the\n * audit sink (when configured). Extracted from `enforceCsrf` to keep that\n * function's complexity within ceiling.\n */\nfunction dispatchCsrfWarn(\n req: IncomingMessage,\n reason: string,\n logger: CsrfLogger | undefined,\n auditLogger: AuditLogger | undefined,\n pathFallback = '',\n): void {\n const payload: CsrfWarnPayload = {\n event: 'csrf.warn',\n method: req.method ?? 'UNKNOWN',\n path: logger?.path ?? pathFallback,\n reason,\n code: CSRF_WARN_CODE,\n docsUrl: CSRF_WARN_DOCS_URL,\n }\n logger?.warn(payload)\n // `metadata` is typed as Record<string, unknown>; CsrfWarnPayload is a\n // structurally-equivalent shape but lacks the index signature.\n safeAudit(auditLogger, {\n action: 'csrf.warn',\n actor: { type: 'anonymous' },\n metadata: { ...payload },\n })\n}\n\nexport function enforceCsrf(\n req: IncomingMessage,\n mode: CsrfMode,\n logger?: CsrfLogger,\n disallowed?: DisallowedConfig,\n auditLogger?: AuditLogger,\n): { allow: boolean; reason?: string } {\n if (mode === 'off') {\n // `off` short-circuits before disallowed dispatch — users who set\n // csrf: 'off' globally have explicitly turned validation off, and\n // disallowed must never re-introduce it. The escape hatch is to\n // set csrf: 'warn' and use disallowed for surgical strict pockets.\n return { allow: true }\n }\n\n const check = validateCsrf(req)\n if (check.valid) {\n return { allow: true }\n }\n\n // T5.1 — disallowed dispatch: when the failing request matches a\n // disallowed pattern AND behavior is 'raise', escalate to 403 even if\n // global mode is 'warn'. Strict mode would 403 anyway, so the branch\n // is a no-op there.\n if (disallowed?.behavior === 'raise') {\n const path = logger?.path ?? req.url ?? ''\n if (matchDisallowed(path, disallowed.routes)) {\n return { allow: false, reason: check.reason }\n }\n }\n\n if (mode === 'warn') {\n // T2.1: emit via warnOnce by default — callers can override via the\n // injected logger.warn (tests, custom log routers).\n // T2.2: include the stable cutover code + docsUrl so logs are\n // grep-able and click-through-able.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: true, reason: check.reason }\n }\n\n // strict — 403 the request, AND emit a warn payload so the dev (and\n // devtools UI) sees WHY it was blocked + the docsUrl to fix it.\n // Without this, strict-mode users get a silent 403 with no context.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: false, reason: check.reason }\n}\n"],"mappings":"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","names":["dispatchCsrfWarn"]}
|
|
@@ -178,7 +178,7 @@ async function agentCommand(name, message, deps = {}) {
|
|
|
178
178
|
async function createAgentSsrLoader(projectRoot) {
|
|
179
179
|
const { createServer } = await import("vite");
|
|
180
180
|
const react = (await import("@vitejs/plugin-react")).default;
|
|
181
|
-
const { theoPluginAsync } = await import("./vite-plugin-
|
|
181
|
+
const { theoPluginAsync } = await import("./vite-plugin-54XIRMKT.js");
|
|
182
182
|
const { loadConfig } = await import("./load-config-52H3EKAA.js");
|
|
183
183
|
const config = await loadConfig(projectRoot);
|
|
184
184
|
const theoPlugins = await theoPluginAsync({
|
|
@@ -212,4 +212,4 @@ export {
|
|
|
212
212
|
agentCommand,
|
|
213
213
|
createAgentSsrLoader
|
|
214
214
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-HDAILYVM.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
findControllerFiles
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FNJVSFEC.js";
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/build/emit-controllers.ts
|
|
8
8
|
import "reflect-metadata";
|
|
@@ -189,4 +189,4 @@ export {
|
|
|
189
189
|
emitControllerArtifacts,
|
|
190
190
|
describeControllerArtifacts
|
|
191
191
|
};
|
|
192
|
-
//# sourceMappingURL=chunk-
|
|
192
|
+
//# sourceMappingURL=chunk-LOOOB46A.js.map
|
|
@@ -26,14 +26,14 @@ import {
|
|
|
26
26
|
parseAgentRequestBody,
|
|
27
27
|
readAgentPolicy,
|
|
28
28
|
subjectFromContext
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-EDBQM6RB.js";
|
|
30
30
|
import {
|
|
31
31
|
getApprovalRegistry
|
|
32
32
|
} from "./chunk-BINRCILF.js";
|
|
33
33
|
import {
|
|
34
34
|
sendError,
|
|
35
35
|
validateCsrfRequest
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-FNJVSFEC.js";
|
|
37
37
|
import {
|
|
38
38
|
generateNonce
|
|
39
39
|
} from "./chunk-WSMLSIS5.js";
|
|
@@ -901,4 +901,4 @@ export {
|
|
|
901
901
|
applyNonceToInlineScripts,
|
|
902
902
|
setupSsrDevMiddleware
|
|
903
903
|
};
|
|
904
|
-
//# sourceMappingURL=chunk-
|
|
904
|
+
//# sourceMappingURL=chunk-M3DT6EMK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/resolve-plugin-specifiers.ts","../src/server/transformer.ts","../src/vite-plugin/hoist-head-tags.ts","../src/vite-plugin/ssr-dev-middleware.ts","../src/server/agent/agent-card-handler.ts","../src/server/agent/approve-agent.ts","../src/server/agent/handle-agent-run-reconnect.ts","../src/server/agent/thread-dispatcher.ts","../src/server/agent/thread-run-registry.ts","../src/server/agent/handle-thread-routes.ts","../src/server/agent/list-approvals-handler.ts","../src/server/agent/serve-aux-routes.ts","../src/server/http/plugin-lifecycle.ts","../src/server/http/resolve-agent-subject.ts"],"sourcesContent":["/**\n * Turn `config.plugins` entries that are module SPECIFIERS into plugins (usetheokit/theokit#425).\n *\n * ## Why the field grew a second shape\n *\n * `config.plugins` holds constructed objects, and a generated deploy entry cannot carry a closure:\n * there is no literal for one. So on all six Web-standards targets the entry built its request\n * context with no runner and every lifecycle hook was dead on a deployed app while firing locally.\n *\n * Two ways out were weighed:\n *\n * - **Bundle `theo.config.ts` into the entry.** Rejected on measurement. It silently drops\n * `theo.config.<NODE_ENV>.ts`, which `loadConfig` merges (`config/load-config.ts:92`) — a new\n * silent drop, which is the exact class of defect this work exists to remove. And it pulls every\n * module the config imports (database drivers, build-only helpers) into a Worker bundle that\n * builds today, so the common case would pay for the rare one.\n * - **Name the module.** A string entry says which module the plugin comes from, so the build emits\n * a static import for that module and nothing else. One declaration serves the local server and\n * the deployed entry, which is what keeps them from disagreeing.\n *\n * The second is what this implements. It is ADDITIVE: an app passing constructed objects is\n * untouched, and gets the same treatment it always got.\n *\n * ## Why this lives in `config/` and not beside `createPluginRunnerFromConfig`\n *\n * Resolving a specifier means importing a path, and that means `node:url`/`node:path`. `server/`\n * holds a no-`node:*` invariant so the same code serves the Web, Tauri and TUI targets\n * (`docs/program/three-target-parity.md`). `config/` already reads the filesystem — `load-config.ts` is\n * the module that finds `theo.config.ts` at all — and it is the one place BOTH local entry points\n * (`theokit start` and the Vite dev server) already import from. So the specifier is resolved\n * where the config is read, and `createPluginRunnerFromConfig` keeps taking what it always took:\n * objects.\n */\nimport { isAbsolute, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\n/**\n * A declared plugin module that could not be turned into a plugin.\n *\n * Its own error type because the alternative — skipping the entry — leaves an app running with one\n * fewer plugin than it declared and nothing saying so. That is the failure this issue is about,\n * reproduced at the door.\n */\nexport class UnresolvablePluginSpecifierError extends Error {\n constructor(index: number, specifier: string, reason: string) {\n super(`plugins[${index}] (${specifier}) could not be loaded: ${reason}`)\n this.name = 'UnresolvablePluginSpecifierError'\n }\n}\n\n/**\n * Resolve every string entry in `plugins` to the module's default export, in order.\n *\n * @param plugins - the raw `config.plugins` array: constructed plugins, module specifiers, or both.\n * @param cwd - the project root a relative specifier is resolved against.\n */\nexport async function resolvePluginSpecifiers(\n plugins: readonly unknown[],\n cwd: string,\n): Promise<unknown[]> {\n const resolved: unknown[] = []\n // Sequential on purpose. Plugin order is hook order, and resolving concurrently then collecting\n // as each settles would reorder an app's lifecycle by module size.\n for (const [index, entry] of plugins.entries()) {\n resolved.push(typeof entry === 'string' ? await importPlugin(entry, index, cwd) : entry)\n }\n return resolved\n}\n\nasync function importPlugin(specifier: string, index: number, cwd: string): Promise<unknown> {\n const target = isAbsolute(specifier) ? specifier : resolve(cwd, specifier)\n let mod: { default?: unknown }\n try {\n mod = (await import(/* @vite-ignore */ pathToFileURL(target).href)) as { default?: unknown }\n } catch (err) {\n throw new UnresolvablePluginSpecifierError(index, specifier, messageOf(err))\n }\n if (mod.default === undefined) {\n // Registering `undefined` would fail later inside `createPluginRunnerFromConfig` with an error\n // naming a shape rather than a file, which is the harder half of the diagnosis.\n throw new UnresolvablePluginSpecifierError(\n index,\n specifier,\n 'the module has no default export; a plugin module must `export default` its plugin',\n )\n }\n return mod.default\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * Moves the document metadata a route rendered into the `<head>` where it belongs.\n *\n * ## Why this exists\n *\n * React 19 hoists `<title>`, `<meta>` and `<link>` into the head — **in the browser**, by moving\n * DOM nodes after hydration. On the server it emits them inline, wherever the component sat, and\n * the SSR output is injected inside `<div id=\"root\">`. So a route's own metadata ships in the\n * BODY.\n *\n * For a reader that changes nothing: hydration moves the tags a moment later. For a crawler it\n * changes everything, because the ones that matter never run JavaScript. Every social unfurler —\n * X, LinkedIn, Slack, Discord, WhatsApp — reads the served `<head>` and stops. Without this, every\n * page of a site unfurls with whatever static fallback `index.html` happens to carry: share ten\n * different documentation pages, get ten identical cards.\n *\n * Turning SSR on to fix social previews and finding they still do not work is a bad afternoon, so\n * the framework does the hoist itself (usetheokit/theokit#319).\n *\n * ## Precedence\n *\n * The route wins over the template. `index.html` holds site-wide defaults; a page that states its\n * own title, description or canonical is being specific on purpose, and shipping both would leave\n * the crawler to pick — in practice the first one, which is the generic one.\n */\n\n/**\n * Tags React hoists, and therefore the ones worth moving.\n *\n * Two separate patterns rather than one with an alternation: a single expression covering both the\n * self-closing tags and the `<title>…</title>` pair needs a lazy `[\\s\\S]*?` next to a lazy\n * `[^>]*?`, and that nests two unbounded quantifiers — catastrophic backtracking on hostile input,\n * which here is a served HTML document. Each pattern below is linear: `[^>]` and `[^<]` cannot\n * cross the delimiter that ends the match.\n */\nconst VOID_METADATA = /<(?:meta|link)\\b[^>]*>/gi\n/** `<title>` content is text, so it cannot contain `<` — the class is what keeps this linear. */\nconst TITLE_TAG = /<title\\b[^>]*>[^<]*<\\/title>/gi\n\n/** Runs `replacer` over every hoistable tag, in document order. */\nfunction replaceHoistable(html: string, replacer: (tag: string) => string): string {\n return html.replace(TITLE_TAG, replacer).replace(VOID_METADATA, replacer)\n}\n\n/**\n * The identity of a metadata tag, used to decide what the route replaces.\n *\n * `<meta name=\"description\">` and `<meta property=\"og:title\">` are distinct slots; two `<meta>`\n * tags with different names are not duplicates. A `<link>` is keyed by `rel`, so a route's\n * canonical replaces the template's while a stylesheet link is left alone.\n *\n * Anything unkeyed (a `<link rel=\"preconnect\">`, say) returns `undefined` and is simply appended —\n * additive tags must not evict each other.\n */\nexport function metadataKey(tag: string): string | undefined {\n if (/^<title\\b/i.test(tag)) return 'title'\n\n const name = /\\bname=[\"']([^\"']+)[\"']/i.exec(tag)?.[1]\n const property = /\\bproperty=[\"']([^\"']+)[\"']/i.exec(tag)?.[1]\n const rel = /\\brel=[\"']([^\"']+)[\"']/i.exec(tag)?.[1]\n\n if (/^<meta\\b/i.test(tag)) {\n if (property !== undefined) return `property:${property.toLowerCase()}`\n if (name !== undefined) return `name:${name.toLowerCase()}`\n return undefined\n }\n\n if (/^<link\\b/i.test(tag) && rel !== undefined) {\n const slug = rel.toLowerCase()\n // Only single-valued rels are slots. `stylesheet`, `preload` and friends are additive: keying\n // them would let one page's stylesheet evict another's.\n return slug === 'canonical' || slug === 'manifest' ? `link:${slug}` : undefined\n }\n\n return undefined\n}\n\nexport interface HoistedHead {\n /** The rendered HTML with its metadata tags removed. */\n html: string\n /** Those tags, ready to be placed in the head. */\n headTags: string[]\n}\n\n/** Pulls hoistable metadata out of rendered SSR markup. */\nexport function extractHeadTags(ssrHtml: string): HoistedHead {\n const headTags: string[] = []\n const html = replaceHoistable(ssrHtml, (tag) => {\n headTags.push(tag)\n return ''\n })\n return { html, headTags }\n}\n\n/**\n * Inserts `headTags` into the template's head, dropping any template tag the route supersedes.\n *\n * Returns the template untouched when there is nothing to hoist or no `</head>` to hoist into —\n * a missing head is a malformed template, and rewriting it further would not help anyone.\n */\nexport function injectIntoHead(template: string, headTags: string[]): string {\n if (headTags.length === 0) return template\n\n const closingHead = template.toLowerCase().lastIndexOf('</head>')\n if (closingHead === -1) return template\n\n const supersededKeys = new Set(\n headTags.map((tag) => metadataKey(tag)).filter((key): key is string => key !== undefined),\n )\n\n let head = template.slice(0, closingHead)\n if (supersededKeys.size > 0) {\n head = replaceHoistable(head, (tag) => {\n const key = metadataKey(tag)\n return key !== undefined && supersededKeys.has(key) ? '' : tag\n })\n }\n\n return `${head} ${headTags.join('\\n ')}\\n ${template.slice(closingHead)}`\n}\n\n/**\n * The whole operation: strip metadata from the rendered markup and place it in the template's head.\n */\nexport function hoistHeadTags(\n template: string,\n ssrHtml: string,\n): { template: string; html: string } {\n const { html, headTags } = extractHeadTags(ssrHtml)\n const hoisted = injectIntoHead(template, headTags)\n\n // Fail SAFE, not silent. `injectIntoHead` returns the template untouched when it finds no\n // `</head>` — and if we returned the stripped body alongside it, the metadata would be removed\n // from one place and added to neither. It would vanish, with nothing to show for it.\n //\n // That is not hypothetical: a template whose COMMENT mentioned `<div id=\"root\">` split at the\n // comment, leaving a \"head\" half with no `</head>` in it, and every route silently lost its\n // title and canonical. Metadata in the wrong place still works after hydration; metadata that is\n // gone never comes back.\n if (hoisted === template) return { template, html: ssrHtml }\n\n return { template: hoisted, html }\n}\n","/**\n * T2.2 (architecture-medium-deferrals plan, ADR D2) — SSR dev middleware\n * extracted from `vite-plugin/index.ts` for SRP.\n *\n * `setupSsrDevMiddleware(server, opts)` registers a Connect-style middleware\n * on the Vite dev server that:\n * 1. Skips API, static, and HMR requests (let other middlewares handle).\n * 2. Reads `index.html`, runs `transformIndexHtml`.\n * 3. Generates per-request nonce, applies security headers (CSP + Cache-Control).\n * 4. Calls `ssrLoadModule(VIRTUAL_ENTRY_SERVER_ID).render(url, { nonce })`.\n * 5. Injects rendered HTML (with hydration script) into root div.\n * 6. On error: ssrFixStacktrace + fallback to CSR via `next()`.\n *\n * No-op when `ssrEnabled === false`. Caller's responsibility to gate.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { ViteDevServer } from 'vite'\n\nimport { findRootDiv } from '../core/contracts/find-root-div.js'\nimport {\n applySecurityHeaders,\n generateNonce,\n type SecurityHeadersConfig,\n} from '../server/internal-api.js'\n\nimport { hoistHeadTags } from './hoist-head-tags.js'\n\ninterface SsrRenderResult {\n html: string\n hydrationData: {\n loaderData?: unknown\n actionData?: unknown\n errors?: unknown\n }\n}\n\ninterface SsrEntryServer {\n render: (\n url: string,\n opts: { nonce: string },\n ) => Promise<SsrRenderResult | { redirect: Response } | string>\n}\n\n/**\n * Stamps the request nonce onto every inline `<script>` the HTML already carries.\n *\n * `transformIndexHtml` lets Vite plugins inject their own scripts, and they know nothing about our\n * CSP. `@vitejs/plugin-react` injects its refresh preamble as an INLINE module script with no\n * nonce, so a nonce-based `script-src` blocks it, `window.$RefreshReg$` is never defined, and the\n * first component module throws \"@vitejs/plugin-react can't detect preamble\". SSR still produced\n * the HTML, so the page looks fine and simply never hydrates — nothing interactive works, and the\n * one console error points at Vite rather than at us (usetheokit/theokit#319).\n *\n * Only scripts WITHOUT `src` are stamped: a same-origin `src` is already covered by `'self'`, and\n * an inline script is the only kind a nonce is needed for. Scripts that already carry a nonce are\n * left alone, so the render's own output is never rewritten.\n *\n * Deliberately not a general HTML parser: this runs per request in dev, on markup we produced or a\n * Vite plugin injected, and the pattern only ever matches an opening `<script>` tag.\n */\nexport function applyNonceToInlineScripts(html: string, nonce: string): string {\n return html.replace(\n /<script(?![^>]*\\ssrc=)(?![^>]*\\snonce=)([^>]*)>/gi,\n `<script nonce=\"${nonce}\"$1>`,\n )\n}\n\nfunction isSsrRenderResult(value: unknown): value is SsrRenderResult {\n if (typeof value !== 'object' || value === null) return false\n if (!('html' in value)) return false\n return typeof (value as Record<string, unknown>).html === 'string'\n}\n\ninterface SsrDevMiddlewareOptions {\n projectRoot: string\n virtualEntryServerId: string\n securityHeaders: SecurityHeadersConfig | undefined\n}\n\n/**\n * Attach the SSR dev middleware to a Vite dev server. Caller decides whether\n * to invoke this based on `ssrEnabled` — this function does not gate.\n */\nexport function setupSsrDevMiddleware(server: ViteDevServer, opts: SsrDevMiddlewareOptions): void {\n server.middlewares.use((req, res, next) => {\n void (async () => {\n const url = req.url ?? '/'\n // Skip API, static, and HMR requests\n if (\n url.startsWith('/api/') ||\n url.startsWith('/@') ||\n url.startsWith('/node_modules/') ||\n url.includes('.')\n ) {\n next()\n return\n }\n\n try {\n const indexPath = resolve(opts.projectRoot, 'index.html')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- projectRoot is from `theokit dev`'s caller-controlled cwd\n let template = readFileSync(indexPath, 'utf-8')\n\n // T4.1 — Generate a per-request nonce and apply security headers BEFORE render.\n // The same nonce flows into React's renderToPipeableStream({ nonce }) so every\n // emitted <script> carries it AND into the CSP script-src directive.\n // EC-3: applySecurityHeaders also forces Cache-Control: private, no-store.\n //\n // The nonce is minted BEFORE `transformIndexHtml` so the scripts Vite plugins inject can be\n // stamped with it. Minting it afterwards left the React refresh preamble unnonced, the CSP\n // blocked it, and the app never hydrated (usetheokit/theokit#319).\n const nonce = generateNonce()\n\n template = await server.transformIndexHtml(url, template)\n template = applyNonceToInlineScripts(template, nonce)\n applySecurityHeaders(\n res,\n opts.securityHeaders ?? {},\n { production: process.env.NODE_ENV === 'production' },\n { nonce },\n )\n\n const mod = (await server.ssrLoadModule(opts.virtualEntryServerId)) as SsrEntryServer\n const result = await mod.render(url, { nonce })\n\n if (result && typeof result === 'object' && 'redirect' in result) {\n res.writeHead(302, {\n Location: result.redirect.headers.get('location') ?? '/',\n })\n res.end()\n return\n }\n\n // Backward-compat: old render returned string. New shape returns\n // { html, hydrationData } so the framework can emit the hydration\n // data script OUTSIDE the React root (fixes hydration mismatch).\n let ssrHtml: string\n let hydrationScript = ''\n if (typeof result === 'string') {\n ssrHtml = result\n } else if (isSsrRenderResult(result)) {\n ssrHtml = result.html\n const dataJson = JSON.stringify(result.hydrationData).replace(/</g, '\\\\u003c')\n hydrationScript = `<script nonce=\"${nonce}\">window.__staticRouterHydrationData=${dataJson}</script>`\n } else {\n ssrHtml = ''\n }\n // Move the route's <title>/<meta>/<link> out of the rendered body and into the head.\n // React only hoists those in the browser, after hydration — a crawler that does not run JS\n // would otherwise never see a page's own title or social card (usetheokit/theokit#319).\n const hoisted = hoistHeadTags(template, ssrHtml)\n template = hoisted.template\n ssrHtml = hoisted.html\n\n const rootDiv = findRootDiv(template)\n if (!rootDiv) {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(template)\n return\n }\n\n const splitIdx = rootDiv.insertAt\n const html =\n template.slice(0, splitIdx) + ssrHtml + hydrationScript + template.slice(splitIdx)\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(html)\n } catch (err) {\n server.ssrFixStacktrace(err as Error)\n console.error('[SSR Dev Error]', err)\n // Fallback to CSR\n next()\n return\n }\n })()\n })\n}\n","/**\n * M15 (theokit-ai-first) — serve the A2A agent card at `/.well-known/<name>/agent-card.json`.\n *\n * `buildAgentCard` (@theokit/agents) is the pure generator; this handler compiles a loaded agent\n * module to its tools + streaming capability, builds the card, and returns it as a Web-Standard\n * JSON `Response` (G8). The dev middleware + prod handler branch to this before the agent POST route.\n */\nimport { type AgentManifestEntry, buildAgentCard, compileLoadedAgentModule } from '@theokit/agents'\n\nconst WELL_KNOWN = /^\\/\\.well-known\\/([^/]+)\\/agent-card\\.json$/\n\n/** Return the agent name when `urlPath` is a well-known card path, else `null`. */\nexport function isAgentCardPath(urlPath: string): string | null {\n const match = WELL_KNOWN.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\n/** Build a minimal manifest entry (the subset `buildAgentCard` reads) from a compiled agent. */\nfunction toManifestEntry(name: string, route: string, mod: unknown): AgentManifestEntry {\n const compiled = compileLoadedAgentModule(mod, `agent card for \"${name}\"`)\n return {\n name,\n route,\n stream: compiled.stream,\n mainLoop: { method: '', strategy: '' },\n guards: [],\n interceptors: [],\n tools: compiled.tools.map((t) => ({\n name: t.name,\n description: t.description,\n approval: false,\n trace: false,\n audit: false,\n })),\n subAgents: [],\n }\n}\n\n/**\n * Serve the A2A card for a loaded agent module. Returns 200 with the card JSON, or 500 with an\n * error body if the module is not a valid agent (fail-clear, not a silent empty card).\n */\nexport function handleAgentCard(\n mod: unknown,\n name: string,\n route: string,\n baseUrl: string,\n): Response {\n try {\n const card = buildAgentCard(toManifestEntry(name, route, mod), { baseUrl })\n return new Response(JSON.stringify(card), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n } catch (err) {\n return new Response(\n JSON.stringify({\n error: {\n code: 'AGENT_CARD_FAILED',\n message: err instanceof Error ? err.message : 'card build failed',\n },\n }),\n { status: 500, headers: { 'content-type': 'application/json; charset=utf-8' } },\n )\n }\n}\n","/**\n * M4 (theokit-ai-first) — the HITL approve endpoint: `POST /api/agents/<name>/approve/<approvalId>`.\n *\n * The counterpart to `mountAgent`'s HITL pause. While a gated tool holds the SDK run paused (the\n * awaited `pre_tool_call` hook), the client POSTs here with `{ approved }`; this resolves the\n * pending approval in the shared registry, which un-pauses the run (allow) or vetoes the tool (deny).\n *\n * Web-Standard `Request` → `Response`, one wiring point shared by dev (vite middleware) and prod\n * (built server) so the two never drift (EC-4 parity with `mountAgent`). The registry is INJECTED —\n * dev/prod pass the process singleton (`getApprovalRegistry`), tests pass a fresh instance.\n */\nimport type { RoutePolicy } from '../../core/contracts/route-policy.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { admitAgentRequest, agentAccessDenied, type AgentSubjectResolver } from './agent-access.js'\nimport type { ApprovalDecision, ApprovalRegistry } from './approval-registry.js'\nimport { closePauseSpan } from './hitl-pause-spans.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\n\nconst APPROVE_PATH = /^\\/api\\/agents\\/([^/]+)\\/approve\\/([^/]+)$/\n\n/**\n * The agent named by a `/api/agents/<name>/approve/<id>` path, or `null`.\n *\n * The route's own gate is the agent's declared policy, and reading the policy needs the agent —\n * which this path carries and nothing previously read (usetheokit/theokit#365).\n */\nexport function parseApprovalAgentName(urlPath: string): string | null {\n const match = APPROVE_PATH.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\n/**\n * Extract the `<approvalId>` from a `/api/agents/<name>/approve/<approvalId>` path.\n * Returns `null` when the path has no `/approve/` segment or an empty / nested id.\n */\nexport function parseApprovalId(urlPath: string): string | null {\n const at = urlPath.indexOf(APPROVE_SEGMENT)\n if (at === -1) return null\n const id = urlPath.slice(at + APPROVE_SEGMENT.length)\n return id.length > 0 && !id.includes('/') ? id : null\n}\n\n/** True when `urlPath` targets a HITL approve endpoint (used by dev/prod routing to branch early). */\nexport function isApprovalPath(urlPath: string): boolean {\n return urlPath.includes(APPROVE_SEGMENT)\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * M20 — cap on the serialized custom payload (16 KiB). A payload is a small structured note\n * (edited args, a reviewer comment), not a data channel — an oversized one is rejected fail-fast\n * rather than silently truncated (Rule 8).\n */\nconst MAX_PAYLOAD_BYTES = 16 * 1024\n\n/**\n * Extract an {@link ApprovalDecision} from an untrusted body; `null` when the shape is wrong.\n *\n * M20 — accepts an optional `reason` (string) and `payload` (object, capped at\n * {@link MAX_PAYLOAD_BYTES}). Backward-compatible: `{ approved }` and `{ approved, reason }` parse\n * unchanged. A non-object or oversized `payload` is rejected (returns `null` → the route 400s).\n *\n * @public\n */\nexport function parseApprovalBody(body: unknown): ApprovalDecision | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n if (typeof b.approved !== 'boolean') return null\n const decision: ApprovalDecision = { approved: b.approved }\n if (b.reason !== undefined) {\n if (typeof b.reason !== 'string') return null\n decision.reason = b.reason\n }\n if (b.payload !== undefined) {\n if (typeof b.payload !== 'object' || b.payload === null || Array.isArray(b.payload)) return null\n if (JSON.stringify(b.payload).length > MAX_PAYLOAD_BYTES) return null\n decision.payload = b.payload\n }\n return decision\n}\n\n/**\n * The gates it applies (usetheokit/theokit#365):\n *\n * CSRF, which refuses a cross-origin POST and identifies nobody — and then the agent's declared\n * `policy`, which is what makes \"who is asking\" a question this endpoint can answer at all.\n * Reproduced before the policy existed: a process holding no cookie and no credential read a\n * pending id off the listing, POSTed here, and the gated tool ran.\n *\n * Ownership, which this endpoint could NOT decide until B-016 and now can, within a stated scope.\n * `mountAgent` records the run's subject on each approval it registers, for agents that DECLARE a\n * policy, and this route refuses a caller whose identity does not match. Before it, an\n * authenticated tenant could settle another tenant's approval on an agent both were admitted to —\n * the policy was the only thing between them, and the policy cannot see whose approval it is.\n *\n * Two limits, stated rather than implied. An agent that declares `'public'` records no owner, since\n * attributing approvals there would start refusing callers the declaration admits. And a thread\n * continuation runs headless, with no request whose identity could be resolved, so its approvals\n * record nobody. In both cases the endpoint behaves exactly as it did — `params.approvalId` is\n * still passed so an application holding its own owner map can answer more than the framework does.\n *\n * Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\n * 403 FORBIDDEN — the agent's policy refused this caller\n * 400 BAD_REQUEST — no `/approve/<id>` in the path, or body lacks a boolean `approved`\n * 404 NOT_PENDING — the id is unknown or already settled (idempotent double-submit)\n * 200 { resolved:true } — the approval was settled by this call\n */\n/**\n * Refuse a caller who does not own this approval, where the ledger knows the owner (B-016).\n *\n * The agent's policy answers \"may this subject touch this agent's approvals\". It cannot answer \"is\n * this approval theirs\", because it is never told — which is why an authenticated tenant could\n * settle another tenant's approval on an agent both were admitted to. `mountAgent` now records the\n * run's subject on each approval it registers, for agents that DECLARE a policy, and this reads it.\n *\n * `undefined` from `ownerOf` covers three cases the endpoint treats identically — never registered,\n * already settled, and registered with no owner — and in all three there is nothing to compare a\n * caller against, so the endpoint behaves exactly as it did. The check only ever narrows.\n */\nasync function refuseIfNotOwner(\n registry: ApprovalRegistry,\n approvalId: string,\n resolveSubject: AgentSubjectResolver | undefined,\n params: Parameters<typeof agentAccessDenied>[1],\n): Promise<Response | undefined> {\n const owner = registry.ownerOf(approvalId)\n if (owner === undefined) return undefined\n\n const subject = resolveSubject === undefined ? null : await resolveSubject()\n if (subject?.id === owner) return undefined\n\n // An unidentifiable caller is refused, deliberately. An approval that HAS an owner must not be\n // settleable by whoever reaches the endpoint without an identity — admitting there would make the\n // guarantee depend on how the host wired its resolver rather than on who is asking.\n return agentAccessDenied(\n { allowed: false, reason: 'the approval belongs to another subject' },\n params,\n )\n}\n\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\n access: { policy?: RoutePolicy; resolveSubject?: AgentSubjectResolver } = {},\n): Promise<Response> {\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const approvalId = parseApprovalId(urlPath)\n if (approvalId === null) {\n return jsonError(400, 'BAD_REQUEST', 'Approval path must be /api/agents/<name>/approve/<id>.')\n }\n\n const params = {\n agent: parseApprovalAgentName(urlPath) ?? 'unknown',\n endpoint: 'approve' as const,\n approvalId,\n }\n const decision = await admitAgentRequest(access.policy, access.resolveSubject, params)\n if (!decision.allowed) return agentAccessDenied(decision, params)\n\n const notOwner = await refuseIfNotOwner(registry, approvalId, access.resolveSubject, params)\n if (notOwner !== undefined) return notOwner\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n const parsed = parseApprovalBody(body)\n if (parsed === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request body must contain a boolean `approved`.')\n }\n\n const resolved = registry.resolve(approvalId, parsed)\n if (!resolved) {\n return jsonError(404, 'NOT_PENDING', `No pending approval for id '${approvalId}'.`)\n }\n\n // THIS is the resume instant, and it is why the span could not be closed correctly before\n // (B-028). The run's observer never sees this request; it saw the tool's output arriving later\n // and closed the pause there, so the recorded wait was the human's plus the model's — measured at\n // +1523 ms on a run whose human answered at 3306 ms. Closing here, on the request that carries\n // the answer, makes the duration the human's by construction rather than by subtraction.\n closePauseSpan(approvalId, { resumeObserved: true })\n return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","import {\n encodeSse,\n formatSseFrame,\n RUN_ID_HEADER,\n SSE_BASE_HEADERS,\n SSE_DONE_FRAME,\n} from './durable-ui-message-stream-response.js'\nimport type { RunEventCache } from './run-event-cache.js'\n\n/**\n * M37 (ADR-0046 D5) — the reconnect / observe endpoint handler for\n * `GET /api/agents/<name>/runs/<runId>/stream`.\n *\n * Replays the frames the client missed (`seq > Last-Event-ID`, SSE-native), then\n * — if the run is still live — follows the live tail; a SECOND client can\n * observe a run a first started. Run already ended ⇒ replay + `[DONE]`. Unknown\n * `runId` ⇒ 404. The request `AbortSignal` unsubscribes on disconnect.\n */\n\nconst RUN_STREAM_PATH = /^\\/api\\/agents\\/([^/]+)\\/runs\\/([^/]+)\\/stream$/\n\n/**\n * Match `GET /api/agents/<name>/runs/<runId>/stream`; returns the decoded\n * `{ name, runId }` or `null` to fall through (mirrors `isMcpPath` etc.).\n */\nexport function isAgentRunStreamPath(urlPath: string): { name: string; runId: string } | null {\n const m = RUN_STREAM_PATH.exec(urlPath)\n return m ? { name: decodeURIComponent(m[1]), runId: decodeURIComponent(m[2]) } : null\n}\n\n/** Parse `Last-Event-ID` (a frame `seq`) to a number; absent/invalid ⇒ -1 (replay from the start). */\nfunction parseLastEventId(raw: string | null): number {\n if (raw === null) return -1\n const n = Number.parseInt(raw, 10)\n return Number.isInteger(n) && n >= 0 ? n : -1\n}\n\nexport function handleAgentRunReconnect(\n runId: string,\n request: Request,\n cache: RunEventCache,\n): Response {\n // Unknown run ⇒ 404 (a run never started here, or already evicted).\n if (!cache.has(runId)) {\n return new Response(\n JSON.stringify({ error: { code: 'RUN_NOT_FOUND', message: `Unknown run '${runId}'.` } }),\n {\n status: 404,\n headers: { 'content-type': 'application/json' },\n },\n )\n }\n\n const afterSeq = parseLastEventId(request.headers.get('last-event-id'))\n\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let closed = false\n const safeEnqueue = (text: string): void => {\n if (closed) return\n try {\n controller.enqueue(encodeSse(text))\n } catch {\n /* controller already closed by an abort race — ignore */\n }\n }\n const close = (): void => {\n if (closed) return\n safeEnqueue(SSE_DONE_FRAME)\n closed = true\n try {\n controller.close()\n } catch {\n /* already closed */\n }\n }\n\n // Atomic: snapshot replay frames AND subscribe to the live tail in one tick.\n const res = cache.attach(\n runId,\n afterSeq,\n (frame) => {\n safeEnqueue(formatSseFrame(frame.seq, frame.data))\n },\n () => {\n close()\n },\n )\n // Evicted between has() and attach() (rare TOCTOU) ⇒ just end the stream.\n if (!res.known) {\n close()\n return\n }\n for (const frame of res.replay) {\n safeEnqueue(formatSseFrame(frame.seq, frame.data))\n }\n if (res.ended) {\n close()\n return\n }\n // Client disconnects ⇒ detach the live listener + close.\n request.signal.addEventListener('abort', () => {\n res.unsubscribe()\n close()\n })\n },\n })\n\n return new Response(stream, { headers: { ...SSE_BASE_HEADERS, [RUN_ID_HEADER]: runId } })\n}\n","/**\n * M39 (ADR-0048) — the thread follow-up dispatcher.\n *\n * Drives a run over the M37 durable cache HEADLESS — it iterates the SDK chunk\n * generator and `cache.append`s directly, NOT via a `Response` `ReadableStream`\n * (whose backpressure would stall a run with no HTTP reader). Subscribers read\n * from the cache via the thread / reconnect stream.\n *\n * - Post to an IDLE thread ⇒ start a run + pump.\n * - Post to an ACTIVE thread ⇒ FIFO-queue the follow-up.\n * - On terminal ⇒ `registry.endRun` hands back the next queued follow-up, which\n * is dispatched as a continuation on the SAME sessionId (⇒ the SDK continues\n * the conversation via its `ConversationStorageAdapter`).\n *\n * This adds NO agent loop — it reuses the SDK run (`startRun`) + the M37 cache.\n * Single-process (ADR-0048 D2).\n */\n\nimport type { WireChunk as UIMessageChunk } from '@theokit/presenter/wire'\n\nimport { type RunEventCache, mintRunId } from './run-event-cache.js'\nimport type { ThreadRunRegistry } from './thread-run-registry.js'\n\ninterface ThreadDispatchDeps {\n readonly registry: ThreadRunRegistry\n readonly cache: RunEventCache\n /** Start a run for a follow-up on `sessionId`; returns the SDK chunk stream. */\n readonly startRun: (sessionId: string, message: string) => AsyncIterable<UIMessageChunk>\n}\n\n/** Result of posting a follow-up: the started `runId` (idle thread) or `queued` (active thread). */\ntype PostFollowUpResult = { runId: string } | { queued: true }\n\n/**\n * Post a follow-up on a thread. IDLE ⇒ start a run (returns its `runId`); ACTIVE\n * ⇒ FIFO-queue it (returns `{ queued: true }`) — dispatched when the active run ends.\n */\nexport function postThreadFollowUp(\n deps: ThreadDispatchDeps,\n sessionId: string,\n message: string,\n): PostFollowUpResult {\n if (deps.registry.getActive(sessionId) !== null) {\n deps.registry.queue(sessionId, { message })\n return { queued: true }\n }\n return { runId: startAndPump(deps, sessionId, message) }\n}\n\n/** Mint a runId, mark the thread active, and pump the run into the cache headless. */\nfunction startAndPump(deps: ThreadDispatchDeps, sessionId: string, message: string): string {\n const runId = mintRunId()\n // Register the run in the cache SYNCHRONOUSLY (before the async pump appends its\n // first frame) so a subscriber resolving the active runId can attach immediately.\n deps.cache.begin(runId)\n deps.registry.startRun(sessionId, runId)\n pumpIntoCache(deps, sessionId, runId, deps.startRun(sessionId, message))\n return runId\n}\n\n/**\n * Drive `chunks` into the cache under `runId`, then end the run and dispatch the\n * next queued follow-up (if any) as a continuation. Fire-and-forget — the caller\n * does not await the run; subscribers observe it via the cache.\n */\nfunction pumpIntoCache(\n deps: ThreadDispatchDeps,\n sessionId: string,\n runId: string,\n chunks: AsyncIterable<UIMessageChunk>,\n): void {\n void (async () => {\n try {\n for await (const chunk of chunks) {\n deps.cache.append(runId, JSON.stringify(chunk))\n }\n } catch {\n // The SDK translator owns error semantics (surfaces failures as chunks);\n // the transport guarantees only a terminated, cache-ended run.\n } finally {\n deps.cache.end(runId)\n const next = deps.registry.endRun(sessionId, runId)\n if (next !== undefined) startAndPump(deps, sessionId, next.message)\n }\n })()\n}\n","/**\n * M39 (ADR-0048) — the in-process thread→run registry.\n *\n * A thread is the existing `sessionId` (the SDK conversation key). This registry\n * tracks, per thread, the single ACTIVE run, a FIFO queue of follow-ups to\n * dispatch as continuations, and one-shot \"next run\" waiters (so a client can\n * subscribe to a thread before posting a message and attach when the run starts).\n *\n * Pure state only — it never starts a run or touches the transport. The route\n * layer owns dispatch: on `endRun` it receives the next queued follow-up (if any)\n * and starts the continuation over the M37 durable transport.\n *\n * Single-process contract (ADR-0048 D2): a multi-instance deploy needs a shared\n * registry + leasing — that is infra (TheoCloud), explicitly out of M39. The\n * interface is injectable so a durable impl slots in later without touching the\n * routes. We do NOT build a distributed store now (YAGNI).\n */\n\n/** A follow-up message queued on an active thread, dispatched as a continuation. */\nexport interface FollowUp {\n readonly message: string\n}\n\nexport interface ThreadRunRegistry {\n /** The active runId for a thread, or `null` when the thread is idle. */\n getActive(sessionId: string): string | null\n /** Mark a run started for a thread (sets it active; fires + clears one-shot waiters). */\n startRun(sessionId: string, runId: string): void\n /**\n * Mark `runId` ended for a thread. A no-op unless `runId` is the current active\n * run (a stale terminal never clears a newer run). Clears the active run and\n * returns the next FIFO follow-up to dispatch as a continuation, or `undefined`.\n */\n endRun(sessionId: string, runId: string): FollowUp | undefined\n /** FIFO-enqueue a follow-up on a thread (dispatched when the active run ends). */\n queue(sessionId: string, followUp: FollowUp): void\n /**\n * Register a ONE-SHOT waiter fired with the runId of the NEXT run started on\n * this thread. Returns an unsubscribe fn. Used by subscribe-by-thread on an\n * idle thread (attach when the next run starts).\n */\n onNextRun(sessionId: string, cb: (runId: string) => void): () => void\n}\n\ninterface ThreadState {\n activeRunId: string | null\n readonly queue: FollowUp[]\n readonly waiters: Set<(runId: string) => void>\n}\n\nexport function createInProcessThreadRunRegistry(): ThreadRunRegistry {\n const threads = new Map<string, ThreadState>()\n const ensure = (sessionId: string): ThreadState => {\n let s = threads.get(sessionId)\n if (s === undefined) {\n s = { activeRunId: null, queue: [], waiters: new Set() }\n threads.set(sessionId, s)\n }\n return s\n }\n\n return {\n getActive(sessionId) {\n return threads.get(sessionId)?.activeRunId ?? null\n },\n startRun(sessionId, runId) {\n const s = ensure(sessionId)\n s.activeRunId = runId\n // Fire one-shot waiters, then clear them (single-threaded → no re-entrancy race).\n const waiters = [...s.waiters]\n s.waiters.clear()\n for (const cb of waiters) cb(runId)\n },\n endRun(sessionId, runId) {\n const s = threads.get(sessionId)\n if (s?.activeRunId !== runId) return undefined\n s.activeRunId = null\n return s.queue.shift()\n },\n queue(sessionId, followUp) {\n ensure(sessionId).queue.push(followUp)\n },\n onNextRun(sessionId, cb) {\n const s = ensure(sessionId)\n s.waiters.add(cb)\n return () => {\n s.waiters.delete(cb)\n }\n },\n }\n}\n\nlet serverRegistry: ThreadRunRegistry | undefined\n\n/** Process-wide singleton (mirrors `getRunEventCache` / `getApprovalRegistry`). */\nexport function getThreadRunRegistry(): ThreadRunRegistry {\n serverRegistry ??= createInProcessThreadRunRegistry()\n return serverRegistry\n}\n","/**\n * M39 (ADR-0048) — the thread signal routes over the M37 durable transport:\n *\n * - `POST /api/agents/<name>/threads/<sessionId>/message` — a follow-up. ACTIVE\n * run ⇒ FIFO-queue (dispatched as a continuation when it ends); IDLE ⇒ start a\n * run. Returns `202` (the run streams headless into the cache; observe it via\n * the thread stream). Spends LLM tokens ⇒ CSRF-gated (parity with mount-agent).\n * - `GET /api/agents/<name>/threads/<sessionId>/stream` — subscribe. ACTIVE ⇒\n * attach to the durable stream (reuse the M37 reconnect handler); IDLE ⇒ wait\n * (bounded) for the next run on the thread, then attach (subscribe-then-post).\n *\n * These DRIVE the SDK run (via `makeThreadStartRun`) + the M37 cache — no new loop.\n */\n\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApiKeyResolver } from './api-key-resolver.js'\nimport { makeThreadStartRun } from './build-agent-streamer.js'\nimport {\n encodeSse,\n formatSseFrame,\n RUN_ID_HEADER,\n SSE_BASE_HEADERS,\n SSE_DONE_FRAME,\n} from './durable-ui-message-stream-response.js'\nimport { handleAgentRunReconnect } from './handle-agent-run-reconnect.js'\nimport { parseAgentRequestBody } from './mount-agent.js'\nimport { getRunEventCache, type RunEventCache } from './run-event-cache.js'\nimport { postThreadFollowUp } from './thread-dispatcher.js'\nimport { getThreadRunRegistry, type ThreadRunRegistry } from './thread-run-registry.js'\n\nconst THREAD_MESSAGE_PATH = /^\\/api\\/agents\\/([^/]+)\\/threads\\/([^/]+)\\/message$/\nconst THREAD_STREAM_PATH = /^\\/api\\/agents\\/([^/]+)\\/threads\\/([^/]+)\\/stream$/\n\n/** How long a subscribe-to-idle-thread waits for the next run before closing. */\nconst DEFAULT_IDLE_WAIT_MS = 30_000\n\nfunction matchThread(re: RegExp, urlPath: string): { name: string; sessionId: string } | null {\n const m = re.exec(urlPath)\n return m ? { name: decodeURIComponent(m[1]), sessionId: decodeURIComponent(m[2]) } : null\n}\n\nexport const isThreadMessagePath = (urlPath: string) => matchThread(THREAD_MESSAGE_PATH, urlPath)\nexport const isThreadStreamPath = (urlPath: string) => matchThread(THREAD_STREAM_PATH, urlPath)\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/** Inputs for {@link handleThreadMessage} (bundled to stay within the arity budget). */\ninterface ThreadMessageArgs {\n readonly mod: unknown\n /** theokit#328 — a resolver is passed through unresolved so the model can pick the provider. */\n readonly apiKey: string | ApiKeyResolver\n readonly sessionId: string\n readonly request: Request\n /** Labels a fail-fast `AgentDefinitionError`. Human-readable; never a telemetry key. */\n readonly source: string\n /**\n * The agent's name, as the scanner discovered it — the attribute an operator groups runs by.\n * Separate from {@link ThreadMessageArgs.source} since usetheokit/theokit#406: one field was\n * carrying both, and this route's `source` is the label `agent \"chat\"`.\n */\n readonly agentName: string\n readonly csrfMode?: CsrfMode\n}\n\n/** POST a follow-up on a thread. Returns `202` with `{ runId }` (started) or `{ queued: true }`. */\nexport async function handleThreadMessage(args: ThreadMessageArgs): Promise<Response> {\n const { mod, apiKey, sessionId, request, source, agentName, csrfMode = 'strict' } = args\n // A follow-up drives the agent (spends LLM tokens) — reject a cross-origin POST.\n if (csrfMode === 'strict') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid) return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → 400 below */\n }\n const input = parseAgentRequestBody(body)\n if (input === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request must contain a non-empty message.')\n }\n const result = postThreadFollowUp(\n {\n registry: getThreadRunRegistry(),\n cache: getRunEventCache(),\n // usetheokit/theokit#381 — the request goes with it, so the run's spans join the trace of\n // the caller that queued it instead of opening one of their own.\n startRun: makeThreadStartRun(mod, apiKey, source, agentName, request),\n },\n sessionId,\n input.message,\n )\n const headers: Record<string, string> = { 'content-type': 'application/json; charset=utf-8' }\n if ('runId' in result) headers[RUN_ID_HEADER] = result.runId\n return new Response(JSON.stringify(result), { status: 202, headers })\n}\n\n/** GET the thread's durable stream. Active ⇒ attach now; idle ⇒ wait (bounded) for the next run. */\nexport function handleThreadStream(\n sessionId: string,\n request: Request,\n registry: ThreadRunRegistry = getThreadRunRegistry(),\n cache: RunEventCache = getRunEventCache(),\n idleWaitMs = DEFAULT_IDLE_WAIT_MS,\n): Response {\n const active = registry.getActive(sessionId)\n if (active !== null && cache.has(active)) {\n // Reuse the M37 reconnect handler — replay + live tail on the active run.\n return handleAgentRunReconnect(active, request, cache)\n }\n return waitThenAttachStream(sessionId, request, registry, cache, idleWaitMs)\n}\n\n/** Subscribe to an idle thread: wait (bounded) for the NEXT run to start, then attach + tail. */\nfunction waitThenAttachStream(\n sessionId: string,\n request: Request,\n registry: ThreadRunRegistry,\n cache: RunEventCache,\n idleWaitMs: number,\n): Response {\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let closed = false\n const send = (text: string): void => {\n if (closed) return\n try {\n controller.enqueue(encodeSse(text))\n } catch {\n /* closed by an abort race */\n }\n }\n let detachAttach: (() => void) | undefined\n const close = (): void => {\n if (closed) return\n send(SSE_DONE_FRAME)\n closed = true\n try {\n controller.close()\n } catch {\n /* already closed */\n }\n }\n const offNext = registry.onNextRun(sessionId, (runId) => {\n const res = cache.attach(\n runId,\n -1,\n (frame) => {\n send(formatSseFrame(frame.seq, frame.data))\n },\n () => {\n close()\n },\n )\n if (!res.known) {\n close()\n return\n }\n for (const frame of res.replay) send(formatSseFrame(frame.seq, frame.data))\n if (res.ended) {\n close()\n return\n }\n detachAttach = res.unsubscribe\n })\n // HIGH-1 — the waiter teardown MUST run on BOTH the idle-wait timeout AND the\n // client abort, else the (one-shot) `onNextRun` waiter leaks in the registry\n // when a timed-out subscriber's session never receives another run.\n const teardownWaiter = (): void => {\n offNext()\n detachAttach?.()\n }\n const timer = setTimeout(() => {\n teardownWaiter()\n close()\n }, idleWaitMs)\n timer.unref()\n request.signal.addEventListener('abort', () => {\n teardownWaiter()\n clearTimeout(timer)\n close()\n })\n },\n })\n return new Response(stream, { headers: { ...SSE_BASE_HEADERS } })\n}\n","/**\n * M14 (theokit-ai-first) — GET /api/agents/<name>/approvals: list pending HITL approvals.\n *\n * Serves the approvals the CALLER owns, plus the ownerless ones, as JSON. Web Standards Response (G8).\n *\n * ## Why this is scoped, and what it closes\n *\n * The registry is process-wide by contract (ADR 0017): `list()` returns every pending approval, and\n * the `<name>` segment is accepted for a future per-agent store. That was the whole answer here, so\n * one admitted tenant of one agent received every pending approval in the process — other tenants',\n * other agents' — each with the `approvalId` the settle route needs.\n *\n * The draft advisory `GHSA-g94h-459g-rjhj` describes two steps: list without authentication, then\n * settle with an id from the listing. Both halves were closed around this file and neither in it —\n * `admitAux` refuses a caller the agent's policy does not admit (usetheokit/theokit#365), and\n * `approve-agent.ts` refuses a settle by a caller who is not the owner (B-016). The door was gated\n * and the window left open: an ADMITTED caller still read everyone's ids.\n *\n * ## An ownerless approval stays visible, deliberately\n *\n * The registry records an owner only for agents that DECLARE a policy — `admitAgentRequest` resolves\n * no subject for `'public'` or for an undeclared agent, so there is nothing to attribute. The settle\n * route already treats an absent owner as \"nothing to compare a caller against\" and refuses nobody\n * on it. Listing follows the same rule rather than inventing a second one, because hiding those\n * would blank a public agent's own approvals UI while protecting nobody.\n *\n * The filter asks `ownerOf` per approval rather than reading an owner off the listing, because\n * `owner` is held BESIDE `info` precisely so `list()` cannot leak it (B-016). Scoping the response\n * must not put the identity back into it.\n */\nimport type { ApprovalRegistry } from './approval-registry.js'\n\nconst LIST_PATH = /^\\/api\\/agents\\/([^/]+)\\/approvals$/\n\n/** Return the agent name when `urlPath` is the approvals-listing path, else `null`. */\nexport function isListApprovalsPath(urlPath: string): string | null {\n const match = LIST_PATH.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\n/**\n * Serve the pending approvals this caller may see as `{ approvals: [...], scope }` JSON.\n *\n * `scope` says how far the answer reaches — `'instance'` for the in-process registry this\n * framework ships, which is every answer it can currently give (B-236). It is present on every\n * response, empty or not.\n *\n * `subject` is the admitted caller's id, or `undefined` when the agent declares no policy and none\n * was resolved. An approval is included when it has no owner, or when its owner is this subject.\n */\nexport function handleListApprovals(\n registry: ApprovalRegistry,\n subject: string | undefined,\n): Response {\n const visible = registry.list().filter((approval) => {\n const owner = registry.ownerOf(approval.approvalId)\n return owner === undefined || owner === subject\n })\n // B-236 — every answer carries how far it reaches, including a non-empty one: a listing of two\n // from one instance can be two of five, so a caveat that appeared only when the list was empty\n // would vanish exactly when a caller starts trusting the numbers. Read from the registry, not\n // written here, so a shared implementation reports itself without this file changing.\n return new Response(JSON.stringify({ approvals: visible, scope: registry.scope ?? 'instance' }), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n","/**\n * M15/M16 follow-up — shared dispatcher for the agent AUXILIARY routes that BOTH dev (vite\n * middleware) and prod (`theokit start` handler) must serve identically. Before this, these routes\n * were wired only into the dev middleware, so a built/deployed app served none of them (agent cards,\n * MCP, pending-approvals listing all 404'd in production).\n *\n * Single source of truth (DRY): one Web-Request→Response dispatcher, two callers. It handles the\n * routes that derive purely from the agent module + shared registry:\n * - **M15** `GET /.well-known/<name>/agent-card.json` → {@link handleAgentCard}\n * - **M14** `GET /api/agents/<name>/approvals` → {@link handleListApprovals}\n * - **M16** `POST /api/agents/<name>/mcp` → {@link handleMcpJsonRpc}\n * - **M37** `GET /api/agents/<name>/runs/<id>/stream` → {@link handleAgentRunReconnect}\n * - **M39** the two thread routes → {@link handleThreadMessage} / {@link handleThreadStream}\n *\n * Channels (M27) are NOT here: a channel webhook needs app-supplied `validators` + `onMessage`, so\n * the app wires `handleChannelWebhook` in its own route (it cannot be auto-derived from the module).\n * The HITL approve route stays in each caller (it carries caller-specific rate-limiting/CSRF plumbing).\n *\n * ## Deciding and answering are two functions, and that is the fix\n *\n * This used to be one call that took a not-yet-converted request, decided whether it owned the path\n * and answered in the same breath. A caller could therefore learn \"this is an agent aux route\" only\n * by receiving the finished `Response` — too late to run anything around the handler. So the plugin\n * lifecycle, which every other branch of `theokit start` runs, was never run here: `onRequest`,\n * `onResponse` and `onError` fired for a file route and for the plain agent route and for none of\n * these six, and the observability plugin therefore emitted no `http.request` span for the endpoints\n * that spend tokens and settle human decisions (usetheokit/theokit#405).\n *\n * {@link matchAgentAuxRoute} decides; {@link serveMatchedAuxRoute} answers. A caller brackets the\n * gap with whatever the request lifecycle owes — and a seventh route added to the match table\n * inherits that bracket instead of having to remember it.\n */\nimport type { RouteSubject } from '../../core/contracts/route-policy.js'\nimport type { AgentNode } from '../scan/agent-scan.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport {\n AgentIdentityUnavailableError,\n admitAgentRequest,\n agentAccessDenied,\n readAgentPolicy,\n type AgentAccessParams,\n type AgentSubjectResolver,\n} from './agent-access.js'\nimport { isAgentCardPath, handleAgentCard } from './agent-card-handler.js'\nimport type { ApiKeyResolver } from './api-key-resolver.js'\nimport { getApprovalRegistry } from './approval-registry.js'\nimport { handleAgentRunReconnect, isAgentRunStreamPath } from './handle-agent-run-reconnect.js'\nimport {\n handleThreadMessage,\n handleThreadStream,\n isThreadMessagePath,\n isThreadStreamPath,\n} from './handle-thread-routes.js'\nimport { isListApprovalsPath, handleListApprovals } from './list-approvals-handler.js'\nimport { extractAppResources } from './mcp-app-resources.js'\nimport { isMcpPath, handleMcpJsonRpc } from './mcp-handler.js'\nimport { getRunEventCache } from './run-event-cache.js'\n\n/** JSON error envelope (mirrors mount-agent.ts:37 — the parity source for the MCP CSRF gate). */\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/** Dependencies the aux dispatcher needs from its caller (dev or prod). */\ninterface AuxRouteDeps {\n /** Discovered agents (from `scanAgents`). */\n agents: readonly AgentNode[]\n /** Load an agent module from its file path (dev: vite loader; prod: dynamic import). */\n loadModule: (filePath: string) => Promise<unknown>\n /** Absolute base URL (`http(s)://host`) for the agent-card endpoint URLs. */\n baseUrl: string\n /**\n * M34 (#97) — CSRF enforcement mode for the MCP route. `POST /api/agents/<name>/mcp` drives the\n * agent (spends LLM tokens), so a cross-origin POST MUST be rejected in `'strict'` — parity with\n * the agent-run route (`mount-agent.ts:83-91`). Defaults to `'strict'` (safe-by-default); a caller\n * that already gated upstream may pass `'off'`.\n */\n csrfMode?: CsrfMode\n /**\n * M39 — lazily resolve the provider apiKey. Required only for the thread\n * follow-up route (which drives the agent); resolved on demand so non-agent\n * aux routes (card, approvals, stream) never need a provider key.\n *\n * theokit#328 — it receives the model the agent declares, so the credential matches the provider\n * the agent asked for. It was called with no argument, before the module was even compiled, so\n * an agent declaring `anthropic/…` was handed whichever key env priority found first.\n */\n resolveApiKey?: ApiKeyResolver\n /**\n * Who is asking (usetheokit/theokit#365). Invoked ONLY from {@link serveMatchedAuxRoute}, and only\n * when the matched path's agent declares a policy — so the application's `createContext` never\n * runs for a url this dispatcher merely declines. {@link matchAgentAuxRoute} is not given it, for\n * the same reason it is not given the request body: deciding must cost nothing.\n */\n resolveSubject?: AgentSubjectResolver\n}\n\n/**\n * One aux route, already decided. Carries what the match resolved so the serve half re-derives\n * nothing: the agent node it looked up, and for MCP the module it had to open in order to answer\n * the opt-in question at all.\n */\nexport type AgentAuxRoute =\n | { readonly kind: 'card'; readonly agent: AgentNode }\n | { readonly kind: 'approvals'; readonly agent: AgentNode }\n | { readonly kind: 'run-stream'; readonly agent: AgentNode; readonly runId: string }\n | { readonly kind: 'thread-stream'; readonly agent: AgentNode; readonly sessionId: string }\n | { readonly kind: 'thread-message'; readonly agent: AgentNode; readonly sessionId: string }\n | { readonly kind: 'mcp'; readonly agent: AgentNode; readonly mod: unknown }\n\n/**\n * Resolve `name` to a discovered agent and build a route from it, or `null` when there is no name\n * (the path did not match) or no such agent (fall through to the caller's 404).\n *\n * Both misses collapse to `null` deliberately: every one of them means \"this dispatcher does not\n * answer this url\", and the path families below are mutually exclusive, so a family that declines\n * can safely let the next matcher look.\n */\nfunction routeFor<R>(\n deps: AuxRouteDeps,\n name: string | null,\n build: (agent: AgentNode) => R,\n): R | null {\n if (name === null) return null\n const agent = deps.agents.find((a) => a.name === name)\n return agent === undefined ? null : build(agent)\n}\n\n/** The GET-only aux routes: agent card, approvals listing, run stream, thread stream. */\nfunction matchGetAuxRoute(verb: string, urlPath: string, deps: AuxRouteDeps): AgentAuxRoute | null {\n if (verb !== 'GET') return null\n\n // M15 — A2A agent card at `/.well-known/<name>/agent-card.json`.\n const card = routeFor(\n deps,\n isAgentCardPath(urlPath),\n (agent) => ({ kind: 'card', agent }) as const,\n )\n if (card !== null) return card\n\n // M14 — the pending HITL approvals of one agent.\n const approvals = routeFor(\n deps,\n isListApprovalsPath(urlPath),\n (agent) => ({ kind: 'approvals', agent }) as const,\n )\n if (approvals !== null) return approvals\n\n // M37 — the durable run stream (`/runs/<runId>/stream`), reconnect or observe.\n const run = isAgentRunStreamPath(urlPath)\n if (run !== null) {\n return routeFor(\n deps,\n run.name,\n (agent) => ({ kind: 'run-stream', agent, runId: run.runId }) as const,\n )\n }\n\n // M39 — subscribe to a thread (`/threads/<sessionId>/stream`).\n const stream = isThreadStreamPath(urlPath)\n if (stream === null) return null\n return routeFor(\n deps,\n stream.name,\n (agent) => ({ kind: 'thread-stream', agent, sessionId: stream.sessionId }) as const,\n )\n}\n\n/** The POST-only aux routes: thread follow-up and MCP. */\nasync function matchPostAuxRoute(\n verb: string,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<AgentAuxRoute | null> {\n if (verb !== 'POST') return null\n\n // M39 — a follow-up message on a thread.\n const msg = isThreadMessagePath(urlPath)\n if (msg !== null) {\n return routeFor(\n deps,\n msg.name,\n (agent) => ({ kind: 'thread-message', agent, sessionId: msg.sessionId }) as const,\n )\n }\n\n // M16 — the JSON-RPC MCP server, behind the M34 opt-in.\n const agent = routeFor(deps, isMcpPath(urlPath), (found) => found)\n if (agent === null) return null\n // M34 — DEFAULT-DENY: an agent is NOT exposed on MCP unless it explicitly opts in with a named\n // `export const mcp = true` (blueprint D5 — default-EXPOSE is the footgun magnified by the\n // multi-surface thesis). Absent the opt-in, fall through to 404 (the agent is web-only). This is a\n // breaking change from the M16 auto-mount (documented in the CHANGELOG § Security).\n const mod = await deps.loadModule(agent.filePath)\n return isMcpExposed(mod) ? { kind: 'mcp', agent, mod } : null\n}\n\n/**\n * Does this dispatcher own `urlPath`? Returns the matched route, or `null` to fall through (not an\n * aux path, wrong method, unknown agent, or an agent that did not opt into MCP).\n *\n * theokit#400 — this function is where the \"convert only on a path you are about to answer\" rule\n * now lives, and it is enforced by the signature rather than by discipline: it is handed a method\n * and a url and has no request to convert. Converting in order to decide is what drained the Node\n * body stream on every path this dispatcher declined, so an ordinary `POST /api/…` file route then\n * waited forever for an `'end'` that had already fired — no status, no timeout, no response.\n *\n * The one branch that does real work here is MCP, which must open the module to read its\n * `export const mcp` opt-in. That is deliberate: a match that could still fall through would hand\n * the caller a request it had already started a lifecycle for, and the span opened for it would\n * either double-count or never close.\n */\nexport async function matchAgentAuxRoute(\n method: string,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<AgentAuxRoute | null> {\n const verb = method.toUpperCase()\n return matchGetAuxRoute(verb, urlPath, deps) ?? (await matchPostAuxRoute(verb, urlPath, deps))\n}\n\n/** What {@link admitAux} decided, and — when a policy declared one — who was admitted. */\ninterface Admission {\n readonly refusal: Response | null\n /** The admitted caller's id, or `undefined` when no policy asked for one. */\n readonly subject: string | undefined\n}\n\n/**\n * Evaluate the agent's declared policy for one aux endpoint.\n *\n * Returns the refusal `Response`, or `null` when the caller is admitted. Loading the module here is\n * what makes the gate reachable at all: the policy is an export of the agent file, and three of\n * these branches previously answered without ever opening it.\n */\nasync function admitAux(\n deps: AuxRouteDeps,\n agent: AgentNode,\n params: AgentAccessParams,\n body?: unknown,\n): Promise<Admission> {\n const mod = await deps.loadModule(agent.filePath)\n // The subject is RECORDED rather than resolved twice. `admitAgentRequest` returns before touching\n // the resolver when the policy is absent or `'public'`, which is the guarantee `resolveSubject`'s\n // own docblock makes: the application's `createContext` must not run for a url this dispatcher\n // merely declines. Wrapping preserves that exactly — `seen` stays `undefined` in those cases —\n // while the approvals branch gets the id it needs to scope its answer.\n const resolve = deps.resolveSubject\n // A box rather than a `let`: the assignment happens inside a closure the compiler cannot see as\n // reachable before the read, so a plain binding narrows to `never` and `seen?.id` stops compiling.\n const admitted: { subject: RouteSubject | null } = { subject: null }\n const recording: AgentSubjectResolver | undefined =\n resolve === undefined\n ? undefined\n : async () => {\n // B-254 — the APP's resolution is wrapped, not the whole admission. A policy that throws\n // is a different failure with a different cause, and tagging both the same way would hand\n // an operator one code for two problems.\n try {\n admitted.subject = await resolve()\n } catch (cause) {\n throw new AgentIdentityUnavailableError(cause)\n }\n return admitted.subject\n }\n let decision\n try {\n decision = await admitAgentRequest(\n readAgentPolicy(mod, agent.filePath),\n recording,\n params,\n body,\n )\n } catch (err) {\n // B-254 — only the identity failure is shaped here. Anything else keeps escaping exactly as it\n // did, because turning every throw on this path into a 500 would swallow the ones a caller\n // SHOULD see differently — `AgentPolicyTypeError` is a developer's mistake, not an outage.\n if (!(err instanceof AgentIdentityUnavailableError)) throw err\n console.error('[theokit] agent identity resolution failed', err)\n // Returned through `refusal`, which is this function's channel for \"answer with this Response\"\n // — the caller writes it out and never looks at `subject`. `subject: undefined` is the honest\n // value: nobody was identified, and it must not read as an anonymous caller who WAS.\n return {\n refusal: jsonError(\n 500,\n 'IDENTITY_UNAVAILABLE',\n 'The application could not resolve the caller. This is not a refusal: `createContext` in ' +\n 'server/context.ts threw, so there was no identity to judge. The server log carries the cause.',\n ),\n subject: undefined,\n }\n }\n const refusal = decision.allowed ? null : agentAccessDenied(decision, params)\n return { refusal, subject: admitted.subject?.id }\n}\n\n/**\n * Answer a route {@link matchAgentAuxRoute} already claimed. Always returns a `Response` — every\n * fall-through was decided upstream, which is what lets a caller open a request span before this\n * runs and be sure something will close it.\n */\nexport async function serveMatchedAuxRoute(\n route: AgentAuxRoute,\n request: Request,\n deps: AuxRouteDeps,\n): Promise<Response> {\n if (route.kind === 'card') {\n const mod = await deps.loadModule(route.agent.filePath)\n return handleAgentCard(mod, route.agent.name, route.agent.agentPath, deps.baseUrl)\n }\n\n // usetheokit/theokit#365 — the approvals listing used to answer 200 with every pending approval\n // id to anyone who asked, and the id is all the approve route needs to settle a paused tool.\n if (route.kind === 'approvals') {\n const admission = await admitAux(deps, route.agent, {\n agent: route.agent.name,\n endpoint: 'approvals',\n })\n if (admission.refusal !== null) return admission.refusal\n // SCOPED to this caller. `admitAux` answered \"may you touch this agent's approvals\"; it cannot\n // answer \"which of them are yours\", and the registry is process-wide by contract (ADR 0017).\n return handleListApprovals(getApprovalRegistry(), admission.subject)\n }\n\n // M37 — INTENTIONALLY open (no CSRF, no auth gate): a GET is not CSRF-vulnerable, the run-start\n // POST is already gated, and the `runId` is a 122-bit UUID minted BY THE SERVER (`mintRunId`) and\n // handed only to the caller that started the run — a capability the framework issued rather than\n // a name the caller chose. That is the property the thread and conversation keys lack, and it is\n // the whole of the difference. Observe-by-runId is a FEATURE (ADR-0046 D5). Do NOT add a\n // custom-header CSRF check here: browsers send NO custom headers with `EventSource`, so it would\n // break native SSE reconnect.\n if (route.kind === 'run-stream') {\n return handleAgentRunReconnect(route.runId, request, getRunEventCache())\n }\n\n if (route.kind === 'mcp') return serveMcpRoute(route, request, deps)\n\n return serveThreadRoute(route, request, deps)\n}\n\n/**\n * M39 — serve the thread routes:\n * - `POST .../threads/<sessionId>/message` (follow-up) — loads the module, drives\n * the run headless via the thread dispatcher. Needs `resolveApiKey` (drives the\n * agent) → 501 when absent (rather than a silent 404).\n * - `GET .../threads/<sessionId>/stream` (subscribe) — attach to the active/next\n * run's durable stream. INTENTIONALLY open (GET, no custom headers — like the\n * M37 reconnect route).\n *\n * SECURITY (thread stream): unlike the M37 reconnect route — keyed on an\n * `mintRunId()` UUID (122-bit unguessable) — the thread stream is keyed on the\n * caller-supplied `sessionId`, so an app using a PREDICTABLE sessionId (a user id,\n * an email, a tenant-derived key) is one guess away from another party reading the\n * thread's live conversation.\n *\n * usetheokit/theokit#365 — this paragraph used to end by telling the application it\n * \"MUST add its own auth gate before this endpoint\", and no such gate was constructible:\n * the URL is dispatched before route matching, so no `route()` and no middleware ever\n * saw it. The gate is the agent's own `export const policy`, evaluated here, and the\n * instruction is now one an application can follow.\n */\nasync function serveThreadRoute(\n route: Extract<AgentAuxRoute, { kind: 'thread-stream' | 'thread-message' }>,\n request: Request,\n deps: AuxRouteDeps,\n): Promise<Response> {\n const { agent, sessionId } = route\n\n if (route.kind === 'thread-stream') {\n const { refusal } = await admitAux(deps, agent, {\n agent: agent.name,\n endpoint: 'thread-stream',\n sessionId,\n })\n return refusal ?? handleThreadStream(sessionId, request)\n }\n\n const { refusal } = await admitAux(deps, agent, {\n agent: agent.name,\n endpoint: 'thread-message',\n sessionId,\n })\n if (refusal !== null) return refusal\n if (deps.resolveApiKey === undefined) {\n // MEDIUM-1 — the path matched but the caller wired no provider-key resolver.\n // Fail loudly (501) instead of a silent 404 that reads as \"route not found\".\n return jsonError(\n 501,\n 'NOT_CONFIGURED',\n 'Thread follow-up requires a provider API key (resolveApiKey was not provided to serveMatchedAuxRoute).',\n )\n }\n const mod = await deps.loadModule(agent.filePath)\n return handleThreadMessage({\n mod,\n // Passed unresolved: `makeThreadStartRun` calls it once the module is compiled and the\n // model is known (theokit#328).\n apiKey: deps.resolveApiKey,\n sessionId,\n request,\n source: `agent \"${agent.name}\"`,\n // usetheokit/theokit#406 — the label above reads well in an `AgentDefinitionError` and is not\n // a name; the spans get the name, so the same agent is one series whichever route started it.\n agentName: agent.name,\n csrfMode: deps.csrfMode ?? 'strict',\n })\n}\n\n/**\n * Serve the MCP route with the M34 gates. The opt-in check already ran in the match (it is what\n * decides ownership), so what is left here is: default-DENY policy → CSRF → dispatch.\n */\nasync function serveMcpRoute(\n route: Extract<AgentAuxRoute, { kind: 'mcp' }>,\n request: Request,\n deps: AuxRouteDeps,\n): Promise<Response> {\n const { agent, mod } = route\n\n // usetheokit/theokit#365 — the MCP route drives the agent and reaches its tools, so it answers to\n // the same declared policy as the run route.\n const mcpParams = { agent: agent.name, endpoint: 'mcp' as const }\n const mcpDecision = await admitAgentRequest(\n readAgentPolicy(mod, agent.filePath),\n deps.resolveSubject,\n mcpParams,\n )\n if (!mcpDecision.allowed) return agentAccessDenied(mcpDecision, mcpParams)\n\n // M34 (#97) — enforce CSRF BEFORE any work. The MCP route drives the agent (real LLM tokens), so a\n // cross-origin POST must be rejected — parity with the agent-run route (`mount-agent.ts:83-91`).\n const csrfMode = deps.csrfMode ?? 'strict'\n if (csrfMode === 'strict') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid) return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* malformed/empty JSON → handleMcpJsonRpc returns a -32600 envelope */\n }\n // M30 — pass the agent's declared `ui://` App resources (named `appResources` export) so the MCP\n // server advertises + serves them via resources/list + resources/read.\n return handleMcpJsonRpc(mod, agent.name, body, extractAppResources(mod))\n}\n\n/**\n * M34 — DEFAULT-DENY opt-in check: is this agent module exposed on the MCP surface? An agent opts in\n * with a named `export const mcp = true` (mirroring the `appResources` named-export convention).\n * Anything else (absent / falsy) → NOT exposed. Read at the emit layer (blueprint D5).\n */\nfunction isMcpExposed(mod: unknown): boolean {\n return (mod as { mcp?: unknown } | null | undefined)?.mcp === true\n}\n","/**\n * One bracket around a Node-dispatched request, so every agent branch has the same lifecycle\n * (theokit#324, usetheokit/theokit#405).\n *\n * ## Why this is a function and not a paragraph in a review checklist\n *\n * `executeRoute` and `executeAction` have run the plugin lifecycle since the beginning. The agent\n * branches did not, and they were fixed one at a time: theokit#324 taught the plain\n * `POST /api/agents/<name>` to call `applyDecorations` → `runOnRequest` → handler →\n * `runOnResponse`, with `runOnError` on the failure path, and copied that shape into the dev\n * middleware. The aux routes (thread message and stream, MCP, agent card, approvals listing) and the\n * HITL approve route kept answering without it, in BOTH surfaces, so an application embedding\n * TheoKit had no supported place to observe six endpoints — two of which spend tokens and one of\n * which settles a human decision. The observability plugin is the case that made it legible: no\n * `onRequest` means no `http.request` span, and an operator reading HTTP latency or error rate sees\n * no traffic for endpoints that are serving traffic (usetheokit/theokit#405).\n *\n * A copied bracket is what let five branches drift from one. This is the bracket, once.\n *\n * ## The conversion is here, and only here\n *\n * The lifecycle needs a Web `Request` (that is what `PluginContext` carries), and converting an\n * `IncomingMessage` drains its body exactly once — a second conversion yields a Request whose body\n * is an empty closed stream, which is a silent truncation (theokit#400). So callers hand over a\n * {@link WebRequestSource}, whose `toRequest()` memoizes, and hand the SAME source to the handler\n * they wrap. Converting is therefore idempotent across the bracket, by construction rather than by\n * anyone noticing.\n *\n * The caller must still only enter this bracket on a path it is about to answer: `toRequest()` runs\n * here, and running it to decide ownership is the theokit#400 hang.\n */\nimport type { ServerResponse } from 'node:http'\n\nimport type { PluginContext } from '../plugin-types.js'\nimport type { PluginRunner } from '../plugins/plugin-runner.js'\n\nimport type { WebRequestSource } from './node-request.js'\nimport { sendError } from './send-response.js'\n\n/** What the bracket needs from the branch it wraps. */\nexport interface PluginLifecycleTarget {\n /** The unconverted request. Converted once, here — see the module docstring. */\n source: WebRequestSource\n res: ServerResponse\n requestId: string\n /** Absent ⇒ no plugins are registered, and the bracket costs one object allocation. */\n pluginRunner: PluginRunner | undefined\n /** The 500 message when the handler (or the conversion) throws, e.g. `'Agent handler failed'`. */\n failureMessage: string\n}\n\nfunction messageOf(err: unknown, fallback: string): string {\n return err instanceof Error ? err.message : fallback\n}\n\n/**\n * Run `serve` inside the plugin lifecycle: decorations, `onRequest` (which may short-circuit),\n * the handler, `onError` on a throw, and `onResponse` after the response is written.\n *\n * Mirrors `executeRoute`'s shape deliberately — same `PluginContext`, same short-circuit contract,\n * same `onError` placement. An agent route should not have a lifecycle of its own.\n *\n * Never throws: a handler failure becomes a 500 through the same `sendError` envelope the branches\n * used before, so the caller's `logRequest` still reads a settled `res.statusCode`.\n */\nexport async function serveThroughPluginLifecycle(\n target: PluginLifecycleTarget,\n serve: (request: Request, pluginCtx: PluginContext) => Promise<void>,\n): Promise<void> {\n const { res, requestId, pluginRunner, failureMessage } = target\n\n let request: Request\n try {\n request = target.source.toRequest()\n } catch (err) {\n // A request the adapter cannot represent is a 500, exactly as it was before these branches grew\n // a lifecycle — the conversion used to sit inside the handler's own try.\n sendError(res, 'INTERNAL', messageOf(err, failureMessage), 500, undefined, requestId)\n return\n }\n\n const pluginCtx: PluginContext = { request, response: res, ctx: {}, requestId }\n\n if (pluginRunner) {\n pluginRunner.applyDecorations(pluginCtx.ctx)\n const onRequest = await pluginRunner.runOnRequest(pluginCtx)\n // A hook that answered the request stops the pipeline — the same guarantee `executeRoute` gives.\n if (onRequest.shortCircuited) return\n }\n\n try {\n await serve(request, pluginCtx)\n } catch (err) {\n if (pluginRunner) await pluginRunner.runOnError(pluginCtx, err)\n sendError(res, 'INTERNAL', messageOf(err, failureMessage), 500, undefined, requestId)\n }\n\n // After the response is written, as `executeRoute` does — a hook here observes a completed turn.\n if (pluginRunner) await pluginRunner.runOnResponse(pluginCtx)\n}\n","/**\n * Who is asking, on the Node dispatch path that serves the agent endpoints\n * (usetheokit/theokit#365).\n *\n * `executeRoute` answers this with `subjectFromContext(ctx)`, where `ctx` is the run context built\n * from the application's `server/context.ts` plus plugin decorations. The agent branches build no\n * such context — which is why `mountAgent`'s `subject` option had nowhere to come from and every\n * caller left it out. This builds the same context from the same two sources, so the agent surface\n * reads identity from the seam the routes already read it from rather than from a second one.\n *\n * ## Why it returns a resolver instead of a subject\n *\n * `tryServeAgentAux` runs for EVERY url, including the ones it does not own. Resolving eagerly\n * there would execute the application's `createContext` twice on every route request — once in the\n * aux branch that falls through, once in the route executor. So callers get a memoized thunk and\n * invoke it only on a path they are about to answer, which is the same discipline theokit#400\n * imposed on `source.toRequest()` for the same dispatcher.\n *\n * ## What `createContext` can read here, and what it cannot (usetheokit/theokit#415)\n *\n * **Headers and cookies: yes. The request body: no.**\n *\n * This used to state the opposite as a MUST — \"invoke it before converting the request to a Web\n * `Request`\" — and no caller could honour it. The laziness argued for directly above is what makes\n * it unsatisfiable: the invocation necessarily happens INSIDE the handler, and the handler is\n * entered after `serveThroughPluginLifecycle` has already called `source.toRequest()` at the top of\n * its bracket. `incomingMessageToWebRequest` attaches the Node readable as the request body\n * (`body: webStream, duplex: 'half'`), so by the time an application's `createContext` receives\n * that `IncomingMessage`, the stream is consumed.\n *\n * The contract was written for an EAGER resolver and kept when the resolver became lazy. Both\n * decisions were right on their own; the sentence joining them was not, and it pointed callers at\n * something impossible while implying a capability that does not exist.\n *\n * Practically this costs little — identity is overwhelmingly a header or a cookie, and both survive\n * the conversion untouched. What it costs an application that resolves identity from the BODY is an\n * empty read or a wait for an `'end'` that already fired, which is why saying so plainly matters\n * more than the frequency suggests.\n *\n * Restoring body access would mean resolving eagerly — running the application's `createContext`\n * twice on every route request, which is exactly what the laziness exists to prevent — or buffering\n * every agent request body to replay it, which is a cost paid by every caller for a case almost\n * none has.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { subjectFromContext, type RouteSubject } from '../../core/contracts/route-policy.js'\nimport type { PluginRunner } from '../plugins/plugin-runner.js'\nimport type { LoadModule } from '../scan/module-loader.js'\n\nimport { createServerContext } from './middleware-runner.js'\n\n/** What {@link createAgentSubjectResolver} needs in order to build the run context. */\nexport interface AgentSubjectSources {\n req: IncomingMessage\n res: ServerResponse\n loadModule: LoadModule\n /** The app's `server/` directory. Absent ⇒ there is no `context.ts` to consult. */\n serverDir: string | undefined\n pluginRunner: PluginRunner | undefined\n}\n\n/**\n * Build a memoized resolver for the caller's identity.\n *\n * Decorations are applied ON TOP of the factory's result, matching `executeRoute`: a plugin\n * decoration wins only where `context.ts` did not set the same key.\n *\n * A `createContext` that throws is not swallowed — an application whose identity resolution is\n * broken must not be treated as an anonymous caller, because that reads as a clean refusal and\n * hides the fault. The throw reaches the branch's own error handler and becomes a 500.\n */\nexport function createAgentSubjectResolver(\n sources: AgentSubjectSources,\n): () => Promise<RouteSubject | null> {\n let pending: Promise<RouteSubject | null> | undefined\n return () => {\n pending ??= resolve(sources)\n return pending\n }\n}\n\nasync function resolve(sources: AgentSubjectSources): Promise<RouteSubject | null> {\n const { req, res, loadModule, serverDir, pluginRunner } = sources\n const produced =\n serverDir === undefined ? {} : await createServerContext(req, res, loadModule, serverDir)\n return subjectFrom(produced, pluginRunner)\n}\n\n/**\n * The app's `createContext`, as a host that is not Node sees it.\n *\n * Deliberately NOT `middleware-runner`'s `ContextFactory`, which types its argument as\n * `IncomingMessage`/`ServerResponse`. Those are exactly right for the dev path and wrong here: on\n * every deploy target the pair is what `createWebShim` synthesises over a Web `Request`. Widening\n * to `unknown` says what is actually true at this boundary rather than casting a lie past the\n * type system — the app's own `createContext` is typed by the app, and a generated fragment is the\n * one caller that cannot promise it Node objects.\n */\nexport type HostContextFactory = (args: { request: unknown; response: unknown }) => unknown\n\n/**\n * The half that CALLS an already-resolved context factory (B-185, ADR 0014).\n *\n * {@link createAgentSubjectResolver} does two things: it LOCATES `context.ts` on a filesystem —\n * `existsSync` plus `loadModule`, inside `createServerContext` — and then CALLS what it found. A\n * deploy target with no filesystem can do the second and not the first, so the generator bakes the\n * module as a static import (the way routes, agents and plugins already are) and hands the factory\n * here directly.\n *\n * Measured before this existed: passing `serverDir: undefined` to the resolver above takes its\n * `produced = {}` branch, never touches `req`/`res`/`loadModule`, and returns\n * `subjectFromContext({})` — which is null for every caller. Satisfying its types would have\n * satisfied nothing.\n *\n * `request`/`response` are whatever the host has. On every deploy target that is the\n * `ShimRequest`/`ShimResponse` pair `createWebShim` synthesises over a Web `Request`, which is the\n * same contract routes on those targets have crossed since they were baked.\n */\nexport function createSubjectResolverFromFactory(\n createContext: HostContextFactory | undefined,\n request: unknown,\n response: unknown,\n pluginRunner?: PluginRunner,\n): () => Promise<RouteSubject | null> {\n let pending: Promise<RouteSubject | null> | undefined\n return () => {\n pending ??= (async () =>\n subjectFrom(\n // An app with no `context.ts` bakes no factory, and an anonymous caller is the honest\n // answer — not an error, and not a subject invented to fill the slot.\n createContext === undefined ? {} : await createContext({ request, response }),\n pluginRunner,\n ))()\n return pending\n }\n}\n\n/**\n * Decorations are applied ON TOP of the factory's result, matching `executeRoute`. Shared by both\n * entries so the rule — and the deliberate non-swallowing of a throwing factory, which reaches the\n * branch's own error handler as a 500 rather than reading as a clean refusal — has one home.\n */\nfunction subjectFrom(\n produced: unknown,\n pluginRunner: PluginRunner | undefined,\n): RouteSubject | null {\n const ctx = (produced ?? {}) as Record<string, unknown>\n pluginRunner?.applyDecorations(ctx)\n return subjectFromContext(ctx)\n}\n"],"mappings":"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","names":["resolve","resolve","jsonError","jsonError","agent","resolve","refusal","messageOf","resolve"]}
|