theokit 0.48.13 → 0.48.14

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.
Files changed (105) hide show
  1. package/dist/{actions-virtual-module-YQXRSSMW.js → actions-virtual-module-E4PISKMB.js} +3 -3
  2. package/dist/{actions-virtual-module-LB4NGONI.js → actions-virtual-module-YZRZMHAX.js} +3 -3
  3. package/dist/{agent-7JJW3TPB.js → agent-XAQCUQNB.js} +3 -3
  4. package/dist/{app-typed-client-GHO3EWZK.js → app-typed-client-HLP26EYM.js} +3 -3
  5. package/dist/{app-typed-client-G3J5G7E5.js → app-typed-client-I4X2UJWW.js} +3 -3
  6. package/dist/{aws-lambda-CYNDGU5O.js → aws-lambda-KES4MFJV.js} +3 -3
  7. package/dist/{build-WU6AKKIB.js → build-F2Q5QTSC.js} +6 -6
  8. package/dist/{bun-33RD6K7O.js → bun-A5B2MJMO.js} +3 -3
  9. package/dist/{chunk-S433N3CT.js → chunk-AATSV4UV.js} +2 -2
  10. package/dist/{chunk-6E5WKMDS.js → chunk-AXKUA57G.js} +31 -4
  11. package/dist/chunk-AXKUA57G.js.map +1 -0
  12. package/dist/{chunk-E576KYYT.js → chunk-BHOTGS7N.js} +2 -2
  13. package/dist/{chunk-GVW5EKEU.js → chunk-CMYSHZFT.js} +2 -2
  14. package/dist/{chunk-6GGWS7J3.js → chunk-DQIPV7L5.js} +29 -2
  15. package/dist/chunk-DQIPV7L5.js.map +1 -0
  16. package/dist/{chunk-PXFXSFJL.js → chunk-DS5MCU2X.js} +5 -5
  17. package/dist/{chunk-GXEX4CBH.js → chunk-FTFN4MYN.js} +98 -32
  18. package/dist/chunk-FTFN4MYN.js.map +1 -0
  19. package/dist/{chunk-OJLL4YD6.js → chunk-KTKTHIUF.js} +5 -3
  20. package/dist/chunk-KTKTHIUF.js.map +1 -0
  21. package/dist/{chunk-GRI4XHBJ.js → chunk-NTWNO24W.js} +6 -4
  22. package/dist/{chunk-GRI4XHBJ.js.map → chunk-NTWNO24W.js.map} +1 -1
  23. package/dist/{chunk-YRBR424R.js → chunk-OOBYPCZC.js} +99 -35
  24. package/dist/chunk-OOBYPCZC.js.map +1 -0
  25. package/dist/{chunk-7VPQHEAJ.js → chunk-WSXNGF7X.js} +1 -1
  26. package/dist/{chunk-7VPQHEAJ.js.map → chunk-WSXNGF7X.js.map} +1 -1
  27. package/dist/{chunk-BIVCP6X5.js → chunk-WYHYOYRH.js} +6 -4
  28. package/dist/{chunk-BIVCP6X5.js.map → chunk-WYHYOYRH.js.map} +1 -1
  29. package/dist/{chunk-J5MXCGKA.js → chunk-YXG2SGQ3.js} +1 -1
  30. package/dist/{chunk-J5MXCGKA.js.map → chunk-YXG2SGQ3.js.map} +1 -1
  31. package/dist/cli/index.js +8 -8
  32. package/dist/{cloudflare-KYUWCEPL.js → cloudflare-IN2WB7FZ.js} +3 -3
  33. package/dist/{deno-deploy-CCTB7HGS.js → deno-deploy-LYXQEJ6A.js} +3 -3
  34. package/dist/{dev-MUWJAZRD.js → dev-CDFPXISP.js} +8 -8
  35. package/dist/{index-BEtEv6ZH.d.ts → index-D_PxPXgQ.d.ts} +2 -2
  36. package/dist/index.js +3 -3
  37. package/dist/{info-XW7HGUPH.js → info-QXZW722N.js} +4 -4
  38. package/dist/{internal-api-V5L65YSJ.js → internal-api-FBK2YXSI.js} +3 -3
  39. package/dist/{internal-api-WEOSTMSN.js → internal-api-ZCJWJ5EC.js} +3 -3
  40. package/dist/{load-config-VKD6LKWU.js → load-config-FIWNKOJO.js} +4 -4
  41. package/dist/{mcp-ULUFJNSZ.js → mcp-KTYP4ZON.js} +4 -4
  42. package/dist/{netlify-AFMDNKT4.js → netlify-DLEOCFAD.js} +3 -3
  43. package/dist/node-QXYSUWQ4.js +10 -0
  44. package/dist/{openapi-HZXR335W.js → openapi-OUXNB2F2.js} +4 -4
  45. package/dist/{plugin-runner-BFEcgtmJ.d.ts → plugin-runner-DHVr1cC5.d.ts} +1 -1
  46. package/dist/{plugin-types-D69-lJZ9.d.ts → plugin-types-4VN1GMqV.d.ts} +12 -0
  47. package/dist/registry-S7OMF3OS.js +23 -0
  48. package/dist/{routes-OLNQX7CT.js → routes-NJR4ISCT.js} +4 -4
  49. package/dist/{schema-ZO3OSWVG.js → schema-DNIT7JQX.js} +3 -3
  50. package/dist/server/agent/index.d.ts +20 -5
  51. package/dist/server/agent/index.js +1 -1
  52. package/dist/server/define/index.d.ts +9 -1
  53. package/dist/server/define/index.js +1 -1
  54. package/dist/server/http/index.d.ts +3 -3
  55. package/dist/server/index.d.ts +4 -4
  56. package/dist/server/index.js +2 -2
  57. package/dist/server/plugins/index.d.ts +3 -3
  58. package/dist/{services-typed-client-PSAGRPFF.js → services-typed-client-P4JERLBL.js} +2 -2
  59. package/dist/{start-KR2JJR7K.js → start-25OV2CAK.js} +198 -29
  60. package/dist/start-25OV2CAK.js.map +1 -0
  61. package/dist/{static-M2W4HUWS.js → static-RTO637XW.js} +3 -3
  62. package/dist/{theo-cloud-XAHWOUFK.js → theo-cloud-UZ3GINRU.js} +2 -2
  63. package/dist/{vercel-SRS2DH6O.js → vercel-6D6EZUVD.js} +3 -3
  64. package/dist/vite-plugin/index.js +3 -3
  65. package/dist/{vite-plugin-VPJFD4HK.js → vite-plugin-RJFKLF2U.js} +8 -8
  66. package/package.json +3 -3
  67. package/dist/chunk-6E5WKMDS.js.map +0 -1
  68. package/dist/chunk-6GGWS7J3.js.map +0 -1
  69. package/dist/chunk-GXEX4CBH.js.map +0 -1
  70. package/dist/chunk-OJLL4YD6.js.map +0 -1
  71. package/dist/chunk-YRBR424R.js.map +0 -1
  72. package/dist/node-ZJQEJSP3.js +0 -10
  73. package/dist/registry-GKR3EWRW.js +0 -23
  74. package/dist/start-KR2JJR7K.js.map +0 -1
  75. /package/dist/{actions-virtual-module-YQXRSSMW.js.map → actions-virtual-module-E4PISKMB.js.map} +0 -0
  76. /package/dist/{actions-virtual-module-LB4NGONI.js.map → actions-virtual-module-YZRZMHAX.js.map} +0 -0
  77. /package/dist/{agent-7JJW3TPB.js.map → agent-XAQCUQNB.js.map} +0 -0
  78. /package/dist/{app-typed-client-GHO3EWZK.js.map → app-typed-client-HLP26EYM.js.map} +0 -0
  79. /package/dist/{app-typed-client-G3J5G7E5.js.map → app-typed-client-I4X2UJWW.js.map} +0 -0
  80. /package/dist/{aws-lambda-CYNDGU5O.js.map → aws-lambda-KES4MFJV.js.map} +0 -0
  81. /package/dist/{build-WU6AKKIB.js.map → build-F2Q5QTSC.js.map} +0 -0
  82. /package/dist/{bun-33RD6K7O.js.map → bun-A5B2MJMO.js.map} +0 -0
  83. /package/dist/{chunk-S433N3CT.js.map → chunk-AATSV4UV.js.map} +0 -0
  84. /package/dist/{chunk-E576KYYT.js.map → chunk-BHOTGS7N.js.map} +0 -0
  85. /package/dist/{chunk-GVW5EKEU.js.map → chunk-CMYSHZFT.js.map} +0 -0
  86. /package/dist/{chunk-PXFXSFJL.js.map → chunk-DS5MCU2X.js.map} +0 -0
  87. /package/dist/{cloudflare-KYUWCEPL.js.map → cloudflare-IN2WB7FZ.js.map} +0 -0
  88. /package/dist/{deno-deploy-CCTB7HGS.js.map → deno-deploy-LYXQEJ6A.js.map} +0 -0
  89. /package/dist/{dev-MUWJAZRD.js.map → dev-CDFPXISP.js.map} +0 -0
  90. /package/dist/{info-XW7HGUPH.js.map → info-QXZW722N.js.map} +0 -0
  91. /package/dist/{internal-api-V5L65YSJ.js.map → internal-api-FBK2YXSI.js.map} +0 -0
  92. /package/dist/{internal-api-WEOSTMSN.js.map → internal-api-ZCJWJ5EC.js.map} +0 -0
  93. /package/dist/{load-config-VKD6LKWU.js.map → load-config-FIWNKOJO.js.map} +0 -0
  94. /package/dist/{mcp-ULUFJNSZ.js.map → mcp-KTYP4ZON.js.map} +0 -0
  95. /package/dist/{netlify-AFMDNKT4.js.map → netlify-DLEOCFAD.js.map} +0 -0
  96. /package/dist/{node-ZJQEJSP3.js.map → node-QXYSUWQ4.js.map} +0 -0
  97. /package/dist/{openapi-HZXR335W.js.map → openapi-OUXNB2F2.js.map} +0 -0
  98. /package/dist/{registry-GKR3EWRW.js.map → registry-S7OMF3OS.js.map} +0 -0
  99. /package/dist/{routes-OLNQX7CT.js.map → routes-NJR4ISCT.js.map} +0 -0
  100. /package/dist/{schema-ZO3OSWVG.js.map → schema-DNIT7JQX.js.map} +0 -0
  101. /package/dist/{services-typed-client-PSAGRPFF.js.map → services-typed-client-P4JERLBL.js.map} +0 -0
  102. /package/dist/{static-M2W4HUWS.js.map → static-RTO637XW.js.map} +0 -0
  103. /package/dist/{theo-cloud-XAHWOUFK.js.map → theo-cloud-UZ3GINRU.js.map} +0 -0
  104. /package/dist/{vercel-SRS2DH6O.js.map → vercel-6D6EZUVD.js.map} +0 -0
  105. /package/dist/{vite-plugin-VPJFD4HK.js.map → vite-plugin-RJFKLF2U.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/start/index.ts","../src/server/cron/cron-runtime-node.ts","../src/server/observability/trace-context-propagation.ts","../src/server/define/health-route.ts","../src/server/rate-limit/client-ip.ts","../src/server/rate-limit/rate-limit-per-route.ts","../src/cli/commands/start/assert-sdk-compatible.ts","../src/server/agent/sdk-compat.ts","../src/cli/commands/start/bootstrap-stages.ts","../src/cli/commands/start/cron-bootstrap.ts","../src/cli/commands/start/graceful-shutdown.ts","../src/cli/commands/start/manifest-loader.ts","../src/cli/commands/start/request-handler.ts","../src/cli/commands/start/handlers.ts","../src/server/http/static.ts","../src/cli/commands/start/ssr-setup.ts","../src/cli/commands/start/websocket-handler.ts"],"sourcesContent":["/**\n * theokit start — production server orchestration spine.\n *\n * T4.2 (architecture-cleanup, ADR-0017): stages extracted to sibling modules.\n * - start-bootstrap-stages.ts — config/registry/storage bootstrap + resolveSsrEntry\n * - start-manifest-loader.ts — manifest.json or scan fallback\n * - start-ssr-setup.ts — SSR entry-server + HTML template split\n * - start-handlers.ts — branch handlers (action/route/static/404)\n * - start-request-handler.ts — request lifecycle wiring\n * - start-websocket-handler.ts — WS upgrade (opt-in)\n * - start-graceful-shutdown.ts — SIGTERM/SIGINT drain\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join, resolve } from 'node:path'\n\nimport { loadConfig } from '../../../config/load-config.js'\nimport { loadEnv } from '../../../config/load-env.js'\nimport { createCronScheduler } from '../../../server/cron/cron-runtime-node.js'\nimport { defineHealthRoute } from '../../../server/define/health-route.js'\nimport { createPluginRunnerFromConfig } from '../../../server/plugins/load-plugins.js'\nimport { createRouteRateLimiter } from '../../../server/rate-limit/rate-limit-per-route.js'\nimport { createProductionLoader } from '../../../server/scan/module-loader.js'\nimport { resolveTransformer } from '../../../server/transformer.js'\nimport { preflightNodeAndBindings } from '../../preflight-node-version.js'\nimport { CONTROLLER_MANIFEST_FILE } from '../build/emit-controllers.js'\n\nimport { assertSdkCompatible } from './assert-sdk-compatible.js'\nimport {\n configureAgentRegistryFromConfig,\n configureStorageManagerFromConfig,\n} from './bootstrap-stages.js'\nimport { loadCronDefinitions } from './cron-bootstrap.js'\nimport { installGracefulShutdown } from './graceful-shutdown.js'\nimport type { RequestHandlerCtx } from './handlers.js'\nimport { loadRoutesAndActions } from './manifest-loader.js'\nimport { createRequestHandler } from './request-handler.js'\nimport { setupSsr } from './ssr-setup.js'\nimport { attachWebSocketHandler } from './websocket-handler.js'\n\n// Backwards-compat: external test fixtures may import resolveSsrEntry from here.\nexport { resolveSsrEntry } from './bootstrap-stages.js'\n\ninterface StartOptions {\n port?: number\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n const cwd = process.cwd()\n // Preflight (FIRST — BEFORE anything that touches native bindings).\n preflightNodeAndBindings(cwd)\n loadEnv({ cwd, mode: 'production' })\n const config = await loadConfig(cwd)\n\n // M48 — fail fast if the installed @theokit/sdk is present but incompatible (before serving any\n // request). Absent SDK stays silent here (an api-only app is valid); the request path guards it lazily.\n assertSdkCompatible()\n\n await configureAgentRegistryFromConfig(config.agents?.registry)\n await configureStorageManagerFromConfig(config.storage)\n\n const distDir = resolve(cwd, '.theokit')\n const clientDir = resolve(distDir, 'client')\n // theokit#123 — compiled controllers, when `theokit build` emitted any.\n //\n // Keyed on the MANIFEST rather than on the directory existing: a stale `dist/controllers` left by\n // an earlier build whose sources were since deleted would otherwise keep serving routes the app\n // no longer declares. `theokit build` writes the manifest only when it compiles something, and\n // `cleanOutDir` removes both, so the manifest is the authoritative \"this build has controllers\".\n const controllersDistDir = existsSync(resolve(distDir, CONTROLLER_MANIFEST_FILE))\n ? resolve(distDir, 'controllers')\n : undefined\n // #95 — honor config `serverDir` (default \"server\") in production start, matching dev.\n const serverDir = resolve(cwd, config.serverDir)\n\n if (!existsSync(clientDir)) {\n throw new Error('No build found. Run `theo build` first.')\n }\n\n const indexHtml = readFileSync(join(clientDir, 'index.html'), 'utf-8')\n const loadModule = createProductionLoader()\n const port = options.port ?? config.port\n const pluginRunner = await createPluginRunnerFromConfig(config.plugins)\n const transformer = resolveTransformer(config.serialization)\n\n const custom404Path = join(clientDir, '404.html')\n const custom500Path = join(clientDir, '500.html')\n const custom404Html = existsSync(custom404Path) ? readFileSync(custom404Path, 'utf-8') : null\n const custom500Html = existsSync(custom500Path) ? readFileSync(custom500Path, 'utf-8') : null\n\n const {\n routes: cachedRoutes,\n actions: cachedActions,\n wsRoutes: cachedWsRoutes,\n agents: cachedAgents,\n } = loadRoutesAndActions(distDir, serverDir, config.agentsDir)\n\n // `createRouteRateLimiter` accepts BOTH config shapes — it detects the legacy flat form and\n // treats it as the default bucket — so one call covers everything the schema allows.\n //\n // The previous code built a limiter only for the flat shape, on the belief that the per-route\n // variant was handled by an api-middleware path. No such path runs under `theokit start`, so a\n // per-route config produced `null` here and `handlers.ts` skipped limiting on every request. The\n // app booted clean, the config validated, and nothing was ever limited — see\n // usetheokit/theokit#321. A config that validates and then does nothing is worse than one that\n // fails loudly, because the operator has no reason to look.\n const rateLimiter = config.rateLimit ? createRouteRateLimiter(config.rateLimit) : null\n\n const ssr = await setupSsr({\n distDir,\n indexHtml,\n ssrConfigEnabled: config.ssr,\n ssrStreamingConfig: config.ssrStreaming,\n })\n\n const server = createServer(\n createRequestHandler({\n buildCtx: (req, res, requestId, startTime): RequestHandlerCtx => ({\n req,\n res,\n url: req.url ?? '/',\n requestId,\n startTime,\n clientDir,\n custom404Html,\n cachedRoutes,\n cachedActions,\n cachedAgents,\n loadModule,\n serverDir,\n projectRoot: cwd,\n controllersDistDir,\n pluginRunner,\n transformer,\n csrfMode: config.security?.csrf ?? 'strict',\n disallowed: config.security?.disallowed,\n rateLimiter,\n }),\n securityHeadersConfig: config.security?.headers ?? {},\n ssrRender: ssr.render,\n ssrRenderStreaming: ssr.renderStreaming,\n ssrStreamingEnabled: ssr.streamingEnabled,\n htmlHead: ssr.htmlHead,\n htmlTail: ssr.htmlTail,\n indexHtml,\n custom500Html,\n // M7-2: serve a built-in liveness route on the Node listener. Readiness\n // probe wiring from theo.config.ts is a documented follow-up (see the M7\n // implementation summary § Scope note).\n reservedRoutes: { health: defineHealthRoute() },\n }),\n )\n\n await attachWebSocketHandler(server, cachedWsRoutes, loadModule)\n\n // theokit#324: `theokit build --target node` announces an in-process\n // scheduler here. Drive it, or the announcement is false.\n const cronDefinitions = await loadCronDefinitions(resolve(distDir, 'crons.json'), cwd, loadModule)\n if (cronDefinitions.length > 0) {\n createCronScheduler(cronDefinitions).start()\n }\n\n server.listen(port, () => {\n console.log(`\\n Theo production server`)\n console.log(` → http://localhost:${String(port)}\\n`)\n if (cronDefinitions.length > 0) {\n console.log(` Crons: ${String(cronDefinitions.length)} scheduled in-process\\n`)\n }\n })\n\n installGracefulShutdown(server)\n}\n","import { CronExpressionParser } from 'cron-parser'\n\nimport { generateNewTraceContext } from '../observability/trace-context-propagation.js'\n\nimport type { CronContext, CronDefinition } from './cron-types.js'\n\n/**\n * In-memory cron scheduler for `theokit dev` (T1.4).\n *\n * Algorithm:\n * - For each cron, compute `nextFireAt = cron-parser.next()`.\n * - Schedule a `setTimeout(handler, nextFireAt - now)`.\n * - After handler invocation (sync return or Promise scheduled), recompute\n * next fire from CURRENT time (drift-free vs scheduled time).\n *\n * Per-cron isolation (EC-109):\n * - Each cron's handler invocation is fire-and-forget (`void` scheduled).\n * A hanging handler does NOT block the scheduler loop nor other crons.\n * - `concurrency: 'forbid'` (default) tracks an in-flight flag per-cron;\n * subsequent ticks skip + warn while the in-flight flag is set.\n * - `concurrency: 'allow'` runs handlers concurrently — caller's responsibility.\n *\n * Production deploys use platform-native triggers (T1.5 adapter translators);\n * this scheduler exists only for local dev iteration.\n */\n\nexport interface CronScheduler {\n start(): void\n stop(): void\n}\n\ninterface CronJobState {\n readonly def: CronDefinition\n inFlight: boolean\n timer: NodeJS.Timeout | null\n abortController: AbortController | null\n}\n\nexport function createCronScheduler(definitions: readonly CronDefinition[]): CronScheduler {\n const states: CronJobState[] = definitions.map((def) => ({\n def,\n inFlight: false,\n timer: null,\n abortController: null,\n }))\n\n let started = false\n\n const fireAndReschedule = (state: CronJobState, scheduledAt: Date): void => {\n state.timer = null\n\n if (state.inFlight && state.def.concurrency === 'forbid') {\n console.warn(\n `[theokit:cron] \"${state.def.name}\" skipped tick at ${scheduledAt.toISOString()} ` +\n '— previous handler still running (concurrency: forbid).',\n )\n scheduleNext(state)\n return\n }\n\n state.inFlight = true\n state.abortController = new AbortController()\n const traceCtx = generateNewTraceContext()\n const ctx: CronContext = {\n traceId: traceCtx.trace_id,\n scheduledAt,\n signal: state.abortController.signal,\n }\n\n // Fire-and-forget: EC-109 — never await here so a hanging handler\n // can't block the scheduler loop or other crons.\n void Promise.resolve()\n .then(() => state.def.handler(ctx))\n .catch((err: unknown) => {\n console.error(\n `[theokit:cron] \"${state.def.name}\" handler error:`,\n err instanceof Error ? err.message : err,\n )\n })\n .finally(() => {\n state.inFlight = false\n })\n\n scheduleNext(state)\n }\n\n const scheduleNext = (state: CronJobState): void => {\n if (!started) return\n const interval = CronExpressionParser.parse(state.def.schedule, {\n tz: 'UTC',\n currentDate: new Date(),\n })\n const next = interval.next().toDate()\n const delayMs = Math.max(0, next.getTime() - Date.now())\n state.timer = setTimeout(() => {\n fireAndReschedule(state, next)\n }, delayMs)\n // setTimeout returns a Timeout object; in Node, .unref() is available but\n // we DO want this to keep the event loop alive in dev — explicit no-unref.\n }\n\n return {\n start(): void {\n if (started) return\n started = true\n for (const state of states) {\n scheduleNext(state)\n }\n },\n stop(): void {\n started = false\n for (const state of states) {\n if (state.timer) {\n clearTimeout(state.timer)\n state.timer = null\n }\n state.abortController?.abort()\n state.abortController = null\n }\n },\n }\n}\n","// T5a.1a — Web Standards migration (leaf-first slice). Web Crypto's\n// `crypto.getRandomValues()` is available on globalThis in every supported\n// runtime per ADR-0028 (Node 22+, CF Workers, Bun, Deno, browsers). No\n// node:crypto import needed — Web Crypto returns a typed array filled with\n// CSPRNG bytes, which we hex-encode locally instead of relying on Buffer.\n\n/**\n * W3C Trace Context propagation helpers for non-HTTP carriers (job\n * leases, webhook reply headers, agent SSE frames). Works against any\n * `Headers`-shaped carrier — Web Standards only.\n *\n * The HTTP-side extractor `extractTraceId` in `../http/trace-context.ts`\n * is request-scoped and returns only the trace_id string. This module\n * exposes the full `TraceContext` shape (trace_id + span_id + flags)\n * because downstream jobs/webhooks need to propagate a NEW span_id\n * (child span) while keeping the same trace_id.\n *\n * Format spec (W3C Trace Context Level 2):\n * traceparent: version-trace_id-parent_id-trace_flags\n * - version = '00' (current)\n * - trace_id = 32 hex chars (128-bit)\n * - parent_id = 16 hex chars (64-bit) — \"span_id\" of the producer\n * - trace_flags = 2 hex chars (sampled bit)\n *\n * @see https://www.w3.org/TR/trace-context/\n */\n\nexport interface TraceContext {\n /** 32 hex chars. NEVER the reserved all-zeros value. */\n readonly trace_id: string\n /** 16 hex chars. NEVER the reserved all-zeros value. */\n readonly span_id: string\n /** 2 hex chars. Sampled bit + reserved. */\n readonly flags: string\n}\n\nconst TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/\nconst ALL_ZEROS_TRACE = '00000000000000000000000000000000'\nconst ALL_ZEROS_SPAN = '0000000000000000'\n\n/**\n * Extract a TraceContext from a Web Headers object. Returns `null` for:\n * - missing header\n * - malformed (not matching the version-trace-span-flags regex)\n * - reserved all-zeros trace_id (W3C-invalid)\n * - reserved all-zeros span_id (W3C-invalid)\n *\n * NEVER throws — defensive read.\n */\nexport function extractTraceContext(headers: Headers): TraceContext | null {\n const value = headers.get('traceparent')\n if (!value) return null\n const m = TRACEPARENT_RE.exec(value)\n if (!m) return null\n const trace_id = m[1]\n const span_id = m[2]\n const flags = m[3]\n if (trace_id === ALL_ZEROS_TRACE) return null\n if (span_id === ALL_ZEROS_SPAN) return null\n return { trace_id, span_id, flags }\n}\n\n/**\n * Write a canonical traceparent into a Web Headers object. Always\n * succeeds (defensive write — the caller is responsible for passing\n * a valid TraceContext; passing malformed input is a programming bug).\n */\nexport function injectTraceContext(headers: Headers, ctx: TraceContext): void {\n headers.set('traceparent', `00-${ctx.trace_id}-${ctx.span_id}-${ctx.flags}`)\n}\n\n/**\n * Generate a fresh TraceContext (new trace_id + span_id, flags='01' for\n * sampled). Used when no upstream traceparent exists — e.g., when a\n * cron fires or a webhook arrives without an upstream tracer.\n */\nexport function generateNewTraceContext(): TraceContext {\n return {\n trace_id: randomHex(16), // 16 bytes = 32 hex chars\n span_id: randomHex(8), // 8 bytes = 16 hex chars\n flags: '01',\n }\n}\n\nfunction randomHex(bytes: number): string {\n // crypto.getRandomValues is overwhelmingly unlikely to produce all-zeros,\n // but we guard anyway — the W3C spec rejects all-zeros and tests assert\n // this. Web Crypto API returns a Uint8Array; we hex-encode without Buffer\n // to stay runtime-agnostic (CF Workers / Bun / Deno have no Buffer global).\n for (;;) {\n const buf = new Uint8Array(bytes)\n globalThis.crypto.getRandomValues(buf)\n let hex = ''\n for (const b of buf) hex += b.toString(16).padStart(2, '0')\n if (!/^0+$/.test(hex)) return hex\n }\n}\n","/**\n * M7-2 — health/ready reserved routes for the convention/filesystem-route\n * server. Liveness (`/__theo/health`, always 200) and readiness\n * (`/__theo/ready`, 200/503 from a probe) are registered on a reserved\n * namespace BEFORE the user-route catch-all + 404 branch — mirroring nitro's\n * `/_nitro/*` reserved-namespace pattern (knowledge-base/references/nitro/src/\n * runtime/internal/routes/dev-tasks.ts). Liveness and readiness are kept\n * separate by design: liveness says \"the process is up\", readiness says\n * \"dependencies are up\".\n *\n * @public\n */\n\n/** Reserved path for the liveness endpoint. */\nexport const HEALTH_PATH = '/__theo/health'\n/** Reserved path for the readiness endpoint. */\nexport const READY_PATH = '/__theo/ready'\n\n/** Config produced by {@link defineHealthRoute}. */\nexport interface HealthRouteConfig {\n readonly kind: 'health'\n /** Returns the liveness body (auto-serialized to JSON). Default: `{ status: 'ok' }`. */\n readonly handler: () => unknown\n}\n\n/** Config produced by {@link defineReadyRoute}. */\nexport interface ReadyRouteConfig {\n readonly kind: 'ready'\n /** Resolves `true` when dependencies are ready. A throw/reject is treated as not-ready (503). */\n readonly probe: () => boolean | Promise<boolean>\n}\n\n/** The reserved-route registry consulted by {@link serveReservedRoute}. */\nexport interface ReservedRoutes {\n readonly health?: HealthRouteConfig\n readonly ready?: ReadyRouteConfig\n}\n\n/** A reserved-route response: an HTTP status + a JSON-serializable body. */\nexport interface ReservedResponse {\n readonly status: number\n readonly body: unknown\n}\n\n/**\n * Define the liveness route. The default handler returns `{ status: 'ok' }`.\n * Liveness is always 200 — it asserts the process is up, not that dependencies are.\n */\nexport function defineHealthRoute(\n handler: () => unknown = () => ({ status: 'ok' }),\n): HealthRouteConfig {\n return { kind: 'health', handler }\n}\n\n/**\n * Define the readiness route. The `probe` decides 200 (ready) vs 503 (not-ready).\n * A probe that throws/rejects is treated as not-ready (503) — never a 500 crash.\n */\nexport function defineReadyRoute(probe: () => boolean | Promise<boolean>): ReadyRouteConfig {\n return { kind: 'ready', probe }\n}\n\n/**\n * Pure dispatcher: map a request pathname to a reserved-route response, or\n * `null` when the path is not reserved (so the user catch-all + 404 take over).\n * Liveness defaults to 200 `{status:'ok'}`; readiness without a probe is\n * trivially ready (200). Registered BEFORE the user catch-all by the caller.\n */\nexport async function serveReservedRoute(\n pathname: string,\n routes: ReservedRoutes = {},\n): Promise<ReservedResponse | null> {\n if (pathname === HEALTH_PATH) {\n const handler = routes.health?.handler ?? (() => ({ status: 'ok' }))\n return { status: 200, body: handler() }\n }\n if (pathname === READY_PATH) {\n const probe = routes.ready?.probe\n if (probe === undefined) {\n return { status: 200, body: { status: 'ready' } }\n }\n try {\n const ready = await probe()\n return ready\n ? { status: 200, body: { status: 'ready' } }\n : { status: 503, body: { status: 'not-ready' } }\n } catch (err) {\n // 503 (the dependency is not ready) but NEVER swallow the cause — a\n // readiness probe failing silently is invisible to ops (project rule:\n // \"NEVER swallow exceptions\"). Log the cause, keep the 503 contract.\n console.error('[theo] readiness probe threw — treating as not-ready', err)\n return { status: 503, body: { status: 'not-ready' } }\n }\n }\n return null\n}\n","import type { IncomingMessage } from 'node:http'\n\n/**\n * Resolves the address a rate-limit bucket should be keyed on.\n *\n * ## Why this is not just `req.socket.remoteAddress`\n *\n * Behind any reverse proxy — Caddy, nginx, a load balancer, an ingress controller — the socket\n * address is the PROXY's, identical for every visitor on the internet. Keying buckets on it gives\n * the whole world one shared budget, so the first few requests each minute exhaust it and everyone\n * else is refused. That is worse than no limiting: a limit meant to stop one abusive client becomes\n * a denial of service any single client can trigger for everybody.\n *\n * ## Why it is opt-in\n *\n * `x-forwarded-for` is a request header, and a request header is whatever the client typed. Reading\n * it without being told to would let anyone bypass the limiter by rotating a forged value — a\n * one-line `curl -H`. So the default is to trust nothing and use the socket.\n *\n * ## Why the RIGHTMOST entry\n *\n * Each proxy APPENDS the address that connected to it. A client that sends `x-forwarded-for: 1.2.3.4`\n * through one proxy produces `1.2.3.4, <real client>` — the forgery lands on the left, and the entry\n * the trusted proxy wrote is last. Counting from the right therefore skips exactly the hops the\n * operator vouched for, and everything a client can influence stays to the left of where we look.\n *\n * This mirrors the `'trusted-proxy'` policy in `adapters/web-shim.ts`, which documents the same rule\n * for the Web-Request runtimes.\n */\n\n/**\n * How many proxies sit in front of the app.\n *\n * `false` (the default) trusts nothing and uses the socket address. `true` means one trusted proxy.\n * A number names the hop count for a longer chain — a CDN in front of your own proxy is `2`.\n */\nexport type TrustProxy = boolean | number\n\n/** `true` is shorthand for a single proxy; `false` for none. A number passes through. */\nfunction hopCount(trustProxy: TrustProxy): number {\n if (trustProxy === true) return 1\n if (trustProxy === false) return 0\n return trustProxy\n}\n\n/** Reads a header that Node may hand back as an array. */\nfunction headerValue(req: IncomingMessage, name: string): string | undefined {\n const raw = req.headers[name]\n if (Array.isArray(raw)) return raw[raw.length - 1]\n return raw\n}\n\nexport function resolveClientIp(req: IncomingMessage, trustProxy: TrustProxy = false): string {\n // `req.socket` is typed as always-present in Node typings; the optional chain keeps the fallback\n // reachable for test doubles built as plain object literals.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for test doubles\n const socketAddress = req.socket?.remoteAddress ?? 'unknown'\n\n const hops = hopCount(trustProxy)\n if (hops < 1) return socketAddress\n\n const forwarded = headerValue(req, 'x-forwarded-for')\n if (forwarded) {\n const entries = forwarded\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n // Count in from the right by the number of hops the operator vouched for. A chain shorter than\n // configured means a request did NOT come through the expected proxies — a direct hit on the\n // app's port, say — so we fall back to the socket rather than reading an entry the client could\n // have written.\n const index = entries.length - hops\n if (index >= 0 && index < entries.length) return entries[index]\n }\n\n // `x-real-ip` carries a single address and is written by the proxy, not appended to, so there is\n // no hop arithmetic to do. Only consulted when a proxy is trusted at all.\n const realIp = headerValue(req, 'x-real-ip')\n if (realIp) return realIp.trim()\n\n return socketAddress\n}\n","// T5a.1d — Web Crypto migration. The last node:crypto consumer in server/.\n// `createHash('sha256')` is sync but Web Crypto's `subtle.digest('SHA-256', ...)`\n// is async. This propagates through hashFragment → deriveKey → the factory's\n// returned checker, which is why the checker is async. `theokit start` awaits it\n// (usetheokit/theokit#321 — before that fix this factory had no production consumer at all, and a\n// per-route config silently disabled rate limiting outright). IncomingMessage stays as a type-only\n// import (TS-erased; runtime-clean).\nimport type { IncomingMessage } from 'node:http'\n\nimport { parseCookieHeader } from '../http/cookies.js'\n\nimport { resolveClientIp, type TrustProxy } from './client-ip.js'\nimport { InMemoryStore, type RateLimitStore } from './rate-limit-store.js'\nimport type { RateLimitConfig, RateLimitResult } from './rate-limit.js'\n\n/**\n * T2.2 — Per-route + per-user rate limiting.\n *\n * Layered on top of `rate-limit-store.ts`. The route map allows\n * declarative policies (\"strict /api/login, loose everything else\")\n * driven by config, not handler-decorated. `keyBy` selects what\n * identifier the limiter buckets on.\n *\n * ADR D2: per-route via path matching, NOT per-handler decorator.\n * Operators can tune policies without touching route definitions.\n */\n\nexport type KeyByMode = 'ip' | 'session' | 'user' | ((req: IncomingMessage) => string)\n\nexport interface RouteRateLimitConfig {\n /** Fallback config used when no per-route entry matches. */\n default?: RateLimitConfig\n /** Map of path pattern → config. Exact-string keys (RegExp via API). */\n routes?: Record<string, RateLimitConfig>\n /** Same as `routes` but each entry is a [pattern, config] tuple, RegExp allowed. */\n routePatterns?: readonly [string | RegExp, RateLimitConfig][]\n /** Bucket identifier strategy. Default 'ip'. */\n keyBy?: KeyByMode\n /** Cookie name used by keyBy='session'. Defaults to 'theo_session'. */\n cookieName?: string\n /** Optional shared store (for multi-route correlation). Default per-limiter InMemoryStore. */\n store?: RateLimitStore\n /**\n * How many reverse proxies sit in front of the app, for `keyBy: 'ip'`. Default `false` — trust\n * none and key on the socket address.\n *\n * Set this whenever the app is behind Caddy, nginx, a load balancer or an ingress controller:\n * without it every visitor keys on the proxy's address and shares one bucket, so a handful of\n * requests exhausts the budget for the entire internet. With it, the client address is read from\n * `x-forwarded-for` counting in from the right, past exactly the hops declared here.\n *\n * Leaving it off by default is deliberate — `x-forwarded-for` is client-writable, and honouring it\n * uninvited turns the limiter into a one-header bypass. See `client-ip.ts`.\n */\n trustProxy?: TrustProxy\n}\n\n/**\n * Normalize a path for matching: strip query string, drop trailing slash\n * unless root. EC-5: `/api/login` and `/api/login/` collapse to the same\n * canonical form so attackers can't bypass strict limits.\n */\nfunction normalizePath(input: string): string {\n const noQuery = input.split('?')[0]\n if (noQuery.length > 1 && noQuery.endsWith('/')) return noQuery.slice(0, -1)\n return noQuery\n}\n\n/**\n * Test whether `path` matches `pattern`. String patterns are compared\n * after trailing-slash normalization (EC-5). RegExp uses `.test` after\n * resetting `lastIndex` (defensive against `/g` flag).\n */\nexport function matchRoutePattern(path: string, pattern: string | RegExp): boolean {\n const canonical = normalizePath(path)\n if (typeof pattern === 'string') {\n return canonical === normalizePath(pattern)\n }\n pattern.lastIndex = 0\n return pattern.test(canonical)\n}\n\n/**\n * Hash a string with SHA-256 and return the first 16 base64url chars.\n * Used by `keyBy='session'` so the raw cookie value never lands in a\n * rate-limit key (which may flow into audit logs).\n *\n * T5a.1d — async via Web Crypto subtle.digest (no node:crypto). The\n * base64url encoding is done manually because btoa+url-safe transform is\n * available everywhere but `digest('base64url')` is Node-only.\n */\nasync function hashFragment(input: string): Promise<string> {\n const buf = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))\n const bytes = new Uint8Array(buf)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n // eslint-disable-next-line sonarjs/slow-regex -- input is fixed-length 44 chars (SHA-256 base64), trailing '=' padding ≤ 2 chars, no ReDoS surface\n const b64url = btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n return b64url.slice(0, 16)\n}\n\n// T3.2 DRY consolidation: cookie parsing moved to ../http/cookies.ts.\n// The canonical `parseCookieHeader` returns a Map for O(1) lookup; this\n// wrapper preserves the original `readCookie(req, name)` signature so the\n// rest of the file stays untouched.\nfunction readCookie(req: IncomingMessage, name: string): string | undefined {\n return parseCookieHeader(req.headers.cookie ?? undefined).get(name)\n}\n\n/**\n * Build the rate-limit bucket key for the request based on `keyBy`.\n *\n * EC-6: session mode reads the configured `cookieName`. With the wrong\n * cookie name (e.g., default 'theo_session' but app uses 'app_session'),\n * we fall back to IP so anonymous users still get rate-limited rather\n * than sharing an empty bucket.\n */\nexport async function deriveKey(\n req: IncomingMessage,\n keyBy: KeyByMode,\n cookieName: string,\n trustProxy: TrustProxy = false,\n): Promise<string> {\n if (typeof keyBy === 'function') return keyBy(req)\n // Behind a proxy the socket address is the proxy's, the same for every visitor. `resolveClientIp`\n // returns it unchanged unless the operator declared how many proxies to trust — see its comment\n // for why reading `x-forwarded-for` uninvited would hand out a one-header bypass.\n const ip = resolveClientIp(req, trustProxy)\n switch (keyBy) {\n case 'session': {\n const cookie = readCookie(req, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n const userId = (req as unknown as { user?: { id?: string } }).user?.id\n return userId ? `user:${userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * Per-route rate limiter factory. Returns a sync checker compatible with\n * the existing api-middleware shape.\n *\n * Backwards-compatibility (ADR D2): a flat `{ windowMs, max }` config is\n * accepted and treated as `default` (no per-route variants).\n */\nexport function createRouteRateLimiter(config: RouteRateLimitConfig | RateLimitConfig) {\n // Detect legacy flat shape\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n // CR-005: validate store shape ONCE at construction. The previous\n // implementation ran `instanceof InMemoryStore` on every request and\n // threw at request-time if a non-InMemoryStore was passed — which\n // turned a clear config error into a runtime 500 on the first request.\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiter: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the sync facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = cfg.keyBy ?? 'ip'\n const cookieName = cfg.cookieName ?? 'theo_session'\n const trustProxy = cfg.trustProxy ?? false\n\n // Build a pre-compiled list of (pattern, config) tuples for matching.\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimit(req: IncomingMessage): Promise<RateLimitResult> {\n const url = req.url ?? ''\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n // No route match + no default → not limited.\n return { limited: false, headers: {} }\n }\n\n // Bucket key includes normalized path so /api/login and /api/login/\n // collapse to the same bucket (EC-5).\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKey(req, keyBy, cookieName, trustProxy)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter inputs context.\n *\n * Web `Request` has no equivalent of `req.socket.remoteAddress` (Node\n * runtime concept) or `req.user` (set by upstream middleware). The\n * Web-shaped rate-limiter requires the caller to pass these explicitly:\n *\n * - `clientIp` — resolved per-runtime (Node: `socket.remoteAddress`;\n * CF Workers: `request.headers.get('cf-connecting-ip')`; Vercel:\n * `x-forwarded-for` first hop; Bun/Deno: adapter-specific).\n * - `userId` — resolved by auth middleware (Phase D slice 3/3 ships\n * the Web-shaped session helper).\n *\n * Defaults: `clientIp = 'unknown'`, `userId = undefined` (matches the\n * IncomingMessage path's fallback semantics).\n */\nexport interface DeriveKeyRequestContext {\n clientIp?: string\n userId?: string\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards-shaped key derivation.\n *\n * Mirror of `deriveKey(req: IncomingMessage, keyBy, cookieName)` for the\n * Web `Request` shape. Same `'ip' | 'session' | 'user'` enum cases (the\n * `function` callback case is IncomingMessage-only because the existing\n * `KeyByMode` callback type is Node-shaped; Web callers use the enum\n * cases or call a future `KeyByModeWeb` shape — out of T5a.2 Phase D scope).\n *\n * Uses `getCookieFromRequest` (Phase B slice 6/6) for session-mode cookie\n * lookup. Cookie parsing has the same CR-009 percent-encoding safety.\n */\nexport async function deriveKeyFromRequest(\n request: Request,\n keyBy: Exclude<KeyByMode, (req: IncomingMessage) => string>,\n cookieName: string,\n ctx: DeriveKeyRequestContext = {},\n): Promise<string> {\n const ip = ctx.clientIp ?? 'unknown'\n switch (keyBy) {\n case 'session': {\n // Reuse the Web cookie helper extracted in Phase B slice 6/6 so\n // CR-009 percent-encoding sanity stays consistent across paths.\n const { getCookieFromRequest } = await import('../http/cookies.js')\n const cookie = getCookieFromRequest(request, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n return ctx.userId ? `user:${ctx.userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter factory.\n *\n * Mirror of `createRouteRateLimiter(config)` returning a checker that\n * accepts `(request: Request, ctx?: DeriveKeyRequestContext)` instead of\n * `(req: IncomingMessage)`. Same `RouteRateLimitConfig` accepted; same\n * `keyBy` enum cases; same `InMemoryStore` constraint (CR-005 guard).\n *\n * Same returned `RateLimitResult` shape (headers + limited boolean).\n *\n * Web `Request` has no `req.url` path-only property — uses\n * `new URL(request.url).pathname + search` to derive the URL the way the\n * IncomingMessage path's `req.url ?? ''` would.\n */\nexport function createRouteRateLimiterWeb(config: RouteRateLimitConfig | RateLimitConfig) {\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiterWeb: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the Web facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = (cfg.keyBy ?? 'ip') as Exclude<KeyByMode, (req: IncomingMessage) => string>\n const cookieName = cfg.cookieName ?? 'theo_session'\n\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimitWeb(\n request: Request,\n ctx: DeriveKeyRequestContext = {},\n ): Promise<RateLimitResult> {\n // Web Request guarantees absolute URL — extract path+query for pattern\n // matching (mirror of IncomingMessage's `req.url ?? ''`).\n const parsed = new URL(request.url)\n const url = `${parsed.pathname}${parsed.search}`\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n return { limited: false, headers: {} }\n }\n\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKeyFromRequest(request, keyBy, cookieName, ctx)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) T3.2 — boot-time SDK compatibility fail-fast.\n *\n * The agent runtime is `@theokit/sdk` (G2). Today a missing / mis-versioned SDK is invisible until\n * the FIRST agent request, where `sdk-adapter.ts` yields a lazy `SDK_NOT_INSTALLED` stream event.\n * This runs at `theokit start` boot instead: if the installed SDK is present but outside the\n * supported range it throws a typed error naming found-vs-required; if it is absent (a legitimately\n * api-only app — the SDK is an OPTIONAL peer) it stays silent unless the caller marks it required.\n *\n * CLI/adapter layer → `node:*` is allowed here (unlike `server/`). The version range check itself is\n * the pure `server/agent/sdk-compat.ts` helper (one source of truth with the contract-test drift guard).\n */\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport { SUPPORTED_SDK_RANGE, satisfiesSdkRange } from '../../../server/agent/sdk-compat.js'\n\n/** Typed error surfaced at boot when the installed `@theokit/sdk` is incompatible or absent-but-required. */\nexport class SdkIncompatibleError extends Error {\n readonly code = 'SDK_INCOMPATIBLE' as const\n readonly found: string | undefined\n readonly required: string\n constructor(found: string | undefined, required: string) {\n super(\n found === undefined\n ? `@theokit/sdk is required but not installed — run: pnpm add @theokit/sdk@${required}`\n : `@theokit/sdk ${found} does not satisfy the required range ${required} — run: pnpm add @theokit/sdk@${required}`,\n )\n this.name = 'SdkIncompatibleError'\n this.found = found\n this.required = required\n }\n}\n\n/** Resolve the version of the ACTUALLY-installed `@theokit/sdk`, or `undefined` when absent. */\nfunction defaultResolveSdkVersion(): string | undefined {\n try {\n const require = createRequire(import.meta.url)\n const pkgPath = require.resolve('@theokit/sdk/package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }\n return typeof pkg.version === 'string' ? pkg.version : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Fail fast at boot when the installed `@theokit/sdk` is incompatible.\n *\n * - present + in range → returns (silent)\n * - present + out of range → throws {@link SdkIncompatibleError} (found + required)\n * - absent + `required !== true` → returns (api-only app; the request path still guards lazily)\n * - absent + `required === true` → throws {@link SdkIncompatibleError}\n *\n * `resolveVersion` is injectable for tests (DIP); production uses {@link defaultResolveSdkVersion}.\n */\nexport function assertSdkCompatible(opts?: {\n required?: boolean\n resolveVersion?: () => string | undefined\n}): void {\n const version = (opts?.resolveVersion ?? defaultResolveSdkVersion)()\n if (version === undefined) {\n if (opts?.required === true) throw new SdkIncompatibleError(undefined, SUPPORTED_SDK_RANGE)\n return\n }\n if (!satisfiesSdkRange(version, SUPPORTED_SDK_RANGE)) {\n throw new SdkIncompatibleError(version, SUPPORTED_SDK_RANGE)\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) — the supported `@theokit/sdk` version range + a pure,\n * dependency-free `||`-aware caret semver check. Shared by the contract test's version-drift guard\n * (`tests/integration/contract-sdk-seam.test.ts`) and the boot-time fail-fast\n * (`cli/commands/start/assert-sdk-compatible.ts`) — one source of truth for \"which SDK theokit runs on\".\n *\n * Web-Standards discipline (G8/R3a): `server/` code uses no `node:*`. This is pure string logic.\n * Rule 9 (Don't Reinvent) trade-off (ADR D1): a small inline caret checker instead of a `semver`\n * dependency, matching the theo-ui seam's existing precedent (`contract-usetheo-ui-vite-plugin.test.ts`).\n */\n\n/** The single source of truth for the supported SDK range. Mirrors the `package.json` peer floor. */\nexport const SUPPORTED_SDK_RANGE = '^4.0.1'\n\n// Bounded semver: three `\\d+` tuples + an optional `-tag.N` prerelease. No nested quantifiers →\n// no catastrophic backtracking; the security/detect-unsafe-regex heuristic false-positives here.\n// eslint-disable-next-line security/detect-unsafe-regex -- bounded \\d+ groups anchored by `.`/`$`, no backtracking\nconst SEMVER_RE = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([a-z]+)\\.(\\d+))?$/\n\ninterface Semver {\n major: string\n minor: string\n patch: string\n tag: string | undefined\n pre: string | undefined\n}\n\nfunction parseSemver(value: string): Semver | null {\n const m = SEMVER_RE.exec(value)\n if (!m) return null\n return { major: m[1], minor: m[2], patch: m[3], tag: m[4], pre: m[5] }\n}\n\n/**\n * Return `true` when `version` satisfies `range`, where `range` is a `||`-joined series of caret\n * pins (e.g. `^4.0.1` or `^0.14.0 || ^1.0.0`). A version satisfies the range when it satisfies ANY\n * clause. Covers the semver subset the monorepo uses (caret pins, optional `-tag.N` prerelease);\n * no range-sets or build metadata.\n */\nexport function satisfiesSdkRange(version: string, range: string): boolean {\n return range\n .split('||')\n .map((clause) => clause.trim())\n .some((clause) => satisfiesSingleCaret(version, clause))\n}\n\nfunction satisfiesSingleCaret(version: string, range: string): boolean {\n if (!range.startsWith('^')) return false\n const pin = parseSemver(range.slice(1))\n const ver = parseSemver(version)\n if (!pin || !ver) return false\n if (pin.tag !== undefined) return satisfiesPrereleaseCaret(ver, pin)\n if (ver.tag !== undefined) return false // a prerelease never satisfies a stable caret pin\n return satisfiesStableCaret(ver, pin)\n}\n\nfunction satisfiesPrereleaseCaret(ver: Semver, pin: Semver): boolean {\n // Prerelease pin: same X.Y.Z + same tag + prerelease number >= pin's.\n if (pin.major !== ver.major || pin.minor !== ver.minor || pin.patch !== ver.patch) return false\n if (ver.tag !== pin.tag) return false\n return Number(ver.pre ?? '0') >= Number(pin.pre ?? '0')\n}\n\nfunction satisfiesStableCaret(ver: Semver, pin: Semver): boolean {\n if (pin.major !== ver.major) return false\n if (pin.major === '0') {\n // 0.X.Y caret: minor must match exactly; patch can be >=.\n if (pin.minor !== ver.minor) return false\n return Number(ver.patch) >= Number(pin.patch)\n }\n // >= 1.0.0 caret: minor can be >=, patch anything within the same/greater minor.\n if (Number(ver.minor) > Number(pin.minor)) return true\n if (Number(ver.minor) === Number(pin.minor)) return Number(ver.patch) >= Number(pin.patch)\n return false\n}\n","/**\n * Bootstrap stages extracted from `start.ts` per T4.2 (architecture-cleanup, ADR-0017).\n *\n * The full goal of T4.2 is a ≤30-LOC `startCommand` spine + 6-8 stage files. This\n * file ships the first batch: the configure-from-config bootstrap helpers + the\n * SSR entry resolver. They are stand-alone, side-effect-free at module load, and\n * already individually testable.\n *\n * Remaining stages (request-handler extraction, graceful-shutdown extraction,\n * signal-handlers extraction) are deferred to a follow-up sprint — see plan\n * ks.\n */\n\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\ninterface SdkAgentRegistry {\n configure?: (opts: { maxAgents?: number; idleTimeoutMs?: number }) => void\n}\ninterface SdkModule {\n Agent?: { registry?: SdkAgentRegistry }\n}\n\n/**\n * Configure SDK's Agent.registry from `theo.config.ts > agents.registry`.\n * Lazy at boot; EC-3 sync flag flip prevents race under concurrent boot.\n * Silent no-op when registry config is absent or SDK is uninstalled.\n */\nexport async function configureAgentRegistryFromConfig(\n registryConfig: { maxAgents: number; idleTimeoutMs: number } | undefined,\n): Promise<void> {\n if (registryConfig === undefined) return\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as SdkModule | null\n const sdkConfigure = sdk?.Agent?.registry?.configure\n if (sdkConfigure === undefined) return\n const { configureAgentRegistryOnce } =\n await import('../../../server/agent/configure-agent-registry.js')\n configureAgentRegistryOnce(\n {\n configure: (opts) => {\n sdkConfigure(opts)\n },\n },\n registryConfig,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.agent_registry_skip', {\n event: 'bootstrap.agent_registry_skip',\n message: msg,\n })\n }\n}\n\n/**\n * Configure the StorageManager from `theo.config.ts > storage` (ADR-0007).\n * Manager enforces configure-once internally (D3); this helper bridges the\n * config to the singleton with actionable error handling.\n */\nexport async function configureStorageManagerFromConfig(storageConfig: unknown): Promise<void> {\n if (storageConfig === undefined || storageConfig === null) return\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const { storageSchema } = await import('../../../config/schema.js')\n // Re-validate at boot so a malformed config from a non-Zod source\n // (test fixtures, dynamic configs) surfaces a clear error early.\n const parsed = storageSchema.parse(storageConfig)\n getStorageManager().configure(parsed)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.storage_skip', {\n event: 'bootstrap.storage_skip',\n message: msg,\n })\n }\n}\n\nconst SSR_EXTENSIONS = ['.mjs', '.js'] as const\n\n/**\n * Resolve the SSR entry-server module path. tsup may emit `.mjs` or `.js`\n * depending on output format. Try `.mjs` first (modern default) then fall\n * back to `.js`. Returns null when neither exists — SSR stays disabled.\n *\n * Exported so unit tests can pin the resolution order without booting the\n * full CLI.\n */\nexport function resolveSsrEntry(distDir: string): string | null {\n for (const ext of SSR_EXTENSIONS) {\n const path = resolve(distDir, `server/entry-server${ext}`)\n\n if (existsSync(path)) return path\n }\n return null\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { CronManifest } from '../../../server/cron/cron-manifest.js'\nimport type { CronDefinition } from '../../../server/cron/cron-types.js'\n\n/**\n * Reads the cron manifest `theokit build` writes and re-loads each handler so\n * `theokit start` can drive the in-process scheduler.\n *\n * The build prints \"Cron → in-process scheduler (theokit start)\" for\n * `target: node`, but nothing on the serving side ever read `dist/crons.json`\n * — the crons were declared, validated, written, and then never ran\n * (theokit#324).\n *\n * The manifest names WHICH files hold a cron; the definition itself is taken\n * from the module, which is the source `defineCron` produced. Trusting the\n * manifest's copy of the schedule would let a stale build silently run a cron\n * on the wrong cadence.\n */\nexport async function loadCronDefinitions(\n manifestPath: string,\n projectRoot: string,\n loadModule: (filePath: string) => unknown,\n): Promise<CronDefinition[]> {\n if (!existsSync(manifestPath)) return []\n\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as CronManifest\n const definitions: CronDefinition[] = []\n\n for (const entry of manifest.crons) {\n const filePath = resolve(projectRoot, entry.filePath)\n const mod = (await loadModule(filePath)) as { default?: unknown }\n const exported = mod.default\n\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron \"${entry.name}\" declared in \"${entry.filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`. ' +\n 'Re-run `theokit build` if the file changed since the last build.',\n )\n }\n\n definitions.push(exported)\n }\n\n return definitions\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.schedule === 'string' &&\n typeof candidate.handler === 'function'\n )\n}\n","/**\n * Graceful shutdown stage for `theokit start` (T4.2 architecture-cleanup,\n * ADR-0007 D6 — SIGTERM evicts agents + drains StorageManager).\n *\n * EC-13: SIGTERM evicts agents IMMEDIATELY (no per-request drain). In-flight\n * requests get aborted mid-stream — acceptable because the platform LB\n * removed this pod from rotation BEFORE sending SIGTERM (K8s preStop hook +\n * terminationGracePeriodSeconds; same on Vercel/CF/Render).\n *\n * Re-entry guard: multiple SIGTERMs in quick succession run shutdown ONCE.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\nexport function installGracefulShutdown(server: HttpServer): void {\n let shuttingDown = false\n const shutdown = (signal: NodeJS.Signals): void => {\n if (shuttingDown) return\n shuttingDown = true\n console.log(`\\n [theokit] ${signal} received — evicting agents`)\n void (async () => {\n // Lazy-import SDK only at shutdown time to avoid forcing the dep on\n // apps that don't use agents at all.\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as {\n Agent?: { registry?: { evictAll?: () => Promise<void> } }\n } | null\n if (sdk?.Agent?.registry?.evictAll !== undefined) {\n await sdk.Agent.registry.evictAll()\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.evict_error', {\n event: 'shutdown.evict_error',\n message: msg,\n })\n }\n // T3.1 — drain the StorageManager AFTER agent eviction. Order matters:\n // agents may still hold open pool refs while evicting; closing pools\n // first would break in-flight queries.\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const manager = getStorageManager()\n await manager.dispose()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.dispose_error', {\n event: 'shutdown.dispose_error',\n message: msg,\n })\n }\n console.log(` [theokit] shutdown complete`)\n server.close(() => {\n process.exit(0)\n })\n setTimeout(() => {\n warnOnce('shutdown.forced_exit', {\n event: 'shutdown.forced_exit',\n message: 'forced exit after 25s timeout',\n })\n process.exit(0)\n }, 25_000).unref()\n })()\n }\n process.on('SIGTERM', () => {\n shutdown('SIGTERM')\n })\n process.on('SIGINT', () => {\n shutdown('SIGINT')\n })\n}\n","/**\n * Manifest loading stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads pre-built manifest from `.theokit/manifest.json` if present; otherwise\n * scans server/ for routes/actions/ws at startup with a structured warn.\n */\n\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { dirname } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport { scanServerActions } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { scanAgents } from '../../../server/scan/agent-scan.js'\nimport { loadManifest } from '../../../server/scan/manifest.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport { scanServerRoutes } from '../../../server/scan/scan.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\nimport { scanWebSocketRoutes } from '../../../server/scan/ws-scan.js'\n\ninterface LoadedRoutes {\n routes: ServerRouteNode[]\n actions: ActionNode[]\n wsRoutes: WebSocketRouteNode[]\n agents: AgentNode[]\n}\n\nexport function loadRoutesAndActions(\n distDir: string,\n serverDir: string,\n // #95 follow-up — agents dir name (config `agentsDir`, default \"agents\") for the live-scan fallback.\n agentsDir = 'agents',\n): LoadedRoutes {\n const manifestPath = join(distDir, 'manifest.json')\n\n if (existsSync(manifestPath)) {\n const manifest = loadManifest(distDir, serverDir)\n return {\n routes: manifest.routes,\n actions: manifest.actions,\n wsRoutes: manifest.websockets,\n agents: manifest.agents,\n }\n }\n warnOnce('bootstrap.manifest_not_found', {\n event: 'bootstrap.manifest_not_found',\n message:\n 'No manifest found, scanning routes at startup. Run \"theo build\" to generate manifest.',\n serverDir,\n })\n return {\n routes: scanServerRoutes(serverDir),\n actions: scanServerActions(serverDir),\n wsRoutes: scanWebSocketRoutes(serverDir),\n // Agents live at <projectRoot>/<agentsDir>; projectRoot = serverDir's parent for the canonical layout.\n agents: scanAgents(dirname(serverDir), agentsDir),\n }\n}\n","/**\n * Inline request handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires the per-request flow: security headers → action/route/static branches\n * → SSR fallback → CSR fallback → 500 page.\n *\n * Refactored: CC reduced from 33 to ≤10 per function\n * (architecture-remediation T2.1, 2026-06-12).\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { generateNonce } from '../../../server/auth/nonce.js'\nimport { type ReservedRoutes, serveReservedRoute } from '../../../server/define/health-route.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { buildSecurityHeaders } from '../../../server/security/security-headers.js'\nimport { extractHeadTags, injectIntoHead } from '../../../vite-plugin/hoist-head-tags.js'\nimport { applyNonceToInlineScripts } from '../../../vite-plugin/ssr-dev-middleware.js'\n\nimport {\n tryServeAction,\n tryServeAgent,\n tryServeAgentAux,\n tryServeApiRoute,\n tryServeCustom404,\n tryServeStatic,\n type RequestHandlerCtx,\n} from './handlers.js'\nimport {\n isSsrRenderResult,\n type SsrRender,\n type SsrRenderResult,\n type SsrRenderStreaming,\n} from './ssr-setup.js'\n\ninterface RequestHandlerContext {\n buildCtx: (\n req: IncomingMessage,\n res: ServerResponse,\n requestId: string,\n startTime: number,\n ) => RequestHandlerCtx\n securityHeadersConfig: Parameters<typeof buildSecurityHeaders>[0]\n ssrRender: SsrRender | null\n ssrRenderStreaming: SsrRenderStreaming | null\n ssrStreamingEnabled: boolean\n htmlHead: string\n htmlTail: string\n indexHtml: string\n custom500Html: string | null\n /** M7-2: reserved health/ready routes served before the user catch-all. */\n reservedRoutes?: ReservedRoutes\n}\n\n/**\n * M7-2: serve a reserved `/__theo/*` route (health/ready) before any user\n * branch. Returns true when the request was handled.\n */\nasync function tryServeReserved(\n ctx: RequestHandlerContext,\n url: string,\n res: ServerResponse,\n): Promise<boolean> {\n const pathname = new URL(url, 'http://localhost').pathname\n const reserved = await serveReservedRoute(pathname, ctx.reservedRoutes ?? {})\n if (reserved === null) return false\n const payload = JSON.stringify(reserved.body)\n res.writeHead(reserved.status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload),\n })\n res.end(payload)\n return true\n}\n\nfunction asSsrRenderResult(value: SsrRenderResult): SsrRenderResult {\n return value\n}\n\nfunction isRedirectResult(result: unknown): result is { redirect: Response } {\n return result !== null && typeof result === 'object' && 'redirect' in result\n}\n\nfunction sendRedirect(res: ServerResponse, result: { redirect: Response }): void {\n res.writeHead(302, { Location: result.redirect.headers.get('location') ?? '/' })\n res.end()\n}\n\nfunction send500(res: ServerResponse, custom500Html: string | null): void {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n }\n if (!res.writableEnded) {\n res.end(custom500Html ?? '<h1>500 — Server Error</h1>')\n }\n}\n\n/**\n * Moves the route's `<title>`/`<meta>`/`<link>` from the rendered body into the head.\n *\n * `ctx.htmlHead` is the template up to and including `<div id=\"root\">`, so it still contains the\n * `</head>` this injects before. Without it, a route's metadata ships inside the body and only\n * reaches the head after hydration — which never happens for a crawler that does not run\n * JavaScript, and those are exactly the ones that render social cards.\n *\n * The dev middleware does the same thing; both paths have to, or previews work in one and not the\n * other, which is worse than neither.\n */\nexport function withHoistedHead(\n htmlHead: string,\n ssrHtml: string,\n nonce: string,\n): { head: string; body: string } {\n const { html, headTags } = extractHeadTags(ssrHtml)\n\n // The nonce is stamped onto the template's own inline scripts here, per request, because the\n // nonce differs per request while `ctx.htmlHead` is computed once at startup. Without it, a CSP\n // with a nonce blocks anything the template inlines — including the theme-init script that\n // applications put in `<head>` specifically to avoid a flash of the wrong theme on load.\n const head = applyNonceToInlineScripts(injectIntoHead(htmlHead, headTags), nonce)\n return { head, body: html }\n}\n\nfunction buildSsrHtml(\n ctx: RequestHandlerContext,\n result: string | SsrRenderResult,\n nonce: string,\n): string {\n if (typeof result === 'string') {\n const { head, body } = withHoistedHead(ctx.htmlHead, result, nonce)\n return head + body + ctx.htmlTail\n }\n if (isSsrRenderResult(result)) {\n const rendered = asSsrRenderResult(result)\n const dataJson = JSON.stringify(rendered.hydrationData).replace(/</g, '\\\\u003c')\n const hydrationScript = `<script${\n nonce ? ` nonce=\"${nonce}\"` : ''\n }>window.__staticRouterHydrationData=${dataJson}</script>`\n const { head, body } = withHoistedHead(ctx.htmlHead, rendered.html, nonce)\n return head + body + hydrationScript + ctx.htmlTail\n }\n return applyNonceToInlineScripts(ctx.htmlHead, nonce) + ctx.htmlTail\n}\n\nasync function handleSsrStreaming(\n ctx: RequestHandlerContext,\n req: IncomingMessage,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrStreamingEnabled || !ctx.ssrRenderStreaming) return false\n\n const controller = new AbortController()\n const onClose = (): void => {\n controller.abort()\n }\n req.on('close', onClose)\n try {\n const result = await ctx.ssrRenderStreaming(url, res, {\n signal: controller.signal,\n nonce,\n })\n if (isRedirectResult(result)) sendRedirect(res, result)\n return true\n } catch (streamErr) {\n console.error('[SSR Stream Error]', (streamErr as Error).message)\n send500(res, ctx.custom500Html)\n return true\n } finally {\n req.removeListener('close', onClose)\n }\n}\n\nasync function handleSsrSync(\n ctx: RequestHandlerContext,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrRender) return false\n\n try {\n const result = await ctx.ssrRender(url, { nonce })\n if (isRedirectResult(result)) {\n sendRedirect(res, result)\n return true\n }\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(buildSsrHtml(ctx, result, nonce))\n return true\n } catch (ssrErr) {\n console.error('[SSR Error] Falling back to CSR:', (ssrErr as Error).message)\n return false\n }\n}\n\nfunction handleFatalError(ctx: RequestHandlerContext, res: ServerResponse, err: unknown): void {\n if (ctx.custom500Html && !res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n res.end(ctx.custom500Html)\n } else if (!res.headersSent) {\n sendError(res, 'INTERNAL_ERROR', (err as Error).message, 500)\n } else {\n res.end()\n }\n}\n\nexport function createRequestHandler(\n ctx: RequestHandlerContext,\n): (req: IncomingMessage, res: ServerResponse) => void {\n return (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n const url = req.url ?? '/'\n const requestId = randomUUID()\n const start = Date.now()\n\n const nonce = generateNonce()\n const securityHeaders = buildSecurityHeaders(\n ctx.securityHeadersConfig,\n { production: true },\n { nonce },\n )\n for (const [k, v] of Object.entries(securityHeaders)) {\n res.setHeader(k, v)\n }\n\n const handlerCtx = ctx.buildCtx(req, res, requestId, start)\n\n try {\n if (await tryServeReserved(ctx, url, res)) return\n if (await tryServeAction(handlerCtx)) return\n if (await tryServeAgentAux(handlerCtx)) return\n if (await tryServeAgent(handlerCtx)) return\n if (await tryServeApiRoute(handlerCtx)) return\n if (tryServeStatic(handlerCtx)) return\n if (tryServeCustom404(handlerCtx)) return\n\n if (await handleSsrStreaming(ctx, req, res, url, nonce)) return\n if (await handleSsrSync(ctx, res, url, nonce)) return\n\n // CSR fallback\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(ctx.indexHtml)\n } catch (err) {\n handleFatalError(ctx, res, err)\n }\n })()\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { extname } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport { getApprovalRegistry } from '../../../server/agent/approval-registry.js'\nimport { handleAgentApproval, isApprovalPath } from '../../../server/agent/approve-agent.js'\nimport { mountAgent } from '../../../server/agent/mount-agent.js'\nimport { resolveProvider } from '../../../server/agent/provider-resolver.js'\nimport { serveAgentAuxRoute } from '../../../server/agent/serve-aux-routes.js'\nimport { executeAction } from '../../../server/http/action-execute.js'\nimport { dispatchControllerRequest } from '../../../server/http/controller-dispatch.js'\nimport { executeRoute } from '../../../server/http/execute.js'\nimport {\n incomingMessageToWebRequest,\n writeWebResponseToServerResponse,\n} from '../../../server/http/node-web-adapter.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { serveStaticFile } from '../../../server/http/static.js'\nimport { logRequest } from '../../../server/observability/logger.js'\nimport { findSuggestion } from '../../../server/observability/suggest.js'\nimport type { PluginContext } from '../../../server/plugin-types.js'\nimport type { PluginRunner } from '../../../server/plugins/plugin-runner.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { matchRoute } from '../../../server/scan/match.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { CsrfMode, DisallowedConfig } from '../../../server/security/csrf.js'\nimport type { TheoTransformer } from '../../../server/transformer.js'\n\n/**\n * Load a controller module that `theokit build` already compiled — theokit#123.\n *\n * A plain dynamic `import()`, deliberately: production must not need `@swc/core`. That peer is a\n * native binary the app would otherwise carry solely to re-do work the build already did, and a\n * missing optional peer would degrade into a runtime 404 instead of a build failure.\n */\nasync function loadCompiledController(absPath: string): Promise<Record<string, unknown>> {\n return (await import(pathToFileURL(absPath).href)) as Record<string, unknown>\n}\n\n/** Response header carrying the per-request correlation id. */\nconst X_REQUEST_ID = 'x-request-id'\n\n/**\n * T6.1 (PV-7 SRP): start.ts request orchestrator decomposed into 5 focused\n * per-branch handlers. Each handler returns `true` if it handled the\n * request (response sent) so the orchestrator can stop iterating.\n *\n * The original 455-LOC monolith closed over 14+ locals; the shared shape\n * `RequestHandlerCtx` makes the dependencies explicit + reviewable.\n */\nexport interface RequestHandlerCtx {\n req: IncomingMessage\n res: ServerResponse\n url: string\n requestId: string\n startTime: number\n // Pre-loaded build artifacts\n clientDir: string\n custom404Html: string | null\n // Manifest-resolved tables\n cachedRoutes: ServerRouteNode[]\n cachedActions: ActionNode[]\n cachedAgents: AgentNode[]\n // Runtime infra\n loadModule: LoadModule\n serverDir: string\n /** App root (= `process.cwd()` at `theokit start`); mountAgent points `.theokit/` discovery here. */\n projectRoot: string\n /**\n * theokit#123 — absolute path to the COMPILED controllers emitted by `theokit build`\n * (`<distDir>/controllers`), or `undefined` when the build produced none.\n *\n * `undefined` is the routes-only app, and it must stay free: no scan, no import, no cost.\n */\n controllersDistDir: string | undefined\n pluginRunner: PluginRunner | undefined\n transformer: TheoTransformer | undefined\n csrfMode: CsrfMode\n disallowed: DisallowedConfig | undefined\n /**\n * Async because the per-route limiter hashes the session cookie with Web Crypto when\n * `keyBy: 'session'`, and `subtle.digest` is promise-based.\n */\n rateLimiter:\n | ((req: IncomingMessage) => Promise<{ limited: boolean; headers: Record<string, string> }>)\n | null\n}\n\n/** Apply rate limit; return true if request was limited (response sent). */\nasync function applyRateLimit(c: RequestHandlerCtx, method: string): Promise<boolean> {\n if (!c.rateLimiter) return false\n const check = await c.rateLimiter(c.req)\n for (const [k, v] of Object.entries(check.headers)) c.res.setHeader(k, v)\n if (check.limited) {\n sendError(c.res, 'RATE_LIMITED', 'Too many requests', 429, undefined, c.requestId)\n logRequest({\n method,\n url: c.url,\n status: 429,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n return false\n}\n\n/** Branch 1: action routes (`/api/__actions/{file}/{exportName}`). */\nexport async function tryServeAction(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/__actions/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const pathAfterPrefix = c.url.slice('/api/__actions/'.length).split('?')[0]\n const segments = pathAfterPrefix.split('/').filter(Boolean)\n if (segments.length < 2) {\n sendError(\n c.res,\n 'BAD_REQUEST',\n 'Action URL must be /api/__actions/{file}/{exportName}',\n 400,\n undefined,\n c.requestId,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 400,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const exportName = segments[segments.length - 1]\n const actionPath = segments.slice(0, -1).join('/')\n const action = c.cachedActions.find((a) => a.actionPath === actionPath)\n if (!action) {\n const actionPaths = c.cachedActions.map((a) => a.actionPath)\n const suggestion = findSuggestion(actionPath, actionPaths)\n const msg = suggestion\n ? `Action \"${actionPath}\" not found. Did you mean: ${suggestion}?`\n : `Action \"${actionPath}\" not found`\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n await executeAction(\n action.filePath,\n exportName,\n c.req,\n c.res,\n c.loadModule,\n c.serverDir,\n c.requestId,\n c.pluginRunner,\n c.csrfMode,\n c.disallowed,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.4: agent AUXILIARY routes served identically in dev + prod (M15/M16 follow-up) —\n * agent cards (`/.well-known/<name>/agent-card.json`), MCP (`/api/agents/<name>/mcp`), and the\n * pending-approvals listing (`/api/agents/<name>/approvals`). Before this, they were dev-only, so a\n * built/deployed app 404'd them. Delegates to the shared `serveAgentAuxRoute` dispatcher (DRY).\n */\nexport async function tryServeAgentAux(c: RequestHandlerCtx): Promise<boolean> {\n const urlPath = c.url.split('?')[0]\n const baseUrl = `http://${c.req.headers.host ?? 'localhost'}`\n const request = incomingMessageToWebRequest(c.req)\n const response = await serveAgentAuxRoute(request, urlPath, {\n agents: c.cachedAgents,\n loadModule: c.loadModule,\n baseUrl,\n // M34 (#97) — the MCP aux route drives the agent (spends tokens); enforce CSRF like the run route.\n csrfMode: c.csrfMode,\n // M39 — the thread follow-up route drives the agent; resolve the key on demand.\n // theokit#328 — the thread route drives an agent, so its key follows the model too.\n resolveApiKey: (model) => resolveProvider(model).apiKey,\n })\n if (response === null) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n await writeWebResponseToServerResponse(response, c.res)\n logRequest({\n method: (c.req.method ?? 'GET').toUpperCase(),\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.5: agent convention routes (`/api/agents/<name>`, M2). Runs BEFORE the generic `/api/*`\n * branch so a scanned `agents/<name>.ts` owns its path (parity with dev). Loads the module, resolves\n * the provider apiKey (fail-fast), and streams the M0/M1 UIMessageStream via `mountAgent`.\n */\nexport async function tryServeAgent(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/agents/')) return false\n const urlPath = c.url.split('?')[0]\n\n // HITL approve route (`/api/agents/<name>/approve/<id>`, M4) — resolve the pending approval.\n // Handled BEFORE the agent-path exact match (the approve path never equals an `agentPath`).\n if (isApprovalPath(urlPath)) {\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Approve endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n try {\n const request = incomingMessageToWebRequest(c.req)\n const response = await handleAgentApproval(\n request,\n urlPath,\n getApprovalRegistry(),\n c.csrfMode,\n )\n await writeWebResponseToServerResponse(response, c.res)\n } catch (err) {\n sendError(\n c.res,\n 'INTERNAL',\n err instanceof Error ? err.message : 'Approve handler failed',\n 500,\n undefined,\n c.requestId,\n )\n }\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n const agent = c.cachedAgents.find((a) => a.agentPath === urlPath)\n if (!agent) return false // fall through to the generic /api/* branch (may 404 there)\n\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Agent endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n await serveAgentTurn(c, agent, method)\n return true\n}\n\n/**\n * Serves one agent turn through the plugin lifecycle.\n *\n * Extracted from `tryServeAgent` so that function stays what its name says — a router over the\n * agent paths — while the turn itself, which is what grew a lifecycle, reads in one piece.\n */\nasync function serveAgentTurn(\n c: RequestHandlerCtx,\n agent: { filePath: string },\n method: string,\n): Promise<void> {\n // theokit#324 — the plugin lifecycle runs here too.\n //\n // This branch used to mount the agent without ever consulting the runner, so `onRequest`,\n // `onResponse` and `onError` fired for every OTHER route and never for an agent turn — leaving an\n // app embedding TheoKit with no supported place to observe or bound agent state. Reported with a repro\n // by a consumer who found it by instrumenting a hook and watching it stay silent — the failure is\n // invisible by construction, since a hook that never runs looks exactly like one with nothing to\n // say.\n //\n // Mirrors `executeRoute`'s shape deliberately: same `PluginContext`, same short-circuit contract,\n // same `onError` on the failure path — an agent route should not have a lifecycle of its own.\n let pluginCtx: PluginContext | undefined\n let request: Request\n\n try {\n request = incomingMessageToWebRequest(c.req)\n pluginCtx = { request, response: c.res, ctx: {}, requestId: c.requestId }\n } catch (err) {\n // A request the adapter cannot represent is a 500, exactly as before this branch grew a\n // lifecycle — the conversion used to sit inside the try below and this preserves that.\n sendError(\n c.res,\n 'INTERNAL',\n err instanceof Error ? err.message : 'Agent handler failed',\n 500,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n\n return\n }\n\n if (c.pluginRunner) {\n c.pluginRunner.applyDecorations(pluginCtx.ctx)\n const onRequest = await c.pluginRunner.runOnRequest(pluginCtx)\n if (onRequest.shortCircuited) {\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return\n }\n }\n\n try {\n const mod = await c.loadModule(agent.filePath)\n // theokit#326 — resolve against the model the agent declares, not by env priority.\n const apiKey = (model: string | undefined): string => resolveProvider(model).apiKey\n const response = await mountAgent(mod, request, apiKey, {\n source: agent.filePath,\n csrfMode: c.csrfMode,\n projectRoot: c.projectRoot,\n })\n await writeWebResponseToServerResponse(response, c.res)\n } catch (err) {\n if (c.pluginRunner) await c.pluginRunner.runOnError(pluginCtx, err)\n sendError(\n c.res,\n 'INTERNAL',\n err instanceof Error ? err.message : 'Agent handler failed',\n 500,\n undefined,\n c.requestId,\n )\n }\n\n // After the response is written, as `executeRoute` does — a hook here observes a completed turn.\n if (c.pluginRunner) await c.pluginRunner.runOnResponse(pluginCtx)\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n}\n\n/** Branch 2: API routes (`/api/*` excluding actions). */\nexport async function tryServeApiRoute(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'GET')) return true\n\n const match = matchRoute(c.url, c.cachedRoutes)\n if (!match) {\n // theokit#123 — controller fall-through, mirroring the dev `api-middleware` arm.\n //\n // Before this, `theokit dev` served a decorator controller and `theokit start` 404'd it: the\n // production path has no Vite/swc transform, so an uncompiled `.controller.ts` could not load.\n // `theokit build` now emits compiled modules and this branch serves them, so the SAME app\n // answers the same routes in both. Reached only after a file-route miss, so file routes keep\n // precedence exactly as in dev.\n if (c.controllersDistDir !== undefined) {\n const handled = await dispatchControllerRequest({\n controllersDir: c.controllersDistDir,\n loadModule: loadCompiledController,\n req: c.req,\n res: c.res,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n requestId: c.requestId,\n })\n if (handled) return true\n }\n const urlPath = c.url.split('?')[0]\n const routePaths = c.cachedRoutes.map((r) => r.routePath)\n const suggestion = findSuggestion(urlPath, routePaths)\n const msg = suggestion\n ? `API route not found: ${urlPath}. Did you mean: ${suggestion}?`\n : 'API route not found'\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'GET',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const method = (c.req.method ?? 'GET').toUpperCase()\n // T3.1 (ADR-0016) — context object replaces 12 positional args\n await executeRoute({\n route: match.route,\n method,\n params: match.params,\n req: c.req,\n res: c.res,\n loadModule: c.loadModule,\n serverDir: c.serverDir,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n transformer: c.transformer,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n })\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/** Branch 3: static files (returns true if a static asset was served). */\nexport function tryServeStatic(c: RequestHandlerCtx): boolean {\n return serveStaticFile(c.req, c.res, c.clientDir)\n}\n\n/** Branch 4: custom 404 for URLs that look like missing assets. */\nexport function tryServeCustom404(c: RequestHandlerCtx): boolean {\n const urlPath = c.url.split('?')[0]\n if (c.custom404Html && extname(urlPath)) {\n c.res.writeHead(404, { 'Content-Type': 'text/html' })\n c.res.end(c.custom404Html)\n return true\n }\n return false\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Static-file server. The URL path IS user-controlled, BUT before any fs\n * call we resolve to absolute and reject with 403 if the resolved path\n * escapes `clientDir` (see EC-1 guard at line below). The guard is\n * authoritative; the rule cannot see it.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { resolve, extname } from 'node:path'\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain',\n '.map': 'application/json',\n}\n\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n clientDir: string,\n): boolean {\n const urlPath = (req.url ?? '/').split('?')[0]\n\n // Path traversal prevention (EC-1)\n const filePath = resolve(clientDir, '.' + urlPath)\n if (!filePath.startsWith(clientDir)) {\n res.writeHead(403)\n res.end('Forbidden')\n return true\n }\n\n if (!existsSync(filePath)) return false\n\n const stat = statSync(filePath)\n if (!stat.isFile()) return false\n\n const ext = extname(filePath)\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'\n const content = readFileSync(filePath)\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': content.length,\n })\n res.end(content)\n return true\n}\n","/**\n * SSR setup stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads the SSR entry-server module if configured + builds template split\n * around the React root div. Returns null renderers when SSR is disabled.\n */\n\nimport type { ServerResponse } from 'node:http'\n\nimport { findRootDiv } from '../../../core/contracts/find-root-div.js'\n\nimport { resolveSsrEntry } from './bootstrap-stages.js'\n\nexport interface SsrRenderResult {\n html: string\n hydrationData: {\n loaderData?: unknown\n actionData?: unknown\n errors?: unknown\n }\n}\n\nexport type RenderStreamingResult = { redirect: Response } | { streaming: true } | undefined\n\nexport type SsrRender = (\n url: string,\n options?: { nonce?: string },\n) => Promise<SsrRenderResult | { redirect: Response } | string>\n\nexport type SsrRenderStreaming = (\n url: string,\n response: ServerResponse,\n options?: { signal?: AbortSignal; nonce?: string },\n) => Promise<RenderStreamingResult>\n\ninterface SsrSetupResult {\n enabled: boolean\n streamingEnabled: boolean\n render: SsrRender | null\n renderStreaming: SsrRenderStreaming | null\n htmlHead: string\n htmlTail: string\n}\n\nexport function isSsrRenderResult(value: unknown): value is SsrRenderResult {\n if (typeof value !== 'object' || value === null) return false\n if (!('html' in value)) return false\n const html = (value as Record<string, unknown>).html\n if (typeof html !== 'string') return false\n return true\n}\n\ninterface SsrEntryServer {\n render: SsrRender\n renderStreaming?: SsrRenderStreaming\n}\n\nexport async function setupSsr(opts: {\n distDir: string\n indexHtml: string\n ssrConfigEnabled: boolean\n ssrStreamingConfig: boolean | undefined\n}): Promise<SsrSetupResult> {\n const ssrServerPath: string | null = opts.ssrConfigEnabled ? resolveSsrEntry(opts.distDir) : null\n const enabled = ssrServerPath !== null\n const streamingEnabled = enabled && Boolean(opts.ssrStreamingConfig)\n\n if (ssrServerPath === null) {\n return {\n enabled: false,\n streamingEnabled: false,\n render: null,\n renderStreaming: null,\n htmlHead: '',\n htmlTail: '',\n }\n }\n\n const mod = (await import(ssrServerPath)) as SsrEntryServer\n const render = mod.render\n const renderStreaming = typeof mod.renderStreaming === 'function' ? mod.renderStreaming : null\n\n // Split HTML template on root div\n let htmlHead = ''\n let htmlTail = ''\n const rootDiv = findRootDiv(opts.indexHtml)\n if (rootDiv) {\n htmlHead = opts.indexHtml.slice(0, rootDiv.insertAt)\n htmlTail = opts.indexHtml.slice(rootDiv.insertAt)\n }\n\n return { enabled, streamingEnabled, render, renderStreaming, htmlHead, htmlTail }\n}\n","/**\n * WebSocket upgrade handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires `server.on('upgrade')` for declared WS routes. Opt-in: only attached\n * when `wsRoutes.length > 0`. Lazy-imports `ws` package — throws an actionable\n * error when wsRoutes declared but `ws` not installed.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport type * as WsLib from 'ws'\n\nimport type { WebSocketHandler } from '../../../server/define/define-websocket.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\n\nexport async function attachWebSocketHandler(\n server: HttpServer,\n wsRoutes: WebSocketRouteNode[],\n loadModule: LoadModule,\n): Promise<void> {\n if (wsRoutes.length === 0) return\n\n let WebSocketServerCtor: typeof WsLib.WebSocketServer\n try {\n const wsModule = await import('ws')\n WebSocketServerCtor = wsModule.WebSocketServer\n } catch {\n throw new Error('WebSocket routes found but \"ws\" package is not installed. Run: npm install ws')\n }\n\n const wss = new WebSocketServerCtor({ noServer: true })\n\n server.on('upgrade', (request, socket, head) => {\n void (async () => {\n const url = request.url ?? '/'\n if (!url.startsWith('/ws/')) {\n socket.destroy()\n return\n }\n\n const wsPath = url.split('?')[0]\n const match = wsRoutes.find((r) => r.wsPath === wsPath)\n if (!match) {\n socket.destroy()\n return\n }\n\n try {\n const mod = await loadModule(match.filePath)\n const handler = ((mod as { default?: unknown }).default ?? mod) as WebSocketHandler\n\n wss.handleUpgrade(request, socket, head, (ws) => {\n handler.onOpen?.(ws, request)\n ws.on('message', (data: Buffer) => {\n handler.onMessage?.(ws, data.toString())\n })\n ws.on('close', (code: number, reason: Buffer) => {\n handler.onClose?.(ws, code, reason)\n })\n ws.on('error', (err: Error) => {\n handler.onError?.(ws, err)\n })\n })\n } catch {\n socket.destroy()\n }\n })()\n })\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","resolve","require","existsSync","readFileSync","resolve","existsSync","existsSync","extname","existsSync","readFileSync","resolve","method","extname","resolve","existsSync","readFileSync","join"]}
@@ -5,14 +5,14 @@ import {
5
5
  } from "./chunk-3PWQQWT6.js";
6
6
  import {
7
7
  nodeAdapter
8
- } from "./chunk-E576KYYT.js";
8
+ } from "./chunk-BHOTGS7N.js";
9
9
  import {
10
10
  scanRoutes
11
11
  } from "./chunk-IEES3CHD.js";
12
12
  import {
13
13
  assertServicesUnsupported,
14
14
  readManifest
15
- } from "./chunk-7VPQHEAJ.js";
15
+ } from "./chunk-WSXNGF7X.js";
16
16
 
17
17
  // src/adapters/static.ts
18
18
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -232,4 +232,4 @@ export {
232
232
  detectApiRoutes,
233
233
  staticAdapter
234
234
  };
235
- //# sourceMappingURL=static-M2W4HUWS.js.map
235
+ //# sourceMappingURL=static-RTO637XW.js.map
@@ -3,7 +3,7 @@ import "tsx/esm";
3
3
  import {
4
4
  prepareTheoCloudArtifacts,
5
5
  readManifest
6
- } from "./chunk-7VPQHEAJ.js";
6
+ } from "./chunk-WSXNGF7X.js";
7
7
 
