theokit 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/{actions-virtual-module-3CDQTWOC.js → actions-virtual-module-G4BANOLW.js} +5 -3
  2. package/dist/{actions-virtual-module-3CDQTWOC.js.map → actions-virtual-module-G4BANOLW.js.map} +1 -1
  3. package/dist/{actions-virtual-module-EIPXX4ZB.js → actions-virtual-module-RM7JRY5N.js} +7 -7
  4. package/dist/agent-MN7XGJR3.js +209 -0
  5. package/dist/agent-MN7XGJR3.js.map +1 -0
  6. package/dist/{app-typed-client-7PBFWZUE.js → app-typed-client-5VQE6DNF.js} +7 -7
  7. package/dist/{app-typed-client-CSOK7NPC.js → app-typed-client-Z6BHD4MF.js} +5 -3
  8. package/dist/{app-typed-client-CSOK7NPC.js.map → app-typed-client-Z6BHD4MF.js.map} +1 -1
  9. package/dist/boot/index.js +3 -3
  10. package/dist/{build-HXND27XG.js → build-QDAFSKKW.js} +11 -7
  11. package/dist/{build-HXND27XG.js.map → build-QDAFSKKW.js.map} +1 -1
  12. package/dist/chunk-34YQOXGM.js +37 -0
  13. package/dist/chunk-34YQOXGM.js.map +1 -0
  14. package/dist/{chunk-7YZHAQU7.js → chunk-567NA7Y6.js} +8 -99
  15. package/dist/chunk-567NA7Y6.js.map +1 -0
  16. package/dist/{chunk-3S3BNW5K.js → chunk-5UUOGAJH.js} +2 -2
  17. package/dist/{chunk-BQDGES7C.js → chunk-GBXLKYIA.js} +19 -43
  18. package/dist/chunk-GBXLKYIA.js.map +1 -0
  19. package/dist/chunk-GDN3PXFH.js +101 -0
  20. package/dist/chunk-GDN3PXFH.js.map +1 -0
  21. package/dist/{chunk-TGTNRUH3.js → chunk-H243MR3F.js} +2 -2
  22. package/dist/{chunk-NHJMZCAS.js → chunk-JZHT3LW7.js} +1 -1
  23. package/dist/chunk-JZHT3LW7.js.map +1 -0
  24. package/dist/chunk-KGFNWIMS.js +94 -0
  25. package/dist/chunk-KGFNWIMS.js.map +1 -0
  26. package/dist/{chunk-EXP56GFQ.js → chunk-NBWB4S46.js} +38 -9
  27. package/dist/chunk-NBWB4S46.js.map +1 -0
  28. package/dist/{chunk-F4YUPDJ2.js → chunk-NXTF5PPW.js} +15 -41
  29. package/dist/chunk-NXTF5PPW.js.map +1 -0
  30. package/dist/{chunk-UNLA45FY.js → chunk-OTFIRP6S.js} +76 -46
  31. package/dist/chunk-OTFIRP6S.js.map +1 -0
  32. package/dist/chunk-P37RZRFV.js +31 -0
  33. package/dist/chunk-P37RZRFV.js.map +1 -0
  34. package/dist/{chunk-RYTZYFSD.js → chunk-UOR6JTCI.js} +205 -33
  35. package/dist/chunk-UOR6JTCI.js.map +1 -0
  36. package/dist/{chunk-WFNLNIJX.js → chunk-Z5IGLBWE.js} +1 -1
  37. package/dist/{chunk-O62MW4MT.js → chunk-ZJDKAD3L.js} +55 -14
  38. package/dist/chunk-ZJDKAD3L.js.map +1 -0
  39. package/dist/cli/index.js +22 -6
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/client/index.d.ts +3 -152
  42. package/dist/client/index.js +13 -236
  43. package/dist/client/index.js.map +1 -1
  44. package/dist/{dev-OWW4XVIH.js → dev-TEE4T6ZB.js} +13 -8
  45. package/dist/{dev-OWW4XVIH.js.map → dev-TEE4T6ZB.js.map} +1 -1
  46. package/dist/{dev-emit-5MDSBP5D.js → dev-emit-VJ5CFMPY.js} +5 -3
  47. package/dist/{dev-emit-5MDSBP5D.js.map → dev-emit-VJ5CFMPY.js.map} +1 -1
  48. package/dist/index.js +12 -13
  49. package/dist/index.js.map +1 -1
  50. package/dist/{info-OUEUZOT7.js → info-7PE2PZJI.js} +6 -5
  51. package/dist/{info-OUEUZOT7.js.map → info-7PE2PZJI.js.map} +1 -1
  52. package/dist/{internal-api-4YTJDITC.js → internal-api-5BDI6N5U.js} +20 -20
  53. package/dist/{internal-api-EFKZWIYZ.js → internal-api-J27TYE2I.js} +7 -4
  54. package/dist/load-config-JKYO5RFK.js +14 -0
  55. package/dist/{openapi-FHY6HC6I.js → openapi-MXMLZCXC.js} +7 -4
  56. package/dist/{openapi-FHY6HC6I.js.map → openapi-MXMLZCXC.js.map} +1 -1
  57. package/dist/{registry-34LL7NF4.js → registry-XJUYD2OU.js} +2 -2
  58. package/dist/{routes-EW7TP7NJ.js → routes-NNBEZSGN.js} +5 -3
  59. package/dist/{routes-EW7TP7NJ.js.map → routes-NNBEZSGN.js.map} +1 -1
  60. package/dist/server/auth/index.js +1 -1
  61. package/dist/server/define/index.d.ts +3 -70
  62. package/dist/server/define/index.js +2 -4
  63. package/dist/server/http/index.js +2 -2
  64. package/dist/server/index.d.ts +1 -3
  65. package/dist/server/index.js +90 -105
  66. package/dist/server/index.js.map +1 -1
  67. package/dist/server/jobs/index.js +4 -4
  68. package/dist/server/observability/index.js +1 -1
  69. package/dist/server/scan/index.js +1 -1
  70. package/dist/{start-KIQ5TTLR.js → start-7MQEEQH4.js} +68 -8
  71. package/dist/start-7MQEEQH4.js.map +1 -0
  72. package/dist/{static-55G3LX2I.js → static-7ARBVDJF.js} +4 -4
  73. package/dist/vite-plugin/index.js +12 -13
  74. package/dist/{vite-plugin-RK66K26Z.js → vite-plugin-GC6WCU4P.js} +11 -7
  75. package/package.json +2 -6
  76. package/dist/agent-events-DosDXkSV.d.ts +0 -94
  77. package/dist/chunk-2KZQPDYR.js +0 -226
  78. package/dist/chunk-2KZQPDYR.js.map +0 -1
  79. package/dist/chunk-7YZHAQU7.js.map +0 -1
  80. package/dist/chunk-BQDGES7C.js.map +0 -1
  81. package/dist/chunk-EXP56GFQ.js.map +0 -1
  82. package/dist/chunk-F4YUPDJ2.js.map +0 -1
  83. package/dist/chunk-NHJMZCAS.js.map +0 -1
  84. package/dist/chunk-O62MW4MT.js.map +0 -1
  85. package/dist/chunk-RYTZYFSD.js.map +0 -1
  86. package/dist/chunk-UNLA45FY.js.map +0 -1
  87. package/dist/chunk-ZSTZXR2D.js +0 -208
  88. package/dist/chunk-ZSTZXR2D.js.map +0 -1
  89. package/dist/server/agent/index.d.ts +0 -229
  90. package/dist/server/agent/index.js +0 -17
  91. package/dist/start-KIQ5TTLR.js.map +0 -1
  92. /package/dist/{actions-virtual-module-EIPXX4ZB.js.map → actions-virtual-module-RM7JRY5N.js.map} +0 -0
  93. /package/dist/{app-typed-client-7PBFWZUE.js.map → app-typed-client-5VQE6DNF.js.map} +0 -0
  94. /package/dist/{chunk-3S3BNW5K.js.map → chunk-5UUOGAJH.js.map} +0 -0
  95. /package/dist/{chunk-TGTNRUH3.js.map → chunk-H243MR3F.js.map} +0 -0
  96. /package/dist/{chunk-WFNLNIJX.js.map → chunk-Z5IGLBWE.js.map} +0 -0
  97. /package/dist/{internal-api-4YTJDITC.js.map → internal-api-5BDI6N5U.js.map} +0 -0
  98. /package/dist/{internal-api-EFKZWIYZ.js.map → internal-api-J27TYE2I.js.map} +0 -0
  99. /package/dist/{server/agent/index.js.map → load-config-JKYO5RFK.js.map} +0 -0
  100. /package/dist/{registry-34LL7NF4.js.map → registry-XJUYD2OU.js.map} +0 -0
  101. /package/dist/{static-55G3LX2I.js.map → static-7ARBVDJF.js.map} +0 -0
  102. /package/dist/{vite-plugin-RK66K26Z.js.map → vite-plugin-GC6WCU4P.js.map} +0 -0
