theokit 0.48.2 → 0.48.4
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/LICENSE +2 -2
- package/dist/{actions-virtual-module-2VVLVXHY.js → actions-virtual-module-GB5FWUPS.js} +6 -6
- package/dist/{actions-virtual-module-Z5LXAX7E.js → actions-virtual-module-YQXRSSMW.js} +2 -2
- package/dist/add-3VLFUPF4.js +0 -0
- package/dist/{agent-Y3JKRROD.js → agent-OAFIXOR5.js} +2 -2
- package/dist/{agents-typed-client-X56CQ6FX.js → agents-typed-client-D4OA2TXB.js} +12 -4
- package/dist/agents-typed-client-D4OA2TXB.js.map +1 -0
- package/dist/{agents-typed-client-TNHCUU7Q.js → agents-typed-client-INR2E7B3.js} +12 -4
- package/dist/agents-typed-client-INR2E7B3.js.map +1 -0
- package/dist/{app-typed-client-N4I4RBLT.js → app-typed-client-5QDNHANF.js} +6 -6
- package/dist/{app-typed-client-NI3CM2FA.js → app-typed-client-GHO3EWZK.js} +2 -2
- package/dist/{aws-lambda-7SUTORXM.js → aws-lambda-CYNDGU5O.js} +3 -3
- package/dist/body-parser-web-TIOTXPDW.js +0 -0
- package/dist/broadcast-LUMOJIJT.js +0 -0
- package/dist/{build-SJQDRD2B.js → build-QYEU5OOZ.js} +13 -12
- package/dist/build-QYEU5OOZ.js.map +1 -0
- package/dist/build-request-body-preview-XPZ2GYMG.js +0 -0
- package/dist/{bun-TLYZCEGE.js → bun-33RD6K7O.js} +3 -3
- package/dist/check-45NKUVSQ.js +0 -0
- package/dist/chunk-2CVV6CNN.js +0 -0
- package/dist/{chunk-ORKNVORZ.js → chunk-47CINLWF.js} +2 -2
- package/dist/chunk-47VNWTL4.js +0 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/chunk-6GGWS7J3.js +0 -0
- package/dist/{chunk-O5W7XSUX.js → chunk-6PWNTTPI.js} +4 -4
- package/dist/{chunk-GEHX6CAE.js → chunk-6RJSGTMN.js} +3 -3
- package/dist/{chunk-GEHX6CAE.js.map → chunk-6RJSGTMN.js.map} +1 -1
- package/dist/{chunk-HRCNHWLP.js → chunk-7CQ6DEWZ.js} +2 -2
- package/dist/{chunk-YRRWQDKK.js → chunk-7QAYC757.js} +89 -14
- package/dist/chunk-7QAYC757.js.map +1 -0
- package/dist/{chunk-CUYEA62B.js → chunk-7VPQHEAJ.js} +2 -4
- package/dist/chunk-7VPQHEAJ.js.map +1 -0
- package/dist/{chunk-5JJCNEUS.js → chunk-A4ZEP3FE.js} +17 -8
- package/dist/chunk-A4ZEP3FE.js.map +1 -0
- package/dist/chunk-A7JBF5FO.js +0 -0
- package/dist/{chunk-MNIQ5F3Y.js → chunk-BMHS4WAN.js} +1 -1
- package/dist/{chunk-MNIQ5F3Y.js.map → chunk-BMHS4WAN.js.map} +1 -1
- package/dist/{chunk-DGQO372T.js → chunk-E576KYYT.js} +3 -3
- package/dist/chunk-E576KYYT.js.map +1 -0
- package/dist/{chunk-L5Z6IY5U.js → chunk-FNTLKHZ7.js} +1 -1
- package/dist/{chunk-L5Z6IY5U.js.map → chunk-FNTLKHZ7.js.map} +1 -1
- package/dist/chunk-GEQJAFSV.js +0 -0
- package/dist/{chunk-AOGYAXPY.js → chunk-GH32R55S.js} +2 -2
- package/dist/{chunk-Y2KHTD2W.js → chunk-GQNBZDYK.js} +1 -1
- package/dist/{chunk-Y2KHTD2W.js.map → chunk-GQNBZDYK.js.map} +1 -1
- package/dist/{chunk-VTJMND23.js → chunk-H6YBKIUN.js} +2 -2
- package/dist/chunk-HBU2GIMZ.js +0 -0
- package/dist/chunk-IEES3CHD.js +0 -0
- package/dist/{chunk-62EU2BVZ.js → chunk-J5MXCGKA.js} +1 -1
- package/dist/{chunk-62EU2BVZ.js.map → chunk-J5MXCGKA.js.map} +1 -1
- package/dist/{chunk-HNBWZKIQ.js → chunk-KTMQUYED.js} +1 -1
- package/dist/{chunk-HNBWZKIQ.js.map → chunk-KTMQUYED.js.map} +1 -1
- package/dist/chunk-LNVMHQ2Z.js +0 -0
- package/dist/{chunk-2ADKT37V.js → chunk-LXAESF4Y.js} +2 -2
- package/dist/{chunk-2ADKT37V.js.map → chunk-LXAESF4Y.js.map} +1 -1
- package/dist/{chunk-FXZZS7YW.js → chunk-T5C46IIU.js} +1 -1
- package/dist/chunk-T5C46IIU.js.map +1 -0
- package/dist/{chunk-ZALJIZH2.js → chunk-VQ62FIQW.js} +80 -16
- package/dist/chunk-VQ62FIQW.js.map +1 -0
- package/dist/chunk-VQIUTCK2.js +0 -0
- package/dist/{chunk-FO55VGMQ.js → chunk-XR74JD4M.js} +1 -1
- package/dist/{chunk-FO55VGMQ.js.map → chunk-XR74JD4M.js.map} +1 -1
- package/dist/{chunk-KSZGBUTI.js → chunk-YGAXV5HR.js} +1 -1
- package/dist/{chunk-KSZGBUTI.js.map → chunk-YGAXV5HR.js.map} +1 -1
- package/dist/{chunk-D3425YDV.js → chunk-ZB7D62OA.js} +2 -2
- package/dist/cli/index.js +14 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloudflare-JTBKOX3F.js → cloudflare-KYUWCEPL.js} +3 -3
- package/dist/configure-agent-registry-ZOBVU3MV.js +0 -0
- package/dist/controller-swc-transform-LUJ4VAZJ.js +0 -0
- package/dist/db-IVVCQE5Z.js +0 -0
- package/dist/{define-websocket-CPQcQK9h.d.ts → define-websocket-DzPn1sR6.d.ts} +0 -1
- package/dist/{deno-deploy-3ESNGYZU.js → deno-deploy-CCTB7HGS.js} +3 -3
- package/dist/{dev-6TWDBX6G.js → dev-5BKIKJYI.js} +12 -12
- package/dist/{dev-emit-7ZZRTYF3.js → dev-emit-GWD6IXV7.js} +3 -3
- package/dist/{dev-emit-E5Z26AMV.js → dev-emit-NBT6JD7R.js} +2 -2
- package/dist/dispatcher-KMZBGMM5.js +0 -0
- package/dist/docker-LZZB4D5E.js +0 -0
- package/dist/doctor-Y53QTUSP.js +0 -0
- package/dist/{generate-WQSNW4F6.js → generate-Y3GWM57K.js} +2 -2
- package/dist/generate-Y3GWM57K.js.map +1 -0
- package/dist/index.d.ts +36 -8
- package/dist/index.js +3 -3
- package/dist/{info-QB7Q3SHP.js → info-MSBQ72EM.js} +4 -4
- package/dist/{internal-api-XQA2L663.js → internal-api-ICU7ITJD.js} +6 -6
- package/dist/{internal-api-REGN3WMX.js → internal-api-WEOSTMSN.js} +2 -2
- package/dist/{load-config-JKJ73TOD.js → load-config-2LP2OEKD.js} +4 -4
- package/dist/{mcp-CO4AGAXV.js → mcp-4FNNCFPM.js} +3 -3
- package/dist/{netlify-OFJUYXTZ.js → netlify-AFMDNKT4.js} +3 -3
- package/dist/node-ZJQEJSP3.js +10 -0
- package/dist/{openapi-5CZCL7YP.js → openapi-FWNYHB6T.js} +10 -10
- package/dist/{openapi-5CZCL7YP.js.map → openapi-FWNYHB6T.js.map} +1 -1
- package/dist/registry-UJ6KNQPJ.js +23 -0
- package/dist/{router-6MAACS25.js → router-V64GKAS6.js} +1 -1
- package/dist/router-V64GKAS6.js.map +1 -0
- package/dist/{routes-GWAWUVVQ.js → routes-6AMNVFMI.js} +5 -5
- package/dist/scan-7MJC6PYU.js +0 -0
- package/dist/{schema-BpH6ivDY.d.ts → schema-BdakLtuC.d.ts} +1 -1
- package/dist/{schema-2JEG6TTU.js → schema-Z3Y7SFLE.js} +3 -7
- package/dist/server/agent/index.d.ts +1 -1
- package/dist/server/cost/index.d.ts +0 -3
- package/dist/server/cost/index.js +1 -1
- package/dist/server/cron/index.d.ts +0 -4
- package/dist/server/cron/index.js +1 -1
- package/dist/server/define/index.d.ts +2 -3
- package/dist/server/define/index.js +1 -1
- package/dist/server/index.d.ts +4 -6
- package/dist/server/index.js +6 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +1 -4
- package/dist/server/jobs/index.js +1 -1
- package/dist/server/realtime/index.d.ts +1 -1
- package/dist/server/scan/index.js +1 -1
- package/dist/server/storage/index.d.ts +1 -1
- package/dist/server/webhook/index.d.ts +2 -2
- package/dist/server-error-to-envelope-4TGCDQ2S.js +0 -0
- package/dist/{server-routes-hmr-4W5L2MKN.js → server-routes-hmr-5D2ZPL74.js} +1 -1
- package/dist/server-routes-hmr-5D2ZPL74.js.map +1 -0
- package/dist/{server-routes-hmr-6CRNSEWF.js → server-routes-hmr-BWN3WIGR.js} +1 -1
- package/dist/{server-routes-hmr-6CRNSEWF.js.map → server-routes-hmr-BWN3WIGR.js.map} +1 -1
- package/dist/services-json-7ZXJ4CIS.js +0 -0
- package/dist/{services-typed-client-II7LKXKO.js → services-typed-client-PSAGRPFF.js} +2 -2
- package/dist/{sessions-gc-H4WC4IXB.js → sessions-gc-VAZR654G.js} +3 -3
- package/dist/sessions-gc-VAZR654G.js.map +1 -0
- package/dist/{start-IO3ZYGEX.js → start-QDPYXPW2.js} +14 -14
- package/dist/start-QDPYXPW2.js.map +1 -0
- package/dist/{static-HTT2WNIT.js → static-R25HSXEF.js} +3 -3
- package/dist/{storage-manager-ZSQFLEYT.js → storage-manager-3O5PDO6O.js} +1 -1
- package/dist/storage-manager-3O5PDO6O.js.map +1 -0
- package/dist/{storage-manager-C4jsO0Tp.d.ts → storage-manager-C6hmsaer.d.ts} +1 -1
- package/dist/{theo-cloud-ZTQLXGPS.js → theo-cloud-XAHWOUFK.js} +2 -2
- package/dist/{upgrade-readiness-77P6VNB4.js → upgrade-readiness-OILJRFOV.js} +1 -1
- package/dist/{upgrade-readiness-77P6VNB4.js.map → upgrade-readiness-OILJRFOV.js.map} +1 -1
- package/dist/{vercel-72HFEMJ3.js → vercel-SRS2DH6O.js} +3 -3
- package/dist/vite-plugin/index.d.ts +1 -1
- package/dist/vite-plugin/index.js +3 -3
- package/dist/{vite-plugin-NQ6F7FGV.js → vite-plugin-YOCPU4C6.js} +11 -11
- package/dist/{webhook-types-CNUZY7o1.d.ts → webhook-types-DGIjjcaI.d.ts} +0 -2
- package/package.json +11 -10
- package/dist/agents-typed-client-TNHCUU7Q.js.map +0 -1
- package/dist/agents-typed-client-X56CQ6FX.js.map +0 -1
- package/dist/build-SJQDRD2B.js.map +0 -1
- package/dist/chunk-5JJCNEUS.js.map +0 -1
- package/dist/chunk-CUYEA62B.js.map +0 -1
- package/dist/chunk-DGQO372T.js.map +0 -1
- package/dist/chunk-FXZZS7YW.js.map +0 -1
- package/dist/chunk-YRRWQDKK.js.map +0 -1
- package/dist/chunk-ZALJIZH2.js.map +0 -1
- package/dist/generate-WQSNW4F6.js.map +0 -1
- package/dist/node-ND4MJU3Z.js +0 -10
- package/dist/registry-QTSMLOVG.js +0 -23
- package/dist/router-6MAACS25.js.map +0 -1
- package/dist/server-routes-hmr-4W5L2MKN.js.map +0 -1
- package/dist/sessions-gc-H4WC4IXB.js.map +0 -1
- package/dist/start-IO3ZYGEX.js.map +0 -1
- package/dist/storage-manager-ZSQFLEYT.js.map +0 -1
- /package/dist/{actions-virtual-module-2VVLVXHY.js.map → actions-virtual-module-GB5FWUPS.js.map} +0 -0
- /package/dist/{actions-virtual-module-Z5LXAX7E.js.map → actions-virtual-module-YQXRSSMW.js.map} +0 -0
- /package/dist/{agent-Y3JKRROD.js.map → agent-OAFIXOR5.js.map} +0 -0
- /package/dist/{app-typed-client-N4I4RBLT.js.map → app-typed-client-5QDNHANF.js.map} +0 -0
- /package/dist/{app-typed-client-NI3CM2FA.js.map → app-typed-client-GHO3EWZK.js.map} +0 -0
- /package/dist/{aws-lambda-7SUTORXM.js.map → aws-lambda-CYNDGU5O.js.map} +0 -0
- /package/dist/{bun-TLYZCEGE.js.map → bun-33RD6K7O.js.map} +0 -0
- /package/dist/{chunk-ORKNVORZ.js.map → chunk-47CINLWF.js.map} +0 -0
- /package/dist/{chunk-O5W7XSUX.js.map → chunk-6PWNTTPI.js.map} +0 -0
- /package/dist/{chunk-HRCNHWLP.js.map → chunk-7CQ6DEWZ.js.map} +0 -0
- /package/dist/{chunk-AOGYAXPY.js.map → chunk-GH32R55S.js.map} +0 -0
- /package/dist/{chunk-VTJMND23.js.map → chunk-H6YBKIUN.js.map} +0 -0
- /package/dist/{chunk-D3425YDV.js.map → chunk-ZB7D62OA.js.map} +0 -0
- /package/dist/{cloudflare-JTBKOX3F.js.map → cloudflare-KYUWCEPL.js.map} +0 -0
- /package/dist/{deno-deploy-3ESNGYZU.js.map → deno-deploy-CCTB7HGS.js.map} +0 -0
- /package/dist/{dev-6TWDBX6G.js.map → dev-5BKIKJYI.js.map} +0 -0
- /package/dist/{dev-emit-7ZZRTYF3.js.map → dev-emit-GWD6IXV7.js.map} +0 -0
- /package/dist/{dev-emit-E5Z26AMV.js.map → dev-emit-NBT6JD7R.js.map} +0 -0
- /package/dist/{info-QB7Q3SHP.js.map → info-MSBQ72EM.js.map} +0 -0
- /package/dist/{internal-api-XQA2L663.js.map → internal-api-ICU7ITJD.js.map} +0 -0
- /package/dist/{internal-api-REGN3WMX.js.map → internal-api-WEOSTMSN.js.map} +0 -0
- /package/dist/{load-config-JKJ73TOD.js.map → load-config-2LP2OEKD.js.map} +0 -0
- /package/dist/{mcp-CO4AGAXV.js.map → mcp-4FNNCFPM.js.map} +0 -0
- /package/dist/{netlify-OFJUYXTZ.js.map → netlify-AFMDNKT4.js.map} +0 -0
- /package/dist/{node-ND4MJU3Z.js.map → node-ZJQEJSP3.js.map} +0 -0
- /package/dist/{registry-QTSMLOVG.js.map → registry-UJ6KNQPJ.js.map} +0 -0
- /package/dist/{routes-GWAWUVVQ.js.map → routes-6AMNVFMI.js.map} +0 -0
- /package/dist/{schema-2JEG6TTU.js.map → schema-Z3Y7SFLE.js.map} +0 -0
- /package/dist/{services-typed-client-II7LKXKO.js.map → services-typed-client-PSAGRPFF.js.map} +0 -0
- /package/dist/{static-HTT2WNIT.js.map → static-R25HSXEF.js.map} +0 -0
- /package/dist/{theo-cloud-ZTQLXGPS.js.map → theo-cloud-XAHWOUFK.js.map} +0 -0
- /package/dist/{vercel-72HFEMJ3.js.map → vercel-SRS2DH6O.js.map} +0 -0
- /package/dist/{vite-plugin-NQ6F7FGV.js.map → vite-plugin-YOCPU4C6.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/transformer.ts","../src/server/agent/agent-card-handler.ts","../src/server/agent/approve-agent.ts","../src/server/agent/list-approvals-handler.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/serve-aux-routes.ts","../src/server/http/node-web-adapter.ts"],"sourcesContent":["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 * 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, compileAgentModule } 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 = compileAgentModule(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 { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApprovalDecision, ApprovalRegistry } from './approval-registry.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\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 * Resolve a pending HITL approval. CSRF-guarded like `mountAgent` (the custom `X-Theo-Action`\n * header + Origin match) — a cross-origin POST must not approve a paused tool. Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\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 */\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\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 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 return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M14 (theokit-ai-first) — GET /api/agents/<name>/approvals: list pending HITL approvals.\n *\n * Serves `ApprovalRegistry.list()` as JSON. Single-process contract (ADR 0038) — the list is\n * process-wide; the `<name>` segment is accepted for a future per-agent/durable store but the\n * in-process registry lists all pending approvals. Web Standards Response (G8).\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/** Serve the currently-pending approvals as `{ approvals: [...] }` JSON. */\nexport function handleListApprovals(registry: ApprovalRegistry): Response {\n return new Response(JSON.stringify({ approvals: registry.list() }), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\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 { 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 readonly apiKey: string\n readonly sessionId: string\n readonly request: Request\n readonly source: 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, 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 startRun: makeThreadStartRun(mod, apiKey, source),\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 * 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 *\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 */\nimport type { AgentNode } from '../scan/agent-scan.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { isAgentCardPath, handleAgentCard } from './agent-card-handler.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 resolveApiKey?: () => string\n}\n\n/**\n * Serve an agent auxiliary route. Returns a `Response` when `urlPath` matches an aux route (card /\n * list-approvals / mcp) and the method + agent resolve; returns `null` to fall through (not an aux\n * route, wrong method, or unknown agent — the caller then owns the 404/next).\n */\nexport async function serveAgentAuxRoute(\n request: Request,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const method = request.method.toUpperCase()\n\n // M15 — A2A agent card at `/.well-known/<name>/agent-card.json` (GET).\n const cardName = isAgentCardPath(urlPath)\n if (cardName !== null) {\n if (method !== 'GET') return null\n const agent = deps.agents.find((a) => a.name === cardName)\n if (!agent) return null\n const mod = await deps.loadModule(agent.filePath)\n return handleAgentCard(mod, agent.name, agent.agentPath, deps.baseUrl)\n }\n\n // M14 — GET /api/agents/<name>/approvals (pending HITL approvals).\n if (isListApprovalsPath(urlPath)) {\n if (method !== 'GET') return null\n return handleListApprovals(getApprovalRegistry())\n }\n\n // M37 — GET /api/agents/<name>/runs/<runId>/stream (durable reconnect / observe).\n // INTENTIONALLY open (no CSRF, no auth gate): a GET is not CSRF-vulnerable, the\n // run-start POST is already gated, and the `runId` is a 122-bit UUID (unguessable).\n // Observe-by-runId is a FEATURE (ADR-0046 D5) — a second client resumes a run a\n // first started. Do NOT add a custom-header CSRF check here: browsers send NO\n // custom headers with `EventSource`, so it would break native SSE reconnect.\n const runStream = isAgentRunStreamPath(urlPath)\n if (runStream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === runStream.name)) return null\n return handleAgentRunReconnect(runStream.runId, request, getRunEventCache())\n }\n\n // M39 — thread signal routes (follow-up message + subscribe-by-thread). Extracted\n // to keep this dispatcher within the cognitive-complexity budget (G6).\n const threadResponse = await serveThreadRoute(request, method, urlPath, deps)\n if (threadResponse !== null) return threadResponse\n\n // M16 — POST /api/agents/<name>/mcp (JSON-RPC MCP server). Extracted to keep this dispatcher\n // within the cognitive-complexity budget (G6).\n const mcpName = isMcpPath(urlPath)\n if (mcpName !== null) {\n return serveMcpRoute(request, method, mcpName, deps)\n }\n\n return null\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`. The open GET is safe ONLY if the `sessionId` is\n * unguessable (e.g. a client-minted UUID). An app that uses a PREDICTABLE\n * sessionId (user id, email, sequential id) MUST add its own auth gate before\n * this endpoint, or any party who can guess the sessionId can read the thread's\n * live conversation stream. Documented in `wiki/architecture/multi-surface-architecture.md`.\n *\n * Returns `null` to fall through (not a thread route, wrong method, unknown agent).\n */\nasync function serveThreadRoute(\n request: Request,\n method: string,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const stream = isThreadStreamPath(urlPath)\n if (stream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === stream.name)) return null\n return handleThreadStream(stream.sessionId, request)\n }\n\n const msg = isThreadMessagePath(urlPath)\n if (msg !== null) {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === msg.name)\n if (!agent) return null\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 serveAgentAuxRoute).',\n )\n }\n const mod = await deps.loadModule(agent.filePath)\n return handleThreadMessage({\n mod,\n apiKey: deps.resolveApiKey(),\n sessionId: msg.sessionId,\n request,\n source: `agent \"${msg.name}\"`,\n csrfMode: deps.csrfMode ?? 'strict',\n })\n }\n return null\n}\n\n/**\n * Serve the MCP route with the M34 gates: default-DENY opt-in → CSRF → dispatch. Returns `null` to\n * fall through (wrong method / unknown or non-opted-in agent), a 403 on CSRF failure, else the\n * JSON-RPC response.\n */\nasync function serveMcpRoute(\n request: Request,\n method: string,\n mcpName: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === mcpName)\n if (!agent) return null\n\n const mod = await deps.loadModule(agent.filePath)\n\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 if (!isMcpExposed(mod)) return null\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 * T5a.2 Phase G slice 5/N — Node adapter shim for the Web request handler.\n *\n * Bridges Node's `IncomingMessage` + `ServerResponse` shape to the\n * Web-Standards `executeWebRequest` (Phase A → G slices 1-4). Per\n * ADR-0028 R3a, the Node adapter is the ONLY place IncomingMessage ↔\n * Request conversion happens — every other layer of `server/` flows\n * through Web `Request`/`Response`.\n *\n * **Two conversions:**\n *\n * 1. `incomingMessageToWebRequest(req)` — Node → Web. Reads\n * `req.method`, `req.url`, `req.headers`, and (for POST/PUT/etc.)\n * drains the Node Readable body into a Web `ReadableStream`. The\n * request URL is resolved to absolute form using `req.headers.host`\n * (Web Request guarantees absolute URL).\n *\n * 2. `writeWebResponseToServerResponse(response, res)` — Web → Node.\n * Sets status code + status text, copies headers (including all\n * `Set-Cookie` values via `getSetCookie()`), then drains the Web\n * `ReadableStream` body into the Node ServerResponse.\n *\n * Plus the convenience composer `executeWebRequestFromNode(req, res,\n * routeModule, opts?)` that wires both ends — exactly what api-middleware\n * (and the prod CLI start path) need to migrate from the legacy\n * `executeRoute` to `executeWebRequest` without touching call sites.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase G slice 5/N (closes the executor bridge surface).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { executeWebRequest, type ExecuteWebRequestOptions } from '../web-handler.js'\n\nimport { incomingMessageToWebRequest } from './node-request.js'\n\n// The primitive `IncomingMessage` → Web `Request` converters live in\n// `node-request.js` (dependency-free, shared with the executor). Re-exported\n// here so existing importers of the adapter keep resolving them.\nexport { incomingMessageToWebRequest } from './node-request.js'\n\n/**\n * Write a Web `Response` into a Node `ServerResponse`. Mirrors the Node\n * `res.writeHead` + `res.end` pattern.\n *\n * Set-Cookie is the only multi-value header the Web spec exposes via\n * `getSetCookie()`. We append each entry individually so Node emits\n * separate `Set-Cookie:` lines per the HTTP spec.\n *\n * If the Response body is a `ReadableStream`, it's piped chunk-by-chunk\n * to `res`. Empty body (null) → just close.\n */\nexport async function writeWebResponseToServerResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n // Status + headers FIRST (writeHead locks them).\n // EC-3: Set-Cookie needs special handling (writeHead's plain object\n // shape conflicts with multi-value headers; we set them via setHeader\n // BEFORE writeHead so the array form is preserved).\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) {\n res.setHeader('Set-Cookie', setCookies)\n }\n const otherHeaders: Record<string, string> = {}\n for (const [key, value] of response.headers.entries()) {\n if (key.toLowerCase() === 'set-cookie') continue\n otherHeaders[key] = value\n }\n res.writeHead(response.status, response.statusText, otherHeaders)\n\n // Body — drain ReadableStream OR just end() for null body.\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Node's res.write accepts Uint8Array natively (no conversion needed).\n res.write(value)\n }\n res.end()\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Convenience composer — full request lifecycle Node → Web → Node.\n *\n * Use case: existing api-middleware (and the prod CLI start path)\n * receive Node `(req, res)` from `http.createServer`. To migrate to the\n * Web-Standards executor without rewriting every call site, wrap with\n * this composer:\n *\n * import * as users from './app/users/route.js'\n * await executeWebRequestFromNode(req, res, users, { csrfMode: 'strict' })\n *\n * The Web request is built, dispatched through executeWebRequest, and\n * the Response is drained back into `res`. Caller does NOT need to call\n * `res.end()` afterwards — this composer handles it.\n */\nexport async function executeWebRequestFromNode(\n req: IncomingMessage,\n res: ServerResponse,\n routeModule: Parameters<typeof executeWebRequest>[1],\n opts?: ExecuteWebRequestOptions,\n): Promise<void> {\n const webRequest = incomingMessageToWebRequest(req)\n const webResponse = await executeWebRequest(webRequest, routeModule, opts)\n await writeWebResponseToServerResponse(webResponse, res)\n}\n"],"mappings":"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","names":["jsonError","jsonError"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/transformer.ts","../src/server/agent/agent-card-handler.ts","../src/server/agent/approve-agent.ts","../src/server/agent/list-approvals-handler.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/serve-aux-routes.ts","../src/server/http/node-web-adapter.ts"],"sourcesContent":["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 * 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, compileAgentModule } 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 = compileAgentModule(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 { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApprovalDecision, ApprovalRegistry } from './approval-registry.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\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 * Resolve a pending HITL approval. CSRF-guarded like `mountAgent` (the custom `X-Theo-Action`\n * header + Origin match) — a cross-origin POST must not approve a paused tool. Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\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 */\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\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 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 return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M14 (theokit-ai-first) — GET /api/agents/<name>/approvals: list pending HITL approvals.\n *\n * Serves `ApprovalRegistry.list()` as JSON. Single-process contract (ADR 0038) — the list is\n * process-wide; the `<name>` segment is accepted for a future per-agent/durable store but the\n * in-process registry lists all pending approvals. Web Standards Response (G8).\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/** Serve the currently-pending approvals as `{ approvals: [...] }` JSON. */\nexport function handleListApprovals(registry: ApprovalRegistry): Response {\n return new Response(JSON.stringify({ approvals: registry.list() }), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\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 { 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 readonly apiKey: string\n readonly sessionId: string\n readonly request: Request\n readonly source: 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, 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 startRun: makeThreadStartRun(mod, apiKey, source),\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 * 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 *\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 */\nimport type { AgentNode } from '../scan/agent-scan.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { isAgentCardPath, handleAgentCard } from './agent-card-handler.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 resolveApiKey?: () => string\n}\n\n/**\n * Serve an agent auxiliary route. Returns a `Response` when `urlPath` matches an aux route (card /\n * list-approvals / mcp) and the method + agent resolve; returns `null` to fall through (not an aux\n * route, wrong method, or unknown agent — the caller then owns the 404/next).\n */\nexport async function serveAgentAuxRoute(\n request: Request,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const method = request.method.toUpperCase()\n\n // M15 — A2A agent card at `/.well-known/<name>/agent-card.json` (GET).\n const cardName = isAgentCardPath(urlPath)\n if (cardName !== null) {\n if (method !== 'GET') return null\n const agent = deps.agents.find((a) => a.name === cardName)\n if (!agent) return null\n const mod = await deps.loadModule(agent.filePath)\n return handleAgentCard(mod, agent.name, agent.agentPath, deps.baseUrl)\n }\n\n // M14 — GET /api/agents/<name>/approvals (pending HITL approvals).\n if (isListApprovalsPath(urlPath)) {\n if (method !== 'GET') return null\n return handleListApprovals(getApprovalRegistry())\n }\n\n // M37 — GET /api/agents/<name>/runs/<runId>/stream (durable reconnect / observe).\n // INTENTIONALLY open (no CSRF, no auth gate): a GET is not CSRF-vulnerable, the\n // run-start POST is already gated, and the `runId` is a 122-bit UUID (unguessable).\n // Observe-by-runId is a FEATURE (ADR-0046 D5) — a second client resumes a run a\n // first started. Do NOT add a custom-header CSRF check here: browsers send NO\n // custom headers with `EventSource`, so it would break native SSE reconnect.\n const runStream = isAgentRunStreamPath(urlPath)\n if (runStream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === runStream.name)) return null\n return handleAgentRunReconnect(runStream.runId, request, getRunEventCache())\n }\n\n // M39 — thread signal routes (follow-up message + subscribe-by-thread). Extracted\n // to keep this dispatcher within the cognitive-complexity budget (G6).\n const threadResponse = await serveThreadRoute(request, method, urlPath, deps)\n if (threadResponse !== null) return threadResponse\n\n // M16 — POST /api/agents/<name>/mcp (JSON-RPC MCP server). Extracted to keep this dispatcher\n // within the cognitive-complexity budget (G6).\n const mcpName = isMcpPath(urlPath)\n if (mcpName !== null) {\n return serveMcpRoute(request, method, mcpName, deps)\n }\n\n return null\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`. The open GET is safe ONLY if the `sessionId` is\n * unguessable (e.g. a client-minted UUID). An app that uses a PREDICTABLE\n * sessionId (user id, email, sequential id) MUST add its own auth gate before\n * this endpoint, or any party who can guess the sessionId can read the thread's\n * live conversation stream.\n *\n * Returns `null` to fall through (not a thread route, wrong method, unknown agent).\n */\nasync function serveThreadRoute(\n request: Request,\n method: string,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const stream = isThreadStreamPath(urlPath)\n if (stream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === stream.name)) return null\n return handleThreadStream(stream.sessionId, request)\n }\n\n const msg = isThreadMessagePath(urlPath)\n if (msg !== null) {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === msg.name)\n if (!agent) return null\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 serveAgentAuxRoute).',\n )\n }\n const mod = await deps.loadModule(agent.filePath)\n return handleThreadMessage({\n mod,\n apiKey: deps.resolveApiKey(),\n sessionId: msg.sessionId,\n request,\n source: `agent \"${msg.name}\"`,\n csrfMode: deps.csrfMode ?? 'strict',\n })\n }\n return null\n}\n\n/**\n * Serve the MCP route with the M34 gates: default-DENY opt-in → CSRF → dispatch. Returns `null` to\n * fall through (wrong method / unknown or non-opted-in agent), a 403 on CSRF failure, else the\n * JSON-RPC response.\n */\nasync function serveMcpRoute(\n request: Request,\n method: string,\n mcpName: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === mcpName)\n if (!agent) return null\n\n const mod = await deps.loadModule(agent.filePath)\n\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 if (!isMcpExposed(mod)) return null\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 * T5a.2 Phase G slice 5/N — Node adapter shim for the Web request handler.\n *\n * Bridges Node's `IncomingMessage` + `ServerResponse` shape to the\n * Web-Standards `executeWebRequest` (Phase A → G slices 1-4). Per\n * ADR-0028 R3a, the Node adapter is the ONLY place IncomingMessage ↔\n * Request conversion happens — every other layer of `server/` flows\n * through Web `Request`/`Response`.\n *\n * **Two conversions:**\n *\n * 1. `incomingMessageToWebRequest(req)` — Node → Web. Reads\n * `req.method`, `req.url`, `req.headers`, and (for POST/PUT/etc.)\n * drains the Node Readable body into a Web `ReadableStream`. The\n * request URL is resolved to absolute form using `req.headers.host`\n * (Web Request guarantees absolute URL).\n *\n * 2. `writeWebResponseToServerResponse(response, res)` — Web → Node.\n * Sets status code + status text, copies headers (including all\n * `Set-Cookie` values via `getSetCookie()`), then drains the Web\n * `ReadableStream` body into the Node ServerResponse.\n *\n * Plus the convenience composer `executeWebRequestFromNode(req, res,\n * routeModule, opts?)` that wires both ends — exactly what api-middleware\n * (and the prod CLI start path) need to migrate from the legacy\n * `executeRoute` to `executeWebRequest` without touching call sites.\n *\n * v1.0 § Phase G slice 5/N (closes the executor bridge surface).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { executeWebRequest, type ExecuteWebRequestOptions } from '../web-handler.js'\n\nimport { incomingMessageToWebRequest } from './node-request.js'\n\n// The primitive `IncomingMessage` → Web `Request` converters live in\n// `node-request.js` (dependency-free, shared with the executor). Re-exported\n// here so existing importers of the adapter keep resolving them.\nexport { incomingMessageToWebRequest } from './node-request.js'\n\n/**\n * Write a Web `Response` into a Node `ServerResponse`. Mirrors the Node\n * `res.writeHead` + `res.end` pattern.\n *\n * Set-Cookie is the only multi-value header the Web spec exposes via\n * `getSetCookie()`. We append each entry individually so Node emits\n * separate `Set-Cookie:` lines per the HTTP spec.\n *\n * If the Response body is a `ReadableStream`, it's piped chunk-by-chunk\n * to `res`. Empty body (null) → just close.\n */\nexport async function writeWebResponseToServerResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n // Status + headers FIRST (writeHead locks them).\n // EC-3: Set-Cookie needs special handling (writeHead's plain object\n // shape conflicts with multi-value headers; we set them via setHeader\n // BEFORE writeHead so the array form is preserved).\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) {\n res.setHeader('Set-Cookie', setCookies)\n }\n const otherHeaders: Record<string, string> = {}\n for (const [key, value] of response.headers.entries()) {\n if (key.toLowerCase() === 'set-cookie') continue\n otherHeaders[key] = value\n }\n res.writeHead(response.status, response.statusText, otherHeaders)\n\n // Body — drain ReadableStream OR just end() for null body.\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Node's res.write accepts Uint8Array natively (no conversion needed).\n res.write(value)\n }\n res.end()\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Convenience composer — full request lifecycle Node → Web → Node.\n *\n * Use case: existing api-middleware (and the prod CLI start path)\n * receive Node `(req, res)` from `http.createServer`. To migrate to the\n * Web-Standards executor without rewriting every call site, wrap with\n * this composer:\n *\n * import * as users from './app/users/route.js'\n * await executeWebRequestFromNode(req, res, users, { csrfMode: 'strict' })\n *\n * The Web request is built, dispatched through executeWebRequest, and\n * the Response is drained back into `res`. Caller does NOT need to call\n * `res.end()` afterwards — this composer handles it.\n */\nexport async function executeWebRequestFromNode(\n req: IncomingMessage,\n res: ServerResponse,\n routeModule: Parameters<typeof executeWebRequest>[1],\n opts?: ExecuteWebRequestOptions,\n): Promise<void> {\n const webRequest = incomingMessageToWebRequest(req)\n const webResponse = await executeWebRequest(webRequest, routeModule, opts)\n await writeWebResponseToServerResponse(webResponse, res)\n}\n"],"mappings":"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","names":["jsonError","jsonError"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
safeAudit
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T5C46IIU.js";
|
|
6
6
|
|
|
7
7
|
// src/server/http/batch-handler.ts
|
|
8
8
|
import { z } from "zod";
|
|
@@ -418,4 +418,4 @@ export {
|
|
|
418
418
|
handleCsrfReadiness,
|
|
419
419
|
CsrfReadinessStore
|
|
420
420
|
};
|
|
421
|
-
//# sourceMappingURL=chunk-
|
|
421
|
+
//# sourceMappingURL=chunk-7CQ6DEWZ.js.map
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
loadEnv,
|
|
24
24
|
resolveTransformer
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-XR74JD4M.js";
|
|
26
26
|
import {
|
|
27
27
|
BATCH_PATH,
|
|
28
28
|
TRACE_HEADER,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
scanServerActions,
|
|
57
57
|
scanServerRoutes,
|
|
58
58
|
scanWebSocketRoutes
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-YGAXV5HR.js";
|
|
60
60
|
import {
|
|
61
61
|
CSP_REPORT_PATH,
|
|
62
62
|
CsrfReadinessStore,
|
|
@@ -198,6 +198,9 @@ ${issueLines}
|
|
|
198
198
|
// src/config/schema.ts
|
|
199
199
|
import { z as z8 } from "zod";
|
|
200
200
|
|
|
201
|
+
// src/core/contracts/theo-ui-theme.ts
|
|
202
|
+
var THEME_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
203
|
+
|
|
201
204
|
// src/config/schemas/rate-limit.ts
|
|
202
205
|
import { z } from "zod";
|
|
203
206
|
var baseRateLimitSchema = z.object({
|
|
@@ -270,7 +273,7 @@ var securityHeadersSchema = z6.object({
|
|
|
270
273
|
csp: z6.union([headerSafeString, z6.literal(false)]).optional(),
|
|
271
274
|
// T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.
|
|
272
275
|
// Users who want the old behaviour set `cspMode: 'report-only'`
|
|
273
|
-
// explicitly.
|
|
276
|
+
// explicitly.
|
|
274
277
|
cspMode: z6.enum(["enforce", "report-only", "off"]).default("enforce"),
|
|
275
278
|
hsts: z6.union([headerSafeString, z6.literal(false)]).optional(),
|
|
276
279
|
frameOptions: z6.enum(["DENY", "SAMEORIGIN"]).default("DENY"),
|
|
@@ -306,7 +309,7 @@ var securitySchema = z6.object({
|
|
|
306
309
|
// T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that
|
|
307
310
|
// grep their warn-mode logs from 0.2.x already know which endpoints
|
|
308
311
|
// break; opt back into 'warn' globally OR use disallowedRoutes for
|
|
309
|
-
// surgical migration.
|
|
312
|
+
// surgical migration.
|
|
310
313
|
csrf: z6.enum(["off", "warn", "strict"]).default("strict"),
|
|
311
314
|
headers: securityHeadersSchema.optional(),
|
|
312
315
|
/** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */
|
|
@@ -458,7 +461,15 @@ var theoConfigSchema = z8.object({
|
|
|
458
461
|
ui: z8.union([
|
|
459
462
|
z8.literal(false),
|
|
460
463
|
z8.object({
|
|
461
|
-
theme
|
|
464
|
+
// Any theme @theokit/ui knows — built-in or from `defineTheme()`. This was a closed
|
|
465
|
+
// enum of `violet-forge | noir | paper`, two of which never existed in @theokit/ui, so
|
|
466
|
+
// the only accepted value was the default and every real theme was rejected. The pattern
|
|
467
|
+
// mirrors what @theokit/ui's ThemeProvider enforces, and it is load-bearing: the value is
|
|
468
|
+
// interpolated into the generated entry and into a CSS selector.
|
|
469
|
+
theme: z8.string().regex(
|
|
470
|
+
THEME_NAME_PATTERN,
|
|
471
|
+
"must start with a letter and contain only lowercase letters, digits and hyphens"
|
|
472
|
+
).optional(),
|
|
462
473
|
fonts: z8.enum(["bundled", "cdn"]).optional()
|
|
463
474
|
})
|
|
464
475
|
]).optional(),
|
|
@@ -469,7 +480,7 @@ var theoConfigSchema = z8.object({
|
|
|
469
480
|
*/
|
|
470
481
|
cache: cacheSchema.optional(),
|
|
471
482
|
/**
|
|
472
|
-
* Devtools overlay (Phase 0.4.0+ —
|
|
483
|
+
* Devtools overlay (Phase 0.4.0+ —
|
|
473
484
|
*
|
|
474
485
|
* - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.
|
|
475
486
|
* - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).
|
|
@@ -1922,6 +1933,66 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
|
|
|
1922
1933
|
// src/vite-plugin/ssr-dev-middleware.ts
|
|
1923
1934
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1924
1935
|
import { resolve as resolve5 } from "path";
|
|
1936
|
+
|
|
1937
|
+
// src/vite-plugin/hoist-head-tags.ts
|
|
1938
|
+
var VOID_METADATA = /<(?:meta|link)\b[^>]*>/gi;
|
|
1939
|
+
var TITLE_TAG = /<title\b[^>]*>[^<]*<\/title>/gi;
|
|
1940
|
+
function replaceHoistable(html, replacer) {
|
|
1941
|
+
return html.replace(TITLE_TAG, replacer).replace(VOID_METADATA, replacer);
|
|
1942
|
+
}
|
|
1943
|
+
function metadataKey(tag) {
|
|
1944
|
+
if (/^<title\b/i.test(tag)) return "title";
|
|
1945
|
+
const name = /\bname=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
1946
|
+
const property = /\bproperty=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
1947
|
+
const rel = /\brel=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
1948
|
+
if (/^<meta\b/i.test(tag)) {
|
|
1949
|
+
if (property !== void 0) return `property:${property.toLowerCase()}`;
|
|
1950
|
+
if (name !== void 0) return `name:${name.toLowerCase()}`;
|
|
1951
|
+
return void 0;
|
|
1952
|
+
}
|
|
1953
|
+
if (/^<link\b/i.test(tag) && rel !== void 0) {
|
|
1954
|
+
const slug = rel.toLowerCase();
|
|
1955
|
+
return slug === "canonical" || slug === "manifest" ? `link:${slug}` : void 0;
|
|
1956
|
+
}
|
|
1957
|
+
return void 0;
|
|
1958
|
+
}
|
|
1959
|
+
function extractHeadTags(ssrHtml) {
|
|
1960
|
+
const headTags = [];
|
|
1961
|
+
const html = replaceHoistable(ssrHtml, (tag) => {
|
|
1962
|
+
headTags.push(tag);
|
|
1963
|
+
return "";
|
|
1964
|
+
});
|
|
1965
|
+
return { html, headTags };
|
|
1966
|
+
}
|
|
1967
|
+
function injectIntoHead(template, headTags) {
|
|
1968
|
+
if (headTags.length === 0) return template;
|
|
1969
|
+
const closingHead = template.toLowerCase().lastIndexOf("</head>");
|
|
1970
|
+
if (closingHead === -1) return template;
|
|
1971
|
+
const supersededKeys = new Set(
|
|
1972
|
+
headTags.map((tag) => metadataKey(tag)).filter((key) => key !== void 0)
|
|
1973
|
+
);
|
|
1974
|
+
let head = template.slice(0, closingHead);
|
|
1975
|
+
if (supersededKeys.size > 0) {
|
|
1976
|
+
head = replaceHoistable(head, (tag) => {
|
|
1977
|
+
const key = metadataKey(tag);
|
|
1978
|
+
return key !== void 0 && supersededKeys.has(key) ? "" : tag;
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return `${head} ${headTags.join("\n ")}
|
|
1982
|
+
${template.slice(closingHead)}`;
|
|
1983
|
+
}
|
|
1984
|
+
function hoistHeadTags(template, ssrHtml) {
|
|
1985
|
+
const { html, headTags } = extractHeadTags(ssrHtml);
|
|
1986
|
+
return { template: injectIntoHead(template, headTags), html };
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// src/vite-plugin/ssr-dev-middleware.ts
|
|
1990
|
+
function applyNonceToInlineScripts(html, nonce) {
|
|
1991
|
+
return html.replace(
|
|
1992
|
+
/<script(?![^>]*\ssrc=)(?![^>]*\snonce=)([^>]*)>/gi,
|
|
1993
|
+
`<script nonce="${nonce}"$1>`
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1925
1996
|
function isSsrRenderResult(value) {
|
|
1926
1997
|
if (typeof value !== "object" || value === null) return false;
|
|
1927
1998
|
if (!("html" in value)) return false;
|
|
@@ -1938,8 +2009,9 @@ function setupSsrDevMiddleware(server, opts) {
|
|
|
1938
2009
|
try {
|
|
1939
2010
|
const indexPath = resolve5(opts.projectRoot, "index.html");
|
|
1940
2011
|
let template = readFileSync2(indexPath, "utf-8");
|
|
1941
|
-
template = await server.transformIndexHtml(url, template);
|
|
1942
2012
|
const nonce = generateNonce();
|
|
2013
|
+
template = await server.transformIndexHtml(url, template);
|
|
2014
|
+
template = applyNonceToInlineScripts(template, nonce);
|
|
1943
2015
|
applySecurityHeaders(
|
|
1944
2016
|
res,
|
|
1945
2017
|
opts.securityHeaders ?? {},
|
|
@@ -1966,6 +2038,9 @@ function setupSsrDevMiddleware(server, opts) {
|
|
|
1966
2038
|
} else {
|
|
1967
2039
|
ssrHtml = "";
|
|
1968
2040
|
}
|
|
2041
|
+
const hoisted = hoistHeadTags(template, ssrHtml);
|
|
2042
|
+
template = hoisted.template;
|
|
2043
|
+
ssrHtml = hoisted.html;
|
|
1969
2044
|
const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
|
|
1970
2045
|
if (!rootDivMatch) {
|
|
1971
2046
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -2098,7 +2173,7 @@ async function runConfigureServer(server, ctx) {
|
|
|
2098
2173
|
server.watcher.on("unlink", handleRouteChange);
|
|
2099
2174
|
if (ctx.resolvedOpenApi !== void 0) {
|
|
2100
2175
|
const openApiCfg = ctx.resolvedOpenApi;
|
|
2101
|
-
const { reEmitOpenApi } = await import("./dev-emit-
|
|
2176
|
+
const { reEmitOpenApi } = await import("./dev-emit-NBT6JD7R.js");
|
|
2102
2177
|
const openApiServerDir = ctx.serverDir;
|
|
2103
2178
|
const openApiDistDir = resolve6(ctx.projectRoot, ctx.resolvedDistDir);
|
|
2104
2179
|
const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
|
|
@@ -3108,19 +3183,19 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
3108
3183
|
})
|
|
3109
3184
|
);
|
|
3110
3185
|
}
|
|
3111
|
-
const { appTypedClientPlugin } = await import("./app-typed-client-
|
|
3186
|
+
const { appTypedClientPlugin } = await import("./app-typed-client-GHO3EWZK.js");
|
|
3112
3187
|
const appClientPlugin = appTypedClientPlugin({
|
|
3113
3188
|
cwd: projectRoot,
|
|
3114
3189
|
serverDir: serverDirAbs,
|
|
3115
3190
|
distDir: resolve9(projectRoot, ".theokit")
|
|
3116
3191
|
});
|
|
3117
|
-
const { actionsVirtualModule } = await import("./actions-virtual-module-
|
|
3192
|
+
const { actionsVirtualModule } = await import("./actions-virtual-module-YQXRSSMW.js");
|
|
3118
3193
|
const actionsPlugin = actionsVirtualModule({
|
|
3119
3194
|
serverDir: serverDirAbs,
|
|
3120
3195
|
distDir: resolve9(projectRoot, ".theokit")
|
|
3121
3196
|
});
|
|
3122
|
-
const { agentsTypedClientPlugin } = await import("./agents-typed-client-
|
|
3123
|
-
const { generateManifest } = await import("./internal-api-
|
|
3197
|
+
const { agentsTypedClientPlugin } = await import("./agents-typed-client-INR2E7B3.js");
|
|
3198
|
+
const { generateManifest } = await import("./internal-api-WEOSTMSN.js");
|
|
3124
3199
|
const agentsClientPlugin = agentsTypedClientPlugin({
|
|
3125
3200
|
projectRoot,
|
|
3126
3201
|
agentsDir: agentsDirName,
|
|
@@ -3129,7 +3204,7 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
3129
3204
|
// parent (projectRoot for the canonical layout) + the agents dir name (#95 follow-up).
|
|
3130
3205
|
scanManifest: () => generateManifest(serverDirAbs, projectRoot, agentsDirName)
|
|
3131
3206
|
});
|
|
3132
|
-
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-
|
|
3207
|
+
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-BWN3WIGR.js");
|
|
3133
3208
|
const routesHmrPlugin = serverRoutesHmrPlugin({
|
|
3134
3209
|
serverDir: serverDirAbs
|
|
3135
3210
|
});
|
|
@@ -3310,4 +3385,4 @@ export {
|
|
|
3310
3385
|
theoPluginAsync,
|
|
3311
3386
|
theoPlugin
|
|
3312
3387
|
};
|
|
3313
|
-
//# sourceMappingURL=chunk-
|
|
3388
|
+
//# sourceMappingURL=chunk-7QAYC757.js.map
|