8
8
  // src/adapters/theo-cloud.ts
9
9
  var theoCloudAdapter = {
@@ -21,4 +21,4 @@ var theoCloudAdapter = {
21
21
  export {
22
22
  theoCloudAdapter
23
23
  };
24
- //# sourceMappingURL=theo-cloud-XAHWOUFK.js.map
24
+ //# sourceMappingURL=theo-cloud-UZ3GINRU.js.map
@@ -2,11 +2,11 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  nodeAdapter
5
- } from "./chunk-E576KYYT.js";
5
+ } from "./chunk-BHOTGS7N.js";
6
6
  import {
7
7
  assertServicesUnsupported,
8
8
  readManifest
9
- } from "./chunk-7VPQHEAJ.js";
9
+ } from "./chunk-WSXNGF7X.js";
10
10
 
11
11
  // src/adapters/vercel.ts
12
12
  import { existsSync, mkdirSync, writeFileSync, cpSync } from "fs";
@@ -130,4 +130,4 @@ export {
130
130
  renderVercelVcConfigJson,
131
131
  vercelAdapter
132
132
  };
133
- //# sourceMappingURL=vercel-SRS2DH6O.js.map
133
+ //# sourceMappingURL=vercel-6D6EZUVD.js.map
@@ -5,10 +5,10 @@ import {
5
5
  defineTheoIntegration,
6
6
  theoPlugin,
7
7
  theoPluginAsync
8
- } from "../chunk-GXEX4CBH.js";
8
+ } from "../chunk-FTFN4MYN.js";
9
9
  import "../chunk-SSYDEKWF.js";
