forgeos 0.1.0-alpha.4 → 0.1.0-alpha.41

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 (416) hide show
  1. package/.npmignore +10 -0
  2. package/AGENTS.md +142 -90
  3. package/CHANGELOG.md +433 -0
  4. package/LICENSE +21 -0
  5. package/README.md +67 -8
  6. package/adapters/go/README.md +23 -0
  7. package/adapters/go/go.mod +3 -0
  8. package/adapters/go/http.go +149 -0
  9. package/adapters/go/registry.go +234 -0
  10. package/adapters/go/types.go +136 -0
  11. package/adapters/java/README.md +68 -0
  12. package/adapters/java/pom.xml +34 -0
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  32. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  33. package/adapters/java-spring-boot-starter/README.md +32 -0
  34. package/adapters/java-spring-boot-starter/pom.xml +36 -0
  35. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  36. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  37. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  38. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  39. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  40. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  41. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  42. package/bin/forge.mjs +18 -0
  43. package/docs/cair-protocol.md +103 -0
  44. package/docs/changelog.md +548 -0
  45. package/docs/forge-protocol.md +189 -0
  46. package/examples/go-billing/go.mod +7 -0
  47. package/examples/go-billing/main.go +120 -0
  48. package/examples/java-billing/pom.xml +52 -0
  49. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  50. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  51. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  52. package/package.json +31 -3
  53. package/schemas/forge-manifest.schema.json +57 -0
  54. package/src/forge/_generated/releaseManifest.json +1 -2
  55. package/src/forge/_generated/releaseManifest.ts +3 -3
  56. package/src/forge/agent-adapters/index.ts +1584 -125
  57. package/src/forge/agent-adapters/types.ts +224 -1
  58. package/src/forge/agent-memory/bridge.ts +1333 -0
  59. package/src/forge/agent-memory/context-pack.ts +277 -0
  60. package/src/forge/agent-memory/hook-runner.ts +312 -0
  61. package/src/forge/agent-memory/mcp.ts +224 -0
  62. package/src/forge/agent-memory/normalize.ts +498 -0
  63. package/src/forge/agent-memory/redaction.ts +103 -0
  64. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  65. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
  66. package/src/forge/agent-memory/sources/codex.ts +119 -0
  67. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  68. package/src/forge/agent-memory/types.ts +204 -0
  69. package/src/forge/bench.ts +248 -0
  70. package/src/forge/brownfield-import/index.ts +801 -0
  71. package/src/forge/brownfield-import/types.ts +127 -0
  72. package/src/forge/cair/action-journal.ts +61 -0
  73. package/src/forge/cair/action-parser.ts +314 -0
  74. package/src/forge/cair/action-validator.ts +40 -0
  75. package/src/forge/cair/actions.ts +1818 -0
  76. package/src/forge/cair/format.ts +77 -0
  77. package/src/forge/cair/index.ts +106 -0
  78. package/src/forge/cair/query.ts +478 -0
  79. package/src/forge/cair/snapshot.ts +315 -0
  80. package/src/forge/cair/types.ts +248 -0
  81. package/src/forge/cli/ai.ts +339 -21
  82. package/src/forge/cli/auth.ts +348 -10
  83. package/src/forge/cli/authmd.ts +441 -0
  84. package/src/forge/cli/baseline.ts +112 -0
  85. package/src/forge/cli/build.ts +20 -4
  86. package/src/forge/cli/changed.ts +437 -0
  87. package/src/forge/cli/codex-app-server.ts +877 -0
  88. package/src/forge/cli/commands.ts +1620 -20
  89. package/src/forge/cli/db.ts +209 -2
  90. package/src/forge/cli/deploy.ts +887 -0
  91. package/src/forge/cli/deps.ts +82 -13
  92. package/src/forge/cli/dev.ts +961 -46
  93. package/src/forge/cli/docs.ts +265 -0
  94. package/src/forge/cli/doctor.ts +297 -0
  95. package/src/forge/cli/field-test.ts +329 -0
  96. package/src/forge/cli/handoff.ts +336 -0
  97. package/src/forge/cli/index.ts +1 -0
  98. package/src/forge/cli/last-run.ts +84 -0
  99. package/src/forge/cli/main.ts +107 -3
  100. package/src/forge/cli/new.ts +152 -13
  101. package/src/forge/cli/next-actions.ts +23 -0
  102. package/src/forge/cli/output.ts +389 -6
  103. package/src/forge/cli/parse.ts +1050 -34
  104. package/src/forge/cli/progress.ts +51 -0
  105. package/src/forge/cli/query.ts +32 -0
  106. package/src/forge/cli/release.ts +35 -11
  107. package/src/forge/cli/rls.ts +43 -4
  108. package/src/forge/cli/run.ts +41 -0
  109. package/src/forge/cli/security.ts +112 -0
  110. package/src/forge/cli/self-host.ts +56 -14
  111. package/src/forge/cli/studio.ts +2218 -0
  112. package/src/forge/cli/verify.ts +1455 -32
  113. package/src/forge/cli/windows.ts +23 -0
  114. package/src/forge/cli/workos.ts +1020 -0
  115. package/src/forge/compiler/agent-contract/build.ts +670 -64
  116. package/src/forge/compiler/agent-contract/types.ts +45 -2
  117. package/src/forge/compiler/ai-registry/parse.ts +71 -2
  118. package/src/forge/compiler/api-surface/build.ts +47 -0
  119. package/src/forge/compiler/app-graph/build.ts +112 -9
  120. package/src/forge/compiler/app-graph/extract.ts +107 -0
  121. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  122. package/src/forge/compiler/app-graph/parser.ts +24 -24
  123. package/src/forge/compiler/app-graph/profile.ts +26 -0
  124. package/src/forge/compiler/app-graph/versions.ts +1 -1
  125. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  126. package/src/forge/compiler/classifier/classify.ts +32 -8
  127. package/src/forge/compiler/classifier/secrets.ts +3 -2
  128. package/src/forge/compiler/classifier/signals.ts +91 -1
  129. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  130. package/src/forge/compiler/client-sdk/render-client.ts +295 -13
  131. package/src/forge/compiler/data-graph/parse.ts +20 -4
  132. package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
  133. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  134. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  135. package/src/forge/compiler/diagnostics/codes.ts +30 -0
  136. package/src/forge/compiler/diagnostics/create.ts +21 -2
  137. package/src/forge/compiler/diagnostics/index.ts +2 -0
  138. package/src/forge/compiler/emitter/barrel.ts +3 -0
  139. package/src/forge/compiler/emitter/render.ts +9 -0
  140. package/src/forge/compiler/external-manifest/registry.ts +205 -0
  141. package/src/forge/compiler/external-manifest/types.ts +91 -0
  142. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  143. package/src/forge/compiler/frontend-graph/build.ts +142 -14
  144. package/src/forge/compiler/integration/add.ts +618 -24
  145. package/src/forge/compiler/integration/plan.ts +43 -12
  146. package/src/forge/compiler/integration/render.ts +29 -0
  147. package/src/forge/compiler/integration/snapshot.ts +2 -0
  148. package/src/forge/compiler/integration/templates/convex.ts +70 -0
  149. package/src/forge/compiler/integration/templates/index.ts +1 -0
  150. package/src/forge/compiler/integration/templates/render.ts +38 -0
  151. package/src/forge/compiler/integration/templates/types.ts +3 -0
  152. package/src/forge/compiler/integration/templates/workos.ts +1219 -0
  153. package/src/forge/compiler/make-registry/build.ts +8 -9
  154. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  155. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  156. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  157. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  158. package/src/forge/compiler/orchestrator/plan.ts +247 -10
  159. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  160. package/src/forge/compiler/orchestrator/run.ts +97 -31
  161. package/src/forge/compiler/orchestrator/serialize.ts +33 -8
  162. package/src/forge/compiler/orchestrator/types.ts +1 -1
  163. package/src/forge/compiler/package-graph/compiler.ts +3 -1
  164. package/src/forge/compiler/package-graph/constants.ts +1 -1
  165. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  166. package/src/forge/compiler/package-manager/commands.ts +4 -0
  167. package/src/forge/compiler/package-manager/executor.ts +30 -1
  168. package/src/forge/compiler/package-manager/version.ts +36 -12
  169. package/src/forge/compiler/package-upgrades/planner.ts +155 -18
  170. package/src/forge/compiler/package-upgrades/types.ts +2 -0
  171. package/src/forge/compiler/policy-registry/build.ts +47 -2
  172. package/src/forge/compiler/policy-registry/parse.ts +32 -2
  173. package/src/forge/compiler/recipes/definitions.ts +63 -0
  174. package/src/forge/compiler/recipes/index.ts +2 -0
  175. package/src/forge/compiler/recipes/registry.ts +6 -0
  176. package/src/forge/compiler/test-graph/build.ts +11 -3
  177. package/src/forge/compiler/types/app-graph.ts +8 -2
  178. package/src/forge/compiler/types/cli.ts +75 -1
  179. package/src/forge/compiler/types/dev-manifest.ts +4 -0
  180. package/src/forge/compiler/types/emit.ts +2 -0
  181. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  182. package/src/forge/compiler/types/integration.ts +1 -0
  183. package/src/forge/compiler/types/package-graph.ts +6 -0
  184. package/src/forge/compiler/types/policy-registry.ts +3 -1
  185. package/src/forge/delta/classifier.ts +52 -0
  186. package/src/forge/delta/explain.ts +238 -0
  187. package/src/forge/delta/git-observer.ts +43 -0
  188. package/src/forge/delta/ids.ts +44 -0
  189. package/src/forge/delta/index.ts +25 -0
  190. package/src/forge/delta/recorder.ts +540 -0
  191. package/src/forge/delta/redaction.ts +50 -0
  192. package/src/forge/delta/schema.ts +240 -0
  193. package/src/forge/delta/session.ts +142 -0
  194. package/src/forge/delta/status.ts +1202 -0
  195. package/src/forge/delta/store.ts +3284 -0
  196. package/src/forge/delta/timeline.ts +178 -0
  197. package/src/forge/dev/server.ts +808 -17
  198. package/src/forge/dev/types.ts +15 -1
  199. package/src/forge/dev/watch.ts +17 -7
  200. package/src/forge/dev-console/cycle.ts +280 -33
  201. package/src/forge/dev-console/types.ts +47 -1
  202. package/src/forge/impact/index.ts +204 -9
  203. package/src/forge/impact/types.ts +33 -1
  204. package/src/forge/intent/index.ts +35 -16
  205. package/src/forge/make/fields.ts +26 -0
  206. package/src/forge/make/index.ts +40 -6
  207. package/src/forge/make/templates.ts +155 -2
  208. package/src/forge/make/types.ts +2 -1
  209. package/src/forge/policy.ts +1 -1
  210. package/src/forge/react/index.ts +160 -2
  211. package/src/forge/refactor/index.ts +1 -0
  212. package/src/forge/repair/rules/index.ts +2 -2
  213. package/src/forge/review/index.ts +158 -12
  214. package/src/forge/review/types.ts +15 -0
  215. package/src/forge/runtime/auth/config.ts +17 -0
  216. package/src/forge/runtime/auth/evaluate.ts +15 -2
  217. package/src/forge/runtime/auth/resolve.ts +61 -6
  218. package/src/forge/runtime/auth/types.ts +1 -0
  219. package/src/forge/runtime/db/factory.ts +1 -3
  220. package/src/forge/runtime/db/generated-client.ts +13 -2
  221. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  222. package/src/forge/runtime/db/pglite-adapter.ts +263 -2
  223. package/src/forge/runtime/db/postgres-adapter.ts +6 -3
  224. package/src/forge/runtime/executor.ts +129 -16
  225. package/src/forge/runtime/external/bridge.ts +649 -0
  226. package/src/forge/runtime/runner/run-entry.ts +16 -7
  227. package/src/forge/runtime/telemetry/buffer.ts +4 -1
  228. package/src/forge/runtime/telemetry/scrubber.ts +35 -5
  229. package/src/forge/runtime/webhooks/security.ts +12 -7
  230. package/src/forge/server.ts +17 -3
  231. package/src/forge/ui/index.ts +209 -0
  232. package/src/forge/ui/types.ts +1 -0
  233. package/src/forge/version.ts +1 -1
  234. package/src/forge/vue/index.ts +422 -0
  235. package/src/forge/workspace/baseline.ts +112 -0
  236. package/src/forge/workspace/change-summary.ts +250 -0
  237. package/src/forge/workspace/forge-cli.ts +42 -0
  238. package/src/forge/workspace/git-summary.ts +391 -0
  239. package/templates/agent-workroom/AGENTS.md +29 -0
  240. package/templates/agent-workroom/README.md +34 -0
  241. package/templates/agent-workroom/forge.config.ts +3 -0
  242. package/templates/agent-workroom/package.json +33 -0
  243. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  244. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  245. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  246. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  247. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  248. package/templates/agent-workroom/src/policies.ts +6 -0
  249. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  250. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  251. package/templates/agent-workroom/tsconfig.json +16 -0
  252. package/templates/agent-workroom/web/index.html +12 -0
  253. package/templates/agent-workroom/web/package.json +21 -0
  254. package/templates/agent-workroom/web/src/App.tsx +345 -0
  255. package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
  256. package/templates/agent-workroom/web/src/main.tsx +13 -0
  257. package/templates/agent-workroom/web/src/styles.css +545 -0
  258. package/templates/agent-workroom/web/tsconfig.json +27 -0
  259. package/templates/b2b-support-web/package.json +1 -0
  260. package/templates/b2b-support-web/web/package.json +1 -1
  261. package/templates/minimal-web/package.json +1 -1
  262. package/templates/minimal-web/web/package.json +2 -2
  263. package/templates/nuxt-web/.vscode/settings.json +14 -0
  264. package/templates/nuxt-web/README.md +30 -0
  265. package/templates/nuxt-web/forge.config.ts +3 -0
  266. package/templates/nuxt-web/package.json +33 -0
  267. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
  268. package/templates/nuxt-web/src/commands/createNote.ts +26 -0
  269. package/templates/nuxt-web/src/forge/schema.ts +12 -0
  270. package/templates/nuxt-web/src/policies.ts +6 -0
  271. package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
  272. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
  273. package/templates/nuxt-web/tsconfig.json +17 -0
  274. package/templates/nuxt-web/web/app.vue +67 -0
  275. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
  276. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
  277. package/templates/nuxt-web/web/composables/forge.ts +13 -0
  278. package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
  279. package/templates/nuxt-web/web/nuxt.config.ts +11 -0
  280. package/templates/nuxt-web/web/package.json +18 -0
  281. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
  282. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
  283. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
  284. package/templates/nuxt-web/web/tsconfig.json +6 -0
  285. package/src/forge/_generated/actionSubscriptions.json +0 -2
  286. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  287. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  288. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  289. package/src/forge/_generated/agentContract.json +0 -2
  290. package/src/forge/_generated/agentContract.ts +0 -7829
  291. package/src/forge/_generated/agentQuickstart.md +0 -34
  292. package/src/forge/_generated/agentTools.json +0 -2
  293. package/src/forge/_generated/agentTools.md +0 -16
  294. package/src/forge/_generated/agentTools.ts +0 -12
  295. package/src/forge/_generated/aiContext.ts +0 -125
  296. package/src/forge/_generated/aiModels.json +0 -2
  297. package/src/forge/_generated/aiModels.ts +0 -51
  298. package/src/forge/_generated/aiProviders.json +0 -2
  299. package/src/forge/_generated/aiProviders.ts +0 -23
  300. package/src/forge/_generated/aiRegistry.json +0 -2
  301. package/src/forge/_generated/aiRegistry.ts +0 -31
  302. package/src/forge/_generated/api.json +0 -2
  303. package/src/forge/_generated/api.ts +0 -8
  304. package/src/forge/_generated/appGraph.json +0 -2
  305. package/src/forge/_generated/appGraph.ts +0 -14919
  306. package/src/forge/_generated/appMap.md +0 -55
  307. package/src/forge/_generated/artifactManifest.json +0 -2
  308. package/src/forge/_generated/artifactManifest.ts +0 -7
  309. package/src/forge/_generated/authClaims.json +0 -2
  310. package/src/forge/_generated/authClaims.ts +0 -13
  311. package/src/forge/_generated/authConfig.json +0 -2
  312. package/src/forge/_generated/authConfig.ts +0 -17
  313. package/src/forge/_generated/authContext.ts +0 -23
  314. package/src/forge/_generated/authRegistry.json +0 -2
  315. package/src/forge/_generated/authRegistry.ts +0 -25
  316. package/src/forge/_generated/buildInfo.json +0 -2
  317. package/src/forge/_generated/buildInfo.ts +0 -9
  318. package/src/forge/_generated/capabilityMap.json +0 -2
  319. package/src/forge/_generated/capabilityMap.md +0 -15
  320. package/src/forge/_generated/capabilityMap.ts +0 -17
  321. package/src/forge/_generated/client.ts +0 -282
  322. package/src/forge/_generated/clientApi.ts +0 -9
  323. package/src/forge/_generated/clientManifest.json +0 -2
  324. package/src/forge/_generated/clientManifest.ts +0 -39
  325. package/src/forge/_generated/clientTypes.ts +0 -78
  326. package/src/forge/_generated/configRegistry.json +0 -2
  327. package/src/forge/_generated/configRegistry.ts +0 -4
  328. package/src/forge/_generated/dataGraph.json +0 -2
  329. package/src/forge/_generated/dataGraph.ts +0 -8
  330. package/src/forge/_generated/db.json +0 -2
  331. package/src/forge/_generated/db.ts +0 -2
  332. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  333. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  334. package/src/forge/_generated/dbSessionContext.json +0 -2
  335. package/src/forge/_generated/dbSessionContext.ts +0 -39
  336. package/src/forge/_generated/deployManifest.json +0 -2
  337. package/src/forge/_generated/deployManifest.ts +0 -14
  338. package/src/forge/_generated/devManifest.json +0 -2
  339. package/src/forge/_generated/devManifest.ts +0 -62
  340. package/src/forge/_generated/envSchema.json +0 -2
  341. package/src/forge/_generated/envSchema.ts +0 -59
  342. package/src/forge/_generated/frontendGraph.json +0 -2
  343. package/src/forge/_generated/frontendGraph.ts +0 -27
  344. package/src/forge/_generated/importGuards.json +0 -2
  345. package/src/forge/_generated/importGuards.ts +0 -686
  346. package/src/forge/_generated/index.ts +0 -68
  347. package/src/forge/_generated/liveProductionManifest.json +0 -2
  348. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  349. package/src/forge/_generated/liveProtocol.json +0 -2
  350. package/src/forge/_generated/liveProtocol.ts +0 -21
  351. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  352. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  353. package/src/forge/_generated/liveTransportConfig.json +0 -2
  354. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  355. package/src/forge/_generated/makeRegistry.json +0 -2
  356. package/src/forge/_generated/makeRegistry.ts +0 -177
  357. package/src/forge/_generated/makeTemplates.json +0 -2
  358. package/src/forge/_generated/makeTemplates.ts +0 -66
  359. package/src/forge/_generated/mockMap.json +0 -2
  360. package/src/forge/_generated/mockMap.ts +0 -7
  361. package/src/forge/_generated/operationPlaybooks.md +0 -167
  362. package/src/forge/_generated/packageGraph.json +0 -2
  363. package/src/forge/_generated/packageGraph.ts +0 -249334
  364. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  365. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  366. package/src/forge/_generated/permissionMatrix.json +0 -2
  367. package/src/forge/_generated/permissionMatrix.ts +0 -7
  368. package/src/forge/_generated/policyRegistry.json +0 -2
  369. package/src/forge/_generated/policyRegistry.ts +0 -11
  370. package/src/forge/_generated/queryRegistry.json +0 -2
  371. package/src/forge/_generated/queryRegistry.ts +0 -9
  372. package/src/forge/_generated/react.d.ts +0 -22
  373. package/src/forge/_generated/react.ts +0 -29
  374. package/src/forge/_generated/reactManifest.json +0 -2
  375. package/src/forge/_generated/reactManifest.ts +0 -19
  376. package/src/forge/_generated/rlsPolicies.json +0 -2
  377. package/src/forge/_generated/rlsPolicies.sql +0 -34
  378. package/src/forge/_generated/rlsPolicies.ts +0 -6
  379. package/src/forge/_generated/runtimeGraph.json +0 -2
  380. package/src/forge/_generated/runtimeGraph.ts +0 -8
  381. package/src/forge/_generated/runtimeMatrix.json +0 -2
  382. package/src/forge/_generated/runtimeMatrix.ts +0 -334130
  383. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  384. package/src/forge/_generated/runtimeRules.md +0 -91
  385. package/src/forge/_generated/secretRegistry.json +0 -2
  386. package/src/forge/_generated/secretRegistry.ts +0 -50
  387. package/src/forge/_generated/secretsContext.ts +0 -11
  388. package/src/forge/_generated/serverApi.ts +0 -10
  389. package/src/forge/_generated/sourceMapManifest.json +0 -2
  390. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  391. package/src/forge/_generated/sqlPlan.json +0 -2
  392. package/src/forge/_generated/sqlPlan.ts +0 -88
  393. package/src/forge/_generated/subscriptionManifest.json +0 -2
  394. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  395. package/src/forge/_generated/symbolicationManifest.json +0 -2
  396. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  397. package/src/forge/_generated/telemetryRegistry.json +0 -2
  398. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  399. package/src/forge/_generated/telemetrySinks.json +0 -2
  400. package/src/forge/_generated/telemetrySinks.ts +0 -11
  401. package/src/forge/_generated/tenantScope.json +0 -2
  402. package/src/forge/_generated/tenantScope.ts +0 -8
  403. package/src/forge/_generated/testGraph.json +0 -2
  404. package/src/forge/_generated/testGraph.ts +0 -3430
  405. package/src/forge/_generated/testPlanRegistry.json +0 -2
  406. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  407. package/src/forge/_generated/uiRoutes.json +0 -2
  408. package/src/forge/_generated/uiRoutes.ts +0 -16
  409. package/src/forge/_generated/uiScenarios.json +0 -2
  410. package/src/forge/_generated/uiScenarios.ts +0 -30
  411. package/src/forge/_generated/uiTestManifest.json +0 -2
  412. package/src/forge/_generated/uiTestManifest.ts +0 -27
  413. package/src/forge/_generated/workflowRegistry.json +0 -2
  414. package/src/forge/_generated/workflowRegistry.ts +0 -9
  415. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  416. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -1,4 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