@@ -1,43 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
+ import {
4
+ scanAgents
5
+ } from "./chunk-34YQOXGM.js";
3
6
  import {
4
7
  compilePattern,
5
8
  scanServerActions,
6
9
  scanServerRoutes,
7
- scanWebSocketRoutes,
8
- walkSourceFiles
9
- } from "./chunk-BQDGES7C.js";
10
-
11
- // src/server/scan/agent-scan.ts
12
- import { existsSync, statSync } from "fs";
13
- import { extname, join, relative } from "path";
14
- var AGENT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
15
- var TEST_FILE = /\.(test|spec)$/;
16
- function scanAgents(projectRoot) {
17
- const agentsDir = join(projectRoot, "agents");
18
- if (!existsSync(agentsDir) || !statSync(agentsDir).isDirectory()) {
19
- return [];
20
- }
21
- const results = [];
22
- walkSourceFiles(agentsDir, { extensions: AGENT_EXTENSIONS }, (absPath) => {
23
- let rel = relative(agentsDir, absPath);
24
- rel = rel.replace(/\\/g, "/");
25
- rel = rel.slice(0, -extname(rel).length);
26
- if (TEST_FILE.test(rel)) return;
27
- if (rel.endsWith("/index")) rel = rel.slice(0, -6);
28
- if (rel === "index" || rel === "") return;
29
- results.push({
30
- filePath: absPath,
31
- agentPath: `/api/agents/${rel}`,
32
- name: rel
33
- });
34
- });
35
- return results;
36
- }
10
+ scanWebSocketRoutes
11
+ } from "./chunk-GBXLKYIA.js";
37
12
 
38
13
  // src/server/scan/manifest.ts