10
10
  import "../chunk-OIGJOYX6.js";
11
- import "../chunk-OJLL4YD6.js";
11
+ import "../chunk-KTKTHIUF.js";
12
12
  import "../chunk-XR74JD4M.js";
13
13
  import "../chunk-XALU5LCU.js";
14
14
  import "../chunk-47FFT43U.js";
@@ -26,7 +26,7 @@ import "../chunk-AVQU5CQF.js";
26
26
  import "../chunk-ACKBVBUI.js";
27
27
  import "../chunk-IZ3TIL6K.js";
28
28
  import "../chunk-YOIVCCZT.js";
29
- import "../chunk-6E5WKMDS.js";
29
+ import "../chunk-AXKUA57G.js";
30
30
  import "../chunk-JA2ZAT5Q.js";
31
31
  import "../chunk-52PDWL5S.js";
32
32
  import "../chunk-U267AOWS.js";
@@ -7,18 +7,18 @@ import {
7
7
  defineTheoIntegration,
8
8
  theoPlugin,
9
9
  theoPluginAsync
10
- } from "./chunk-YRBR424R.js";
11
- import "./chunk-GRI4XHBJ.js";
10
+ } from "./chunk-OOBYPCZC.js";
11
+ import "./chunk-NTWNO24W.js";
12
12
  import "./chunk-2CVV6CNN.js";