+ import {
3
+ createHmac,
4
+ timingSafeEqual,
5
+ } from "node:crypto";
6
+ import { createRequire } from "node:module";
2
7
  import {
3
8
  createServer,
4
9
  type IncomingMessage,
@@ -10,6 +15,7 @@ import { pathToFileURL } from "node:url";
10
15
  import { createDiagnostic } from "../compiler/diagnostics/create.ts";
11
16
  import {
12
17
  FORGE_AUTH_DEV_HEADERS_IN_PRODUCTION,
18
+ FORGE_DEV_DB_RESET,
13
19
  FORGE_DEV_INVOKE_FAILED,
14
20
  FORGE_DEV_SERVER_ERROR,
15
21
  FORGE_POLICY_DENIED,
@@ -22,13 +28,16 @@ import type { TableMapEntry } from "../compiler/data-graph/sql/serialize.ts";
22
28
  import type { SqlPlan } from "../compiler/data-graph/sql/types.ts";
23
29
  import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
24
30
  import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
25
- import type { DevManifest } from "../compiler/types/dev-manifest.ts";
31
+ import type { DevManifest, DevRoute } from "../compiler/types/dev-manifest.ts";
32
+ import type { Diagnostic } from "../compiler/types/diagnostic.ts";
26
33
  import type { RuntimeGraph } from "../compiler/types/runtime-graph.ts";
27
34
  import { createDbAdapter } from "../runtime/db/factory.ts";
28
- import { applyMigrations } from "../runtime/db/migrate.ts";
35
+ import { applyMigrations, getMigrationStatus, resetDatabase } from "../runtime/db/migrate.ts";
29
36
  import {
37
+ disposeRuntimeModuleNamespace,
30
38
  listEntries,
31
39
  prepareRuntimeEnvironment,
40
+ refreshRuntimeModuleNamespace,
32
41
  runEntry,
33
42
  } from "../runtime/executor.ts";
34
43
  import { listQueries, runQuery } from "../runtime/query/run-query.ts";
@@ -49,7 +58,7 @@ import { loadWorkflowRegistry } from "../runtime/workflows/registry.ts";
49
58
  import { retryWorkflowRun } from "../runtime/workflows/retry-run.ts";
50
59
  import { hashStable } from "../compiler/primitives/hash.ts";
51
60
  import { canonicalJson } from "../compiler/primitives/serialize.ts";
52
- import type { DevServerHandle, DevServerOptions, DevServerState } from "./types.ts";
61
+ import type { DevServerHandle, DevServerOptions, DevServerReloadResult, DevServerState } from "./types.ts";
53
62
  import { getTelemetrySummary, inspectTrace } from "../runtime/telemetry/flush.ts";
54
63
  import { processTelemetryBatch } from "../runtime/telemetry/process.ts";
55
64
  import { getRuntimeEnvStore } from "../runtime/context/create-context.ts";
@@ -69,12 +78,47 @@ import type { AgentToolRegistry } from "../compiler/agent-contract/types.ts";
69
78
  import { loadLiveQueryRegistry } from "../runtime/live/registry.ts";
70
79
  import { createLiveSubscriptionManager } from "../runtime/live/subscription-manager.ts";
71
80
  import { createSseResponse } from "../runtime/live/sse.ts";
81
+ import {
82
+ MemoryWebhookReplayStore,
83
+ verifyWebhookSignature,
84
+ } from "../runtime/webhooks/security.ts";
72
85
  import {
73
86
  ensureLiveInvalidationSchema,
74
87
  listLiveInvalidations,
75
88
  } from "../runtime/live/invalidation-log.ts";
76
89
  import { currentReleaseInfo } from "../runtime/release/runtime.ts";
77
90
  import { DEFAULT_LIVE_LIMITS } from "../runtime/live/types.ts";
91
+ import {
92
+ loadExternalServiceGraph,
93
+ runExternalEntry,
94
+ } from "../runtime/external/bridge.ts";
95
+
96
+ async function applyDevMigrations(
97
+ adapter: NonNullable<Awaited<ReturnType<typeof createDbAdapter>>["adapter"]>,
98
+ sqlPlan: SqlPlan,
99
+ dbMode: DevServerOptions["db"],
100
+ ): Promise<Diagnostic[]> {
101
+ const shouldAutoReset =
102
+ (dbMode === "pglite" || dbMode === "memory") &&
103
+ process.env.FORGE_DEV_AUTO_RESET_DB !== "0";
104
+
105
+ if (shouldAutoReset) {
106
+ const status = await getMigrationStatus(adapter).catch(() => null);
107
+ const appliedChecksum = status?.applied.at(-1)?.checksum ?? null;
108
+ if (appliedChecksum && appliedChecksum !== sqlPlan.checksum) {
109
+ return [
110
+ createDiagnostic({
111
+ severity: "warning",
112
+ code: FORGE_DEV_DB_RESET,
113
+ message: "local dev database schema changed; reset app tables automatically",
114
+ }),
115
+ ...(await resetDatabase(adapter, sqlPlan)),
116
+ ];
117
+ }
118
+ }
119
+
120
+ return applyMigrations(adapter, sqlPlan);
121
+ }
78
122
 
79
123
  interface FetchServer {
80
124
  hostname?: string;
@@ -96,7 +140,7 @@ async function readIncomingBody(request: IncomingMessage): Promise<Buffer | unde
96
140
 
97
141
  async function nodeRequestToFetch(
98
142
  request: IncomingMessage,
99
- input: { host: string; port: number },
143
+ input: { host: string; port: number; signal?: AbortSignal },
100
144
  ): Promise<Request> {
101
145
  const headers = new Headers();
102
146
  for (const [name, value] of Object.entries(request.headers)) {
@@ -113,6 +157,7 @@ async function nodeRequestToFetch(
113
157
  method: request.method,
114
158
  headers,
115
159
  body: body ? new Blob([new Uint8Array(body)]) : undefined,
160
+ signal: input.signal,
116
161
  ...(body ? { duplex: "half" as const } : {}),
117
162
  };
118
163
  return new Request(url, init);
@@ -155,10 +200,13 @@ async function createNodeFetchServer(
155
200
  ): Promise<FetchServer> {
156
201
  let actualPort = input.port;
157
202
  const nodeServer: NodeHttpServer = createServer(async (request, response) => {
203
+ const controller = new AbortController();
204
+ response.once("close", () => controller.abort());
158
205
  try {
159
206
  const fetchRequest = await nodeRequestToFetch(request, {
160
207
  host: input.hostname,
161
208
  port: actualPort,
209
+ signal: controller.signal,
162
210
  });
163
211
  const fetchResponse = await fetchHandler(fetchRequest);
164
212
  await writeFetchResponse(response, fetchResponse);
@@ -263,17 +311,274 @@ function htmlResponse(body: string, status = 200): Response {
263
311
  });
264
312
  }
265
313
 
314
+ function redirectResponse(location: string, extraHeaders: Record<string, string> = {}): Response {
315
+ return new Response(null, {
316
+ status: 302,
317
+ headers: {
318
+ Location: location,
319
+ "Access-Control-Allow-Origin": "*",
320
+ ...extraHeaders,
321
+ },
322
+ });
323
+ }
324
+
325
+ function markdownResponse(body: string, status = 200): Response {
326
+ return new Response(body, {
327
+ status,
328
+ headers: {
329
+ "Content-Type": "text/markdown; charset=utf-8",
330
+ "Access-Control-Allow-Origin": "*",
331
+ },
332
+ });
333
+ }
334
+
266
335
  function corsPreflight(): Response {
336
+ const allowedHeaders = [
337
+ "Authorization",
338
+ "Content-Type",
339
+ "WorkOS-Signature",
340
+ "x-forge-user-id",
341
+ "x-forge-tenant-id",
342
+ "x-forge-organization-id",
343
+ "x-forge-organization-membership-id",
344
+ "x-forge-role",
345
+ "x-forge-roles",
346
+ "x-forge-permissions",
347
+ "x-forge-claims",
348
+ "x-forge-auth-kind",
349
+ "x-forge-trace-id",
350
+ ];
267
351
  return new Response(null, {
268
352
  status: 204,
269
353
  headers: {
270
354
  "Access-Control-Allow-Origin": "*",
271
355
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
272
- "Access-Control-Allow-Headers": "Authorization, Content-Type, x-forge-user-id, x-forge-tenant-id, x-forge-role",
356
+ "Access-Control-Allow-Headers": allowedHeaders.join(", "),
273
357
  },
274
358
  });
275
359
  }
276
360
 
361
+ function hasWorkOSWebhookEndpoint(workspaceRoot: string): boolean {
362
+ return existsSync(join(workspaceRoot, `${GENERATED_DIR}/integrations/workos/http-handler.ts`));
363
+ }
364
+
365
+ function hasWorkOSAuthRoutes(workspaceRoot: string): boolean {
366
+ return existsSync(join(workspaceRoot, `${GENERATED_DIR}/integrations/workos/auth-routes.ts`));
367
+ }
368
+
369
+ function hasPublicAuthMd(workspaceRoot: string): boolean {
370
+ return existsSync(join(workspaceRoot, "public/auth.md"));
371
+ }
372
+
373
+ function hasPublicAuthMetadata(workspaceRoot: string): boolean {
374
+ return existsSync(join(workspaceRoot, "public/.well-known/oauth-protected-resource"));
375
+ }
376
+
377
+ function withGeneratedIntegrationRoutes(workspaceRoot: string, routes: DevRoute[]): DevRoute[] {
378
+ const next = [...routes];
379
+ if (
380
+ hasWorkOSAuthRoutes(workspaceRoot)
381
+ ) {
382
+ for (const route of [
383
+ { method: "GET" as const, path: "/login" },
384
+ { method: "GET" as const, path: "/callback" },
385
+ { method: "POST" as const, path: "/logout" },
386
+ { method: "GET" as const, path: "/session" },
387
+ ]) {
388
+ if (!next.some((candidate) => candidate.method === route.method && candidate.path === route.path)) {
389
+ next.push({ ...route, purpose: "auth" });
390
+ }
391
+ }
392
+ }
393
+ if (
394
+ hasPublicAuthMd(workspaceRoot) &&
395
+ !next.some((route) => route.method === "GET" && route.path === "/auth.md")
396
+ ) {
397
+ next.push({
398
+ method: "GET",
399
+ path: "/auth.md",
400
+ purpose: "auth-md",
401
+ });
402
+ }
403
+ if (
404
+ hasPublicAuthMetadata(workspaceRoot) &&
405
+ !next.some((route) => route.method === "GET" && route.path === "/.well-known/oauth-protected-resource")
406
+ ) {
407
+ next.push({
408
+ method: "GET",
409
+ path: "/.well-known/oauth-protected-resource",
410
+ purpose: "auth-metadata",
411
+ });
412
+ }
413
+ if (
414
+ hasWorkOSWebhookEndpoint(workspaceRoot) &&
415
+ !next.some((route) => route.method === "POST" && route.path === "/webhooks/workos")
416
+ ) {
417
+ next.push({
418
+ method: "POST",
419
+ path: "/webhooks/workos",
420
+ purpose: "webhook",
421
+ });
422
+ }
423
+ return next.sort((a, b) =>
424
+ a.path === b.path ? a.method.localeCompare(b.method) : a.path.localeCompare(b.path),
425
+ );
426
+ }
427
+
428
+ interface WorkOSDevSession {
429
+ user: Record<string, unknown>;
430
+ accessToken: string;
431
+ refreshToken?: string;
432
+ organizationId?: string;
433
+ organizationMembershipId?: string;
434
+ role?: string;
435
+ roles?: string[];
436
+ permissions?: string[];
437
+ issuedAt: number;
438
+ }
439
+
440
+ function normalizeLocalRedirect(value: string | null | undefined, fallback = "/"): string {
441
+ if (!value || !value.startsWith("/") || value.startsWith("//")) {
442
+ return fallback;
443
+ }
444
+ return value;
445
+ }
446
+
447
+ function workOSDevCookieName(envStore: ReturnType<typeof getRuntimeEnvStore>): string {
448
+ return envStore.resolve("WORKOS_COOKIE_NAME") || "forgeos_workos_session";
449
+ }
450
+
451
+ function base64urlJson(value: unknown): string {
452
+ return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
453
+ }
454
+
455
+ function decodeBase64urlJson(value: string): unknown {
456
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8")) as unknown;
457
+ }
458
+
459
+ function hmacBase64url(secret: string, payload: string): string {
460
+ return createHmac("sha256", secret).update(payload).digest("base64url");
461
+ }
462
+
463
+ function safeEqualString(left: string, right: string): boolean {
464
+ const a = Buffer.from(left);
465
+ const b = Buffer.from(right);
466
+ return a.length === b.length && timingSafeEqual(a, b);
467
+ }
468
+
469
+ function encodeWorkOSDevSession(session: WorkOSDevSession, cookiePassword: string): string {
470
+ const payload = base64urlJson(session);
471
+ return `${payload}.${hmacBase64url(cookiePassword, payload)}`;
472
+ }
473
+
474
+ function decodeWorkOSDevSession(value: string | undefined, cookiePassword: string): WorkOSDevSession | null {
475
+ if (!value) {
476
+ return null;
477
+ }
478
+ const [payload, signature] = value.split(".");
479
+ if (!payload || !signature || !safeEqualString(signature, hmacBase64url(cookiePassword, payload))) {
480
+ return null;
481
+ }
482
+ return decodeBase64urlJson(payload) as WorkOSDevSession;
483
+ }
484
+
485
+ function readRequestCookie(request: Request, name: string): string | undefined {
486
+ return (request.headers.get("cookie") ?? "")
487
+ .split(";")
488
+ .map((part) => part.trim())
489
+ .find((part) => part.startsWith(`${name}=`))
490
+ ?.slice(name.length + 1);
491
+ }
492
+
493
+ function workOSDevSessionClaims(session: WorkOSDevSession): Record<string, unknown> {
494
+ const email = typeof session.user.email === "string" ? session.user.email : undefined;
495
+ const firstName = typeof session.user.firstName === "string" ? session.user.firstName : undefined;
496
+ const lastName = typeof session.user.lastName === "string" ? session.user.lastName : undefined;
497
+ return {
498
+ sub: String(session.user.id ?? ""),
499
+ ...(email ? { email } : {}),
500
+ ...(firstName || lastName ? { name: [firstName, lastName].filter(Boolean).join(" ") } : {}),
501
+ ...(session.organizationId ? { organization_id: session.organizationId } : {}),
502
+ ...(session.organizationMembershipId ? { organization_membership_id: session.organizationMembershipId } : {}),
503
+ ...(session.role ? { role: session.role } : {}),
504
+ ...(session.roles ? { roles: session.roles } : {}),
505
+ ...(session.permissions ? { permissions: session.permissions } : {}),
506
+ };
507
+ }
508
+
509
+ function createWorkOSDevSessionCookie(
510
+ envStore: ReturnType<typeof getRuntimeEnvStore>,
511
+ session: WorkOSDevSession,
512
+ ): string {
513
+ const cookiePassword = envStore.resolve("WORKOS_COOKIE_PASSWORD") ?? "";
514
+ const cookieName = workOSDevCookieName(envStore);
515
+ const value = encodeWorkOSDevSession(session, cookiePassword);
516
+ return `${cookieName}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000`;
517
+ }
518
+
519
+ function clearWorkOSDevSessionCookie(envStore: ReturnType<typeof getRuntimeEnvStore>): string {
520
+ return `${workOSDevCookieName(envStore)}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`;
521
+ }
522
+
523
+ function workOSConfigDiagnostics(envStore: ReturnType<typeof getRuntimeEnvStore>, requireApiKey: boolean): Diagnostic[] {
524
+ const required = [
525
+ "WORKOS_CLIENT_ID",
526
+ "WORKOS_REDIRECT_URI",
527
+ "WORKOS_COOKIE_PASSWORD",
528
+ ...(requireApiKey ? ["WORKOS_API_KEY"] : []),
529
+ ];
530
+ return required
531
+ .filter((name) => !envStore.resolve(name))
532
+ .map((name) =>
533
+ createDiagnostic({
534
+ severity: "error",
535
+ code: FORGE_DEV_SERVER_ERROR,
536
+ message: `${name} is required for WorkOS AuthKit dev routes`,
537
+ fixHint: "Run forge add auth workos, copy .env.example to .env.local, and fill WorkOS values.",
538
+ }),
539
+ );
540
+ }
541
+
542
+ function workOSAuthorizationUrl(envStore: ReturnType<typeof getRuntimeEnvStore>, state: string): string {
543
+ const url = new URL("https://api.workos.com/user_management/authorize");
544
+ url.searchParams.set("provider", "authkit");
545
+ url.searchParams.set("client_id", envStore.resolve("WORKOS_CLIENT_ID") ?? "");
546
+ url.searchParams.set("redirect_uri", envStore.resolve("WORKOS_REDIRECT_URI") ?? "");
547
+ if (state) {
548
+ url.searchParams.set("state", state);
549
+ }
550
+ return url.toString();
551
+ }
552
+
553
+ async function authenticateWorkOSCode(workspaceRoot: string, envStore: ReturnType<typeof getRuntimeEnvStore>, code: string) {
554
+ const req = createRequire(join(workspaceRoot, "package.json"));
555
+ const localEntry = join(workspaceRoot, "node_modules/@workos-inc/node/index.js");
556
+ const resolved = existsSync(localEntry) ? localEntry : req.resolve("@workos-inc/node");
557
+ const workosModule = await import(pathToFileURL(resolved).href) as {
558
+ createWorkOS?: (options: { apiKey: string; clientId: string }) => unknown;
559
+ WorkOS?: new (options: { apiKey: string; clientId: string }) => unknown;
560
+ };
561
+ const client = workosModule.createWorkOS
562
+ ? workosModule.createWorkOS({
563
+ apiKey: envStore.resolve("WORKOS_API_KEY") ?? "",
564
+ clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
565
+ })
566
+ : workosModule.WorkOS
567
+ ? new workosModule.WorkOS({
568
+ apiKey: envStore.resolve("WORKOS_API_KEY") ?? "",
569
+ clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
570
+ })
571
+ : null;
572
+ const userManagement = (client as { userManagement?: { authenticateWithCode?: (input: { code: string; clientId: string }) => Promise<Record<string, unknown>> } } | null)?.userManagement;
573
+ if (!userManagement?.authenticateWithCode) {
574
+ throw new Error("@workos-inc/node does not expose userManagement.authenticateWithCode");
575
+ }
576
+ return userManagement.authenticateWithCode({
577
+ code,
578
+ clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
579
+ });
580
+ }
581
+
277
582
  function acceptsHtml(request: Request): boolean {
278
583
  return (request.headers.get("accept") ?? "").includes("text/html");
279
584
  }
@@ -406,6 +711,22 @@ function parseInvokeName(pathname: string, prefix: string): string | null {
406
711
  return name.length > 0 ? decodeURIComponent(name) : null;
407
712
  }
408
713
 
714
+ function parseExternalInvoke(pathname: string): {
715
+ serviceName: string;
716
+ kind: "command" | "query";
717
+ entryName: string;
718
+ } | null {
719
+ const match = pathname.match(/^\/external\/([^/]+)\/(commands|queries)\/([^/]+)$/);
720
+ if (!match) {
721
+ return null;
722
+ }
723
+ return {
724
+ serviceName: decodeURIComponent(match[1]!),
725
+ kind: match[2] === "commands" ? "command" : "query",
726
+ entryName: decodeURIComponent(match[3]!),
727
+ };
728
+ }
729
+
409
730
  function loadAgentToolRegistry(workspaceRoot: string): AgentToolRegistry | null {
410
731
  return readGeneratedJson<AgentToolRegistry>(
411
732
  workspaceRoot,
@@ -413,6 +734,18 @@ function loadAgentToolRegistry(workspaceRoot: string): AgentToolRegistry | null
413
734
  );
414
735
  }
415
736
 
737
+ async function listDatabaseTables(adapter: NonNullable<DevServerState["adapter"]>): Promise<string[]> {
738
+ const result = await adapter.query(
739
+ `SELECT c.relname AS table_name
740
+ FROM pg_catalog.pg_class c
741
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
742
+ WHERE n.nspname = 'public'
743
+ AND c.relkind IN ('r', 'p')
744
+ ORDER BY c.relname`,
745
+ );
746
+ return result.rows.map((row) => String(row.table_name));
747
+ }
748
+
416
749
  type RuntimeAgentDefinition = {
417
750
  provider?: ForgeAiProvider;
418
751
  model: string;
@@ -562,6 +895,7 @@ async function buildAiSdkTools(input: {
562
895
  telemetry: ReturnType<typeof createTelemetryContext> | ReturnType<typeof createNoopTelemetryContext>;
563
896
  env: Record<string, string | undefined>;
564
897
  auth: Awaited<ReturnType<typeof authenticateHeaders>>;
898
+ deltaRecorder?: DevServerOptions["deltaRecorder"];
565
899
  }): Promise<Record<string, unknown>> {
566
900
  const { tool } = await import("ai");
567
901
  return Object.fromEntries(
@@ -612,6 +946,13 @@ async function buildAiSdkTools(input: {
612
946
  latencyMs: Date.now() - startedAt,
613
947
  status: "completed",
614
948
  });
949
+ await input.deltaRecorder?.recordAgentTool({
950
+ toolName: name,
951
+ risk: definition.risk ?? "external",
952
+ status: "completed",
953
+ traceId: input.traceId,
954
+ durationMs: Date.now() - startedAt,
955
+ });
615
956
  return output;
616
957
  } catch (error) {
617
958
  await input.telemetry.capture("forge.ai.tool.failed", {
@@ -624,6 +965,13 @@ async function buildAiSdkTools(input: {
624
965
  status: "failed",
625
966
  error: error instanceof Error ? error.message : String(error),
626
967
  });
968
+ await input.deltaRecorder?.recordAgentTool({
969
+ toolName: name,
970
+ risk: definition.risk ?? "external",
971
+ status: "failed",
972
+ traceId: input.traceId,
973
+ durationMs: Date.now() - startedAt,
974
+ });
627
975
  throw error;
628
976
  }
629
977
  },
@@ -651,6 +999,7 @@ export async function startDevServer(
651
999
  options: DevServerOptions,
652
1000
  ): Promise<DevServerHandle> {
653
1001
  const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
1002
+ const deltaRecorder = options.deltaRecorder;
654
1003
  const authConfig = loadAuthConfigFromEnv(workspaceRoot, {
655
1004
  defaultMode: "dev-headers",
656
1005
  });
@@ -711,7 +1060,7 @@ export async function startDevServer(
711
1060
  );
712
1061
 
713
1062
  if (sqlPlan) {
714
- const migrationDiagnostics = await applyMigrations(adapter, sqlPlan);
1063
+ const migrationDiagnostics = await applyDevMigrations(adapter, sqlPlan, dbMode);
715
1064
  const errors = migrationDiagnostics.filter(
716
1065
  (diagnostic) => diagnostic.severity === "error",
717
1066
  );
@@ -731,6 +1080,7 @@ export async function startDevServer(
731
1080
  mockAi: options.mockAi,
732
1081
  db: serverState.adapter,
733
1082
  });
1083
+ refreshRuntimeModuleNamespace("dev-start");
734
1084
 
735
1085
  function loadArtifacts(): {
736
1086
  devManifest: DevManifest;
@@ -764,22 +1114,96 @@ export async function startDevServer(
764
1114
  }
765
1115
 
766
1116
  const initialArtifacts = loadArtifacts();
1117
+ let currentRoutes = withGeneratedIntegrationRoutes(workspaceRoot, devManifest.routes);
1118
+ let currentRuntimeEntryCount = runtimeGraph.entries.length;
1119
+
1120
+ async function reloadGeneratedArtifacts(reason = "manual"): Promise<DevServerReloadResult> {
1121
+ try {
1122
+ refreshRuntimeModuleNamespace(reason);
1123
+ const fresh = loadArtifacts();
1124
+ currentRoutes = withGeneratedIntegrationRoutes(workspaceRoot, fresh.devManifest.routes);
1125
+ currentRuntimeEntryCount = fresh.runtimeGraph.entries.length;
1126
+ const diagnostics: Diagnostic[] = [];
1127
+ let migrated = false;
1128
+
1129
+ if (serverState.adapter) {
1130
+ const sqlPlan = readGeneratedJson<SqlPlan>(
1131
+ workspaceRoot,
1132
+ `${GENERATED_DIR}/sqlPlan.json`,
1133
+ );
1134
+ if (sqlPlan) {
1135
+ migrated = true;
1136
+ diagnostics.push(...(await applyDevMigrations(serverState.adapter, sqlPlan, options.db)));
1137
+ }
1138
+ if (diagnostics.every((diagnostic) => diagnostic.severity !== "error")) {
1139
+ await ensureLiveInvalidationSchema(serverState.adapter);
1140
+ }
1141
+ }
1142
+
1143
+ if (
1144
+ options.worker &&
1145
+ serverState.adapter &&
1146
+ diagnostics.every((diagnostic) => diagnostic.severity !== "error")
1147
+ ) {
1148
+ serverState.outboxWorker?.stop();
1149
+ serverState.outboxWorker = startOutboxWorker(
1150
+ serverState.adapter,
1151
+ workspaceRoot,
1152
+ fresh.tableMap,
1153
+ fresh.runtimeGraph.entries,
1154
+ { mock: options.mock, intervalMs: 2_000, telemetrySinks, workspaceRoot },
1155
+ );
1156
+ }
1157
+
1158
+ return {
1159
+ ok: diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
1160
+ reason,
1161
+ migrated,
1162
+ routes: currentRoutes.length,
1163
+ runtimeEntries: currentRuntimeEntryCount,
1164
+ worker: serverState.outboxWorker?.isRunning() ? "running" : "stopped",
1165
+ diagnostics,
1166
+ };
1167
+ } catch (error) {
1168
+ return {
1169
+ ok: false,
1170
+ reason,
1171
+ migrated: false,
1172
+ routes: currentRoutes.length,
1173
+ runtimeEntries: currentRuntimeEntryCount,
1174
+ worker: serverState.outboxWorker?.isRunning() ? "running" : "stopped",
1175
+ diagnostics: [
1176
+ createDiagnostic({
1177
+ severity: "error",
1178
+ code: FORGE_DEV_SERVER_ERROR,
1179
+ message: error instanceof Error ? error.message : "failed to reload generated artifacts",
1180
+ }),
1181
+ ],
1182
+ };
1183
+ }
1184
+ }
1185
+
767
1186
  const liveManager = serverState.adapter
768
1187
  ? createLiveSubscriptionManager({
769
1188
  workspaceRoot,
770
1189
  adapter: serverState.adapter,
771
1190
  loadTableMap: () => loadArtifacts().tableMap,
772
1191
  enablePolling: true,
1192
+ limits: {
1193
+ ...DEFAULT_LIVE_LIMITS,
1194
+ maxSubscriptionsPerClient: Number(process.env.FORGE_DEV_LIVE_MAX_SUBSCRIPTIONS ?? "250"),
1195
+ },
773
1196
  pollIntervalMs: Number(process.env.FORGE_LIVE_POLL_INTERVAL_MS ?? "1000"),
774
1197
  })
775
1198
  : null;
1199
+ const workosReplayStore = new MemoryWebhookReplayStore();
776
1200
 
777
1201
  if (options.worker && serverState.adapter) {
778
1202
  serverState.outboxWorker = startOutboxWorker(
779
1203
  serverState.adapter,
780
1204
  workspaceRoot,
781
1205
  initialArtifacts.tableMap,
782
- runtimeGraph.entries,
1206
+ initialArtifacts.runtimeGraph.entries,
783
1207
  { mock: options.mock, intervalMs: 2_000, telemetrySinks, workspaceRoot },
784
1208
  );
785
1209
  }
@@ -803,6 +1227,7 @@ export async function startDevServer(
803
1227
  const listed = listEntries(workspaceRoot);
804
1228
  const queries = listQueries(workspaceRoot);
805
1229
  const liveQueries = loadLiveQueryRegistry(workspaceRoot);
1230
+ const external = loadExternalServiceGraph(workspaceRoot);
806
1231
  const entries = [
807
1232
  ...queries.queries.map((query) => ({
808
1233
  name: query.name,
@@ -825,10 +1250,18 @@ export async function startDevServer(
825
1250
  : `/actions/${entry.name}`,
826
1251
  method: "POST",
827
1252
  })),
1253
+ ...(external.graph?.services.flatMap((service) =>
1254
+ service.entries.map((entry) => ({
1255
+ name: `${service.name}.${entry.name}`,
1256
+ kind: `external:${entry.kind}`,
1257
+ path: `/external/${encodeURIComponent(service.name)}/${entry.kind === "command" ? "commands" : "queries"}/${encodeURIComponent(entry.name)}`,
1258
+ method: entry.method ?? "POST",
1259
+ }))
1260
+ ) ?? []),
828
1261
  ].sort((a, b) =>
829
1262
  a.path === b.path ? a.kind.localeCompare(b.kind) : a.path.localeCompare(b.path),
830
1263
  );
831
- const routes = currentDevManifest.routes.map((route) => ({
1264
+ const routes = currentRoutes.map((route) => ({
832
1265
  method: route.method,
833
1266
  path: route.path,
834
1267
  purpose: route.purpose,
@@ -846,6 +1279,7 @@ export async function startDevServer(
846
1279
  ...listed.diagnostics,
847
1280
  ...queries.diagnostics,
848
1281
  ...(liveQueries.registry?.diagnostics ?? []),
1282
+ ...external.diagnostics,
849
1283
  ],
850
1284
  };
851
1285
 
@@ -884,6 +1318,16 @@ export async function startDevServer(
884
1318
  const aiRegistry = loadAiRegistry(workspaceRoot);
885
1319
  const aiCheck = checkAiProviders(envStore, aiRegistry, secretRegistry);
886
1320
  const mockAi = isMockAiEnabled({ mockAi: options.mockAi });
1321
+ const productionAuthMode = authConfig.mode === "oidc" || authConfig.mode === "jwt";
1322
+ const missingAuthConfig = [
1323
+ ...(productionAuthMode && !authConfig.issuer ? ["FORGE_AUTH_ISSUER"] : []),
1324
+ ...(productionAuthMode && !authConfig.jwksUri ? ["FORGE_AUTH_JWKS_URI"] : []),
1325
+ ];
1326
+ const workosClientId = envStore.resolve("WORKOS_CLIENT_ID") || envStore.resolve("VITE_WORKOS_CLIENT_ID");
1327
+ const suggestedWorkOSJwksUri = workosClientId
1328
+ ? `https://api.workos.com/sso/jwks/${workosClientId}`
1329
+ : "https://api.workos.com/sso/jwks/<WORKOS_CLIENT_ID>";
1330
+ const authProductionReady = !productionAuthMode || missingAuthConfig.length === 0;
887
1331
 
888
1332
  return jsonResponse({
889
1333
  ok: true,
@@ -912,6 +1356,12 @@ export async function startDevServer(
912
1356
  audienceConfigured: Boolean(authConfig.audience),
913
1357
  jwksConfigured: Boolean(authConfig.jwksUri),
914
1358
  requiresTenant: authConfig.requiresTenant,
1359
+ ready: authProductionReady,
1360
+ productionReady: authProductionReady,
1361
+ missing: missingAuthConfig,
1362
+ ...(missingAuthConfig.includes("FORGE_AUTH_JWKS_URI")
1363
+ ? { suggestedWorkOSJwksUri }
1364
+ : {}),
915
1365
  },
916
1366
  env: {
917
1367
  loadedFiles: envStore.loadedFiles,
@@ -931,6 +1381,268 @@ export async function startDevServer(
931
1381
  });
932
1382
  }
933
1383
 
1384
+ if (["/login", "/callback", "/logout", "/session"].includes(pathname)) {
1385
+ if (!hasWorkOSAuthRoutes(workspaceRoot)) {
1386
+ return jsonResponse(
1387
+ {
1388
+ ok: false,
1389
+ diagnostics: [
1390
+ createDiagnostic({
1391
+ severity: "error",
1392
+ code: FORGE_RUNTIME_NOT_FOUND,
1393
+ message: "WorkOS AuthKit routes are not installed",
1394
+ fixHint: "Run forge add auth workos, then forge generate.",
1395
+ }),
1396
+ ],
1397
+ },
1398
+ 404,
1399
+ );
1400
+ }
1401
+
1402
+ const envStore = getRuntimeEnvStore(workspaceRoot);
1403
+
1404
+ if (pathname === "/login") {
1405
+ if (request.method !== "GET") {
1406
+ return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
1407
+ }
1408
+ const diagnostics = workOSConfigDiagnostics(envStore, false);
1409
+ if (diagnostics.length > 0) {
1410
+ return jsonResponse({ ok: false, diagnostics }, 503);
1411
+ }
1412
+ const returnTo = normalizeLocalRedirect(new URL(request.url).searchParams.get("returnTo"));
1413
+ return redirectResponse(workOSAuthorizationUrl(envStore, returnTo));
1414
+ }
1415
+
1416
+ if (pathname === "/callback") {
1417
+ if (request.method !== "GET") {
1418
+ return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
1419
+ }
1420
+ const diagnostics = workOSConfigDiagnostics(envStore, true);
1421
+ if (diagnostics.length > 0) {
1422
+ return jsonResponse({ ok: false, diagnostics }, 503);
1423
+ }
1424
+ const url = new URL(request.url);
1425
+ const code = url.searchParams.get("code");
1426
+ if (!code) {
1427
+ return jsonResponse({ ok: false, error: "missing_code" }, 400);
1428
+ }
1429
+ try {
1430
+ const result = await authenticateWorkOSCode(workspaceRoot, envStore, code);
1431
+ const user = (result.user ?? {}) as Record<string, unknown>;
1432
+ const organization = (result.organization ?? {}) as Record<string, unknown>;
1433
+ const membership = (result.organizationMembership ?? {}) as Record<string, unknown>;
1434
+ const accessToken = typeof result.accessToken === "string" ? result.accessToken : "";
1435
+ const refreshToken = typeof result.refreshToken === "string" ? result.refreshToken : undefined;
1436
+ const session: WorkOSDevSession = {
1437
+ user,
1438
+ accessToken,
1439
+ ...(refreshToken ? { refreshToken } : {}),
1440
+ ...(typeof result.organizationId === "string"
1441
+ ? { organizationId: result.organizationId }
1442
+ : typeof organization.id === "string"
1443
+ ? { organizationId: organization.id }
1444
+ : {}),
1445
+ ...(typeof result.organizationMembershipId === "string"
1446
+ ? { organizationMembershipId: result.organizationMembershipId }
1447
+ : typeof membership.id === "string"
1448
+ ? { organizationMembershipId: membership.id }
1449
+ : {}),
1450
+ ...(typeof result.role === "string" ? { role: result.role } : {}),
1451
+ ...(Array.isArray(result.roles)
1452
+ ? { roles: result.roles.filter((role): role is string => typeof role === "string") }
1453
+ : {}),
1454
+ ...(Array.isArray(result.permissions)
1455
+ ? {
1456
+ permissions: result.permissions.filter(
1457
+ (permission): permission is string => typeof permission === "string",
1458
+ ),
1459
+ }
1460
+ : {}),
1461
+ issuedAt: Math.floor(Date.now() / 1000),
1462
+ };
1463
+ return redirectResponse(
1464
+ normalizeLocalRedirect(
1465
+ url.searchParams.get("state"),
1466
+ normalizeLocalRedirect(envStore.resolve("WORKOS_POST_LOGIN_REDIRECT_URI"), "/"),
1467
+ ),
1468
+ { "Set-Cookie": createWorkOSDevSessionCookie(envStore, session) },
1469
+ );
1470
+ } catch (error) {
1471
+ return jsonResponse(
1472
+ {
1473
+ ok: false,
1474
+ diagnostics: [
1475
+ createDiagnostic({
1476
+ severity: "error",
1477
+ code: FORGE_DEV_SERVER_ERROR,
1478
+ message: error instanceof Error ? error.message : "WorkOS callback failed",
1479
+ fixHint: "Ensure @workos-inc/node is installed and WorkOS env values are valid.",
1480
+ }),
1481
+ ],
1482
+ },
1483
+ 500,
1484
+ );
1485
+ }
1486
+ }
1487
+
1488
+ if (pathname === "/logout") {
1489
+ if (request.method !== "POST") {
1490
+ return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "POST, OPTIONS" });
1491
+ }
1492
+ return redirectResponse(
1493
+ normalizeLocalRedirect(envStore.resolve("WORKOS_POST_LOGOUT_REDIRECT_URI"), "/"),
1494
+ { "Set-Cookie": clearWorkOSDevSessionCookie(envStore) },
1495
+ );
1496
+ }
1497
+
1498
+ if (pathname === "/session") {
1499
+ if (request.method !== "GET") {
1500
+ return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
1501
+ }
1502
+ const cookiePassword = envStore.resolve("WORKOS_COOKIE_PASSWORD");
1503
+ if (!cookiePassword) {
1504
+ return jsonResponse({ ok: false, diagnostics: workOSConfigDiagnostics(envStore, false) }, 503);
1505
+ }
1506
+ const session = decodeWorkOSDevSession(
1507
+ readRequestCookie(request, workOSDevCookieName(envStore)),
1508
+ cookiePassword,
1509
+ );
1510
+ return session
1511
+ ? jsonResponse({ ok: true, session: { user: session.user, claims: workOSDevSessionClaims(session) } })
1512
+ : jsonResponse({ ok: false, session: null }, 401);
1513
+ }
1514
+ }
1515
+
1516
+ if ((request.method === "GET" || request.method === "HEAD") && pathname === "/auth.md") {
1517
+ const authMdPath = join(workspaceRoot, "public/auth.md");
1518
+ if (!existsSync(authMdPath)) {
1519
+ return jsonResponse(
1520
+ {
1521
+ ok: false,
1522
+ diagnostics: [
1523
+ createDiagnostic({
1524
+ severity: "error",
1525
+ code: FORGE_RUNTIME_NOT_FOUND,
1526
+ message: "public/auth.md was not found",
1527
+ fixHint: "Run forge authmd generate.",
1528
+ }),
1529
+ ],
1530
+ },
1531
+ 404,
1532
+ );
1533
+ }
1534
+ return request.method === "HEAD" ? new Response(null, { status: 200, headers: { "content-type": "text/markdown; charset=utf-8" } }) : markdownResponse(readFileSync(authMdPath, "utf8"));
1535
+ }
1536
+
1537
+ if ((request.method === "GET" || request.method === "HEAD") && pathname === "/.well-known/oauth-protected-resource") {
1538
+ const metadataPath = join(workspaceRoot, "public/.well-known/oauth-protected-resource");
1539
+ if (!existsSync(metadataPath)) {
1540
+ return jsonResponse(
1541
+ {
1542
+ ok: false,
1543
+ diagnostics: [
1544
+ createDiagnostic({
1545
+ severity: "error",
1546
+ code: FORGE_RUNTIME_NOT_FOUND,
1547
+ message: "public/.well-known/oauth-protected-resource was not found",
1548
+ fixHint: "Run forge authmd generate.",
1549
+ }),
1550
+ ],
1551
+ },
1552
+ 404,
1553
+ );
1554
+ }
1555
+ return request.method === "HEAD" ? new Response(null, { status: 200, headers: { "content-type": "application/json; charset=utf-8" } }) : jsonResponse(JSON.parse(readFileSync(metadataPath, "utf8")));
1556
+ }
1557
+
1558
+ if (pathname === "/webhooks/workos") {
1559
+ if (!hasWorkOSWebhookEndpoint(workspaceRoot)) {
1560
+ return jsonResponse(
1561
+ {
1562
+ ok: false,
1563
+ diagnostics: [
1564
+ createDiagnostic({
1565
+ severity: "error",
1566
+ code: FORGE_RUNTIME_NOT_FOUND,
1567
+ message: "WorkOS webhook endpoint is not installed",
1568
+ fixHint: "Run forge add auth workos, then forge generate.",
1569
+ }),
1570
+ ],
1571
+ },
1572
+ 404,
1573
+ );
1574
+ }
1575
+ if (request.method !== "POST") {
1576
+ return jsonResponse(
1577
+ {
1578
+ ok: false,
1579
+ diagnostics: [
1580
+ createDiagnostic({
1581
+ severity: "error",
1582
+ code: FORGE_DEV_INVOKE_FAILED,
1583
+ message: "WorkOS webhook endpoint requires POST /webhooks/workos",
1584
+ }),
1585
+ ],
1586
+ example: {
1587
+ method: "POST",
1588
+ path: "/webhooks/workos",
1589
+ headers: { "WorkOS-Signature": "t=<ms>,v1=<hex>" },
1590
+ },
1591
+ },
1592
+ 405,
1593
+ { Allow: "POST, OPTIONS" },
1594
+ );
1595
+ }
1596
+
1597
+ const payload = await request.text();
1598
+ let eventId: string | undefined;
1599
+ let eventType: string | undefined;
1600
+ try {
1601
+ const parsed = JSON.parse(payload) as { id?: unknown; event?: unknown; type?: unknown };
1602
+ eventId = typeof parsed.id === "string" ? parsed.id : undefined;
1603
+ eventType =
1604
+ typeof parsed.event === "string"
1605
+ ? parsed.event
1606
+ : typeof parsed.type === "string"
1607
+ ? parsed.type
1608
+ : undefined;
1609
+ } catch {
1610
+ // Signature verification still returns the authoritative failure for malformed bodies.
1611
+ }
1612
+
1613
+ const envStore = getRuntimeEnvStore(workspaceRoot);
1614
+ const secret = envStore.resolve("WORKOS_WEBHOOK_SECRET") ?? "";
1615
+ const verification = await verifyWebhookSignature({
1616
+ provider: "workos",
1617
+ secret,
1618
+ payload,
1619
+ signatureHeader: request.headers.get("WorkOS-Signature"),
1620
+ eventId,
1621
+ replayStore: workosReplayStore,
1622
+ });
1623
+ if (!verification.ok) {
1624
+ return jsonResponse(
1625
+ {
1626
+ ok: false,
1627
+ diagnostics: [
1628
+ createDiagnostic({
1629
+ severity: "error",
1630
+ code: verification.code ?? FORGE_DEV_SERVER_ERROR,
1631
+ message: verification.reason ?? "WorkOS webhook signature verification failed",
1632
+ }),
1633
+ ],
1634
+ },
1635
+ 401,
1636
+ );
1637
+ }
1638
+ return jsonResponse({
1639
+ ok: true,
1640
+ provider: "workos",
1641
+ eventId: eventId ?? null,
1642
+ event: eventType ?? "unknown",
1643
+ });
1644
+ }
1645
+
934
1646
  if (request.method === "GET" && pathname === "/live") {
935
1647
  const liveQueries = loadLiveQueryRegistry(workspaceRoot).liveQueries;
936
1648
  return jsonResponse({
@@ -1028,6 +1740,13 @@ export async function startDevServer(
1028
1740
  "NaN",
1029
1741
  );
1030
1742
  let subscriptionId: string | null = null;
1743
+ const cleanupSubscription = () => {
1744
+ if (subscriptionId) {
1745
+ liveManager.unsubscribe(subscriptionId);
1746
+ subscriptionId = null;
1747
+ }
1748
+ };
1749
+ request.signal.addEventListener("abort", cleanupSubscription, { once: true });
1031
1750
 
1032
1751
  return createSseResponse(
1033
1752
  async (send, close) => {
@@ -1039,6 +1758,11 @@ export async function startDevServer(
1039
1758
  send,
1040
1759
  });
1041
1760
  subscriptionId = subscription.id;
1761
+ if (request.signal.aborted) {
1762
+ cleanupSubscription();
1763
+ close();
1764
+ return;
1765
+ }
1042
1766
  const known = loadLiveQueryRegistry(workspaceRoot).liveQueries.some(
1043
1767
  (liveQuery) => liveQuery.name === name,
1044
1768
  );
@@ -1047,9 +1771,7 @@ export async function startDevServer(
1047
1771
  }
1048
1772
  },
1049
1773
  () => {
1050
- if (subscriptionId) {
1051
- liveManager.unsubscribe(subscriptionId);
1052
- }
1774
+ cleanupSubscription();
1053
1775
  },
1054
1776
  {
1055
1777
  heartbeatIntervalMs: Number(process.env.FORGE_LIVE_HEARTBEAT_MS ?? "15000"),
@@ -1314,6 +2036,7 @@ export async function startDevServer(
1314
2036
  telemetry,
1315
2037
  env: envStore.snapshot(),
1316
2038
  auth,
2039
+ deltaRecorder,
1317
2040
  });
1318
2041
  const { ToolLoopAgent, createAgentUIStreamResponse, hasToolCall, stepCountIs } = await import("ai");
1319
2042
  const stopWhen =
@@ -1485,12 +2208,11 @@ export async function startDevServer(
1485
2208
 
1486
2209
  if (request.method === "GET" && pathname === "/db/tables") {
1487
2210
  if (serverState.adapter) {
1488
- const result = await serverState.adapter.query(
1489
- `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`,
1490
- );
1491
2211
  return jsonResponse({
1492
2212
  ok: true,
1493
- tables: result.rows.map((row) => String(row.table_name)),
2213
+ tables: serverState.adapter.kind === "memory"
2214
+ ? Object.keys(tableMap).sort()
2215
+ : await listDatabaseTables(serverState.adapter),
1494
2216
  });
1495
2217
  }
1496
2218
 
@@ -1501,6 +2223,15 @@ export async function startDevServer(
1501
2223
  }
1502
2224
 
1503
2225
  if (request.method === "GET") {
2226
+ const externalInvoke = parseExternalInvoke(pathname);
2227
+ if (externalInvoke) {
2228
+ return methodHelpResponse({
2229
+ kind: externalInvoke.kind,
2230
+ name: `${externalInvoke.serviceName}.${externalInvoke.entryName}`,
2231
+ path: `/external/${externalInvoke.serviceName}/${externalInvoke.kind === "command" ? "commands" : "queries"}/${externalInvoke.entryName}`,
2232
+ });
2233
+ }
2234
+
1504
2235
  const queryName = parseInvokeName(pathname, "/queries/");
1505
2236
  if (queryName) {
1506
2237
  return methodHelpResponse({
@@ -1658,6 +2389,43 @@ export async function startDevServer(
1658
2389
  return jsonResponse({ ok: true, ...result });
1659
2390
  }
1660
2391
 
2392
+ const externalInvoke = parseExternalInvoke(pathname);
2393
+ if (externalInvoke) {
2394
+ const args = await parseRequestArgs(request);
2395
+ const auth = await authenticateHeaders(request.headers, authConfig);
2396
+ const result = await runExternalEntry(
2397
+ workspaceRoot,
2398
+ {
2399
+ ...externalInvoke,
2400
+ args,
2401
+ auth,
2402
+ requestHeaders: request.headers,
2403
+ },
2404
+ { adapter: serverState.adapter },
2405
+ );
2406
+ const policyDenied = result.diagnostics.some(
2407
+ (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
2408
+ );
2409
+ await deltaRecorder?.recordRuntimeCall({
2410
+ entryName: `${externalInvoke.serviceName}.${externalInvoke.entryName}`,
2411
+ entryKind: externalInvoke.kind,
2412
+ service: externalInvoke.serviceName,
2413
+ result: policyDenied ? "denied" : result.ok ? "success" : "failed",
2414
+ diagnostics: result.diagnostics,
2415
+ traceId: result.traceId,
2416
+ });
2417
+ return jsonResponse(
2418
+ {
2419
+ ok: result.ok,
2420
+ result: result.result,
2421
+ diagnostics: result.diagnostics,
2422
+ traceId: result.traceId,
2423
+ },
2424
+ result.ok ? 200 : policyDenied ? 403 : 400,
2425
+ result.traceId ? { "x-forge-trace-id": result.traceId } : {},
2426
+ );
2427
+ }
2428
+
1661
2429
  let entryName: string | null = null;
1662
2430
  let expectedKind: "command" | "action" | null = null;
1663
2431
  let queryName: string | null = null;
@@ -1707,6 +2475,13 @@ export async function startDevServer(
1707
2475
  const policyDenied = result.diagnostics.some(
1708
2476
  (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
1709
2477
  );
2478
+ await deltaRecorder?.recordRuntimeCall({
2479
+ entryName: queryName,
2480
+ entryKind: "query",
2481
+ result: policyDenied ? "denied" : result.ok ? "success" : "failed",
2482
+ diagnostics: result.diagnostics,
2483
+ traceId: result.traceId,
2484
+ });
1710
2485
 
1711
2486
  return jsonResponse(
1712
2487
  {
@@ -1777,6 +2552,13 @@ export async function startDevServer(
1777
2552
  const policyDenied = result.diagnostics.some(
1778
2553
  (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
1779
2554
  );
2555
+ await deltaRecorder?.recordRuntimeCall({
2556
+ entryName,
2557
+ entryKind: entry.kind,
2558
+ result: policyDenied ? "denied" : result.ok ? "success" : "failed",
2559
+ diagnostics: result.diagnostics,
2560
+ traceId: result.traceId,
2561
+ });
1780
2562
 
1781
2563
  if (policyDenied) {
1782
2564
  const denied = result.diagnostics.find(
@@ -1862,23 +2644,32 @@ export async function startDevServer(
1862
2644
  const protocol = "http";
1863
2645
  const url = `${protocol}://${host}:${port}`;
1864
2646
 
1865
- return {
2647
+ const handle: DevServerHandle = {
1866
2648
  host,
1867
2649
  port,
1868
2650
  url,
1869
- routes: devManifest.routes,
2651
+ routes: currentRoutes,
1870
2652
  state: serverState,
1871
2653
  outboxWorker: serverState.outboxWorker,
2654
+ reload: async (reason?: string) => {
2655
+ const result = await reloadGeneratedArtifacts(reason);
2656
+ handle.routes = currentRoutes;
2657
+ handle.outboxWorker = serverState.outboxWorker;
2658
+ return result;
2659
+ },
1872
2660
  stop: () => {
1873
2661
  serverState.outboxWorker?.stop();
1874
2662
  liveManager?.stop();
1875
2663
  server.stop(true);
1876
2664
  restoreRuntimeEnvironment();
2665
+ disposeRuntimeModuleNamespace();
1877
2666
  const adapter = serverState.adapter;
1878
2667
  serverState.adapter = null;
1879
2668
  void adapter?.close().catch(() => undefined);
1880
2669
  },
1881
2670
  };
2671
+
2672
+ return handle;
1882
2673
  }
1883
2674
 
1884
2675
  export function resolveDevPort(explicit?: number): number {