theokit 0.46.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-virtual-module-LLDDAQWY.js → actions-virtual-module-2VVLVXHY.js} +6 -6
- package/dist/{actions-virtual-module-67K4POQ2.js → actions-virtual-module-Z5LXAX7E.js} +8 -7
- package/dist/actions-virtual-module-Z5LXAX7E.js.map +1 -0
- package/dist/{agent-WFULT3OB.js → agent-Y3JKRROD.js} +4 -4
- package/dist/{app-typed-client-S3VR4ZA3.js → app-typed-client-N4I4RBLT.js} +6 -6
- package/dist/app-typed-client-N4I4RBLT.js.map +1 -0
- package/dist/{app-typed-client-N5WUE2CP.js → app-typed-client-NI3CM2FA.js} +8 -7
- package/dist/app-typed-client-NI3CM2FA.js.map +1 -0
- package/dist/{aws-lambda-473QCOL6.js → aws-lambda-7SUTORXM.js} +3 -3
- package/dist/boot/index.js +1 -1
- package/dist/{build-YQ6MXOV6.js → build-SJQDRD2B.js} +10 -10
- package/dist/{bun-OQUCL3XW.js → bun-TLYZCEGE.js} +3 -3
- package/dist/{chunk-HEK4SET7.js → chunk-5JJCNEUS.js} +2 -2
- package/dist/chunk-5JJCNEUS.js.map +1 -0
- package/dist/chunk-6E5WKMDS.js +67 -0
- package/dist/chunk-6E5WKMDS.js.map +1 -0
- package/dist/{chunk-TDNNBLGW.js → chunk-6GGWS7J3.js} +1 -1
- package/dist/chunk-6GGWS7J3.js.map +1 -0
- package/dist/{chunk-FZG3JYB3.js → chunk-7ZOQN7UH.js} +1 -1
- package/dist/{chunk-B7ONQ4QJ.js → chunk-AOGYAXPY.js} +3 -3
- package/dist/{chunk-QPVXGC7O.js → chunk-CUYEA62B.js} +1 -1
- package/dist/{chunk-QPVXGC7O.js.map → chunk-CUYEA62B.js.map} +1 -1
- package/dist/{chunk-OXCALO3W.js → chunk-D3425YDV.js} +2 -2
- package/dist/{chunk-SMP2T335.js → chunk-DGQO372T.js} +2 -2
- package/dist/{chunk-C34PMLSN.js → chunk-DV34VG3N.js} +2 -10
- package/dist/chunk-DV34VG3N.js.map +1 -0
- package/dist/{chunk-U3H53IIN.js → chunk-FO55VGMQ.js} +3 -3
- package/dist/{chunk-VFU7DTUL.js → chunk-GEHX6CAE.js} +10 -8
- package/dist/chunk-GEHX6CAE.js.map +1 -0
- package/dist/{chunk-GDN3PXFH.js → chunk-HBU2GIMZ.js} +1 -1
- package/dist/chunk-HBU2GIMZ.js.map +1 -0
- package/dist/{chunk-7SABX4DJ.js → chunk-IZ3TIL6K.js} +28 -2
- package/dist/{chunk-7SABX4DJ.js.map → chunk-IZ3TIL6K.js.map} +1 -1
- package/dist/{chunk-L4Y6RJ4E.js → chunk-KSZGBUTI.js} +2 -2
- package/dist/{chunk-226KMIGZ.js → chunk-MNIQ5F3Y.js} +2 -10
- package/dist/chunk-MNIQ5F3Y.js.map +1 -0
- package/dist/{chunk-KBGGQK4I.js → chunk-O3JIAU3V.js} +4 -4
- package/dist/{chunk-UYMCKHML.js → chunk-O5W7XSUX.js} +40 -40
- package/dist/chunk-O5W7XSUX.js.map +1 -0
- package/dist/{chunk-EIRQM55C.js → chunk-OJLL4YD6.js} +5 -46
- package/dist/chunk-OJLL4YD6.js.map +1 -0
- package/dist/{chunk-6LNIGF5P.js → chunk-ORKNVORZ.js} +1 -1
- package/dist/chunk-ORKNVORZ.js.map +1 -0
- package/dist/{chunk-KPKLIUUL.js → chunk-TNI4MARH.js} +3 -3
- package/dist/{chunk-6KDXP472.js → chunk-UDPJP42U.js} +4 -4
- package/dist/{chunk-HGZL5EOI.js → chunk-VQIUTCK2.js} +28 -2
- package/dist/chunk-VQIUTCK2.js.map +1 -0
- package/dist/{chunk-6VDYQ4I6.js → chunk-VTJMND23.js} +4 -4
- package/dist/{chunk-6VDYQ4I6.js.map → chunk-VTJMND23.js.map} +1 -1
- package/dist/{chunk-37ZO643S.js → chunk-XALU5LCU.js} +3 -3
- package/dist/{chunk-SEELKQUM.js → chunk-Y2KHTD2W.js} +6 -2
- package/dist/chunk-Y2KHTD2W.js.map +1 -0
- package/dist/{chunk-SZU6BOMV.js → chunk-YRRWQDKK.js} +22 -18
- package/dist/chunk-YRRWQDKK.js.map +1 -0
- package/dist/{chunk-5AMXBU2R.js → chunk-ZALJIZH2.js} +13 -13
- package/dist/chunk-ZALJIZH2.js.map +1 -0
- package/dist/cli/index.js +31 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/{cloudflare-4YT5CK3P.js → cloudflare-JTBKOX3F.js} +3 -3
- package/dist/{deno-deploy-YYN6BNLF.js → deno-deploy-3ESNGYZU.js} +3 -3
- package/dist/{dev-YNO26JS3.js → dev-6TWDBX6G.js} +12 -12
- package/dist/{dev-emit-J7V6GWSK.js → dev-emit-7ZZRTYF3.js} +3 -3
- package/dist/{dev-emit-F54Y5TIU.js → dev-emit-E5Z26AMV.js} +3 -3
- package/dist/devtools/entry.js.map +1 -1
- package/dist/doctor-Y53QTUSP.js +56 -0
- package/dist/doctor-Y53QTUSP.js.map +1 -0
- package/dist/{error-envelope-BsNzzAV5.d.ts → error-envelope-DG47lt8E.d.ts} +4 -4
- package/dist/{generate-GXWPUVS7.js → generate-WQSNW4F6.js} +1 -1
- package/dist/{generate-GXWPUVS7.js.map → generate-WQSNW4F6.js.map} +1 -1
- package/dist/{index-BeAsvfPr.d.ts → index-BEtEv6ZH.d.ts} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/{info-ICDTPJDC.js → info-QB7Q3SHP.js} +5 -5
- package/dist/{internal-api-S7WK2WCY.js → internal-api-REGN3WMX.js} +11 -9
- package/dist/{internal-api-NO24QAXC.js → internal-api-XQA2L663.js} +6 -6
- package/dist/load-config-JKJ73TOD.js +14 -0
- package/dist/{mcp-3S6A4A4Q.js → mcp-CO4AGAXV.js} +5 -5
- package/dist/{netlify-2WZUQYI3.js → netlify-OFJUYXTZ.js} +3 -3
- package/dist/node-ND4MJU3Z.js +10 -0
- package/dist/{openapi-NIYA2VBG.js → openapi-5CZCL7YP.js} +7 -7
- package/dist/registry-QTSMLOVG.js +23 -0
- package/dist/{routes-2RD4S5PZ.js → routes-GWAWUVVQ.js} +6 -6
- package/dist/{schema-S553QCAU.js → schema-2JEG6TTU.js} +3 -3
- package/dist/server/agent/index.d.ts +90 -1
- package/dist/server/agent/index.js +14 -2
- package/dist/server/cost/index.d.ts +1 -0
- package/dist/server/cost/index.js +3 -1
- package/dist/server/http/index.d.ts +2 -2
- package/dist/server/http/index.js +2 -2
- package/dist/server/index.d.ts +499 -6
- package/dist/server/index.js +491 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/observability/index.d.ts +4 -4
- package/dist/server/observability/index.js +1 -1
- package/dist/server/rate-limit/index.js +2 -2
- package/dist/server/scan/index.js +2 -2
- package/dist/{server-error-to-envelope-UM7DEVM4.js → server-error-to-envelope-2MQOME5Q.js} +2 -2
- package/dist/{server-error-to-envelope-UUXDSLAZ.js → server-error-to-envelope-4TGCDQ2S.js} +2 -2
- package/dist/{services-typed-client-EO4RFNTZ.js → services-typed-client-II7LKXKO.js} +2 -2
- package/dist/sessions-gc-H4WC4IXB.js +38 -0
- package/dist/sessions-gc-H4WC4IXB.js.map +1 -0
- package/dist/{start-TVT7BZ4V.js → start-IO3ZYGEX.js} +13 -13
- package/dist/start-IO3ZYGEX.js.map +1 -0
- package/dist/{static-25H5K42R.js → static-HTT2WNIT.js} +3 -3
- package/dist/{theo-cloud-JL2KXHIS.js → theo-cloud-ZTQLXGPS.js} +2 -2
- package/dist/theo-cloud-ZTQLXGPS.js.map +1 -0
- package/dist/{vercel-FP3C6LN6.js → vercel-72HFEMJ3.js} +3 -3
- package/dist/vite-plugin/index.js +9 -8
- package/dist/{vite-plugin-K64KQZSU.js → vite-plugin-NQ6F7FGV.js} +12 -12
- package/package.json +6 -7
- package/dist/actions-virtual-module-67K4POQ2.js.map +0 -1
- package/dist/app-typed-client-N5WUE2CP.js.map +0 -1
- package/dist/app-typed-client-S3VR4ZA3.js.map +0 -1
- package/dist/chunk-226KMIGZ.js.map +0 -1
- package/dist/chunk-5AMXBU2R.js.map +0 -1
- package/dist/chunk-6LNIGF5P.js.map +0 -1
- package/dist/chunk-C34PMLSN.js.map +0 -1
- package/dist/chunk-EIRQM55C.js.map +0 -1
- package/dist/chunk-GDN3PXFH.js.map +0 -1
- package/dist/chunk-HEK4SET7.js.map +0 -1
- package/dist/chunk-HGZL5EOI.js.map +0 -1
- package/dist/chunk-SEELKQUM.js.map +0 -1
- package/dist/chunk-SZU6BOMV.js.map +0 -1
- package/dist/chunk-TDNNBLGW.js.map +0 -1
- package/dist/chunk-UYMCKHML.js.map +0 -1
- package/dist/chunk-VFU7DTUL.js.map +0 -1
- package/dist/load-config-ICCNMO74.js +0 -14
- package/dist/node-CMAYKDCM.js +0 -10
- package/dist/registry-72YH7ZJM.js +0 -23
- package/dist/start-TVT7BZ4V.js.map +0 -1
- package/dist/theo-cloud-JL2KXHIS.js.map +0 -1
- /package/dist/{actions-virtual-module-LLDDAQWY.js.map → actions-virtual-module-2VVLVXHY.js.map} +0 -0
- /package/dist/{agent-WFULT3OB.js.map → agent-Y3JKRROD.js.map} +0 -0
- /package/dist/{aws-lambda-473QCOL6.js.map → aws-lambda-7SUTORXM.js.map} +0 -0
- /package/dist/{build-YQ6MXOV6.js.map → build-SJQDRD2B.js.map} +0 -0
- /package/dist/{bun-OQUCL3XW.js.map → bun-TLYZCEGE.js.map} +0 -0
- /package/dist/{chunk-FZG3JYB3.js.map → chunk-7ZOQN7UH.js.map} +0 -0
- /package/dist/{chunk-B7ONQ4QJ.js.map → chunk-AOGYAXPY.js.map} +0 -0
- /package/dist/{chunk-OXCALO3W.js.map → chunk-D3425YDV.js.map} +0 -0
- /package/dist/{chunk-SMP2T335.js.map → chunk-DGQO372T.js.map} +0 -0
- /package/dist/{chunk-U3H53IIN.js.map → chunk-FO55VGMQ.js.map} +0 -0
- /package/dist/{chunk-L4Y6RJ4E.js.map → chunk-KSZGBUTI.js.map} +0 -0
- /package/dist/{chunk-KBGGQK4I.js.map → chunk-O3JIAU3V.js.map} +0 -0
- /package/dist/{chunk-KPKLIUUL.js.map → chunk-TNI4MARH.js.map} +0 -0
- /package/dist/{chunk-6KDXP472.js.map → chunk-UDPJP42U.js.map} +0 -0
- /package/dist/{chunk-37ZO643S.js.map → chunk-XALU5LCU.js.map} +0 -0
- /package/dist/{cloudflare-4YT5CK3P.js.map → cloudflare-JTBKOX3F.js.map} +0 -0
- /package/dist/{deno-deploy-YYN6BNLF.js.map → deno-deploy-3ESNGYZU.js.map} +0 -0
- /package/dist/{dev-YNO26JS3.js.map → dev-6TWDBX6G.js.map} +0 -0
- /package/dist/{dev-emit-J7V6GWSK.js.map → dev-emit-7ZZRTYF3.js.map} +0 -0
- /package/dist/{dev-emit-F54Y5TIU.js.map → dev-emit-E5Z26AMV.js.map} +0 -0
- /package/dist/{info-ICDTPJDC.js.map → info-QB7Q3SHP.js.map} +0 -0
- /package/dist/{internal-api-S7WK2WCY.js.map → internal-api-REGN3WMX.js.map} +0 -0
- /package/dist/{internal-api-NO24QAXC.js.map → internal-api-XQA2L663.js.map} +0 -0
- /package/dist/{load-config-ICCNMO74.js.map → load-config-JKJ73TOD.js.map} +0 -0
- /package/dist/{mcp-3S6A4A4Q.js.map → mcp-CO4AGAXV.js.map} +0 -0
- /package/dist/{netlify-2WZUQYI3.js.map → netlify-OFJUYXTZ.js.map} +0 -0
- /package/dist/{node-CMAYKDCM.js.map → node-ND4MJU3Z.js.map} +0 -0
- /package/dist/{openapi-NIYA2VBG.js.map → openapi-5CZCL7YP.js.map} +0 -0
- /package/dist/{registry-72YH7ZJM.js.map → registry-QTSMLOVG.js.map} +0 -0
- /package/dist/{routes-2RD4S5PZ.js.map → routes-GWAWUVVQ.js.map} +0 -0
- /package/dist/{schema-S553QCAU.js.map → schema-2JEG6TTU.js.map} +0 -0
- /package/dist/{server-error-to-envelope-UM7DEVM4.js.map → server-error-to-envelope-2MQOME5Q.js.map} +0 -0
- /package/dist/{server-error-to-envelope-UUXDSLAZ.js.map → server-error-to-envelope-4TGCDQ2S.js.map} +0 -0
- /package/dist/{services-typed-client-EO4RFNTZ.js.map → services-typed-client-II7LKXKO.js.map} +0 -0
- /package/dist/{static-25H5K42R.js.map → static-HTT2WNIT.js.map} +0 -0
- /package/dist/{vercel-FP3C6LN6.js.map → vercel-72HFEMJ3.js.map} +0 -0
- /package/dist/{vite-plugin-K64KQZSU.js.map → vite-plugin-NQ6F7FGV.js.map} +0 -0
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
loadEnv
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HBU2GIMZ.js";
|
|
6
6
|
import {
|
|
7
7
|
theoConfigSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5JJCNEUS.js";
|
|
9
9
|
|
|
10
10
|
// src/config/load-config.ts
|
|
11
11
|
import { existsSync } from "fs";
|
|
@@ -140,4 +140,4 @@ export {
|
|
|
140
140
|
deepMerge,
|
|
141
141
|
loadConfig
|
|
142
142
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-AOGYAXPY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/adapters-bridge/adapter-support.ts","../src/services/generators/caddy-generator.ts","../src/services/generators/compose-generator.ts","../src/services/generators/openapi-client-gen.ts","../src/services/adapters-bridge/manifest.ts","../src/services/schema.ts","../src/services/runtime/orchestrator.ts","../src/services/runtime/healthcheck-poller.ts","../src/services/runtime/log-merge.ts","../src/services/runtime/process-spawn-helpers.ts","../src/services/adapters-bridge/theo-cloud-adapter-stub.ts","../src/services/adapters-bridge/vite-proxy-builder.ts"],"sourcesContent":["/**\n * Adapter support gates (Wave 2 — TheoCloud-first focus).\n *\n * Wave 2 SUPPORTS polyglot `services: {}` on TWO targets ONLY:\n * - `node` (local docker-compose harness — TheoCloud-shaped)\n * - `theo-cloud` (the principal deploy target — Wave 3 adapter)\n *\n * All other in-tree deploy adapters (vercel, cloudflare, bun, deno-deploy,\n * aws-lambda, netlify, static) ARE STILL FIRST-CLASS DEPLOY TARGETS for\n * the TS app, but `services: {}` Wave 2 is NOT yet wired through them.\n * They reject loudly with an actionable message when the manifest has\n * non-empty services — pointing the user at `node` (local) or TheoCloud.\n *\n * Per owner decision 2026-05-27: TheoKit's polyglot services energy is\n * 100% TheoCloud; Vercel/Cloudflare/etc. polyglot-services wire-ups are\n * out of Wave 2 scope and deferred to fresh ADRs if demand emerges.\n */\nimport type { ServicesManifest } from './manifest.js'\n\nconst SUPPORTED_IN_WAVE_2 = ['node (local)', 'theo-cloud (Wave 3)'] as const\n\n/**\n * Reject if the adapter does not support polyglot services. Adapters\n * outside `node` + `theo-cloud` call this; the message points to the\n * supported alternatives.\n *\n * Throws when manifest has non-empty services. No-op when manifest is null\n * or empty (Wave 1 BC — the app still deploys, just without sidecars).\n */\nexport function assertServicesUnsupported(\n adapterName: string,\n manifest: ServicesManifest | null,\n): void {\n if (!manifest || manifest.services.length === 0) return\n const names = manifest.services.map((s) => s.name).join(', ')\n throw new Error(\n `Adapter '${adapterName}' does not support polyglot services in Wave 2.\\n` +\n `Detected services in theo.config.ts: ${names}.\\n\\n` +\n `Wave 2 supports: ${SUPPORTED_IN_WAVE_2.join(', ')}.\\n` +\n `TheoCloud is the strategic deploy target — the adapter ships in Wave 3.\\n` +\n `For local dev/prod-like validation today, use \\`theokit build --target node\\`\\n` +\n `(emits docker-compose + Caddyfile — TheoCloud-shaped harness).\\n\\n` +\n `See docs/concepts/services.md.`,\n )\n}\n","/**\n * Caddyfile generator (T3.3).\n *\n * Emits a Caddyfile that fronts the TheoKit web app + declared services\n * with W3C trace context propagation enabled (Caddy 2.11+ `tracing` directive).\n *\n * Generated output sits at `<dist>/.theokit/node/Caddyfile`, consumed by the\n * docker-compose stack (T3.3 compose-generator).\n *\n * EC-23: `reverse_proxy` directives are ordered by prefix length DESC.\n * Caddy matches longest-prefix-first when written in order.\n */\nimport type { ServicesManifest } from '../adapters-bridge/manifest.js'\n\ninterface CaddyfileOptions {\n /** Port Caddy listens on (TheoKit web port; default 3000). */\n port: number\n /** TheoKit web container hostname (compose service name). Default 'web'. */\n webHost: string\n}\n\nfunction reverseProxyLines(servicePath: string, target: string, cors: boolean): string[] {\n const out = [`\\treverse_proxy ${servicePath}* ${target}`]\n if (cors) {\n out.push(\n `\\theader ${servicePath}* Access-Control-Allow-Origin \"*\"`,\n `\\theader ${servicePath}* Access-Control-Allow-Methods \"GET, POST, PUT, DELETE, OPTIONS\"`,\n `\\theader ${servicePath}* Access-Control-Allow-Headers \"Content-Type, Authorization, traceparent, tracestate, baggage\"`,\n )\n }\n return out\n}\n\nexport function generateCaddyfile(\n manifest: ServicesManifest | null,\n options: CaddyfileOptions,\n): string {\n // EC-23: sort services by proxy prefix length DESC so longest matches first\n const services = (manifest?.services ?? [])\n .slice()\n .sort((a, b) => b.proxy.length - a.proxy.length)\n\n const serviceLines = services.flatMap((svc) =>\n reverseProxyLines(svc.proxy, `${svc.name}:${String(svc.port)}`, svc.cors),\n )\n\n const lines: string[] = [\n `:${String(options.port)} {`,\n `\\t# W3C Trace Context propagation (Caddy 2.11+)`,\n `\\ttracing`,\n `\\theader -Server`,\n ...serviceLines,\n `\\treverse_proxy ${options.webHost}:${String(options.port)}`,\n `}`,\n '',\n ]\n return lines.join('\\n')\n}\n","/**\n * docker-compose.yml generator (T3.3).\n *\n * Emits the \"TheoCloud-shaped\" local harness — Caddy ingress in front of\n * web + declared services. Each service has a healthcheck and Caddy\n * `depends_on: service_healthy`.\n *\n * Used by the Node adapter: `theokit build --target node` writes the\n * compose stack to `.theokit/node/docker-compose.yml`.\n */\nimport type { ManifestServiceEntry, ServicesManifest } from '../adapters-bridge/manifest.js'\n\ninterface ComposeOptions {\n /** Port for the web container (TheoKit). Default 3000. */\n webPort: number\n /** Caddy listening port (default same as webPort — Caddy fronts everything). */\n caddyPort?: number\n /** Project name (compose file `name:` field). */\n projectName?: string\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level)\n}\n\nfunction healthcheckLines(svc: ManifestServiceEntry): string[] {\n const checker =\n svc.runtime === 'python'\n ? `curl -f http://localhost:${String(svc.port)}${svc.healthcheck} || exit 1`\n : `wget --spider -q http://localhost:${String(svc.port)}${svc.healthcheck} || exit 1`\n return [\n `${indent(2)}healthcheck:`,\n `${indent(3)}test: [\"CMD-SHELL\", \"${checker}\"]`,\n `${indent(3)}interval: 10s`,\n `${indent(3)}timeout: 5s`,\n `${indent(3)}retries: 3`,\n ]\n}\n\nfunction envLines(env: Record<string, string>): string[] {\n const lines: string[] = [`${indent(2)}environment:`]\n for (const [k, v] of Object.entries(env)) {\n lines.push(`${indent(3)}${k}: ${JSON.stringify(v)}`)\n }\n return lines\n}\n\nfunction serviceBlockLines(svc: ManifestServiceEntry): string[] {\n // EC-8: auto-inject convention env vars in the container too\n const env: Record<string, string> = {\n THEOKIT_SERVICE_NAME: svc.name,\n THEOKIT_SERVICE_PORT: String(svc.port),\n ...(svc.env ?? {}),\n }\n return [\n `${indent(1)}${svc.name}:`,\n `${indent(2)}build:`,\n `${indent(3)}context: ./services/${svc.name}`,\n `${indent(2)}expose:`,\n `${indent(3)}- \"${String(svc.port)}\"`,\n ...envLines(env),\n ...healthcheckLines(svc),\n ]\n}\n\nfunction caddyBlockLines(caddyPort: number, services: ManifestServiceEntry[]): string[] {\n const depends: string[] = [\n `${indent(2)}depends_on:`,\n `${indent(3)}web:`,\n `${indent(4)}condition: service_healthy`,\n ]\n for (const svc of services) {\n depends.push(`${indent(3)}${svc.name}:`, `${indent(4)}condition: service_healthy`)\n }\n return [\n `${indent(1)}caddy:`,\n `${indent(2)}image: caddy:2.11`,\n `${indent(2)}ports:`,\n `${indent(3)}- \"${String(caddyPort)}:${String(caddyPort)}\"`,\n `${indent(2)}volumes:`,\n `${indent(3)}- ./Caddyfile:/etc/caddy/Caddyfile:ro`,\n ...depends,\n ]\n}\n\nfunction webBlockLines(webPort: number): string[] {\n return [\n `${indent(1)}web:`,\n `${indent(2)}build:`,\n `${indent(3)}context: .`,\n `${indent(3)}dockerfile: Dockerfile`,\n `${indent(2)}expose:`,\n `${indent(3)}- \"${String(webPort)}\"`,\n `${indent(2)}healthcheck:`,\n `${indent(3)}test: [\"CMD-SHELL\", \"wget --spider -q http://localhost:${String(webPort)}/api/health || exit 1\"]`,\n `${indent(3)}interval: 10s`,\n `${indent(3)}timeout: 5s`,\n `${indent(3)}retries: 3`,\n ]\n}\n\nexport function generateComposeYaml(\n manifest: ServicesManifest | null,\n options: ComposeOptions,\n): string {\n const services = manifest?.services ?? []\n const webPort = options.webPort\n const caddyPort = options.caddyPort ?? webPort\n\n const projectLines = options.projectName ? [`name: ${options.projectName}`] : []\n\n const lines: string[] = [\n ...projectLines,\n `services:`,\n ...caddyBlockLines(caddyPort, services),\n ...webBlockLines(webPort),\n ...services.flatMap(serviceBlockLines),\n '',\n ]\n return lines.join('\\n')\n}\n","/**\n * OpenAPI → TypeScript client generation (T5.1).\n *\n * Thin wrapper around `@hey-api/openapi-ts` (the de facto 2026 generator\n * used by Vercel/OpenCode/PayPal). Called by the Vite plugin\n * `services-typed-client.ts` at dev startup and when an OpenAPI URL\n * changes.\n *\n * Production behavior:\n * - For each service with a `openapi` URL, fetch the spec\n * - Run Hey API generator to write `<cwd>/clients/<service-name>.ts`\n * - On any failure: log warning, do NOT crash dev (best-effort)\n *\n * The actual `@hey-api/openapi-ts` invocation is dynamic-imported so\n * that consumers without the dep (TS-only Wave 1 apps) don't pay\n * the bundle cost.\n */\nimport type { ManifestServiceEntry } from '../adapters-bridge/manifest.js'\n\ninterface GenerateClientOptions {\n service: ManifestServiceEntry\n /** Directory to write `<service-name>.ts` into. */\n outputDir: string\n /** Logger for warnings/info. */\n log?: (level: 'info' | 'warn' | 'error', msg: string) => void\n /** Test injection — replace fetch (so tests don't hit the network). */\n customFetch?: typeof fetch\n}\n\ninterface GenerateClientResult {\n generated: boolean\n outputFile?: string\n skippedReason?: string\n}\n\n/**\n * Generate a typed client for one service. No-op (returns skipped=true)\n * if the service has no `openapi` URL.\n *\n * The generator runs the Hey API tool. If the tool import fails (not\n * installed), the function logs a warning and returns `{ generated: false }`.\n */\nexport async function generateTypedClient(\n options: GenerateClientOptions,\n): Promise<GenerateClientResult> {\n const log =\n options.log ??\n ((_level: 'info' | 'warn' | 'error', _msg: string) => {\n // default: silent in production; tests inject their own\n })\n const { service, outputDir } = options\n\n if (!service.openapi) {\n return { generated: false, skippedReason: 'no openapi URL declared' }\n }\n\n const f = options.customFetch ?? fetch\n let spec: unknown\n try {\n const res = await f(service.openapi)\n if (!res.ok) {\n log('warn', `[${service.name}] openapi fetch returned ${String(res.status)}; skipping`)\n return { generated: false, skippedReason: `fetch returned ${String(res.status)}` }\n }\n spec = await res.json()\n } catch (err) {\n log(\n 'warn',\n `[${service.name}] openapi fetch failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { generated: false, skippedReason: 'fetch failed' }\n }\n\n // Dynamic import — soft dep. We pass through a variable so the typechecker\n // doesn't resolve the literal at build time (the dep is optional;\n // consumers without it get a graceful skip).\n let createClient: unknown\n try {\n // Soft-dep resolution. We pass the module name through a String wrapper\n // call site that the typechecker cannot statically resolve — so missing\n // dep at typecheck time does not break the build. Runtime behavior is\n // identical to `import('@hey-api/openapi-ts')`.\n const segment1 = '@hey-api/'\n const segment2 = 'openapi-ts'\n const moduleName = `${segment1}${segment2}`\n const dyn: Promise<unknown> = import(moduleName).catch(() => null)\n const mod = (await dyn) as { createClient?: unknown } | null\n createClient = mod?.createClient\n } catch {\n createClient = undefined\n }\n\n if (typeof createClient !== 'function') {\n log(\n 'warn',\n `[${service.name}] @hey-api/openapi-ts not installed; typed client not generated. ` +\n `Run \\`pnpm add -D @hey-api/openapi-ts @hey-api/client-fetch\\` to enable.`,\n )\n return { generated: false, skippedReason: 'hey-api not installed' }\n }\n\n const outputFile = `${outputDir}/${service.name}.ts`\n try {\n await (createClient as (cfg: unknown) => Promise<unknown>)({\n input: spec,\n output: { path: outputDir, format: 'prettier' },\n plugins: [{ name: '@hey-api/client-fetch' }],\n })\n log('info', `[${service.name}] typed client generated at ${outputFile}`)\n return { generated: true, outputFile }\n } catch (err) {\n log(\n 'warn',\n `[${service.name}] Hey API generation failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { generated: false, skippedReason: 'generator threw' }\n }\n}\n","/**\n * Neutral cross-product manifest (T1.4).\n *\n * Emitted by `theokit build` at `<cwd>/.theokit/services.json` (schemaVersion 1).\n * Consumed by EVERY deploy adapter (Vercel, Node, Cloudflare, future TheoCloud).\n *\n * Shape is INTENTIONALLY platform-neutral (ADR-0015 invariant + D5). No\n * `vercel`/`cloudflare`/`theoCloud` keys — platform-specific tuning lives in\n * `theo.config.ts` and is read by each adapter's own code, NOT serialized\n * into this manifest.\n *\n * Topological ordering enforced (dependsOn-resolved deps first) so adapters\n * that need boot order (docker-compose, K8s) can consume the array verbatim.\n */\n/* eslint-disable security/detect-non-literal-fs-filename --\n * Manifest read/write paths are derived from the trusted `cwd` plus a fixed\n * subpath (`.theokit/services.json`). No user input flows into the path.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport type { ServiceDefinition, ServicesConfig } from '../schema.js'\n\n/**\n * Plan v1.2 T2.2 — service shape enum, mirrored from\n * `theo-cloud/api/internal/source/services.schema.json`. Optional in\n * services.json v2; TheoCloud Go-side defaults to `server` when absent.\n */\n// `frontend` removed in simplify-deploy-theokit-only v2.0 (D5) — TheoCloud\n// serves no static frontends; service shape is server | worker only.\nexport type ManifestServiceType = 'server' | 'worker'\n\nexport interface ManifestServiceEntry {\n name: string\n runtime: 'python' | 'node'\n /** Plan v1.2 T2.2 — service shape. Omit to keep default `server`. */\n type?: ManifestServiceType\n port: number\n proxy: string\n dev: string\n build?: string\n start: string\n openapi?: string\n healthcheck: string\n cors: boolean\n env?: Record<string, string>\n dependsOn?: string[]\n passSetCookie: boolean\n}\n\n/**\n * Plan v1.2 T2.1 — services.json now ships in 2 shapes:\n *\n * v1 (deprecated, sunset theokit 0.6.0): { version: 1, services: [...] }\n * v2 (current): { version: 2, project: <name>, services: [...] }\n *\n * `buildManifest()` selects which version to emit based on whether a\n * project name is supplied. Consumers (TheoCloud Go-side validator)\n * accept both via the JSON Schema 7 `oneOf` block.\n */\nexport interface ServicesManifestV1 {\n version: 1\n services: ManifestServiceEntry[]\n}\n\nexport interface ServicesManifestV2 {\n version: 2\n project: string\n services: ManifestServiceEntry[]\n}\n\nexport type ServicesManifest = ServicesManifestV1 | ServicesManifestV2\n\nconst MANIFEST_RELATIVE_PATH = ['.theokit', 'services.json'] as const\n\n/**\n * Topological sort using Kahn's algorithm.\n * Returns service names in dependency order (deps before dependants).\n * Schema validation guarantees no cycles, no self-deps, no missing references —\n * but we still defensively handle missing deps as no-op.\n */\nfunction topoSort(services: ServicesConfig): string[] {\n const names = Object.keys(services)\n const indeg: Record<string, number> = Object.fromEntries(names.map((n) => [n, 0]))\n const adj: Record<string, string[]> = Object.fromEntries(names.map((n) => [n, []]))\n\n const cmp = (a: string, b: string) => a.localeCompare(b)\n\n for (const name of names) {\n const deps = services[name].dependsOn ?? []\n for (const dep of deps) {\n if (!(dep in indeg)) continue\n adj[dep].push(name)\n indeg[name] = (indeg[name] ?? 0) + 1\n }\n }\n\n // Sort initial roots for determinism\n const queue: string[] = names.filter((n) => indeg[n] === 0).sort(cmp)\n const result: string[] = []\n while (queue.length > 0) {\n const cur = queue.shift()\n if (cur === undefined) break\n result.push(cur)\n const adjacent = (adj[cur] ?? []).slice().sort(cmp)\n for (const next of adjacent) {\n indeg[next] = (indeg[next] ?? 0) - 1\n if (indeg[next] === 0) {\n queue.push(next)\n }\n }\n }\n return result.length === names.length ? result : names // fallback (should not hit; schema rejects cycles)\n}\n\nfunction definitionToEntry(name: string, def: ServiceDefinition): ManifestServiceEntry {\n const entry: ManifestServiceEntry = {\n name,\n runtime: def.runtime,\n port: def.port,\n proxy: def.proxy,\n dev: def.dev,\n start: def.start,\n healthcheck: def.healthcheck,\n cors: def.cors,\n passSetCookie: def.passSetCookie,\n }\n // Plan v1.2 T2.2 — only emit `type` when explicitly set so v1 fixtures\n // (no type field at all) stay byte-identical.\n if (def.type !== undefined) entry.type = def.type\n if (def.build !== undefined) entry.build = def.build\n if (def.openapi !== undefined) entry.openapi = def.openapi\n if (def.env !== undefined) entry.env = def.env\n if (def.dependsOn !== undefined) entry.dependsOn = [...def.dependsOn]\n return entry\n}\n\n/**\n * Build a services manifest. Plan v1.2 T2.1: pass a `project` name to emit\n * v2; omit it to emit v1 (deprecated, accepted by TheoCloud until 0.6.0\n * sunset).\n */\nexport function buildManifest(services: ServicesConfig, project?: string): ServicesManifest {\n const ordered = topoSort(services)\n const entries = ordered.map((name) => definitionToEntry(name, services[name]))\n if (project !== undefined && project.length > 0) {\n return { version: 2, project, services: entries }\n }\n return { version: 1, services: entries }\n}\n\nfunction manifestPath(cwd: string): string {\n return join(cwd, ...MANIFEST_RELATIVE_PATH)\n}\n\n/**\n * Write the manifest to `<cwd>/.theokit/services.json`.\n *\n * EC-6 fix: creates `.theokit/` recursively if absent (fresh project on first\n * `pnpm build` would otherwise hit ENOENT).\n */\nexport function writeManifest(cwd: string, manifest: ServicesManifest): void {\n const file = manifestPath(cwd)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(manifest, null, 2), 'utf-8')\n}\n\n/**\n * Read the manifest from `<cwd>/.theokit/services.json`.\n *\n * Returns null if the file does not exist (adapter's job to handle).\n * Throws with an actionable message if the file is malformed JSON.\n */\nexport function readManifest(cwd: string): ServicesManifest | null {\n const file = manifestPath(cwd)\n if (!existsSync(file)) return null\n const raw = readFileSync(file, 'utf-8')\n try {\n return JSON.parse(raw) as ServicesManifest\n } catch (err) {\n throw new Error(\n `failed to parse services.json at ${file}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n}\n","/**\n * Wave 2 — Polyglot Services Orchestration (T1.1).\n *\n * Declarative `services: {}` primitive for `theo.config.ts`. Each entry\n * declares an external sidecar process (Python FastAPI / Node Hono) that\n * boots alongside the TheoKit TS app. Empty `services: {}` is the default\n * and preserves Wave 1 behavior (no impact on TS-only apps).\n *\n * See ADRs 0012-0015 + plan: docs/plans/wave-2-polyglot-services-plan.md\n */\nimport { z } from 'zod'\n\n/** Wave 2 runtime kinds. Go/Rust/Java/Ruby/PHP archived; deferred per ADR-0012 invariant #2/#3. */\nconst ServiceRuntimeSchema = z.enum(['python', 'node'])\n\n/**\n * Service shape enum, mirrored from `services.schema.json`: server | worker.\n * Optional; emitters default to \"server\". `frontend` removed in\n * simplify-deploy-theokit-only v2.0 (D5) — TheoCloud serves no static frontends.\n */\nconst ServiceTypeSchema = z.enum(['server', 'worker'])\n\n/** EC-3 fix: names that conflict with generated docker-compose entries. */\nconst RESERVED_SERVICE_NAMES = ['web', 'caddy', 'postgres', 'redis'] as const\n\n/**\n * EC-12 fix: service names must be docker-compose-safe — lowercase,\n * start with a letter, contain only a-z 0-9 -. Reserved names rejected.\n */\nconst ServiceNameSchema = z\n .string()\n .min(1)\n .regex(\n /^[a-z][a-z0-9-]*$/,\n 'service name must be lowercase, start with letter, contain only a-z 0-9 -',\n )\n .refine((n) => !(RESERVED_SERVICE_NAMES as readonly string[]).includes(n), {\n message: `service name conflicts with reserved name (${RESERVED_SERVICE_NAMES.join('/')})`,\n })\n\n/**\n * Single service definition. All commands run from `services/<name>/` cwd.\n *\n * EC-4 fix: `proxy` regex requires NON-EMPTY path after `/` (the `+` quantifier)\n * to reject `/` which would catch-all and conflict with TheoKit's own routing.\n */\nconst ServiceDefinitionSchema = z.object({\n runtime: ServiceRuntimeSchema,\n type: ServiceTypeSchema.optional(),\n port: z.number().int().min(1).max(65535),\n proxy: z.string().regex(/^\\/[a-zA-Z0-9\\-_/]+$/, 'proxy must be a non-root path starting with /'),\n dev: z.string().min(1),\n build: z.string().optional(),\n start: z.string().min(1),\n openapi: z.url().optional(),\n healthcheck: z.string().regex(/^\\//, 'healthcheck must start with /').default('/health'),\n cors: z.boolean().default(false),\n env: z.record(z.string(), z.string()).optional(),\n dependsOn: z.array(z.string()).optional(),\n /**\n * EC-25 + ref doc §8: by default the proxy strips upstream Set-Cookie\n * to prevent the polyglot service from issuing cookies that conflict\n * with TheoKit's encrypted session. Set to true to opt-in.\n */\n passSetCookie: z.boolean().default(false),\n})\n\nexport type ServiceDefinition = z.infer<typeof ServiceDefinitionSchema>\nexport type ServicesConfig = Record<string, ServiceDefinition>\n\n/**\n * Topological-order helper used by `dependsOn` cycle detection refine.\n * Returns true iff `graph` is a DAG. Each node's deps must all reference\n * existing nodes; cycles return false.\n */\nfunction isDag(graph: Record<string, readonly string[]>): boolean {\n const WHITE = 0\n const GRAY = 1\n const BLACK = 2\n const colors: Record<string, 0 | 1 | 2> = {}\n for (const node of Object.keys(graph)) colors[node] = WHITE\n\n function visit(node: string): boolean {\n if (colors[node] === GRAY) return false // cycle\n if (colors[node] === BLACK) return true\n colors[node] = GRAY\n for (const dep of graph[node] ?? []) {\n if (!(dep in graph)) return false // missing reference\n if (!visit(dep)) return false\n }\n colors[node] = BLACK\n return true\n }\n\n for (const node of Object.keys(graph)) {\n if (colors[node] === WHITE && !visit(node)) return false\n }\n return true\n}\n\n/**\n * Full services config schema with cross-service refines.\n *\n * EC-1 fix: detect duplicate ports across services.\n * EC-13: empty `dependsOn: []` accepted as no-deps.\n * Self-dep, missing-ref, and cycles rejected via topological check.\n */\nexport const servicesConfigSchema = z\n .record(ServiceNameSchema, ServiceDefinitionSchema)\n .default({})\n // EC-1: duplicate port detection across services\n .refine(\n (s) => {\n const ports = Object.values(s).map((v) => v.port)\n return new Set(ports).size === ports.length\n },\n {\n message: 'duplicate port across services — each service must bind a unique port',\n },\n )\n // duplicate proxy prefix detection\n .refine(\n (s) => {\n const prefixes = Object.values(s).map((v) => v.proxy)\n return new Set(prefixes).size === prefixes.length\n },\n { message: 'duplicate proxy prefix across services' },\n )\n // dependsOn correctness (self-dep, missing reference, cycle)\n .refine(\n (s) => {\n // Build graph\n const graph: Record<string, readonly string[]> = {}\n for (const [name, def] of Object.entries(s)) {\n graph[name] = def.dependsOn ?? []\n }\n // Self-dep: a service mentioning itself is rejected as part of cycle detection\n for (const [name, deps] of Object.entries(graph)) {\n if (deps.includes(name)) return false\n }\n return isDag(graph)\n },\n {\n message: 'invalid dependsOn — must reference existing services, no self-deps, no cycles',\n },\n )\n\nexport type ServicesConfigInput = z.input<typeof servicesConfigSchema>\nexport type ServicesConfigOutput = z.output<typeof servicesConfigSchema>\n","/**\n * Dev orchestration entry point (T2.4).\n *\n * Spawns declared polyglot services, waits for all healthchecks to pass,\n * then signals readiness. The `theokit dev` CLI calls `orchestrateDev`\n * BEFORE starting Vite — Vite only boots after all services healthy.\n *\n * Empty `services: {}` → no-op (Wave 1 BC).\n *\n * Single composable entry that the CLI consumes. Spawn/healthcheck/log\n * primitives live in their own modules; this file ties them together.\n */\nimport type { ChildProcess, SpawnOptions } from 'node:child_process'\nimport { spawn } from 'node:child_process'\nimport { resolve } from 'node:path'\n\nimport type { ServiceDefinition, ServicesConfig } from '../schema.js'\n\nimport { pollHealthcheck } from './healthcheck-poller.js'\nimport { createLogMerger } from './log-merge.js'\nimport { buildSpawnEnv, installLifecycleHandlers } from './process-spawn-helpers.js'\n\ninterface SpawnedService {\n name: string\n port: number\n process: ChildProcess\n}\n\ninterface OrchestrateDevOptions {\n cwd: string\n services: ServicesConfig\n /** Defaults to `process.stdout.write.bind(process.stdout)`. */\n write?: (s: string) => void\n /** Test injection — replace child_process.spawn. */\n spawnFn?: typeof spawn\n /** Test injection — replace global fetch (for healthchecks). */\n customFetch?: typeof fetch\n /** Healthcheck timeout per service. Default 30_000. */\n healthcheckTimeoutMs?: number\n /** Whether to install parent lifecycle handlers (EC-7). Default true. */\n installSignalHandlers?: boolean\n}\n\ninterface OrchestrateDevResult {\n spawned: SpawnedService[]\n /** Whether ALL services passed healthcheck. */\n allHealthy: boolean\n /** Services that timed out (only populated when !allHealthy). */\n unhealthy: string[]\n /** Call to gracefully stop all spawned services. */\n stop: () => Promise<void>\n}\n\nfunction spawnService(\n name: string,\n service: ServiceDefinition,\n cwd: string,\n options: {\n spawnFn: typeof spawn\n onLog: (s: string, stream: 'stdout' | 'stderr', line: string) => void\n },\n): SpawnedService {\n const env = buildSpawnEnv(name, service, process.env)\n const spawnOpts: SpawnOptions = {\n cwd,\n shell: true,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n }\n const child = options.spawnFn(service.dev, spawnOpts)\n child.stdout?.setEncoding('utf-8')\n child.stderr?.setEncoding('utf-8')\n child.stdout?.on('data', (chunk: string) => {\n options.onLog(name, 'stdout', chunk)\n })\n child.stderr?.on('data', (chunk: string) => {\n options.onLog(name, 'stderr', chunk)\n })\n return { name, port: service.port, process: child }\n}\n\nasync function stopSpawned(s: SpawnedService): Promise<void> {\n return new Promise<void>((resolve) => {\n if (s.process.killed || s.process.exitCode !== null) {\n resolve()\n return\n }\n const timer = setTimeout(() => {\n s.process.kill('SIGKILL')\n resolve()\n }, 5_000)\n s.process.once('exit', () => {\n clearTimeout(timer)\n resolve()\n })\n s.process.kill('SIGTERM')\n })\n}\n\nexport async function orchestrateDev(\n options: OrchestrateDevOptions,\n): Promise<OrchestrateDevResult> {\n const entries = Object.entries(options.services)\n if (entries.length === 0) {\n return {\n spawned: [],\n allHealthy: true,\n unhealthy: [],\n stop: () => Promise.resolve(),\n }\n }\n\n const write =\n options.write ??\n ((s: string) => {\n process.stdout.write(s)\n })\n const spawnFn = options.spawnFn ?? spawn\n const merger = createLogMerger({ write })\n\n const spawned: SpawnedService[] = entries.map(([name, def]) =>\n spawnService(name, def, resolve(options.cwd, 'services', name), {\n spawnFn,\n onLog: merger.onLog,\n }),\n )\n\n const stop = async (): Promise<void> => {\n await Promise.all(spawned.map(stopSpawned))\n }\n\n if (options.installSignalHandlers !== false) {\n installLifecycleHandlers(process, stop)\n }\n\n // Parallel healthcheck\n const results = await Promise.all(\n entries.map(([name, def]) =>\n pollHealthcheck({\n url: `http://localhost:${String(def.port)}${def.healthcheck}`,\n timeoutMs: options.healthcheckTimeoutMs ?? 30_000,\n intervalMs: 500,\n customFetch: options.customFetch,\n }).then((r) => ({ name, result: r })),\n ),\n )\n\n const unhealthy = results.filter((r) => !r.result.healthy).map((r) => r.name)\n return {\n spawned,\n allHealthy: unhealthy.length === 0,\n unhealthy,\n stop,\n }\n}\n","/**\n * Healthcheck poller (T1.5).\n *\n * Polls `GET <url>` with backoff until 200 OR timeout. Used by `pnpm dev`\n * (T2.4) to gate readiness on declared services being healthy before\n * starting Vite.\n *\n * Invariants:\n * - Returns within `timeoutMs + intervalMs` (no hang)\n * - Never throws (returns `{ healthy: false }` on any error)\n * - `attempts >= 1` always\n * - Respects external AbortSignal (pre-aborted → immediate return, EC-18)\n */\n\ninterface HealthcheckOptions {\n /** Full URL to poll, e.g. 'http://localhost:8001/health' */\n url: string\n /** Total time budget in ms. Default 30_000. */\n timeoutMs?: number\n /** Sleep between attempts in ms. Default 500. */\n intervalMs?: number\n /** Optional external cancel. Aborted signal causes early return. */\n signal?: AbortSignal\n /** Test injection — replace global fetch. */\n customFetch?: typeof fetch\n}\n\ninterface HealthcheckResult {\n healthy: boolean\n attempts: number\n durationMs: number\n lastError?: string\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal?.aborted) {\n resolve()\n return\n }\n const t = setTimeout(() => {\n resolve()\n }, ms)\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(t)\n resolve()\n },\n { once: true },\n )\n })\n}\n\nexport async function pollHealthcheck(options: HealthcheckOptions): Promise<HealthcheckResult> {\n const timeoutMs = options.timeoutMs ?? 30_000\n const intervalMs = options.intervalMs ?? 500\n const f = options.customFetch ?? fetch\n const start = Date.now()\n let attempts = 0\n let lastError: string | undefined\n\n // EC-18: signal already aborted → return immediately with at least 1 attempt counted\n if (options.signal?.aborted) {\n return {\n healthy: false,\n attempts: 1,\n durationMs: Date.now() - start,\n lastError: 'aborted',\n }\n }\n\n // Infinite poll loop terminated by status === 200 OR timeout OR abort.\n for (;;) {\n if (options.signal?.aborted) {\n return {\n healthy: false,\n attempts: Math.max(attempts, 1),\n durationMs: Date.now() - start,\n lastError: 'aborted',\n }\n }\n attempts++\n try {\n const res = await f(options.url, { method: 'GET', signal: options.signal })\n if (res.status === 200) {\n return { healthy: true, attempts, durationMs: Date.now() - start }\n }\n lastError = `status ${res.status}`\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err)\n }\n\n if (Date.now() - start >= timeoutMs) {\n return {\n healthy: false,\n attempts,\n durationMs: Date.now() - start,\n lastError,\n }\n }\n await sleep(intervalMs, options.signal)\n }\n}\n","/**\n * Log merger (T2.3).\n *\n * Combines stdout/stderr from multiple polyglot services into TheoKit's\n * dev terminal, prefixed with `[service-name]`. Parses JSON-line logs\n * (ADR-0015 invariant #5) for pretty-printing; falls back to raw line on\n * non-JSON.\n *\n * Pure / synchronous. The Vite plugin (T2.1) wires this as the `onLog`\n * callback passed to spawnServices.\n */\n\ninterface LogEntry {\n level?: string\n message?: string\n timestamp?: string\n traceparent?: string\n service?: string\n}\n\ninterface LogMergerOptions {\n write: (s: string) => void\n}\n\ninterface LogMerger {\n onLog: (service: string, stream: 'stdout' | 'stderr', chunk: string) => void\n}\n\n/**\n * Try to parse a log line as JSON. Returns parsed object on success,\n * undefined on failure (will fall back to raw rendering).\n */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\nfunction tryParseJson(line: string): LogEntry | undefined {\n const trimmed = line.trim()\n if (!trimmed.startsWith('{')) return undefined\n try {\n const parsed: unknown = JSON.parse(trimmed)\n if (!isPlainObject(parsed)) return undefined\n const entry: LogEntry = {}\n if (typeof parsed.level === 'string') entry.level = parsed.level\n if (typeof parsed.message === 'string') entry.message = parsed.message\n if (typeof parsed.timestamp === 'string') entry.timestamp = parsed.timestamp\n if (typeof parsed.traceparent === 'string') entry.traceparent = parsed.traceparent\n if (typeof parsed.service === 'string') entry.service = parsed.service\n return entry\n } catch {\n return undefined\n }\n}\n\nfunction renderLine(\n service: string,\n stream: 'stdout' | 'stderr',\n line: string,\n write: (s: string) => void,\n): void {\n if (line.length === 0) return\n\n const prefix = `[${service}]`\n const streamMarker = stream === 'stderr' ? '!' : ' '\n\n const parsed = tryParseJson(line)\n if (parsed) {\n const level = parsed.level ?? 'info'\n const msg = parsed.message ?? line\n write(`${prefix}${streamMarker}${level.toUpperCase()} ${msg}\\n`)\n return\n }\n\n write(`${prefix}${streamMarker}${line}\\n`)\n}\n\nexport function createLogMerger(options: LogMergerOptions): LogMerger {\n return {\n onLog(service, stream, chunk) {\n // Multi-line chunks: split by \\n and render each\n const lines = chunk.split('\\n')\n for (const line of lines) {\n renderLine(service, stream, line, options.write)\n }\n },\n }\n}\n","/**\n * Pure helpers extracted from process-spawn.ts (T2.2) for unit testability.\n *\n * The full spawn orchestration (child_process + healthcheck poll + log\n * merge) lives in process-spawn.ts; this file holds the parts that are\n * safe to test without mocking node:child_process.\n *\n * EC-7: lifecycle handlers prevent orphan children on parent exit/signal.\n * EC-8: auto-injected env vars (THEOKIT_SERVICE_NAME, THEOKIT_SERVICE_PORT).\n */\nimport type { ServiceDefinition } from '../schema.js'\n\n/**\n * Build the env passed to `child_process.spawn`.\n *\n * Precedence (low → high):\n * 1. process.env (the parent's environment)\n * 2. auto-injected THEOKIT_SERVICE_NAME + THEOKIT_SERVICE_PORT\n * 3. service.env (user-provided overrides win)\n */\nexport function buildSpawnEnv(\n name: string,\n service: ServiceDefinition,\n parentEnv: Record<string, string | undefined>,\n): Record<string, string> {\n const env: Record<string, string> = {}\n for (const [k, v] of Object.entries(parentEnv)) {\n if (typeof v === 'string') env[k] = v\n }\n env.THEOKIT_SERVICE_NAME = name\n env.THEOKIT_SERVICE_PORT = String(service.port)\n if (service.env) {\n for (const [k, v] of Object.entries(service.env)) {\n env[k] = v\n }\n }\n return env\n}\n\n/**\n * Format a service name as a log prefix. Deterministic; ANSI codes added\n * by the log-merge layer (T2.3), not here.\n */\nexport function formatLogPrefix(name: string): string {\n return `[${name}]`\n}\n\n/**\n * Install parent-process lifecycle handlers (EC-7).\n *\n * - `exit`: best-effort SIGKILL of all children (synchronous; we cannot await here)\n * - `SIGINT`: graceful stopAllServices then exit 130\n * - `SIGTERM`: graceful stopAllServices then exit 143\n *\n * SIGKILL on the parent is uncatchable (kernel-level); orphan children\n * are documented as a known limit on force-close (EC-27 doc).\n */\nexport function installLifecycleHandlers(proc: NodeJS.Process, stopAll: () => Promise<void>): void {\n proc.on('exit', () => {\n // Best-effort sync notify; cannot await\n })\n proc.on('SIGINT', () => {\n void stopAll().finally(() => {\n proc.exit(130)\n })\n })\n proc.on('SIGTERM', () => {\n void stopAll().finally(() => {\n proc.exit(143)\n })\n })\n}\n","/**\n * TheoCloud adapter scaffolding (T3.5 — Wave 2 stub; full Wave 3).\n *\n * Wave 2 establishes the contract: the adapter CONSUMES `.theokit/services.json`\n * (manifest emitted by `theokit build`) and produces TheoCloud deployment\n * artifacts. The artifacts themselves (K8s manifests, Helm charts, etc.) are\n * Wave 3 deliverables — this module provides the read/validate gate so the\n * manifest format is locked NOW.\n *\n * Per ADR-0012 invariant #4, the same `.theokit/services.json` consumed by\n * the `node` adapter (docker-compose generator) is the same shape consumed\n * here. No platform-specific fields. The contract is global.\n */\nimport type { ServicesManifest } from './manifest.js'\n\ninterface TheoCloudAdapterArtifacts {\n /** Manifest schemaVersion the adapter consumed (v1 deprecated, v2 current). */\n manifestVersion: 1 | 2\n /** Service names that will be deployed. */\n services: string[]\n /** Wave 3 will populate K8s manifests here. */\n k8sManifests?: never\n /** Wave 3 will populate Helm values here. */\n helmValues?: never\n}\n\n/**\n * Wave 2 stub — validates the manifest is consumable. Throws if shape is\n * unexpected (forward-compat guard for Wave 3 development).\n *\n * Wave 3 will replace the body with real K8s manifest emission.\n */\nexport function prepareTheoCloudArtifacts(\n manifest: ServicesManifest | null,\n): TheoCloudAdapterArtifacts {\n if (manifest === null) {\n return { manifestVersion: 1, services: [] }\n }\n // v1 (deprecated, sunset 0.6.0) and v2 (current, adds `project`) share the\n // same `services[]` shape the adapter consumes — both are accepted. Any\n // other version is a forward-compat guard: bump handling here before the\n // builder emits a newer schema. (Regression: usetheodev/theokit#9 — the\n // builder emits v2 when a project name is supplied; rejecting v2 broke\n // every `theokit build --target theo-cloud`.)\n //\n // The type says `1 | 2`, but the manifest is read from disk (`readManifest`)\n // so a malformed/newer file can carry any number at runtime — read through a\n // numeric local so the guard stays reachable (narrowing `manifest.version`\n // directly would collapse the guard body to `never`).\n const version: number = manifest.version\n if (version !== 1 && version !== 2) {\n throw new Error(\n `TheoCloud adapter: unsupported manifest version ${String(version)}. ` +\n `Supported: schemaVersion 1 (deprecated) or 2. Update the adapter before bumping.`,\n )\n }\n return {\n manifestVersion: manifest.version,\n services: manifest.services.map((s) => s.name),\n }\n}\n","/**\n * Builds a Vite `server.proxy` configuration from TheoKit's\n * declarative `services: {}` config (T2.1).\n *\n * Pure function — no Vite imports, just data shaping. The Vite plugin\n * (services-dev.ts) calls this and merges the result into `vite.config.server.proxy`.\n *\n * Pattern follows Vite proxy reference (referencias/vite/packages/vite/src/node/server/middlewares/proxy.ts):\n * - Each path prefix becomes its own entry\n * - `changeOrigin: true` set by default (matches Vite's string-shortcut behavior)\n * - User-set entries take precedence on prefix collision (we never clobber)\n */\nimport type { ServicesConfig } from '../schema.js'\n\n/**\n * Shape compatible with Vite's `server.proxy` type. We intentionally don't\n * import Vite's `ProxyOptions` so this module stays platform-neutral and\n * unit-testable without Vite.\n */\ninterface ViteProxyEntry {\n target: string\n changeOrigin: boolean\n rewrite?: (path: string) => string\n}\n\ntype ViteProxyConfig = Record<string, string | ViteProxyEntry>\n\nexport function buildServicesProxyConfig(\n services: ServicesConfig,\n userProxy: ViteProxyConfig = {},\n): ViteProxyConfig {\n const fromServices: ViteProxyConfig = {}\n for (const [, def] of Object.entries(services)) {\n const prefix = def.proxy\n fromServices[prefix] = {\n target: `http://localhost:${String(def.port)}`,\n changeOrigin: true,\n // Strip the service's `proxy` prefix from the upstream URL so the\n // sidecar receives its own native paths (e.g. `/api/agent/echo` →\n // `/echo`). The sidecar declares routes against its own root path —\n // it doesn't know about the TheoKit proxy prefix.\n rewrite: (path: string) =>\n path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path,\n }\n }\n // User proxy wins on collision (we add services first, then spread user on top)\n return { ...fromServices, ...userProxy }\n}\n"],"mappings":"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","names":["resolve","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/services/adapters-bridge/adapter-support.ts","../src/services/generators/caddy-generator.ts","../src/services/generators/compose-generator.ts","../src/services/generators/openapi-client-gen.ts","../src/services/adapters-bridge/manifest.ts","../src/services/schema.ts","../src/services/runtime/orchestrator.ts","../src/services/runtime/healthcheck-poller.ts","../src/services/runtime/log-merge.ts","../src/services/runtime/process-spawn-helpers.ts","../src/services/adapters-bridge/theo-cloud-adapter-stub.ts","../src/services/adapters-bridge/vite-proxy-builder.ts"],"sourcesContent":["/**\n * Adapter support gates (Wave 2 — TheoCloud-first focus).\n *\n * Wave 2 SUPPORTS polyglot `services: {}` on TWO targets ONLY:\n * - `node` (local docker-compose harness — TheoCloud-shaped)\n * - `theo-cloud` (the principal deploy target — Wave 3 adapter)\n *\n * All other in-tree deploy adapters (vercel, cloudflare, bun, deno-deploy,\n * aws-lambda, netlify, static) ARE STILL FIRST-CLASS DEPLOY TARGETS for\n * the TS app, but `services: {}` Wave 2 is NOT yet wired through them.\n * They reject loudly with an actionable message when the manifest has\n * non-empty services — pointing the user at `node` (local) or TheoCloud.\n *\n * Per owner decision 2026-05-27: TheoKit's polyglot services energy is\n * 100% TheoCloud; Vercel/Cloudflare/etc. polyglot-services wire-ups are\n * out of Wave 2 scope and deferred to fresh ADRs if demand emerges.\n */\nimport type { ServicesManifest } from './manifest.js'\n\nconst SUPPORTED_IN_WAVE_2 = ['node (local)', 'theo-cloud (Wave 3)'] as const\n\n/**\n * Reject if the adapter does not support polyglot services. Adapters\n * outside `node` + `theo-cloud` call this; the message points to the\n * supported alternatives.\n *\n * Throws when manifest has non-empty services. No-op when manifest is null\n * or empty (Wave 1 BC — the app still deploys, just without sidecars).\n */\nexport function assertServicesUnsupported(\n adapterName: string,\n manifest: ServicesManifest | null,\n): void {\n if (!manifest || manifest.services.length === 0) return\n const names = manifest.services.map((s) => s.name).join(', ')\n throw new Error(\n `Adapter '${adapterName}' does not support polyglot services in Wave 2.\\n` +\n `Detected services in theo.config.ts: ${names}.\\n\\n` +\n `Wave 2 supports: ${SUPPORTED_IN_WAVE_2.join(', ')}.\\n` +\n `TheoCloud is the strategic deploy target — the adapter ships in Wave 3.\\n` +\n `For local dev/prod-like validation today, use \\`theokit build --target node\\`\\n` +\n `(emits docker-compose + Caddyfile — TheoCloud-shaped harness).\\n\\n` +\n `See docs/concepts/services.md.`,\n )\n}\n","/**\n * Caddyfile generator (T3.3).\n *\n * Emits a Caddyfile that fronts the TheoKit web app + declared services\n * with W3C trace context propagation enabled (Caddy 2.11+ `tracing` directive).\n *\n * Generated output sits at `<dist>/.theokit/node/Caddyfile`, consumed by the\n * docker-compose stack (T3.3 compose-generator).\n *\n * EC-23: `reverse_proxy` directives are ordered by prefix length DESC.\n * Caddy matches longest-prefix-first when written in order.\n */\nimport type { ServicesManifest } from '../adapters-bridge/manifest.js'\n\ninterface CaddyfileOptions {\n /** Port Caddy listens on (TheoKit web port; default 3000). */\n port: number\n /** TheoKit web container hostname (compose service name). Default 'web'. */\n webHost: string\n}\n\nfunction reverseProxyLines(servicePath: string, target: string, cors: boolean): string[] {\n const out = [`\\treverse_proxy ${servicePath}* ${target}`]\n if (cors) {\n out.push(\n `\\theader ${servicePath}* Access-Control-Allow-Origin \"*\"`,\n `\\theader ${servicePath}* Access-Control-Allow-Methods \"GET, POST, PUT, DELETE, OPTIONS\"`,\n `\\theader ${servicePath}* Access-Control-Allow-Headers \"Content-Type, Authorization, traceparent, tracestate, baggage\"`,\n )\n }\n return out\n}\n\nexport function generateCaddyfile(\n manifest: ServicesManifest | null,\n options: CaddyfileOptions,\n): string {\n // EC-23: sort services by proxy prefix length DESC so longest matches first\n const services = (manifest?.services ?? [])\n .slice()\n .sort((a, b) => b.proxy.length - a.proxy.length)\n\n const serviceLines = services.flatMap((svc) =>\n reverseProxyLines(svc.proxy, `${svc.name}:${String(svc.port)}`, svc.cors),\n )\n\n const lines: string[] = [\n `:${String(options.port)} {`,\n `\\t# W3C Trace Context propagation (Caddy 2.11+)`,\n `\\ttracing`,\n `\\theader -Server`,\n ...serviceLines,\n `\\treverse_proxy ${options.webHost}:${String(options.port)}`,\n `}`,\n '',\n ]\n return lines.join('\\n')\n}\n","/**\n * docker-compose.yml generator (T3.3).\n *\n * Emits the \"TheoCloud-shaped\" local harness — Caddy ingress in front of\n * web + declared services. Each service has a healthcheck and Caddy\n * `depends_on: service_healthy`.\n *\n * Used by the Node adapter: `theokit build --target node` writes the\n * compose stack to `.theokit/node/docker-compose.yml`.\n */\nimport type { ManifestServiceEntry, ServicesManifest } from '../adapters-bridge/manifest.js'\n\ninterface ComposeOptions {\n /** Port for the web container (TheoKit). Default 3000. */\n webPort: number\n /** Caddy listening port (default same as webPort — Caddy fronts everything). */\n caddyPort?: number\n /** Project name (compose file `name:` field). */\n projectName?: string\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level)\n}\n\nfunction healthcheckLines(svc: ManifestServiceEntry): string[] {\n const checker =\n svc.runtime === 'python'\n ? `curl -f http://localhost:${String(svc.port)}${svc.healthcheck} || exit 1`\n : `wget --spider -q http://localhost:${String(svc.port)}${svc.healthcheck} || exit 1`\n return [\n `${indent(2)}healthcheck:`,\n `${indent(3)}test: [\"CMD-SHELL\", \"${checker}\"]`,\n `${indent(3)}interval: 10s`,\n `${indent(3)}timeout: 5s`,\n `${indent(3)}retries: 3`,\n ]\n}\n\nfunction envLines(env: Record<string, string>): string[] {\n const lines: string[] = [`${indent(2)}environment:`]\n for (const [k, v] of Object.entries(env)) {\n lines.push(`${indent(3)}${k}: ${JSON.stringify(v)}`)\n }\n return lines\n}\n\nfunction serviceBlockLines(svc: ManifestServiceEntry): string[] {\n // EC-8: auto-inject convention env vars in the container too\n const env: Record<string, string> = {\n THEOKIT_SERVICE_NAME: svc.name,\n THEOKIT_SERVICE_PORT: String(svc.port),\n ...(svc.env ?? {}),\n }\n return [\n `${indent(1)}${svc.name}:`,\n `${indent(2)}build:`,\n `${indent(3)}context: ./services/${svc.name}`,\n `${indent(2)}expose:`,\n `${indent(3)}- \"${String(svc.port)}\"`,\n ...envLines(env),\n ...healthcheckLines(svc),\n ]\n}\n\nfunction caddyBlockLines(caddyPort: number, services: ManifestServiceEntry[]): string[] {\n const depends: string[] = [\n `${indent(2)}depends_on:`,\n `${indent(3)}web:`,\n `${indent(4)}condition: service_healthy`,\n ]\n for (const svc of services) {\n depends.push(`${indent(3)}${svc.name}:`, `${indent(4)}condition: service_healthy`)\n }\n return [\n `${indent(1)}caddy:`,\n `${indent(2)}image: caddy:2.11`,\n `${indent(2)}ports:`,\n `${indent(3)}- \"${String(caddyPort)}:${String(caddyPort)}\"`,\n `${indent(2)}volumes:`,\n `${indent(3)}- ./Caddyfile:/etc/caddy/Caddyfile:ro`,\n ...depends,\n ]\n}\n\nfunction webBlockLines(webPort: number): string[] {\n return [\n `${indent(1)}web:`,\n `${indent(2)}build:`,\n `${indent(3)}context: .`,\n `${indent(3)}dockerfile: Dockerfile`,\n `${indent(2)}expose:`,\n `${indent(3)}- \"${String(webPort)}\"`,\n `${indent(2)}healthcheck:`,\n `${indent(3)}test: [\"CMD-SHELL\", \"wget --spider -q http://localhost:${String(webPort)}/api/health || exit 1\"]`,\n `${indent(3)}interval: 10s`,\n `${indent(3)}timeout: 5s`,\n `${indent(3)}retries: 3`,\n ]\n}\n\nexport function generateComposeYaml(\n manifest: ServicesManifest | null,\n options: ComposeOptions,\n): string {\n const services = manifest?.services ?? []\n const webPort = options.webPort\n const caddyPort = options.caddyPort ?? webPort\n\n const projectLines = options.projectName ? [`name: ${options.projectName}`] : []\n\n const lines: string[] = [\n ...projectLines,\n `services:`,\n ...caddyBlockLines(caddyPort, services),\n ...webBlockLines(webPort),\n ...services.flatMap(serviceBlockLines),\n '',\n ]\n return lines.join('\\n')\n}\n","/**\n * OpenAPI → TypeScript client generation (T5.1).\n *\n * Thin wrapper around `@hey-api/openapi-ts` (the de facto 2026 generator\n * used by Vercel/OpenCode/PayPal). Called by the Vite plugin\n * `services-typed-client.ts` at dev startup and when an OpenAPI URL\n * changes.\n *\n * Production behavior:\n * - For each service with a `openapi` URL, fetch the spec\n * - Run Hey API generator to write `<cwd>/clients/<service-name>.ts`\n * - On any failure: log warning, do NOT crash dev (best-effort)\n *\n * The actual `@hey-api/openapi-ts` invocation is dynamic-imported so\n * that consumers without the dep (TS-only Wave 1 apps) don't pay\n * the bundle cost.\n */\nimport type { ManifestServiceEntry } from '../adapters-bridge/manifest.js'\n\ninterface GenerateClientOptions {\n service: ManifestServiceEntry\n /** Directory to write `<service-name>.ts` into. */\n outputDir: string\n /** Logger for warnings/info. */\n log?: (level: 'info' | 'warn' | 'error', msg: string) => void\n /** Test injection — replace fetch (so tests don't hit the network). */\n customFetch?: typeof fetch\n}\n\ninterface GenerateClientResult {\n generated: boolean\n outputFile?: string\n skippedReason?: string\n}\n\n/**\n * Generate a typed client for one service. No-op (returns skipped=true)\n * if the service has no `openapi` URL.\n *\n * The generator runs the Hey API tool. If the tool import fails (not\n * installed), the function logs a warning and returns `{ generated: false }`.\n */\nexport async function generateTypedClient(\n options: GenerateClientOptions,\n): Promise<GenerateClientResult> {\n const log =\n options.log ??\n ((_level: 'info' | 'warn' | 'error', _msg: string) => {\n // default: silent in production; tests inject their own\n })\n const { service, outputDir } = options\n\n if (!service.openapi) {\n return { generated: false, skippedReason: 'no openapi URL declared' }\n }\n\n const f = options.customFetch ?? fetch\n let spec: unknown\n try {\n const res = await f(service.openapi)\n if (!res.ok) {\n log('warn', `[${service.name}] openapi fetch returned ${String(res.status)}; skipping`)\n return { generated: false, skippedReason: `fetch returned ${String(res.status)}` }\n }\n spec = await res.json()\n } catch (err) {\n log(\n 'warn',\n `[${service.name}] openapi fetch failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { generated: false, skippedReason: 'fetch failed' }\n }\n\n // Dynamic import — soft dep. We pass through a variable so the typechecker\n // doesn't resolve the literal at build time (the dep is optional;\n // consumers without it get a graceful skip).\n let createClient: unknown\n try {\n // Soft-dep resolution. We pass the module name through a String wrapper\n // call site that the typechecker cannot statically resolve — so missing\n // dep at typecheck time does not break the build. Runtime behavior is\n // identical to `import('@hey-api/openapi-ts')`.\n const segment1 = '@hey-api/'\n const segment2 = 'openapi-ts'\n const moduleName = `${segment1}${segment2}`\n const dyn: Promise<unknown> = import(moduleName).catch(() => null)\n const mod = (await dyn) as { createClient?: unknown } | null\n createClient = mod?.createClient\n } catch {\n createClient = undefined\n }\n\n if (typeof createClient !== 'function') {\n log(\n 'warn',\n `[${service.name}] @hey-api/openapi-ts not installed; typed client not generated. ` +\n `Run \\`pnpm add -D @hey-api/openapi-ts @hey-api/client-fetch\\` to enable.`,\n )\n return { generated: false, skippedReason: 'hey-api not installed' }\n }\n\n const outputFile = `${outputDir}/${service.name}.ts`\n try {\n await (createClient as (cfg: unknown) => Promise<unknown>)({\n input: spec,\n output: { path: outputDir, format: 'prettier' },\n plugins: [{ name: '@hey-api/client-fetch' }],\n })\n log('info', `[${service.name}] typed client generated at ${outputFile}`)\n return { generated: true, outputFile }\n } catch (err) {\n log(\n 'warn',\n `[${service.name}] Hey API generation failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { generated: false, skippedReason: 'generator threw' }\n }\n}\n","/**\n * Neutral cross-product manifest (T1.4).\n *\n * Emitted by `theokit build` at `<cwd>/.theokit/services.json` (schemaVersion 1).\n * Consumed by EVERY deploy adapter (Vercel, Node, Cloudflare, future TheoCloud).\n *\n * Shape is INTENTIONALLY platform-neutral (ADR-0015 invariant + D5). No\n * `vercel`/`cloudflare`/`theoCloud` keys — platform-specific tuning lives in\n * `theo.config.ts` and is read by each adapter's own code, NOT serialized\n * into this manifest.\n *\n * Topological ordering enforced (dependsOn-resolved deps first) so adapters\n * that need boot order (docker-compose, K8s) can consume the array verbatim.\n */\n/* eslint-disable security/detect-non-literal-fs-filename --\n * Manifest read/write paths are derived from the trusted `cwd` plus a fixed\n * subpath (`.theokit/services.json`). No user input flows into the path.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport type { ServiceDefinition, ServicesConfig } from '../schema.js'\n\n/**\n * Plan v1.2 T2.2 — service shape enum, mirrored from\n * `theo-cloud/api/internal/source/services.schema.json`. Optional in\n * services.json v2; TheoCloud Go-side defaults to `server` when absent.\n */\n// `frontend` removed in simplify-deploy-theokit-only v2.0 (D5) — TheoCloud\n// serves no static frontends; service shape is server | worker only.\nexport type ManifestServiceType = 'server' | 'worker'\n\nexport interface ManifestServiceEntry {\n name: string\n runtime: 'python' | 'node'\n /** Plan v1.2 T2.2 — service shape. Omit to keep default `server`. */\n type?: ManifestServiceType\n port: number\n proxy: string\n dev: string\n build?: string\n start: string\n openapi?: string\n healthcheck: string\n cors: boolean\n env?: Record<string, string>\n dependsOn?: string[]\n passSetCookie: boolean\n}\n\n/**\n * Plan v1.2 T2.1 — services.json now ships in 2 shapes:\n *\n * v1 (deprecated, sunset theokit 0.6.0): { version: 1, services: [...] }\n * v2 (current): { version: 2, project: <name>, services: [...] }\n *\n * `buildManifest()` selects which version to emit based on whether a\n * project name is supplied. Consumers (TheoCloud Go-side validator)\n * accept both via the JSON Schema 7 `oneOf` block.\n */\nexport interface ServicesManifestV1 {\n version: 1\n services: ManifestServiceEntry[]\n}\n\nexport interface ServicesManifestV2 {\n version: 2\n project: string\n services: ManifestServiceEntry[]\n}\n\nexport type ServicesManifest = ServicesManifestV1 | ServicesManifestV2\n\nconst MANIFEST_RELATIVE_PATH = ['.theokit', 'services.json'] as const\n\n/**\n * Topological sort using Kahn's algorithm.\n * Returns service names in dependency order (deps before dependants).\n * Schema validation guarantees no cycles, no self-deps, no missing references —\n * but we still defensively handle missing deps as no-op.\n */\nfunction topoSort(services: ServicesConfig): string[] {\n const names = Object.keys(services)\n const indeg: Record<string, number> = Object.fromEntries(names.map((n) => [n, 0]))\n const adj: Record<string, string[]> = Object.fromEntries(names.map((n) => [n, []]))\n\n const cmp = (a: string, b: string) => a.localeCompare(b)\n\n for (const name of names) {\n const deps = services[name].dependsOn ?? []\n for (const dep of deps) {\n if (!(dep in indeg)) continue\n adj[dep].push(name)\n indeg[name] = (indeg[name] ?? 0) + 1\n }\n }\n\n // Sort initial roots for determinism\n const queue: string[] = names.filter((n) => indeg[n] === 0).sort(cmp)\n const result: string[] = []\n while (queue.length > 0) {\n const cur = queue.shift()\n if (cur === undefined) break\n result.push(cur)\n const adjacent = (adj[cur] ?? []).slice().sort(cmp)\n for (const next of adjacent) {\n indeg[next] = (indeg[next] ?? 0) - 1\n if (indeg[next] === 0) {\n queue.push(next)\n }\n }\n }\n return result.length === names.length ? result : names // fallback (should not hit; schema rejects cycles)\n}\n\nfunction definitionToEntry(name: string, def: ServiceDefinition): ManifestServiceEntry {\n const entry: ManifestServiceEntry = {\n name,\n runtime: def.runtime,\n port: def.port,\n proxy: def.proxy,\n dev: def.dev,\n start: def.start,\n healthcheck: def.healthcheck,\n cors: def.cors,\n passSetCookie: def.passSetCookie,\n }\n // Plan v1.2 T2.2 — only emit `type` when explicitly set so v1 fixtures\n // (no type field at all) stay byte-identical.\n if (def.type !== undefined) entry.type = def.type\n if (def.build !== undefined) entry.build = def.build\n if (def.openapi !== undefined) entry.openapi = def.openapi\n if (def.env !== undefined) entry.env = def.env\n if (def.dependsOn !== undefined) entry.dependsOn = [...def.dependsOn]\n return entry\n}\n\n/**\n * Build a services manifest. Plan v1.2 T2.1: pass a `project` name to emit\n * v2; omit it to emit v1 (deprecated, accepted by TheoCloud until 0.6.0\n * sunset).\n */\nexport function buildManifest(services: ServicesConfig, project?: string): ServicesManifest {\n const ordered = topoSort(services)\n const entries = ordered.map((name) => definitionToEntry(name, services[name]))\n if (project !== undefined && project.length > 0) {\n return { version: 2, project, services: entries }\n }\n return { version: 1, services: entries }\n}\n\nfunction manifestPath(cwd: string): string {\n return join(cwd, ...MANIFEST_RELATIVE_PATH)\n}\n\n/**\n * Write the manifest to `<cwd>/.theokit/services.json`.\n *\n * EC-6 fix: creates `.theokit/` recursively if absent (fresh project on first\n * `pnpm build` would otherwise hit ENOENT).\n */\nexport function writeManifest(cwd: string, manifest: ServicesManifest): void {\n const file = manifestPath(cwd)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(manifest, null, 2), 'utf-8')\n}\n\n/**\n * Read the manifest from `<cwd>/.theokit/services.json`.\n *\n * Returns null if the file does not exist (adapter's job to handle).\n * Throws with an actionable message if the file is malformed JSON.\n */\nexport function readManifest(cwd: string): ServicesManifest | null {\n const file = manifestPath(cwd)\n if (!existsSync(file)) return null\n const raw = readFileSync(file, 'utf-8')\n try {\n return JSON.parse(raw) as ServicesManifest\n } catch (err) {\n throw new Error(\n `failed to parse services.json at ${file}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n}\n","/**\n * Wave 2 — Polyglot Services Orchestration (T1.1).\n *\n * Declarative `services: {}` primitive for `theo.config.ts`. Each entry\n * declares an external sidecar process (Python FastAPI / Node Hono) that\n * boots alongside the TheoKit TS app. Empty `services: {}` is the default\n * and preserves Wave 1 behavior (no impact on TS-only apps).\n *\n * See ADRs 0012-0015 + plan: docs/plans/wave-2-polyglot-services-plan.md\n */\nimport { z } from 'zod'\n\n/** Wave 2 runtime kinds. Go/Rust/Java/Ruby/PHP archived; deferred per ADR-0012 invariant #2/#3. */\nconst ServiceRuntimeSchema = z.enum(['python', 'node'])\n\n/**\n * Service shape enum, mirrored from `services.schema.json`: server | worker.\n * Optional; emitters default to \"server\". `frontend` removed in\n * simplify-deploy-theokit-only v2.0 (D5) — TheoCloud serves no static frontends.\n */\nconst ServiceTypeSchema = z.enum(['server', 'worker'])\n\n/** EC-3 fix: names that conflict with generated docker-compose entries. */\nconst RESERVED_SERVICE_NAMES = ['web', 'caddy', 'postgres', 'redis'] as const\n\n/**\n * EC-12 fix: service names must be docker-compose-safe — lowercase,\n * start with a letter, contain only a-z 0-9 -. Reserved names rejected.\n */\nconst ServiceNameSchema = z\n .string()\n .min(1)\n .regex(\n /^[a-z][a-z0-9-]*$/,\n 'service name must be lowercase, start with letter, contain only a-z 0-9 -',\n )\n .refine((n) => !(RESERVED_SERVICE_NAMES as readonly string[]).includes(n), {\n message: `service name conflicts with reserved name (${RESERVED_SERVICE_NAMES.join('/')})`,\n })\n\n/**\n * Single service definition. All commands run from `services/<name>/` cwd.\n *\n * EC-4 fix: `proxy` regex requires NON-EMPTY path after `/` (the `+` quantifier)\n * to reject `/` which would catch-all and conflict with TheoKit's own routing.\n */\nconst ServiceDefinitionSchema = z.object({\n runtime: ServiceRuntimeSchema,\n type: ServiceTypeSchema.optional(),\n port: z.number().int().min(1).max(65535),\n proxy: z.string().regex(/^\\/[a-zA-Z0-9\\-_/]+$/, 'proxy must be a non-root path starting with /'),\n dev: z.string().min(1),\n build: z.string().optional(),\n start: z.string().min(1),\n openapi: z.url().optional(),\n healthcheck: z.string().regex(/^\\//, 'healthcheck must start with /').default('/health'),\n cors: z.boolean().default(false),\n env: z.record(z.string(), z.string()).optional(),\n dependsOn: z.array(z.string()).optional(),\n /**\n * EC-25 + ref doc §8: by default the proxy strips upstream Set-Cookie\n * to prevent the polyglot service from issuing cookies that conflict\n * with TheoKit's encrypted session. Set to true to opt-in.\n */\n passSetCookie: z.boolean().default(false),\n})\n\nexport type ServiceDefinition = z.infer<typeof ServiceDefinitionSchema>\nexport type ServicesConfig = Record<string, ServiceDefinition>\n\n/**\n * Topological-order helper used by `dependsOn` cycle detection refine.\n * Returns true iff `graph` is a DAG. Each node's deps must all reference\n * existing nodes; cycles return false.\n */\nfunction isDag(graph: Record<string, readonly string[]>): boolean {\n const WHITE = 0\n const GRAY = 1\n const BLACK = 2\n const colors: Record<string, 0 | 1 | 2> = {}\n for (const node of Object.keys(graph)) colors[node] = WHITE\n\n function visit(node: string): boolean {\n if (colors[node] === GRAY) return false // cycle\n if (colors[node] === BLACK) return true\n colors[node] = GRAY\n for (const dep of graph[node] ?? []) {\n if (!(dep in graph)) return false // missing reference\n if (!visit(dep)) return false\n }\n colors[node] = BLACK\n return true\n }\n\n for (const node of Object.keys(graph)) {\n if (colors[node] === WHITE && !visit(node)) return false\n }\n return true\n}\n\n/**\n * Full services config schema with cross-service refines.\n *\n * EC-1 fix: detect duplicate ports across services.\n * EC-13: empty `dependsOn: []` accepted as no-deps.\n * Self-dep, missing-ref, and cycles rejected via topological check.\n */\nexport const servicesConfigSchema = z\n .record(ServiceNameSchema, ServiceDefinitionSchema)\n .default({})\n // EC-1: duplicate port detection across services\n .refine(\n (s) => {\n const ports = Object.values(s).map((v) => v.port)\n return new Set(ports).size === ports.length\n },\n {\n message: 'duplicate port across services — each service must bind a unique port',\n },\n )\n // duplicate proxy prefix detection\n .refine(\n (s) => {\n const prefixes = Object.values(s).map((v) => v.proxy)\n return new Set(prefixes).size === prefixes.length\n },\n { message: 'duplicate proxy prefix across services' },\n )\n // dependsOn correctness (self-dep, missing reference, cycle)\n .refine(\n (s) => {\n // Build graph\n const graph: Record<string, readonly string[]> = {}\n for (const [name, def] of Object.entries(s)) {\n graph[name] = def.dependsOn ?? []\n }\n // Self-dep: a service mentioning itself is rejected as part of cycle detection\n for (const [name, deps] of Object.entries(graph)) {\n if (deps.includes(name)) return false\n }\n return isDag(graph)\n },\n {\n message: 'invalid dependsOn — must reference existing services, no self-deps, no cycles',\n },\n )\n\nexport type ServicesConfigInput = z.input<typeof servicesConfigSchema>\nexport type ServicesConfigOutput = z.output<typeof servicesConfigSchema>\n","/**\n * Dev orchestration entry point (T2.4).\n *\n * Spawns declared polyglot services, waits for all healthchecks to pass,\n * then signals readiness. The `theokit dev` CLI calls `orchestrateDev`\n * BEFORE starting Vite — Vite only boots after all services healthy.\n *\n * Empty `services: {}` → no-op (Wave 1 BC).\n *\n * Single composable entry that the CLI consumes. Spawn/healthcheck/log\n * primitives live in their own modules; this file ties them together.\n */\nimport type { ChildProcess, SpawnOptions } from 'node:child_process'\nimport { spawn } from 'node:child_process'\nimport { resolve } from 'node:path'\n\nimport type { ServiceDefinition, ServicesConfig } from '../schema.js'\n\nimport { pollHealthcheck } from './healthcheck-poller.js'\nimport { createLogMerger } from './log-merge.js'\nimport { buildSpawnEnv, installLifecycleHandlers } from './process-spawn-helpers.js'\n\ninterface SpawnedService {\n name: string\n port: number\n process: ChildProcess\n}\n\ninterface OrchestrateDevOptions {\n cwd: string\n services: ServicesConfig\n /** Defaults to `process.stdout.write.bind(process.stdout)`. */\n write?: (s: string) => void\n /** Test injection — replace child_process.spawn. */\n spawnFn?: typeof spawn\n /** Test injection — replace global fetch (for healthchecks). */\n customFetch?: typeof fetch\n /** Healthcheck timeout per service. Default 30_000. */\n healthcheckTimeoutMs?: number\n /** Whether to install parent lifecycle handlers (EC-7). Default true. */\n installSignalHandlers?: boolean\n}\n\ninterface OrchestrateDevResult {\n spawned: SpawnedService[]\n /** Whether ALL services passed healthcheck. */\n allHealthy: boolean\n /** Services that timed out (only populated when !allHealthy). */\n unhealthy: string[]\n /** Call to gracefully stop all spawned services. */\n stop: () => Promise<void>\n}\n\nfunction spawnService(\n name: string,\n service: ServiceDefinition,\n cwd: string,\n options: {\n spawnFn: typeof spawn\n onLog: (s: string, stream: 'stdout' | 'stderr', line: string) => void\n },\n): SpawnedService {\n const env = buildSpawnEnv(name, service, process.env)\n const spawnOpts: SpawnOptions = {\n cwd,\n shell: true,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n }\n const child = options.spawnFn(service.dev, spawnOpts)\n child.stdout?.setEncoding('utf-8')\n child.stderr?.setEncoding('utf-8')\n child.stdout?.on('data', (chunk: string) => {\n options.onLog(name, 'stdout', chunk)\n })\n child.stderr?.on('data', (chunk: string) => {\n options.onLog(name, 'stderr', chunk)\n })\n return { name, port: service.port, process: child }\n}\n\nasync function stopSpawned(s: SpawnedService): Promise<void> {\n return new Promise<void>((resolve) => {\n if (s.process.killed || s.process.exitCode !== null) {\n resolve()\n return\n }\n const timer = setTimeout(() => {\n s.process.kill('SIGKILL')\n resolve()\n }, 5_000)\n s.process.once('exit', () => {\n clearTimeout(timer)\n resolve()\n })\n s.process.kill('SIGTERM')\n })\n}\n\nexport async function orchestrateDev(\n options: OrchestrateDevOptions,\n): Promise<OrchestrateDevResult> {\n const entries = Object.entries(options.services)\n if (entries.length === 0) {\n return {\n spawned: [],\n allHealthy: true,\n unhealthy: [],\n stop: () => Promise.resolve(),\n }\n }\n\n const write =\n options.write ??\n ((s: string) => {\n process.stdout.write(s)\n })\n const spawnFn = options.spawnFn ?? spawn\n const merger = createLogMerger({ write })\n\n const spawned: SpawnedService[] = entries.map(([name, def]) =>\n spawnService(name, def, resolve(options.cwd, 'services', name), {\n spawnFn,\n onLog: merger.onLog,\n }),\n )\n\n const stop = async (): Promise<void> => {\n await Promise.all(spawned.map(stopSpawned))\n }\n\n if (options.installSignalHandlers !== false) {\n installLifecycleHandlers(process, stop)\n }\n\n // Parallel healthcheck\n const results = await Promise.all(\n entries.map(([name, def]) =>\n pollHealthcheck({\n url: `http://localhost:${String(def.port)}${def.healthcheck}`,\n timeoutMs: options.healthcheckTimeoutMs ?? 30_000,\n intervalMs: 500,\n customFetch: options.customFetch,\n }).then((r) => ({ name, result: r })),\n ),\n )\n\n const unhealthy = results.filter((r) => !r.result.healthy).map((r) => r.name)\n return {\n spawned,\n allHealthy: unhealthy.length === 0,\n unhealthy,\n stop,\n }\n}\n","/**\n * Healthcheck poller (T1.5).\n *\n * Polls `GET <url>` with backoff until 200 OR timeout. Used by `pnpm dev`\n * (T2.4) to gate readiness on declared services being healthy before\n * starting Vite.\n *\n * Invariants:\n * - Returns within `timeoutMs + intervalMs` (no hang)\n * - Never throws (returns `{ healthy: false }` on any error)\n * - `attempts >= 1` always\n * - Respects external AbortSignal (pre-aborted → immediate return, EC-18)\n */\n\ninterface HealthcheckOptions {\n /** Full URL to poll, e.g. 'http://localhost:8001/health' */\n url: string\n /** Total time budget in ms. Default 30_000. */\n timeoutMs?: number\n /** Sleep between attempts in ms. Default 500. */\n intervalMs?: number\n /** Optional external cancel. Aborted signal causes early return. */\n signal?: AbortSignal\n /** Test injection — replace global fetch. */\n customFetch?: typeof fetch\n}\n\ninterface HealthcheckResult {\n healthy: boolean\n attempts: number\n durationMs: number\n lastError?: string\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal?.aborted) {\n resolve()\n return\n }\n const t = setTimeout(() => {\n resolve()\n }, ms)\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(t)\n resolve()\n },\n { once: true },\n )\n })\n}\n\nexport async function pollHealthcheck(options: HealthcheckOptions): Promise<HealthcheckResult> {\n const timeoutMs = options.timeoutMs ?? 30_000\n const intervalMs = options.intervalMs ?? 500\n const f = options.customFetch ?? fetch\n const start = Date.now()\n let attempts = 0\n let lastError: string | undefined\n\n // EC-18: signal already aborted → return immediately with at least 1 attempt counted\n if (options.signal?.aborted) {\n return {\n healthy: false,\n attempts: 1,\n durationMs: Date.now() - start,\n lastError: 'aborted',\n }\n }\n\n // Infinite poll loop terminated by status === 200 OR timeout OR abort.\n for (;;) {\n if (options.signal?.aborted) {\n return {\n healthy: false,\n attempts: Math.max(attempts, 1),\n durationMs: Date.now() - start,\n lastError: 'aborted',\n }\n }\n attempts++\n try {\n const res = await f(options.url, { method: 'GET', signal: options.signal })\n if (res.status === 200) {\n return { healthy: true, attempts, durationMs: Date.now() - start }\n }\n lastError = `status ${res.status}`\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err)\n }\n\n if (Date.now() - start >= timeoutMs) {\n return {\n healthy: false,\n attempts,\n durationMs: Date.now() - start,\n lastError,\n }\n }\n await sleep(intervalMs, options.signal)\n }\n}\n","/**\n * Log merger (T2.3).\n *\n * Combines stdout/stderr from multiple polyglot services into TheoKit's\n * dev terminal, prefixed with `[service-name]`. Parses JSON-line logs\n * (ADR-0015 invariant #5) for pretty-printing; falls back to raw line on\n * non-JSON.\n *\n * Pure / synchronous. The Vite plugin (T2.1) wires this as the `onLog`\n * callback passed to spawnServices.\n */\n\ninterface LogEntry {\n level?: string\n message?: string\n timestamp?: string\n traceparent?: string\n service?: string\n}\n\ninterface LogMergerOptions {\n write: (s: string) => void\n}\n\ninterface LogMerger {\n onLog: (service: string, stream: 'stdout' | 'stderr', chunk: string) => void\n}\n\n/**\n * Try to parse a log line as JSON. Returns parsed object on success,\n * undefined on failure (will fall back to raw rendering).\n */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\nfunction tryParseJson(line: string): LogEntry | undefined {\n const trimmed = line.trim()\n if (!trimmed.startsWith('{')) return undefined\n try {\n const parsed: unknown = JSON.parse(trimmed)\n if (!isPlainObject(parsed)) return undefined\n const entry: LogEntry = {}\n if (typeof parsed.level === 'string') entry.level = parsed.level\n if (typeof parsed.message === 'string') entry.message = parsed.message\n if (typeof parsed.timestamp === 'string') entry.timestamp = parsed.timestamp\n if (typeof parsed.traceparent === 'string') entry.traceparent = parsed.traceparent\n if (typeof parsed.service === 'string') entry.service = parsed.service\n return entry\n } catch {\n return undefined\n }\n}\n\nfunction renderLine(\n service: string,\n stream: 'stdout' | 'stderr',\n line: string,\n write: (s: string) => void,\n): void {\n if (line.length === 0) return\n\n const prefix = `[${service}]`\n const streamMarker = stream === 'stderr' ? '!' : ' '\n\n const parsed = tryParseJson(line)\n if (parsed) {\n const level = parsed.level ?? 'info'\n const msg = parsed.message ?? line\n write(`${prefix}${streamMarker}${level.toUpperCase()} ${msg}\\n`)\n return\n }\n\n write(`${prefix}${streamMarker}${line}\\n`)\n}\n\nexport function createLogMerger(options: LogMergerOptions): LogMerger {\n return {\n onLog(service, stream, chunk) {\n // Multi-line chunks: split by \\n and render each\n const lines = chunk.split('\\n')\n for (const line of lines) {\n renderLine(service, stream, line, options.write)\n }\n },\n }\n}\n","/**\n * Pure helpers extracted from process-spawn.ts (T2.2) for unit testability.\n *\n * The full spawn orchestration (child_process + healthcheck poll + log\n * merge) lives in process-spawn.ts; this file holds the parts that are\n * safe to test without mocking node:child_process.\n *\n * EC-7: lifecycle handlers prevent orphan children on parent exit/signal.\n * EC-8: auto-injected env vars (THEOKIT_SERVICE_NAME, THEOKIT_SERVICE_PORT).\n */\nimport type { ServiceDefinition } from '../schema.js'\n\n/**\n * Build the env passed to `child_process.spawn`.\n *\n * Precedence (low → high):\n * 1. process.env (the parent's environment)\n * 2. auto-injected THEOKIT_SERVICE_NAME + THEOKIT_SERVICE_PORT\n * 3. service.env (user-provided overrides win)\n */\nexport function buildSpawnEnv(\n name: string,\n service: ServiceDefinition,\n parentEnv: Record<string, string | undefined>,\n): Record<string, string> {\n const env: Record<string, string> = {}\n for (const [k, v] of Object.entries(parentEnv)) {\n if (typeof v === 'string') env[k] = v\n }\n env.THEOKIT_SERVICE_NAME = name\n env.THEOKIT_SERVICE_PORT = String(service.port)\n if (service.env) {\n for (const [k, v] of Object.entries(service.env)) {\n env[k] = v\n }\n }\n return env\n}\n\n/**\n * Format a service name as a log prefix. Deterministic; ANSI codes added\n * by the log-merge layer (T2.3), not here.\n */\nexport function formatLogPrefix(name: string): string {\n return `[${name}]`\n}\n\n/**\n * Install parent-process lifecycle handlers (EC-7).\n *\n * - `exit`: best-effort SIGKILL of all children (synchronous; we cannot await here)\n * - `SIGINT`: graceful stopAllServices then exit 130\n * - `SIGTERM`: graceful stopAllServices then exit 143\n *\n * SIGKILL on the parent is uncatchable (kernel-level); orphan children\n * are documented as a known limit on force-close (EC-27 doc).\n */\nexport function installLifecycleHandlers(proc: NodeJS.Process, stopAll: () => Promise<void>): void {\n proc.on('exit', () => {\n // Best-effort sync notify; cannot await\n })\n proc.on('SIGINT', () => {\n void stopAll().finally(() => {\n proc.exit(130)\n })\n })\n proc.on('SIGTERM', () => {\n void stopAll().finally(() => {\n proc.exit(143)\n })\n })\n}\n","/**\n * TheoCloud adapter scaffolding (T3.5 — Wave 2 stub; full Wave 3).\n *\n * Wave 2 establishes the contract: the adapter CONSUMES `.theokit/services.json`\n * (manifest emitted by `theokit build`) and produces TheoCloud deployment\n * artifacts. The artifacts themselves (K8s manifests, Helm charts, etc.) are\n * Wave 3 deliverables — this module provides the read/validate gate so the\n * manifest format is locked NOW.\n *\n * Per ADR-0012 invariant #4, the same `.theokit/services.json` consumed by\n * the `node` adapter (docker-compose generator) is the same shape consumed\n * here. No platform-specific fields. The contract is global.\n */\nimport type { ServicesManifest } from './manifest.js'\n\ninterface TheoCloudAdapterArtifacts {\n /** Manifest schemaVersion the adapter consumed (v1 deprecated, v2 current). */\n manifestVersion: 1 | 2\n /** Service names that will be deployed. */\n services: string[]\n /** Wave 3 will populate K8s manifests here. */\n k8sManifests?: never\n /** Wave 3 will populate Helm values here. */\n helmValues?: never\n}\n\n/**\n * Wave 2 stub — validates the manifest is consumable. Throws if shape is\n * unexpected (forward-compat guard for Wave 3 development).\n *\n * Wave 3 will replace the body with real K8s manifest emission.\n */\nexport function prepareTheoCloudArtifacts(\n manifest: ServicesManifest | null,\n): TheoCloudAdapterArtifacts {\n if (manifest === null) {\n return { manifestVersion: 1, services: [] }\n }\n // v1 (deprecated, sunset 0.6.0) and v2 (current, adds `project`) share the\n // same `services[]` shape the adapter consumes — both are accepted. Any\n // other version is a forward-compat guard: bump handling here before the\n // builder emits a newer schema. (Regression: usetheodev/theokit#9 — the\n // builder emits v2 when a project name is supplied; rejecting v2 broke\n // every `theokit build --target theo-cloud`.)\n //\n // The type says `1 | 2`, but the manifest is read from disk (`readManifest`)\n // so a malformed/newer file can carry any number at runtime — read through a\n // numeric local so the guard stays reachable (narrowing `manifest.version`\n // directly would collapse the guard body to `never`).\n const version: number = manifest.version\n if (version !== 1 && version !== 2) {\n throw new Error(\n `TheoCloud adapter: unsupported manifest version ${String(version)}. ` +\n `Supported: schemaVersion 1 (deprecated) or 2. Update the adapter before bumping.`,\n )\n }\n return {\n manifestVersion: manifest.version,\n services: manifest.services.map((s) => s.name),\n }\n}\n","/**\n * Builds a Vite `server.proxy` configuration from TheoKit's\n * declarative `services: {}` config (T2.1).\n *\n * Pure function — no Vite imports, just data shaping. The Vite plugin\n * (services-dev.ts) calls this and merges the result into `vite.config.server.proxy`.\n *\n * Pattern follows Vite proxy reference (references/vite/packages/vite/src/node/server/middlewares/proxy.ts):\n * - Each path prefix becomes its own entry\n * - `changeOrigin: true` set by default (matches Vite's string-shortcut behavior)\n * - User-set entries take precedence on prefix collision (we never clobber)\n */\nimport type { ServicesConfig } from '../schema.js'\n\n/**\n * Shape compatible with Vite's `server.proxy` type. We intentionally don't\n * import Vite's `ProxyOptions` so this module stays platform-neutral and\n * unit-testable without Vite.\n */\ninterface ViteProxyEntry {\n target: string\n changeOrigin: boolean\n rewrite?: (path: string) => string\n}\n\ntype ViteProxyConfig = Record<string, string | ViteProxyEntry>\n\nexport function buildServicesProxyConfig(\n services: ServicesConfig,\n userProxy: ViteProxyConfig = {},\n): ViteProxyConfig {\n const fromServices: ViteProxyConfig = {}\n for (const [, def] of Object.entries(services)) {\n const prefix = def.proxy\n fromServices[prefix] = {\n target: `http://localhost:${String(def.port)}`,\n changeOrigin: true,\n // Strip the service's `proxy` prefix from the upstream URL so the\n // sidecar receives its own native paths (e.g. `/api/agent/echo` →\n // `/echo`). The sidecar declares routes against its own root path —\n // it doesn't know about the TheoKit proxy prefix.\n rewrite: (path: string) =>\n path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path,\n }\n }\n // User proxy wins on collision (we add services first, then spread user on top)\n return { ...fromServices, ...userProxy }\n}\n"],"mappings":"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","names":["resolve","resolve"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
scanServerActions,
|
|
9
9
|
scanServerRoutes,
|
|
10
10
|
scanWebSocketRoutes
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MNIQ5F3Y.js";
|
|
12
12
|
|
|
13
13
|
// src/server/scan/manifest.ts
|
|
14
14
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -86,4 +86,4 @@ export {
|
|
|
86
86
|
writeManifest,
|
|
87
87
|
loadManifest
|
|
88
88
|
};
|
|
89
|
-
//# sourceMappingURL=chunk-
|
|
89
|
+
//# sourceMappingURL=chunk-D3425YDV.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
generateCaddyfile,
|
|
5
5
|
generateComposeYaml,
|
|
6
6
|
readManifest
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CUYEA62B.js";
|
|
8
8
|
|
|
9
9
|
// src/adapters/node.ts
|
|
10
10
|
import { writeFileSync } from "fs";
|
|
@@ -62,4 +62,4 @@ var nodeAdapter = {
|
|
|
62
62
|
export {
|
|
63
63
|
nodeAdapter
|
|
64
64
|
};
|
|
65
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
//# sourceMappingURL=chunk-DGQO372T.js.map
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
// src/core/contracts/http-methods.ts
|
|
2
|
-
var HTTP_METHODS = [
|
|
3
|
-
"GET",
|
|
4
|
-
"POST",
|
|
5
|
-
"PUT",
|
|
6
|
-
"PATCH",
|
|
7
|
-
"DELETE",
|
|
8
|
-
"HEAD",
|
|
9
|
-
"OPTIONS"
|
|
10
|
-
];
|
|
2
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
11
3
|
var HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase());
|
|
12
4
|
|
|
13
5
|
export {
|
|
14
6
|
HTTP_METHODS,
|
|
15
7
|
HTTP_METHOD_LOWERCASE
|
|
16
8
|
};
|
|
17
|
-
//# sourceMappingURL=chunk-
|
|
9
|
+
//# sourceMappingURL=chunk-DV34VG3N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/contracts/http-methods.ts"],"mappings":";AAUO,IAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAIhF,IAAM,wBAAwB,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isZodLike
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XALU5LCU.js";
|
|
4
4
|
import {
|
|
5
5
|
validateCsrfRequest
|
|
6
6
|
} from "./chunk-OLGPYOLO.js";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
TheoError,
|
|
12
12
|
serverErrorToEnvelope
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IZ3TIL6K.js";
|
|
14
14
|
|
|
15
15
|
// src/config/load-env.ts
|
|
16
16
|
import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
|
|
@@ -454,4 +454,4 @@ export {
|
|
|
454
454
|
validateRouteInput,
|
|
455
455
|
executeWebRequest
|
|
456
456
|
};
|
|
457
|
-
//# sourceMappingURL=chunk-
|
|
457
|
+
//# sourceMappingURL=chunk-FO55VGMQ.js.map
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
makeThreadStartRun,
|
|
16
16
|
mintRunId,
|
|
17
17
|
parseAgentRequestBody
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VTJMND23.js";
|
|
19
19
|
import {
|
|
20
20
|
getApprovalRegistry
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-6GGWS7J3.js";
|
|
22
22
|
import {
|
|
23
23
|
validateCsrfRequest
|
|
24
24
|
} from "./chunk-FXZZS7YW.js";
|
|
@@ -61,10 +61,7 @@ function resolveTransformer(selector) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// src/server/agent/agent-card-handler.ts
|
|
64
|
-
import {
|
|
65
|
-
buildAgentCard,
|
|
66
|
-
compileAgentModule
|
|
67
|
-
} from "@theokit/agents";
|
|
64
|
+
import { buildAgentCard, compileAgentModule } from "@theokit/agents";
|
|
68
65
|
var WELL_KNOWN = /^\/\.well-known\/([^/]+)\/agent-card\.json$/;
|
|
69
66
|
function isAgentCardPath(urlPath) {
|
|
70
67
|
const match = WELL_KNOWN.exec(urlPath);
|
|
@@ -98,7 +95,12 @@ function handleAgentCard(mod, name, route, baseUrl) {
|
|
|
98
95
|
});
|
|
99
96
|
} catch (err) {
|
|
100
97
|
return new Response(
|
|
101
|
-
JSON.stringify({
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
error: {
|
|
100
|
+
code: "AGENT_CARD_FAILED",
|
|
101
|
+
message: err instanceof Error ? err.message : "card build failed"
|
|
102
|
+
}
|
|
103
|
+
}),
|
|
102
104
|
{ status: 500, headers: { "content-type": "application/json; charset=utf-8" } }
|
|
103
105
|
);
|
|
104
106
|
}
|
|
@@ -567,4 +569,4 @@ export {
|
|
|
567
569
|
serveAgentAuxRoute,
|
|
568
570
|
writeWebResponseToServerResponse
|
|
569
571
|
};
|
|
570
|
-
//# sourceMappingURL=chunk-
|
|
572
|
+
//# sourceMappingURL=chunk-GEHX6CAE.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/load-env.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Reads `.env` files from a CLI-controlled `cwd`. File names are a fixed\n * literal set. Build-time + CLI tool; no HTTP input.\n */\n/**\n * `loadEnv()` — auto-loads `.env` files into `process.env` for server code.\n *\n * Implements the Next.js `loadEnvConfig` algorithm\n * (`references/next.js/packages/next-env/index.ts:114-180`) with TheoKit\n * adaptations:\n *\n * - EC-1: 1MB file-size cap (anti-OOM, anti-supply-chain).\n * - EC-2: `_resetEnvCache()` test-only side-door.\n * - EC-8: `dotenv-expand` circular-ref safe (lib-provided).\n * - EC-13: log when `.env` is a symlink (transparency).\n * - D6: real `process.env` wins over `.env`-file values.\n * - NODE_ENV stash: `.env`-set NODE_ENV NEVER overrides the real\n * `process.env.NODE_ENV`. Stashed in `__THEOKIT_USER_NODE_ENV` instead.\n */\n\nimport { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport dotenv from 'dotenv'\nimport { expand, type DotenvPopulateInput } from 'dotenv-expand'\n\nimport type { LoadEnvOptions, LoadEnvResult } from './load-env-types.js'\n\n/** EC-1: 1MB cap. Real `.env`s are < 10KB. */\nconst MAX_ENV_FILE_BYTES = 1_048_576\n\nconst cache = new Map<string, LoadEnvResult>()\n\n/** Test-only side-door for EC-2 — clear the module-level cache between vitest test files. */\nexport function _resetEnvCache(): void {\n cache.clear()\n}\n\nfunction envFilesInPriorityOrder(mode: string): string[] {\n // Top of list = highest priority. Will be read in REVERSE so first-in\n // wins via overwrite during merge.\n const files = [`.env.${mode}.local`, `.env.local`, `.env.${mode}`, `.env`]\n // Test mode skips `.env.local` per dotenv convention (avoid leaking dev\n // secrets into the test suite).\n if (mode === 'test') {\n return files.filter((f) => f !== '.env.local')\n }\n return files\n}\n\nfunction readEnvFile(path: string): Record<string, string> | null {\n let stat\n try {\n stat = statSync(path)\n } catch {\n return null // ENOENT / EACCES → skip\n }\n\n // Allow files + FIFOs (1Password/SOPS pipe support).\n if (!stat.isFile() && !stat.isFIFO()) return null\n\n // EC-1 — anti-OOM cap.\n if (stat.size > MAX_ENV_FILE_BYTES) {\n console.warn(\n `[theokit] .env file at ${path} exceeds ${MAX_ENV_FILE_BYTES} bytes — skipping (likely a generated artifact, not a real env file)`,\n )\n return null\n }\n\n // EC-13 — symlink transparency. Don't refuse; just log.\n try {\n const lstat = lstatSync(path)\n if (lstat.isSymbolicLink()) {\n const real = realpathSync(path)\n // eslint-disable-next-line no-console -- intentional transparency log on a build-time tool\n console.info(`[theokit] .env at ${path} is a symlink → ${real}`)\n }\n } catch {\n // lstat failure is non-fatal — fall through to read.\n }\n\n try {\n const content = readFileSync(path, 'utf-8')\n return dotenv.parse(content)\n } catch {\n return null\n }\n}\n\n// eslint-disable-next-line complexity -- canonical env-load algorithm (priority → expand → guards → apply); inlining branches is clearer than extracting micro-helpers\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult {\n const cwd = options.cwd ?? process.cwd()\n const mode = options.mode ?? process.env.NODE_ENV ?? 'development'\n const cacheKey = `${cwd}:${mode}`\n\n if (!options.forceReload) {\n const cached = cache.get(cacheKey)\n if (cached) return cached\n }\n\n const fileNames = envFilesInPriorityOrder(mode)\n const filePaths = fileNames.map((f) => resolve(cwd, f))\n\n // Read in REVERSE — lower-priority files first, so higher-priority\n // (lower-index) overwrites during merge.\n const merged: Record<string, string> = {}\n const loadedFromFiles: string[] = []\n for (const path of [...filePaths].reverse()) {\n const parsed = readEnvFile(path)\n if (parsed === null) continue\n for (const [k, v] of Object.entries(parsed)) {\n merged[k] = v\n }\n loadedFromFiles.unshift(path) // keep priority order in result\n }\n\n // NODE_ENV stash — `.env`-set NODE_ENV never overrides real process.env.NODE_ENV.\n if (merged.NODE_ENV && process.env.__THEOKIT_USER_NODE_ENV === undefined) {\n process.env.__THEOKIT_USER_NODE_ENV = merged.NODE_ENV\n }\n delete merged.NODE_ENV // never propagate\n\n // EC-8 — dotenv-expand@11 stack-overflows on circular refs (A=${B}, B=${A}).\n // Wrap in try/catch — on overflow, leave the parsed map untouched (literal\n // values survive). Tested via test_loadEnv_circular_expand_no_loop_EC8.\n // Pass a CLONE of process.env (string-only, filtered) so expand can\n // reference real env without mutating it.\n const processEnvClone: DotenvPopulateInput = {}\n for (const [k, v] of Object.entries(process.env)) {\n if (typeof v === 'string') processEnvClone[k] = v\n }\n try {\n expand({ parsed: merged, processEnv: processEnvClone })\n } catch (err) {\n if (err instanceof RangeError) {\n console.warn(\n `[theokit] .env expansion overflow (likely circular reference like A=\\${B}, B=\\${A}). Leaving values as literals.`,\n )\n } else {\n throw err\n }\n }\n\n // Apply: D6 — real process.env wins over file values.\n const loaded: Record<string, string> = {}\n for (const [k, v] of Object.entries(merged)) {\n if (process.env[k] !== undefined) continue // real env wins\n process.env[k] = v\n loaded[k] = v\n }\n\n // Sentinel\n process.env.__THEOKIT_PROCESSED_ENV = 'true'\n\n const result: LoadEnvResult = { loaded, loadedFromFiles }\n cache.set(cacheKey, result)\n return result\n}\n"],"mappings":"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","names":[]}
|