13
13
  import "./chunk-3PWQQWT6.js";
14
14
  import "./chunk-IEES3CHD.js";
15
- import "./chunk-S433N3CT.js";
15
+ import "./chunk-AATSV4UV.js";
16
16
  import "./chunk-HBU2GIMZ.js";
17
- import "./chunk-GVW5EKEU.js";
18
- import "./chunk-7VPQHEAJ.js";
17
+ import "./chunk-CMYSHZFT.js";
18
+ import "./chunk-WSXNGF7X.js";
19
19
  import "./chunk-5ODOE6EF.js";
20
- import "./chunk-BIVCP6X5.js";
21
- import "./chunk-6GGWS7J3.js";
20
+ import "./chunk-WYHYOYRH.js";
21
+ import "./chunk-DQIPV7L5.js";
22
22
  import "./chunk-T5C46IIU.js";
23
23
  import "./chunk-ZB7D62OA.js";
24
24
  import "./chunk-LNVMHQ2Z.js";
@@ -33,4 +33,4 @@ export {
33
33
  theoPlugin,
34
34
  theoPluginAsync
35
35
  };
36
- //# sourceMappingURL=vite-plugin-VPJFD4HK.js.map
36
+ //# sourceMappingURL=vite-plugin-RJFKLF2U.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theokit",
3
- "version": "0.48.13",
3
+ "version": "0.48.14",
4
4
  "description": "The TheoKit web framework — file-based routing, typed server surfaces, the Vite plugin, the CLI and the deploy adapters, around agents served from agents/*.ts.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -133,9 +133,9 @@