39
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
40
- import { join as join2, resolve, relative as relative2, dirname } from "path";
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
15
+ import { join, resolve, relative, dirname } from "path";
41
16
  function generateManifest(serverDir, projectRoot = dirname(serverDir)) {
42
17
  const routes = scanServerRoutes(serverDir);
43
18
  const actions = scanServerActions(serverDir);
@@ -47,22 +22,22 @@ function generateManifest(serverDir, projectRoot = dirname(serverDir)) {
47
22
  version: 1,
48
23
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
49
24
  routes: routes.map((r) => ({
50
- filePath: relative2(serverDir, r.filePath),
25
+ filePath: relative(serverDir, r.filePath),
51
26
  routePath: r.routePath,
52
27
  paramNames: r.paramNames,
53
28
  ...r.methods !== void 0 ? { methods: r.methods } : {}
54
29
  })),
55
30
  actions: actions.map((a) => ({
56
- filePath: relative2(serverDir, a.filePath),
31
+ filePath: relative(serverDir, a.filePath),
57
32
  actionPath: a.actionPath
58
33
  })),
59
34
  websockets: websockets.map((w) => ({
60
- filePath: relative2(serverDir, w.filePath),
35
+ filePath: relative(serverDir, w.filePath),
61
36
  wsPath: w.wsPath
62
37
  })),
63
38
  agents: agents.map((a) => ({
64
39
  // Relative to projectRoot (agents/ is outside serverDir).
65
- filePath: relative2(projectRoot, a.filePath),
40
+ filePath: relative(projectRoot, a.filePath),
66
41
  agentPath: a.agentPath,
67
42
  name: a.name
68
43
  }))
@@ -70,12 +45,12 @@ function generateManifest(serverDir, projectRoot = dirname(serverDir)) {
70
45
  }
71
46
  function writeManifest(manifest, outputDir) {
72
47
  mkdirSync(outputDir, { recursive: true });
73
- const manifestPath = join2(outputDir, "manifest.json");
48
+ const manifestPath = join(outputDir, "manifest.json");
74
49
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
75
50
  }
76
51
  function loadManifest(distDir, serverDir) {
77
- const manifestPath = join2(distDir, "manifest.json");
78
- if (!existsSync2(manifestPath)) {
52
+ const manifestPath = join(distDir, "manifest.json");
53
+ if (!existsSync(manifestPath)) {
79
54
  throw new Error(`No manifest found at ${manifestPath}. Run "theo build" first.`);
80
55
  }
81
56
  const raw = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -107,9 +82,8 @@ function loadManifest(distDir, serverDir) {
107
82
  }
108
83
 
109
84
  export {
110
- scanAgents,
111
85
  generateManifest,
112
86
  writeManifest,
113
87
  loadManifest
114
88
  };
115
- //# sourceMappingURL=chunk-F4YUPDJ2.js.map
89
+ //# sourceMappingURL=chunk-NXTF5PPW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/manifest.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time manifest emitter / loader. All paths derived from `distDir`\n * + `serverDir`, themselves resolved from `process.cwd()`. No HTTP input.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { join, resolve, relative, dirname } from 'node:path'\n\nimport { scanServerActions } from './action-scan.js'\nimport type { ActionNode } from './action-scan.js'\nimport { scanAgents } from './agent-scan.js'\nimport type { AgentNode } from './agent-scan.js'\nimport { compilePattern } from './match.js'\nimport type { ServerRouteNode } from './match.js'\nimport { scanServerRoutes } from './scan.js'\nimport { scanWebSocketRoutes } from './ws-scan.js'\nimport type { WebSocketRouteNode } from './ws-scan.js'\n\n// --- Manifest Types ---\n\nexport interface ManifestRoute {\n filePath: string\n routePath: string\n paramNames: string[]\n /** HTTP methods (uppercase) the route file exports. Optional — manifests\n * generated before G1 omit this; loaders treat absence as \"unknown\". */\n methods?: string[]\n}\n\nexport interface ManifestAction {\n filePath: string\n actionPath: string\n}\n\nexport interface ManifestWebSocket {\n filePath: string\n wsPath: string\n}\n\n/** M2 — a top-level `agents/*.ts` convention entry. `filePath` is relative to the\n * project root (agents live OUTSIDE `serverDir`), unlike routes/actions/ws. */\nexport interface ManifestAgent {\n filePath: string\n agentPath: string\n name: string\n}\n\nexport interface TheoManifest {\n version: 1\n generatedAt: string\n routes: ManifestRoute[]\n actions: ManifestAction[]\n websockets: ManifestWebSocket[]\n /** M2 — optional for backward compat: manifests generated before M2 omit it. */\n agents?: ManifestAgent[]\n}\n\nexport interface LoadedManifest {\n routes: ServerRouteNode[]\n actions: ActionNode[]\n websockets: WebSocketRouteNode[]\n agents: AgentNode[]\n}\n\n// --- Generate ---\n\nexport function generateManifest(\n serverDir: string,\n // Agents live at `<projectRoot>/agents`, a sibling of `serverDir` (LOCKED naming).\n // Defaults to the server dir's parent; overridable for tests / non-standard layouts.\n projectRoot: string = dirname(serverDir),\n): TheoManifest {\n const routes = scanServerRoutes(serverDir)\n const actions = scanServerActions(serverDir)\n const websockets = scanWebSocketRoutes(serverDir)\n const agents = scanAgents(projectRoot)\n\n return {\n version: 1,\n generatedAt: new Date().toISOString(),\n routes: routes.map((r) => ({\n filePath: relative(serverDir, r.filePath),\n routePath: r.routePath,\n paramNames: r.paramNames,\n ...(r.methods !== undefined ? { methods: r.methods } : {}),\n })),\n actions: actions.map((a) => ({\n filePath: relative(serverDir, a.filePath),\n actionPath: a.actionPath,\n })),\n websockets: websockets.map((w) => ({\n filePath: relative(serverDir, w.filePath),\n wsPath: w.wsPath,\n })),\n agents: agents.map((a) => ({\n // Relative to projectRoot (agents/ is outside serverDir).\n filePath: relative(projectRoot, a.filePath),\n agentPath: a.agentPath,\n name: a.name,\n })),\n }\n}\n\n// --- Write ---\n\nexport function writeManifest(manifest: TheoManifest, outputDir: string): void {\n mkdirSync(outputDir, { recursive: true })\n const manifestPath = join(outputDir, 'manifest.json')\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))\n}\n\n// --- Load ---\n\nexport function loadManifest(distDir: string, serverDir: string): LoadedManifest {\n const manifestPath = join(distDir, 'manifest.json')\n\n if (!existsSync(manifestPath)) {\n throw new Error(`No manifest found at ${manifestPath}. Run \"theo build\" first.`)\n }\n\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8')) as TheoManifest\n\n const routes: ServerRouteNode[] = raw.routes.map((r) => {\n const { pattern, paramNames } = compilePattern(r.routePath)\n return {\n filePath: resolve(serverDir, r.filePath),\n routePath: r.routePath,\n paramNames,\n pattern,\n ...(r.methods !== undefined ? { methods: r.methods } : {}),\n }\n })\n\n const actions: ActionNode[] = raw.actions.map((a) => ({\n filePath: resolve(serverDir, a.filePath),\n actionPath: a.actionPath,\n }))\n\n const websockets: WebSocketRouteNode[] = raw.websockets.map((w) => ({\n filePath: resolve(serverDir, w.filePath),\n wsPath: w.wsPath,\n }))\n\n // M2 — agents resolve relative to the project root (sibling of serverDir).\n // `?? []` keeps pre-M2 manifests (no `agents` field) loadable (fail-safe).\n const projectRoot = dirname(serverDir)\n const agents: AgentNode[] = (raw.agents ?? []).map((a) => ({\n filePath: resolve(projectRoot, a.filePath),\n agentPath: a.agentPath,\n name: a.name,\n }))\n\n return { routes, actions, websockets, agents }\n}\n"],"mappings":";;;;;;;;;;;;;AAIA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,MAAM,SAAS,UAAU,eAAe;AA4D1C,SAAS,iBACd,WAGA,cAAsB,QAAQ,SAAS,GACzB;AACd,QAAM,SAAS,iBAAiB,SAAS;AACzC,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,aAAa,oBAAoB,SAAS;AAChD,QAAM,SAAS,WAAW,WAAW;AAErC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA,MACzB,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,WAAW,EAAE;AAAA,MACb,YAAY,EAAE;AAAA,MACd,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1D,EAAE;AAAA,IACF,SAAS,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,YAAY,EAAE;AAAA,IAChB,EAAE;AAAA,IACF,YAAY,WAAW,IAAI,CAAC,OAAO;AAAA,MACjC,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,QAAQ,EAAE;AAAA,IACZ,EAAE;AAAA,IACF,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA;AAAA,MAEzB,UAAU,SAAS,aAAa,EAAE,QAAQ;AAAA,MAC1C,WAAW,EAAE;AAAA,MACb,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AACF;AAIO,SAAS,cAAc,UAAwB,WAAyB;AAC7E,YAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,QAAM,eAAe,KAAK,WAAW,eAAe;AACpD,gBAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAC/D;AAIO,SAAS,aAAa,SAAiB,WAAmC;AAC/E,QAAM,eAAe,KAAK,SAAS,eAAe;AAElD,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,UAAM,IAAI,MAAM,wBAAwB,YAAY,2BAA2B;AAAA,EACjF;AAEA,QAAM,MAAM,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAE1D,QAAM,SAA4B,IAAI,OAAO,IAAI,CAAC,MAAM;AACtD,UAAM,EAAE,SAAS,WAAW,IAAI,eAAe,EAAE,SAAS;AAC1D,WAAO;AAAA,MACL,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,MACvC,WAAW,EAAE;AAAA,MACb;AAAA,MACA;AAAA,MACA,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AAED,QAAM,UAAwB,IAAI,QAAQ,IAAI,CAAC,OAAO;AAAA,IACpD,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,IACvC,YAAY,EAAE;AAAA,EAChB,EAAE;AAEF,QAAM,aAAmC,IAAI,WAAW,IAAI,CAAC,OAAO;AAAA,IAClE,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,IACvC,QAAQ,EAAE;AAAA,EACZ,EAAE;AAIF,QAAM,cAAc,QAAQ,SAAS;AACrC,QAAM,UAAuB,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,IACzD,UAAU,QAAQ,aAAa,EAAE,QAAQ;AAAA,IACzC,WAAW,EAAE;AAAA,IACb,MAAM,EAAE;AAAA,EACV,EAAE;AAEF,SAAO,EAAE,QAAQ,SAAS,YAAY,OAAO;AAC/C;","names":[]}
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
+ import {
4
+ getApprovalRegistry
5
+ } from "./chunk-NBWB4S46.js";
3
6
  import {
4
7
  validateCsrfRequest
5
8
  } from "./chunk-WR4F4EEZ.js";
@@ -41,8 +44,63 @@ function resolveTransformer(selector) {
41
44
  return selector;
42
45
  }
43
46
 
47
+ // src/server/agent/approve-agent.ts
48
+ var APPROVE_SEGMENT = "/approve/";
49
+ function parseApprovalId(urlPath) {
50
+ const at = urlPath.indexOf(APPROVE_SEGMENT);
51
+ if (at === -1) return null;
52
+ const id = urlPath.slice(at + APPROVE_SEGMENT.length);
53
+ return id.length > 0 && !id.includes("/") ? id : null;
54
+ }
55
+ function isApprovalPath(urlPath) {
56
+ return urlPath.includes(APPROVE_SEGMENT);
57
+ }
58
+ function jsonError(status, code, message) {
59
+ return new Response(JSON.stringify({ error: { code, message } }), {
60
+ status,
61
+ headers: { "content-type": "application/json" }
62
+ });
63
+ }
64
+ function parseApprovalBody(body) {
65
+ if (typeof body !== "object" || body === null) return null;
66
+ const b = body;
67
+ return typeof b.approved === "boolean" ? { approved: b.approved } : null;
68
+ }
69
+ async function handleAgentApproval(request, urlPath, registry, csrfMode = "strict") {
70
+ if (csrfMode !== "off") {
71
+ const csrf = validateCsrfRequest(request);
72
+ if (!csrf.valid && csrfMode === "strict") {
73
+ return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
74
+ }
75
+ }
76
+ const approvalId = parseApprovalId(urlPath);
77
+ if (approvalId === null) {
78
+ return jsonError(400, "BAD_REQUEST", "Approval path must be /api/agents/<name>/approve/<id>.");
79
+ }
80
+ let body = null;
81
+ try {
82
+ body = await request.json();
83
+ } catch {
84
+ }
85
+ const parsed = parseApprovalBody(body);
86
+ if (parsed === null) {
87
+ return jsonError(400, "BAD_REQUEST", "Request body must contain a boolean `approved`.");
88
+ }
89
+ const resolved = registry.resolve(approvalId, parsed.approved);
90
+ if (!resolved) {
91
+ return jsonError(404, "NOT_PENDING", `No pending approval for id '${approvalId}'.`);
92
+ }
93
+ return new Response(JSON.stringify({ resolved: true }), {
94
+ status: 200,
95
+ headers: { "content-type": "application/json" }
96
+ });
97
+ }
98
+
44
99
  // src/server/agent/mount-agent.ts
45
- import { compileAgentModule, streamAgentUIMessages } from "@theokit/agents";
100
+ import {
101
+ compileAgentModule,
102
+ streamAgentUIMessages
103
+ } from "@theokit/agents";
46
104
 
47
105
  // src/server/define/ui-message-stream-response.ts
48
106
  var UI_MESSAGE_STREAM_HEADERS = {
@@ -78,7 +136,7 @@ function partText(part) {
78
136
  const p = part;
79
137
  return p.type === "text" && typeof p.text === "string" ? p.text : "";
80
138
  }
81
- function jsonError(status, code, message) {
139
+ function jsonError2(status, code, message) {
82
140
  return new Response(JSON.stringify({ error: { code, message } }), {
83
141
  status,
84
142
  headers: { "content-type": "application/json" }
@@ -105,7 +163,7 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
105
163
  if (csrfMode !== "off") {
106
164
  const csrf = validateCsrfRequest(request);
107
165
  if (!csrf.valid && csrfMode === "strict") {
108
- return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
166
+ return jsonError2(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
109
167
  }
110
168
  }
111
169
  const compiled = compileAgentModule(mod, source);
@@ -116,48 +174,19 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
116
174
  }
117
175
  const input = parseAgentRequestBody(body);
118
176
  if (input === null) {
119
- return jsonError(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
120
- }
121
- return uiMessageStreamResponse(streamAgentUIMessages(compiled, apiKey, input));
122
- }
123
-
124
- // src/server/agent/provider-resolver.ts
125
- var DEFAULT_REGISTRY = [
126
- {
127
- name: "openrouter",
128
- envKey: "OPENROUTER_API_KEY",
129
- baseUrl: "https://openrouter.ai/api/v1",
130
- priority: 1
131
- },
132
- {
133
- name: "openai",
134
- envKey: "OPENAI_API_KEY",
135
- baseUrl: "https://api.openai.com/v1",
136
- priority: 2
137
- },
138
- {
139
- name: "anthropic",
140
- envKey: "ANTHROPIC_API_KEY",
141
- baseUrl: "https://api.anthropic.com",
142
- priority: 3
143
- }
144
- ];
145
- var registry = [...DEFAULT_REGISTRY];
146
- function resolveProvider() {
147
- const sorted = [...registry].sort((a, b) => a.priority - b.priority);
148
- for (const desc of sorted) {
149
- const apiKey = process.env[desc.envKey];
150
- if (apiKey && apiKey.length > 0) {
151
- return {
152
- name: desc.name,
153
- apiKey,
154
- baseUrl: desc.baseUrl
155
- };
156
- }
177
+ return jsonError2(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
157
178
  }
158
- const envKeys = sorted.map((p) => p.envKey).join(" OR ");
159
- throw new Error(
160
- `No LLM provider API key found in environment. Set one of: ${envKeys}. Get a free OpenRouter key at https://openrouter.ai/keys (recommended \u2014 one key, many models).`
179
+ const gated = compiled.hitl;
180
+ const registry = getApprovalRegistry();
181
+ const hitl = gated && gated.size > 0 ? {
182
+ gated,
183
+ awaitApproval: (approvalId, opts) => registry.register(approvalId, {
184
+ timeoutMs: opts.timeout ?? 3e5,
185
+ onTimeout: opts.onTimeout ?? "abort"
186
+ })
187
+ } : void 0;
188
+ return uiMessageStreamResponse(
189
+ streamAgentUIMessages(compiled, apiKey, { ...input, hitl, signal: request.signal })
161
190
  );
162
191
  }
163
192
 
@@ -227,9 +256,10 @@ function pickHeaderString(value) {
227
256
 
228
257
  export {
229
258
  resolveTransformer,
259
+ isApprovalPath,
260
+ handleAgentApproval,
230
261
  mountAgent,
231
- resolveProvider,
232
262
  incomingMessageToWebRequest,
233
263
  writeWebResponseToServerResponse
234
264
  };
235
- //# sourceMappingURL=chunk-UNLA45FY.js.map
265
+ //# sourceMappingURL=chunk-OTFIRP6S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/transformer.ts","../src/server/agent/approve-agent.ts","../src/server/agent/mount-agent.ts","../src/server/define/ui-message-stream-response.ts","../src/server/http/node-web-adapter.ts"],"sourcesContent":["import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * M4 (theokit-ai-first) — the HITL approve endpoint: `POST /api/agents/<name>/approve/<approvalId>`.\n *\n * The counterpart to `mountAgent`'s HITL pause. While a gated tool holds the SDK run paused (the\n * awaited `pre_tool_call` hook), the client POSTs here with `{ approved }`; this resolves the\n * pending approval in the shared registry, which un-pauses the run (allow) or vetoes the tool (deny).\n *\n * Web-Standard `Request` → `Response`, one wiring point shared by dev (vite middleware) and prod\n * (built server) so the two never drift (EC-4 parity with `mountAgent`). The registry is INJECTED —\n * dev/prod pass the process singleton (`getApprovalRegistry`), tests pass a fresh instance.\n */\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApprovalRegistry } from './approval-registry.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\n\n/**\n * Extract the `<approvalId>` from a `/api/agents/<name>/approve/<approvalId>` path.\n * Returns `null` when the path has no `/approve/` segment or an empty / nested id.\n */\nexport function parseApprovalId(urlPath: string): string | null {\n const at = urlPath.indexOf(APPROVE_SEGMENT)\n if (at === -1) return null\n const id = urlPath.slice(at + APPROVE_SEGMENT.length)\n return id.length > 0 && !id.includes('/') ? id : null\n}\n\n/** True when `urlPath` targets a HITL approve endpoint (used by dev/prod routing to branch early). */\nexport function isApprovalPath(urlPath: string): boolean {\n return urlPath.includes(APPROVE_SEGMENT)\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/** Extract `{ approved: boolean }` from an untrusted body; `null` when the shape is wrong. */\nfunction parseApprovalBody(body: unknown): { approved: boolean } | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n return typeof b.approved === 'boolean' ? { approved: b.approved } : null\n}\n\n/**\n * Resolve a pending HITL approval. CSRF-guarded like `mountAgent` (the custom `X-Theo-Action`\n * header + Origin match) — a cross-origin POST must not approve a paused tool. Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\n * 400 BAD_REQUEST — no `/approve/<id>` in the path, or body lacks a boolean `approved`\n * 404 NOT_PENDING — the id is unknown or already settled (idempotent double-submit)\n * 200 { resolved:true } — the approval was settled by this call\n */\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\n): Promise<Response> {\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const approvalId = parseApprovalId(urlPath)\n if (approvalId === null) {\n return jsonError(400, 'BAD_REQUEST', 'Approval path must be /api/agents/<name>/approve/<id>.')\n }\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n const parsed = parseApprovalBody(body)\n if (parsed === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request body must contain a boolean `approved`.')\n }\n\n const resolved = registry.resolve(approvalId, parsed.approved)\n if (!resolved) {\n return jsonError(404, 'NOT_PENDING', `No pending approval for id '${approvalId}'.`)\n }\n return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M2 (theokit-ai-first) — mount a scanned `agents/<name>.ts` module as an SSE endpoint.\n *\n * The SINGLE wiring point shared by dev (vite middleware) and prod (built server), so the\n * two never drift (EC-4). Web-Standard `Request` → `Response`: parse the chat body, compile\n * the module (`compileAgentModule`, converges both surfaces), stream via the M0/M1 canonical\n * protocol (`streamAgentUIMessages` → `uiMessageStreamResponse`).\n *\n * Request body — accepts the `@ai-sdk/react` `useChat` shape (`{ id, messages: UIMessage[] }`,\n * the typed-client path) AND a simple `{ message, sessionId? }` shape (M0/M1-style clients).\n */\nimport {\n compileAgentModule,\n streamAgentUIMessages,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\n\nimport { uiMessageStreamResponse } from '../define/ui-message-stream-response.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { getApprovalRegistry } from './approval-registry.js'\n\n/** The message + session extracted from a chat request, or `null` when the body is invalid. */\nexport interface AgentRequestInput {\n message: string\n sessionId: string\n}\n\n/** Extract the text of a `{ type: 'text', text: string }` part from an untrusted value. */\nfunction partText(part: unknown): string {\n if (typeof part !== 'object' || part === null) return ''\n const p = part as Record<string, unknown>\n return p.type === 'text' && typeof p.text === 'string' ? p.text : ''\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Extract `{ message, sessionId }` from a chat request body. Returns `null` when neither the\n * ai-sdk `messages` shape nor the simple `message` shape yields non-empty user text.\n */\nexport function parseAgentRequestBody(body: unknown): AgentRequestInput | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n\n // ai-sdk useChat shape: { id, messages: UIMessage[] } — take the last message's text parts.\n if (Array.isArray(b.messages) && b.messages.length > 0) {\n const last = b.messages[b.messages.length - 1] as Record<string, unknown>\n const parts = Array.isArray(last.parts) ? last.parts : []\n const text = parts.map(partText).join('')\n if (text.length === 0) return null\n const sessionId = typeof b.id === 'string' && b.id.length > 0 ? b.id : crypto.randomUUID()\n return { message: text, sessionId }\n }\n\n // Simple shape: { message, sessionId? }.\n if (typeof b.message === 'string' && b.message.length > 0) {\n const sessionId =\n typeof b.sessionId === 'string' && b.sessionId.length > 0 ? b.sessionId : crypto.randomUUID()\n return { message: b.message, sessionId }\n }\n\n return null\n}\n\n/**\n * Mount a loaded agent module as a `Response`. `apiKey` is resolved by the caller\n * (`resolveProvider`). `source` labels a fail-fast `AgentDefinitionError` (the file path).\n */\nexport async function mountAgent(\n mod: unknown,\n request: Request,\n apiKey: string,\n source = 'agent module',\n csrfMode: CsrfMode = 'strict',\n): Promise<Response> {\n // Enforce CSRF BEFORE any work — an agent run spends real LLM tokens, so a cross-origin\n // POST must be rejected before it reaches the SDK (parity with actions/routes). The custom\n // `X-Theo-Action` header + Origin match is the same defense `executeRoute`/`executeAction`\n // apply; the `useAgent` client sends the header. `off` skips; `warn` never blocks.\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const compiled = compileAgentModule(mod, source)\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n\n const input = parseAgentRequestBody(body)\n if (input === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request must contain a non-empty message or messages[].')\n }\n\n // When the agent has @HumanInTheLoop-gated tools (M4), wire the pause: the plugin's `awaitApproval`\n // registers a pending approval in the shared registry (the Promise that PAUSES the run); the\n // approve route (`handleAgentApproval`) resolves it. No gated tools ⇒ the M2 stream path unchanged.\n const gated = compiled.hitl\n const registry = getApprovalRegistry()\n const hitl =\n gated && gated.size > 0\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions) =>\n registry.register(approvalId, {\n timeoutMs: opts.timeout ?? 300_000,\n onTimeout: opts.onTimeout ?? 'abort',\n }),\n }\n : undefined\n\n return uiMessageStreamResponse(\n streamAgentUIMessages(compiled, apiKey, { ...input, hitl, signal: request.signal }),\n )\n}\n","import type { UIMessageChunk } from 'ai'\n\n/**\n * M0 (theokit-ai-first) — serialize a stream of ai-sdk `UIMessageChunk`s into a\n * Web-Standards `Response` on the UIMessageStream wire so `@ai-sdk/react`'s\n * `useChat` consumes it WITHOUT a custom adapter.\n *\n * Wire contract (must match ai-sdk's consumer transport exactly):\n * - `content-type: text/event-stream`\n * - `x-vercel-ai-ui-message-stream: v1` — the version marker `useChat` checks\n * - each chunk framed as `data: ${JSON.stringify(chunk)}\\n\\n`\n * - a terminal `data: [DONE]\\n\\n` after the last chunk (ignored by the parser)\n *\n * Web Standards only — `Response` + `ReadableStream` (G8, no node:http). The\n * headers commit before the stream starts.\n *\n * Fail-clear (error-handling.md): if the source iterable throws mid-stream, the\n * `[DONE]` terminal is still flushed and the stream is closed — never left\n * hanging. (The translator upstream already closes gracefully; this is defense\n * in depth for any other chunk source.)\n */\nconst UI_MESSAGE_STREAM_HEADERS = {\n 'content-type': 'text/event-stream',\n 'x-vercel-ai-ui-message-stream': 'v1',\n} as const\n\nconst DONE_FRAME = 'data: [DONE]\\n\\n'\n\nfunction encode(text: string): Uint8Array {\n return new TextEncoder().encode(text)\n}\n\nexport function uiMessageStreamResponse(chunks: AsyncIterable<UIMessageChunk>): Response {\n const stream = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of chunks) {\n controller.enqueue(encode(`data: ${JSON.stringify(chunk)}\\n\\n`))\n }\n } catch {\n // The source iterable aborted mid-stream. The upstream translator owns\n // error semantics (it surfaces failures as chunks + closes gracefully);\n // this transport's sole guarantee is a terminated stream — fall through\n // to the DONE terminal rather than re-throw and error an open stream.\n } finally {\n controller.enqueue(encode(DONE_FRAME))\n controller.close()\n }\n },\n })\n return new Response(stream, { headers: UI_MESSAGE_STREAM_HEADERS })\n}\n","/**\n * T5a.2 Phase G slice 5/N — Node adapter shim for the Web request handler.\n *\n * Bridges Node's `IncomingMessage` + `ServerResponse` shape to the\n * Web-Standards `executeWebRequest` (Phase A → G slices 1-4). Per\n * ADR-0028 R3a, the Node adapter is the ONLY place IncomingMessage ↔\n * Request conversion happens — every other layer of `server/` flows\n * through Web `Request`/`Response`.\n *\n * **Two conversions:**\n *\n * 1. `incomingMessageToWebRequest(req)` — Node → Web. Reads\n * `req.method`, `req.url`, `req.headers`, and (for POST/PUT/etc.)\n * drains the Node Readable body into a Web `ReadableStream`. The\n * request URL is resolved to absolute form using `req.headers.host`\n * (Web Request guarantees absolute URL).\n *\n * 2. `writeWebResponseToServerResponse(response, res)` — Web → Node.\n * Sets status code + status text, copies headers (including all\n * `Set-Cookie` values via `getSetCookie()`), then drains the Web\n * `ReadableStream` body into the Node ServerResponse.\n *\n * Plus the convenience composer `executeWebRequestFromNode(req, res,\n * routeModule, opts?)` that wires both ends — exactly what api-middleware\n * (and the prod CLI start path) need to migrate from the legacy\n * `executeRoute` to `executeWebRequest` without touching call sites.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase G slice 5/N (closes the executor bridge surface).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { Readable } from 'node:stream'\n\nimport { executeWebRequest, type ExecuteWebRequestOptions } from '../web-handler.js'\n\n/**\n * Build a Web `Request` from a Node `IncomingMessage`. The Web Request\n * spec requires an absolute URL; we synthesize one from\n * `req.headers.host` (or fall back to `localhost` for test doubles).\n *\n * For methods with a body (POST/PUT/PATCH/DELETE), the Node Readable\n * stream is wrapped as a Web ReadableStream via `Readable.toWeb()` so\n * downstream consumers can call `request.json()` / `request.formData()`\n * / `request.text()` natively.\n *\n * EC-1: Node sometimes provides headers as `string | string[]`. Web\n * `Headers` collapses to single-comma-joined strings — we do the join\n * manually for repeated headers because `Headers.append` would create\n * multi-value entries which behave differently on `.get()`.\n */\nexport function incomingMessageToWebRequest(req: IncomingMessage): Request {\n const host = pickHeaderString(req.headers.host) ?? 'localhost'\n const url = `http://${host}${req.url ?? '/'}`\n\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n headers.set(key, value.join(', '))\n } else {\n headers.set(key, value)\n }\n }\n\n const method = (req.method ?? 'GET').toUpperCase()\n const hasBody = method !== 'GET' && method !== 'HEAD'\n\n if (!hasBody) {\n return new Request(url, { method, headers })\n }\n\n // Drain Node's Readable into a Web ReadableStream. `Readable.toWeb` is\n // available in Node 18+ (theokit's engines.node floor is 22+, so safe).\n const webStream = Readable.toWeb(req) as ReadableStream\n return new Request(url, {\n method,\n headers,\n body: webStream,\n // EC-2: Node 18+ requires `duplex: 'half'` when body is a stream.\n // The `RequestInit` type omits it (Web spec gap); cast accordingly.\n ...({ duplex: 'half' } as { duplex: 'half' }),\n })\n}\n\n/**\n * Write a Web `Response` into a Node `ServerResponse`. Mirrors the Node\n * `res.writeHead` + `res.end` pattern.\n *\n * Set-Cookie is the only multi-value header the Web spec exposes via\n * `getSetCookie()`. We append each entry individually so Node emits\n * separate `Set-Cookie:` lines per the HTTP spec.\n *\n * If the Response body is a `ReadableStream`, it's piped chunk-by-chunk\n * to `res`. Empty body (null) → just close.\n */\nexport async function writeWebResponseToServerResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n // Status + headers FIRST (writeHead locks them).\n // EC-3: Set-Cookie needs special handling (writeHead's plain object\n // shape conflicts with multi-value headers; we set them via setHeader\n // BEFORE writeHead so the array form is preserved).\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) {\n res.setHeader('Set-Cookie', setCookies)\n }\n const otherHeaders: Record<string, string> = {}\n for (const [key, value] of response.headers.entries()) {\n if (key.toLowerCase() === 'set-cookie') continue\n otherHeaders[key] = value\n }\n res.writeHead(response.status, response.statusText, otherHeaders)\n\n // Body — drain ReadableStream OR just end() for null body.\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Node's res.write accepts Uint8Array natively (no conversion needed).\n res.write(value)\n }\n res.end()\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Convenience composer — full request lifecycle Node → Web → Node.\n *\n * Use case: existing api-middleware (and the prod CLI start path)\n * receive Node `(req, res)` from `http.createServer`. To migrate to the\n * Web-Standards executor without rewriting every call site, wrap with\n * this composer:\n *\n * import * as users from './app/users/route.js'\n * await executeWebRequestFromNode(req, res, users, { csrfMode: 'strict' })\n *\n * The Web request is built, dispatched through executeWebRequest, and\n * the Response is drained back into `res`. Caller does NOT need to call\n * `res.end()` afterwards — this composer handles it.\n */\nexport async function executeWebRequestFromNode(\n req: IncomingMessage,\n res: ServerResponse,\n routeModule: Parameters<typeof executeWebRequest>[1],\n opts?: ExecuteWebRequestOptions,\n): Promise<void> {\n const webRequest = incomingMessageToWebRequest(req)\n const webResponse = await executeWebRequest(webRequest, routeModule, opts)\n await writeWebResponseToServerResponse(webResponse, res)\n}\n\n/** Pick the first usable string from Node's `string | string[] | undefined` headers. */\nfunction pickHeaderString(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n for (const v of value) if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n"],"mappings":"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","names":["jsonError"]}
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/server/_internal/scan-walker.ts
5
+ import { readdirSync } from "fs";
6
+ import { extname, join, resolve } from "path";
7
+ function walkSourceFiles(root, opts, onFile) {
8
+ const skipPrefixes = opts.skipPrefixes ?? ["_", "."];
9
+ const visit = (dir) => {
10
+ let entries;
11
+ try {
12
+ entries = readdirSync(dir, { withFileTypes: true });
13
+ } catch {
14
+ return;
15
+ }
16
+ for (const entry of entries) {
17
+ const fullPath = join(dir, entry.name);
18
+ if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {
19
+ visit(fullPath);
20
+ } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {
21
+ onFile(resolve(fullPath));
22
+ }
23
+ }
24
+ };
25
+ visit(root);
26
+ }
27
+
28
+ export {
29
+ walkSourceFiles
30
+ };
31
+ //# sourceMappingURL=chunk-P37RZRFV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/_internal/scan-walker.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks directories derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync } from 'node:fs'\nimport { extname, join, resolve } from 'node:path'\n\n/**\n * Options for walkSourceFiles.\n *\n * Sequential by design — callers (route precedence) depend on insertion\n * order (EC-19 documented decision). Async-callback support is implicit\n * via JavaScript's serial await loop.\n *\n * Tested on macOS + Linux. Windows long-path support not validated (EC-20).\n */\nexport interface WalkOptions {\n /** File extensions to include (e.g., new Set(['.ts', '.tsx'])). */\n extensions: ReadonlySet<string>\n /** Skip directories whose name starts with any of these (default: ['_', '.']). */\n skipPrefixes?: readonly string[]\n}\n\n/**\n * Recursively walk `root`, invoking `onFile(absPath)` for every file matching\n * `opts.extensions`. Directories whose name starts with any `skipPrefixes`\n * char are skipped (defaults to `_` and `.`).\n *\n * Replaces 3 near-identical recursive walkers in scan.ts, action-scan.ts,\n * ws-scan.ts (PV-3 — DRY consolidation). Resolves T3.1 of\n * architecture-review-remediation-plan.\n *\n * Symlink loops are NOT tracked — callers must avoid them or pre-resolve\n * via `fs.realpath`. EC-11 documented but not implemented (rare in dev).\n */\nexport function walkSourceFiles(\n root: string,\n opts: WalkOptions,\n onFile: (absPath: string) => void,\n): void {\n const skipPrefixes = opts.skipPrefixes ?? ['_', '.']\n const visit = (dir: string): void => {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n // Silently skip unreadable directories — caller controls discoverability\n return\n }\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {\n visit(fullPath)\n } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {\n onFile(resolve(fullPath))\n }\n }\n }\n visit(root)\n}\n"],"mappings":";;;;AAIA,SAAS,mBAAmB;AAC5B,SAAS,SAAS,MAAM,eAAe;AA8BhC,SAAS,gBACd,MACA,MACA,QACM;AACN,QAAM,eAAe,KAAK,gBAAgB,CAAC,KAAK,GAAG;AACnD,QAAM,QAAQ,CAAC,QAAsB;AACnC,QAAI;AACJ,QAAI;AACF,gBAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACpD,QAAQ;AAEN;AAAA,IACF;AACA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,KAAK,KAAK,MAAM,IAAI;AACrC,UAAI,MAAM,YAAY,KAAK,CAAC,aAAa,KAAK,CAAC,MAAM,MAAM,KAAK,WAAW,CAAC,CAAC,GAAG;AAC9E,cAAM,QAAQ;AAAA,MAChB,WAAW,MAAM,OAAO,KAAK,KAAK,WAAW,IAAI,QAAQ,MAAM,IAAI,CAAC,GAAG;AACrE,eAAO,QAAQ,QAAQ,CAAC;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACA,QAAM,IAAI;AACZ;","names":[]}