133
133
  "tsx": "^4.22.4",
134
134
  "typescript": "^5.9.3",
135
135
  "vite": "^6.4.3",
136
+ "@theokit/agents": "^10.1.0",
136
137
  "@theokit/http": "^1.1.0",
137
- "@theokit/presenter": "^0.7.0",
138
- "@theokit/agents": "^10.1.0"
138
+ "@theokit/presenter": "^0.7.0"
139
139
  },
140
140
  "peerDependencies": {
141
141
  "@theokit/sdk": "^4.52.1",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/agent/provider-resolver.ts"],"mappings":";AAyDA,IAAM,mBAAyC;AAAA,EAC7C;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAMA,IAAM,WAAiC,CAAC,GAAG,gBAAgB;AAgBpD,SAAS,iBAAiB,YAAsC;AAErE,QAAM,MAAM,SAAS,UAAU,CAAC,MAAM,EAAE,SAAS,WAAW,IAAI;AAChE,MAAI,OAAO,EAAG,UAAS,GAAG,IAAI;AAAA,MACzB,UAAS,KAAK,UAAU;AAC/B;AAOO,SAAS,wBAA8B;AAC5C,WAAS,SAAS;AAClB,WAAS,KAAK,GAAG,gBAAgB;AACnC;AAOO,SAAS,gBAA+C;AAC7D,SAAO,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAC7D;AAUO,SAAS,kBAAoC;AAClD,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACnE,aAAW,QAAQ,QAAQ;AACzB,UAAM,SAAS,QAAQ,IAAI,KAAK,MAAM;AACtC,QAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX;AAAA,QACA,SAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM;AACvD,QAAM,IAAI;AAAA,IACR,6DAA6D,OAAO;AAAA,EAEtE;AACF;AAQO,SAAS,qBAA8C;AAC5D,MAAI;AACF,WAAO,gBAAgB;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/agent/provider-resolver.ts","../src/server/agent/approval-registry.ts"],"sourcesContent":["/**\n * Provider Resolver — Strategy + Registry pattern (FAANG-grade).\n *\n * Inspiration: Dapr Conversation Registry (`dapr/pkg/components/conversation/registry.go`)\n * + Encore Manager provider array (`encore/runtimes/go/pubsub/manager_internal.go`).\n *\n * Principle: provider routing is the FRAMEWORK's responsibility, not the consumer's.\n * The consumer template uses a plain `model: { id: 'gpt-4o-mini' }` — no conditionals.\n *\n * Wire protocol: OpenAI Chat Completions (universal — implemented by every\n * os providers: OpenRouter, Groq, Mistral, Together, Anthropic via proxy, etc).\n *\n * Resolution by priority (FIRST match wins):\n * 1. OPENROUTER_API_KEY → baseUrl=openrouter.ai (multi-model gateway)\n * 2. OPENAI_API_KEY → baseUrl=api.openai.com\n * 3. ANTHROPIC_API_KEY → direct Anthropic (Messages API, not OpenAI-compat)\n *\n * Escape hatch: an explicit `options.apiKey` OVERRIDES auto-resolution\n * (the consumer can force a specific provider if it wants to).\n */\n\n/**\n * Provider configuration descriptor — Registry entry shape.\n *\n * @public\n */\nexport interface ProviderDescriptor {\n /** Stable name used internally — not exposed on the wire. */\n name: string\n /** Environment variable that holds the API key for this provider. */\n envKey: string\n /** Base URL for the provider's OpenAI-compatible (or native) API. */\n baseUrl: string\n /** Resolution priority (lower = higher priority). FIRST match wins. */\n priority: number\n}\n\n/**\n * Resolved provider configuration — output of `resolveProvider()`.\n *\n * @public\n */\nexport interface ResolvedProvider {\n name: string\n apiKey: string\n baseUrl: string\n}\n\n/**\n * Default provider registry. Order = priority (first = highest).\n *\n * Adding a new provider:\n * 1. Append entry below (or register via `registerProvider()`).\n * 2. Set `envKey` matching the user's env var convention.\n * 3. Set `baseUrl` to the OpenAI-compat endpoint (or native if not compat).\n * 4. Provider name used in telemetry/logs only — never wire-exposed.\n */\nconst DEFAULT_REGISTRY: ProviderDescriptor[] = [\n {\n name: 'openrouter',\n envKey: 'OPENROUTER_API_KEY',\n baseUrl: 'https://openrouter.ai/api/v1',\n priority: 1,\n },\n {\n name: 'openai',\n envKey: 'OPENAI_API_KEY',\n baseUrl: 'https://api.openai.com/v1',\n priority: 2,\n },\n {\n name: 'anthropic',\n envKey: 'ANTHROPIC_API_KEY',\n baseUrl: 'https://api.anthropic.com',\n priority: 3,\n },\n]\n\n/**\n * Runtime registry — copy of DEFAULT_REGISTRY mutable via registerProvider().\n * Sorted by priority on every resolve (stable, O(n log n) — n <= ~10 providers).\n */\nconst registry: ProviderDescriptor[] = [...DEFAULT_REGISTRY]\n\n/**\n * Register a new provider (Registry pattern — runtime extension point).\n * Useful for self-hosted endpoints or custom providers without touching theokit src.\n *\n * @example\n * registerProvider({\n * name: 'self-hosted',\n * envKey: 'SELF_HOSTED_API_KEY',\n * baseUrl: 'https://llm.internal.acme.com/v1',\n * priority: 0, // highest priority\n * })\n *\n * @public\n */\nexport function registerProvider(descriptor: ProviderDescriptor): void {\n // Idempotent — replace existing by name.\n const idx = registry.findIndex((p) => p.name === descriptor.name)\n if (idx >= 0) registry[idx] = descriptor\n else registry.push(descriptor)\n}\n\n/**\n * Reset registry to DEFAULT_REGISTRY (test-only / dev escape hatch).\n *\n * @public\n */\nexport function resetProviderRegistry(): void {\n registry.length = 0\n registry.push(...DEFAULT_REGISTRY)\n}\n\n/**\n * Get current registry snapshot (read-only — inspection).\n *\n * @public\n */\nexport function listProviders(): readonly ProviderDescriptor[] {\n return [...registry].sort((a, b) => a.priority - b.priority)\n}\n\n/**\n * Resolve provider from env vars by priority. FIRST env var found wins.\n *\n * @returns ResolvedProvider with apiKey + baseUrl + name\n * @throws Error if NO provider env var is set (actionable message)\n *\n * @public\n */\nexport function resolveProvider(): ResolvedProvider {\n const sorted = [...registry].sort((a, b) => a.priority - b.priority)\n for (const desc of sorted) {\n const apiKey = process.env[desc.envKey]\n if (apiKey && apiKey.length > 0) {\n return {\n name: desc.name,\n apiKey,\n baseUrl: desc.baseUrl,\n }\n }\n }\n // No env var found — emit actionable error.\n const envKeys = sorted.map((p) => p.envKey).join(' OR ')\n throw new Error(\n `No LLM provider API key found in environment. Set one of: ${envKeys}. ` +\n `Get a free OpenRouter key at https://openrouter.ai/keys (recommended — one key, many models).`,\n )\n}\n\n/**\n * Try to resolve — does NOT throw. Returns null if no provider available.\n * Useful for graceful degradation (e.g., mock mode).\n *\n * @public\n */\nexport function tryResolveProvider(): ResolvedProvider | null {\n try {\n return resolveProvider()\n } catch {\n return null\n }\n}\n","/**\n * M4 (theokit-ai-first) — the in-process HITL approval registry.\n *\n * The HITL plugin's `awaitApproval` calls `register(approvalId)` and awaits the returned Promise\n * (this is what genuinely PAUSES the SDK run — the SDK `pre_tool_call` hook is awaited). The\n * approve route calls `resolve(approvalId, approved)` to settle it. A per-approval timeout settles\n * the Promise deterministically per the `@HumanInTheLoop` `onTimeout` policy so a hung approval\n * never leaks the paused stream.\n *\n * Single-process contract (ADR 0038 / plan Drawback 2): a multi-instance deploy needs a shared\n * registry — the interface is injectable so a durable impl (Redis, etc.) slots in without touching\n * the harness. We do NOT build a durable store now (YAGNI).\n */\n// The timeout policy vocabulary is owned by the `@HumanInTheLoop` decorator (DRY / G12) — reuse it\n// rather than re-declaring the union, so the two can never drift.\nimport type { TimeoutAction } from '@theokit/agents'\n\nexport interface RegisterOptions {\n /** Milliseconds before the approval auto-settles per `onTimeout`. */\n timeoutMs: number\n /**\n * What a timeout means. Only `'proceed'` auto-approves; `'abort'` and `'retry'` both deny — the\n * registry does NOT implement retry semantics (a timed-out `'retry'` is a deny, not a re-prompt).\n */\n onTimeout: TimeoutAction\n /** M14 — the gated tool name, surfaced by `list()` (optional; absent for legacy callers). */\n toolName?: string\n /** M14 — the approval question, surfaced by `list()` (optional). */\n question?: string\n /**\n * M20 — an optional JSON-schema descriptor of the custom payload the approver may attach. Carried\n * verbatim into `list()` + the `approval_required` event so the UI knows what to collect. Kept as\n * a plain JSON object (not a live Zod schema) so the registry stays serializable and SDK-free.\n */\n payloadSchema?: Record<string, unknown>\n}\n\n/**\n * M20 — a settled HITL decision. `approved` is the allow/deny bit (backward-compatible with the\n * legacy boolean); `reason` + `payload` are the optional approver-attached extras that surface to\n * the model (on denial, via the veto message) and to the app/UI.\n */\nexport interface ApprovalDecision {\n approved: boolean\n reason?: string\n payload?: unknown\n}\n\n/** M14 — a pending approval as surfaced by {@link ApprovalRegistry.list}. */\nexport interface PendingApproval {\n approvalId: string\n toolName?: string\n question?: string\n /** Epoch millis when the pending approval auto-settles (registeredAt + timeoutMs). */\n expiresAt: number\n /** M20 — the declared custom-payload schema, if the gated tool declares one. */\n payloadSchema?: Record<string, unknown>\n}\n\nexport interface ApprovalRegistry {\n /**\n * Register a pending approval; the returned Promise settles with the full {@link ApprovalDecision}\n * on `resolve` or timeout (M20 — was a bare boolean pre-M20).\n */\n register(approvalId: string, opts: RegisterOptions): Promise<ApprovalDecision>\n /**\n * Settle a pending approval. Accepts a full {@link ApprovalDecision} OR a bare boolean (coerced to\n * `{ approved }` — backward-compatible). Returns false if the id is unknown or already settled.\n */\n resolve(approvalId: string, decision: boolean | ApprovalDecision): boolean\n /** M14 — list the currently-pending approvals (process-wide; single-process contract). */\n list(): PendingApproval[]\n}\n\ninterface Pending {\n settle: (decision: ApprovalDecision) => void\n timer: ReturnType<typeof setTimeout>\n info: PendingApproval\n}\n\n/**\n * The one process-wide registry the stream mount (`mountAgent`) and the approve route share.\n *\n * The in-process impl holds LIVE Promise resolvers in memory — the approval a request awaits and\n * the approval the route resolves MUST be the same object, so a single instance per process is not\n * a convenience but a correctness requirement. Lazily created; a durable/multi-instance deploy\n * swaps this accessor for a shared-store impl (ADR 0038 / plan Drawback 2) without touching callers.\n * Tests use {@link createInProcessApprovalRegistry} directly — never this singleton.\n */\nlet serverRegistry: ApprovalRegistry | undefined\nexport function getApprovalRegistry(): ApprovalRegistry {\n serverRegistry ??= createInProcessApprovalRegistry()\n return serverRegistry\n}\n\nexport function createInProcessApprovalRegistry(): ApprovalRegistry {\n const pending = new Map<string, Pending>()\n\n return {\n register(approvalId, opts) {\n return new Promise<ApprovalDecision>((resolve) => {\n const settle = (decision: ApprovalDecision): void => {\n const entry = pending.get(approvalId)\n if (!entry) return\n clearTimeout(entry.timer)\n pending.delete(approvalId)\n resolve(decision)\n }\n // 'proceed' → allow on timeout; 'abort'/'retry' → deny on timeout.\n const timer = setTimeout(() => {\n settle({ approved: opts.onTimeout === 'proceed' })\n }, opts.timeoutMs)\n const info: PendingApproval = {\n approvalId,\n toolName: opts.toolName,\n question: opts.question,\n expiresAt: Date.now() + opts.timeoutMs,\n ...(opts.payloadSchema !== undefined ? { payloadSchema: opts.payloadSchema } : {}),\n }\n pending.set(approvalId, { settle, timer, info })\n })\n },\n resolve(approvalId, decision) {\n const entry = pending.get(approvalId)\n if (!entry) return false\n // M20 — coerce the legacy bare-boolean form to a decision object.\n entry.settle(typeof decision === 'boolean' ? { approved: decision } : decision)\n return true\n },\n list() {\n return [...pending.values()].map((p) => p.info)\n },\n }\n}\n"],"mappings":";;;;AAyDA,IAAM,mBAAyC;AAAA,EAC7C;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAMA,IAAM,WAAiC,CAAC,GAAG,gBAAgB;AAkDpD,SAAS,kBAAoC;AAClD,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACnE,aAAW,QAAQ,QAAQ;AACzB,UAAM,SAAS,QAAQ,IAAI,KAAK,MAAM;AACtC,QAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX;AAAA,QACA,SAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM;AACvD,QAAM,IAAI;AAAA,IACR,6DAA6D,OAAO;AAAA,EAEtE;AACF;;;AC7DA,IAAI;AACG,SAAS,sBAAwC;AACtD,qBAAmB,gCAAgC;AACnD,SAAO;AACT;AAEO,SAAS,kCAAoD;AAClE,QAAM,UAAU,oBAAI,IAAqB;AAEzC,SAAO;AAAA,IACL,SAAS,YAAY,MAAM;AACzB,aAAO,IAAI,QAA0B,CAAC,YAAY;AAChD,cAAM,SAAS,CAAC,aAAqC;AACnD,gBAAM,QAAQ,QAAQ,IAAI,UAAU;AACpC,cAAI,CAAC,MAAO;AACZ,uBAAa,MAAM,KAAK;AACxB,kBAAQ,OAAO,UAAU;AACzB,kBAAQ,QAAQ;AAAA,QAClB;AAEA,cAAM,QAAQ,WAAW,MAAM;AAC7B,iBAAO,EAAE,UAAU,KAAK,cAAc,UAAU,CAAC;AAAA,QACnD,GAAG,KAAK,SAAS;AACjB,cAAM,OAAwB;AAAA,UAC5B;AAAA,UACA,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,UAC7B,GAAI,KAAK,kBAAkB,SAAY,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,QAClF;AACA,gBAAQ,IAAI,YAAY,EAAE,QAAQ,OAAO,KAAK,CAAC;AAAA,MACjD,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,YAAY,UAAU;AAC5B,YAAM,QAAQ,QAAQ,IAAI,UAAU;AACpC,UAAI,CAAC,MAAO,QAAO;AAEnB,YAAM,OAAO,OAAO,aAAa,YAAY,EAAE,UAAU,SAAS,IAAI,QAAQ;AAC9E,aAAO;AAAA,IACT;AAAA,IACA,OAAO;AACL,aAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IAChD;AAAA,EACF;AACF;","names":[]}