@flowdular/sdk 0.1.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 (883) hide show
  1. package/.ai/agents/README.md +27 -0
  2. package/.ai/agents/module-executor.md +36 -0
  3. package/.ai/agents/reviewer.md +23 -0
  4. package/.ai/agents/sandbox/agentic-engineer.md +31 -0
  5. package/.ai/agents/sandbox/backend-engineer.md +36 -0
  6. package/.ai/agents/sandbox/business-manager.md +23 -0
  7. package/.ai/agents/sandbox/frontend-engineer.md +27 -0
  8. package/.ai/agents/sandbox/ux-designer.md +23 -0
  9. package/.ai/agents/spec-author.md +29 -0
  10. package/.ai/blueprints/add-migration/README.md +5 -0
  11. package/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  12. package/.ai/blueprints/add-migration/blueprint.json +14 -0
  13. package/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  14. package/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  15. package/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  16. package/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  17. package/.ai/blueprints/add-migration/gates.yaml +30 -0
  18. package/.ai/blueprints/add-migration/input.schema.json +23 -0
  19. package/.ai/blueprints/add-migration/plan.schema.json +54 -0
  20. package/.ai/blueprints/add-migration/required-files.yaml +18 -0
  21. package/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  22. package/.ai/blueprints/add-migration/steps.yaml +62 -0
  23. package/.ai/blueprints/author-spec/README.md +5 -0
  24. package/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  25. package/.ai/blueprints/author-spec/blueprint.json +14 -0
  26. package/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  27. package/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  28. package/.ai/blueprints/author-spec/gates.yaml +13 -0
  29. package/.ai/blueprints/author-spec/input.schema.json +20 -0
  30. package/.ai/blueprints/author-spec/plan.schema.json +14 -0
  31. package/.ai/blueprints/author-spec/required-files.yaml +6 -0
  32. package/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  33. package/.ai/blueprints/author-spec/steps.yaml +28 -0
  34. package/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  35. package/.ai/blueprints/bug-fix/README.md +5 -0
  36. package/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  37. package/.ai/blueprints/bug-fix/blueprint.json +14 -0
  38. package/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  39. package/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  40. package/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  41. package/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  42. package/.ai/blueprints/bug-fix/gates.yaml +30 -0
  43. package/.ai/blueprints/bug-fix/input.schema.json +25 -0
  44. package/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  45. package/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  46. package/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  47. package/.ai/blueprints/bug-fix/steps.yaml +51 -0
  48. package/.ai/blueprints/core-extend/README.md +5 -0
  49. package/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  50. package/.ai/blueprints/core-extend/blueprint.json +14 -0
  51. package/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  52. package/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  53. package/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  54. package/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  55. package/.ai/blueprints/core-extend/gates.yaml +16 -0
  56. package/.ai/blueprints/core-extend/input.schema.json +54 -0
  57. package/.ai/blueprints/core-extend/plan.schema.json +39 -0
  58. package/.ai/blueprints/core-extend/required-files.yaml +36 -0
  59. package/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  60. package/.ai/blueprints/core-extend/steps.yaml +54 -0
  61. package/.ai/blueprints/edit-module/README.md +9 -0
  62. package/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  63. package/.ai/blueprints/edit-module/blueprint.json +20 -0
  64. package/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  65. package/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  66. package/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  67. package/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  68. package/.ai/blueprints/edit-module/gates.yaml +30 -0
  69. package/.ai/blueprints/edit-module/input.schema.json +31 -0
  70. package/.ai/blueprints/edit-module/plan.schema.json +65 -0
  71. package/.ai/blueprints/edit-module/required-files.yaml +80 -0
  72. package/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  73. package/.ai/blueprints/edit-module/steps.yaml +115 -0
  74. package/.ai/blueprints/new-module/README.md +7 -0
  75. package/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  76. package/.ai/blueprints/new-module/blueprint.json +20 -0
  77. package/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  78. package/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  79. package/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  80. package/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  81. package/.ai/blueprints/new-module/gates.yaml +30 -0
  82. package/.ai/blueprints/new-module/input.schema.json +21 -0
  83. package/.ai/blueprints/new-module/plan.schema.json +58 -0
  84. package/.ai/blueprints/new-module/required-files.yaml +73 -0
  85. package/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  86. package/.ai/blueprints/new-module/steps.yaml +138 -0
  87. package/.ai/blueprints/release/README.md +5 -0
  88. package/.ai/blueprints/release/allowed-paths.yaml +19 -0
  89. package/.ai/blueprints/release/blueprint.json +14 -0
  90. package/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  91. package/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  92. package/.ai/blueprints/release/examples/valid/input.json +5 -0
  93. package/.ai/blueprints/release/examples/valid/plan.json +20 -0
  94. package/.ai/blueprints/release/gates.yaml +20 -0
  95. package/.ai/blueprints/release/input.schema.json +24 -0
  96. package/.ai/blueprints/release/plan.schema.json +46 -0
  97. package/.ai/blueprints/release/required-files.yaml +19 -0
  98. package/.ai/blueprints/release/spec-requirements.yaml +8 -0
  99. package/.ai/blueprints/release/steps.yaml +47 -0
  100. package/.ai/blueprints/security-review/README.md +5 -0
  101. package/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  102. package/.ai/blueprints/security-review/blueprint.json +14 -0
  103. package/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  104. package/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  105. package/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  106. package/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  107. package/.ai/blueprints/security-review/gates.yaml +22 -0
  108. package/.ai/blueprints/security-review/input.schema.json +20 -0
  109. package/.ai/blueprints/security-review/plan.schema.json +65 -0
  110. package/.ai/blueprints/security-review/required-files.yaml +6 -0
  111. package/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  112. package/.ai/blueprints/security-review/steps.yaml +38 -0
  113. package/.ai/policies/capabilities.yaml +164 -0
  114. package/.ai/policies/model-routing.yaml +72 -0
  115. package/.ai/policies/path-ownership.yaml +65 -0
  116. package/.ai/policies/task-budgets.yaml +37 -0
  117. package/.ai/references/catalog/LICENSE +21 -0
  118. package/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  119. package/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  120. package/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  121. package/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  122. package/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  123. package/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  124. package/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  125. package/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  126. package/.ai/references/catalog/migrations/README.md +3 -0
  127. package/.ai/references/catalog/module.json +27 -0
  128. package/.ai/references/catalog/package.json +49 -0
  129. package/.ai/references/catalog/spec/module.yaml +86 -0
  130. package/.ai/references/catalog/src/acl/permissions.ts +6 -0
  131. package/.ai/references/catalog/src/agent/tools.ts +164 -0
  132. package/.ai/references/catalog/src/api/endpoints.ts +243 -0
  133. package/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  134. package/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  135. package/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  136. package/.ai/references/catalog/src/client/api.ts +111 -0
  137. package/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  138. package/.ai/references/catalog/src/client/index.ts +18 -0
  139. package/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  140. package/.ai/references/catalog/src/client/state.ts +24 -0
  141. package/.ai/references/catalog/src/domain/types.ts +32 -0
  142. package/.ai/references/catalog/src/domain/variables.ts +111 -0
  143. package/.ai/references/catalog/src/index.ts +31 -0
  144. package/.ai/references/catalog/src/platform.ts +35 -0
  145. package/.ai/references/catalog/src/server/index.ts +4 -0
  146. package/.ai/references/catalog/src/server/runtime.ts +86 -0
  147. package/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  148. package/.ai/references/catalog/src/services/database-repository.ts +440 -0
  149. package/.ai/references/catalog/src/services/index.ts +4 -0
  150. package/.ai/references/catalog/src/services/migration.ts +171 -0
  151. package/.ai/references/catalog/src/services/repository.ts +36 -0
  152. package/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  153. package/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  154. package/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  155. package/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  156. package/.ai/references/catalog/tests/migrations.test.ts +149 -0
  157. package/.ai/references/catalog/tests/module.test.ts +271 -0
  158. package/.ai/references/catalog/tests/support/database.ts +76 -0
  159. package/.ai/references/catalog/translations/en.json +101 -0
  160. package/.ai/references/catalog/translations/pl.json +101 -0
  161. package/.ai/references/catalog/tsconfig.json +15 -0
  162. package/.ai/references/catalog/vitest.config.ts +16 -0
  163. package/.ai/references/catalog.provenance.json +55 -0
  164. package/.ai/skills/README.md +36 -0
  165. package/.ai/skills/agent-tool-design/SKILL.md +209 -0
  166. package/.ai/skills/auth-security-review/SKILL.md +96 -0
  167. package/.ai/skills/auto-review/SKILL.md +112 -0
  168. package/.ai/skills/bug-hunt/SKILL.md +110 -0
  169. package/.ai/skills/business-agent-design/SKILL.md +188 -0
  170. package/.ai/skills/cli-extension/SKILL.md +114 -0
  171. package/.ai/skills/core-extend/SKILL.md +104 -0
  172. package/.ai/skills/database-adapter/SKILL.md +204 -0
  173. package/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  174. package/.ai/skills/migration-authoring/SKILL.md +167 -0
  175. package/.ai/skills/module-new/SKILL.md +180 -0
  176. package/.ai/skills/module-update/SKILL.md +100 -0
  177. package/.ai/skills/perf-audit/SKILL.md +105 -0
  178. package/.ai/skills/release-eject-pr/SKILL.md +113 -0
  179. package/.ai/skills/spec-approval/SKILL.md +112 -0
  180. package/.ai/skills/test-hardening/SKILL.md +86 -0
  181. package/.ai/skills/translations-i18n/SKILL.md +85 -0
  182. package/.ai/skills/ux-design/SKILL.md +97 -0
  183. package/.ai/skills/variables/SKILL.md +164 -0
  184. package/.ai/skills/workflow-development/SKILL.md +199 -0
  185. package/AGENTS.md +59 -0
  186. package/LICENSE +21 -0
  187. package/README.md +24 -0
  188. package/docs/agent-contract.md +45 -0
  189. package/docs/design-system.md +217 -0
  190. package/modules/agents/migrations/0001_agents_core.down.sql +4 -0
  191. package/modules/agents/migrations/0001_agents_core.up.sql +106 -0
  192. package/modules/agents/migrations/0002_provider_connections.down.sql +1 -0
  193. package/modules/agents/migrations/0002_provider_connections.up.sql +34 -0
  194. package/modules/agents/migrations/0003_resource_audit.down.sql +1 -0
  195. package/modules/agents/migrations/0003_resource_audit.up.sql +25 -0
  196. package/modules/agents/migrations/0004_run_grants.down.sql +1 -0
  197. package/modules/agents/migrations/0004_run_grants.up.sql +19 -0
  198. package/modules/agents/migrations/0005_long_running_limits.down.sql +2 -0
  199. package/modules/agents/migrations/0005_long_running_limits.up.sql +14 -0
  200. package/modules/agents/migrations/0006_agent_skills.down.sql +3 -0
  201. package/modules/agents/migrations/0006_agent_skills.up.sql +44 -0
  202. package/modules/agents/migrations/0007_model_readiness.down.sql +1 -0
  203. package/modules/agents/migrations/0007_model_readiness.up.sql +18 -0
  204. package/modules/agents/migrations/0008_output_limits.down.sql +2 -0
  205. package/modules/agents/migrations/0008_output_limits.up.sql +8 -0
  206. package/modules/agents/migrations/0009_agent_audit_v3.down.sql +1 -0
  207. package/modules/agents/migrations/0009_agent_audit_v3.up.sql +25 -0
  208. package/modules/agents/migrations/0012_agent_run_costs.down.sql +3 -0
  209. package/modules/agents/migrations/0012_agent_run_costs.up.sql +32 -0
  210. package/modules/agents/migrations/0013_workflow_prerequisites.down.sql +3 -0
  211. package/modules/agents/migrations/0013_workflow_prerequisites.up.sql +97 -0
  212. package/modules/agents/migrations/0014_agent_action_audit.down.sql +1 -0
  213. package/modules/agents/migrations/0014_agent_action_audit.up.sql +25 -0
  214. package/modules/agents/migrations/0015_agent_run_actors.down.sql +1 -0
  215. package/modules/agents/migrations/0015_agent_run_actors.up.sql +17 -0
  216. package/modules/agents/migrations/0016_module_owned_agents.down.sql +6 -0
  217. package/modules/agents/migrations/0016_module_owned_agents.up.sql +68 -0
  218. package/modules/agents/migrations/0017_agent_authorization_subjects.down.sql +2 -0
  219. package/modules/agents/migrations/0017_agent_authorization_subjects.up.sql +18 -0
  220. package/modules/agents/migrations/0018_agent_child_tenant_isolation.down.sql +12 -0
  221. package/modules/agents/migrations/0018_agent_child_tenant_isolation.up.sql +88 -0
  222. package/modules/agents/migrations/0019_background_recovery_grants.down.sql +6 -0
  223. package/modules/agents/migrations/0019_background_recovery_grants.up.sql +17 -0
  224. package/modules/agents/migrations/0020_provider_summary_role.down.sql +2 -0
  225. package/modules/agents/migrations/0020_provider_summary_role.up.sql +17 -0
  226. package/modules/agents/migrations/0021_agents_agent_reconciliation_role.down.sql +4 -0
  227. package/modules/agents/migrations/0021_agents_agent_reconciliation_role.up.sql +7 -0
  228. package/modules/agents/migrations/README.md +39 -0
  229. package/modules/agents/module.json +38 -0
  230. package/modules/agents/package.json +56 -0
  231. package/modules/agents/spec/module.yaml +271 -0
  232. package/modules/agents/spec/procedures-compatibility.md +63 -0
  233. package/modules/agents/src/acl/permissions.ts +13 -0
  234. package/modules/agents/src/api/endpoints.ts +1122 -0
  235. package/modules/agents/src/cli/commands.json +29 -0
  236. package/modules/agents/src/cli/index.ts +162 -0
  237. package/modules/agents/src/client/AgentDashboardWidget.tsrx +32 -0
  238. package/modules/agents/src/client/AgentDefinitionForm.tsrx +362 -0
  239. package/modules/agents/src/client/AgentDefinitionList.tsrx +127 -0
  240. package/modules/agents/src/client/AgentPlaygroundView.tsrx +246 -0
  241. package/modules/agents/src/client/AgentProcedureForm.tsrx +182 -0
  242. package/modules/agents/src/client/AgentProcedureList.tsrx +118 -0
  243. package/modules/agents/src/client/AgentProceduresView.tsrx +253 -0
  244. package/modules/agents/src/client/AgentProvidersView.tsrx +248 -0
  245. package/modules/agents/src/client/AgentRunsView.tsrx +304 -0
  246. package/modules/agents/src/client/AgentUsageView.tsrx +233 -0
  247. package/modules/agents/src/client/AgentsView.tsrx +339 -0
  248. package/modules/agents/src/client/ModuleAgentBindingForm.tsrx +239 -0
  249. package/modules/agents/src/client/ModuleAgentList.tsrx +122 -0
  250. package/modules/agents/src/client/ProviderConnectionForm.tsrx +273 -0
  251. package/modules/agents/src/client/ProviderConnectionList.tsrx +229 -0
  252. package/modules/agents/src/client/ProviderModelEditor.tsrx +313 -0
  253. package/modules/agents/src/client/RunTimeline.tsrx +212 -0
  254. package/modules/agents/src/client/agents.css +349 -0
  255. package/modules/agents/src/client/api.ts +373 -0
  256. package/modules/agents/src/client/contribution.tsrx +181 -0
  257. package/modules/agents/src/client/index.ts +21 -0
  258. package/modules/agents/src/client/presentation.ts +162 -0
  259. package/modules/agents/src/client/state.ts +100 -0
  260. package/modules/agents/src/domain/context-variables.ts +58 -0
  261. package/modules/agents/src/domain/run-timeline.ts +145 -0
  262. package/modules/agents/src/domain/types.ts +481 -0
  263. package/modules/agents/src/index.ts +88 -0
  264. package/modules/agents/src/platform.ts +82 -0
  265. package/modules/agents/src/server/action-execution.ts +811 -0
  266. package/modules/agents/src/server/define-agent.ts +170 -0
  267. package/modules/agents/src/server/index.ts +67 -0
  268. package/modules/agents/src/server/run-execution.ts +231 -0
  269. package/modules/agents/src/server/run-queue.ts +96 -0
  270. package/modules/agents/src/server/runtime.ts +448 -0
  271. package/modules/agents/src/server/tools.ts +18 -0
  272. package/modules/agents/src/services/agent-service.ts +2167 -0
  273. package/modules/agents/src/services/credential-vault.ts +133 -0
  274. package/modules/agents/src/services/database-repository.ts +2786 -0
  275. package/modules/agents/src/services/index.ts +33 -0
  276. package/modules/agents/src/services/migration.ts +1152 -0
  277. package/modules/agents/src/services/module-agent-preflight.ts +76 -0
  278. package/modules/agents/src/services/outbound-policy.ts +136 -0
  279. package/modules/agents/src/services/provider-broker.ts +80 -0
  280. package/modules/agents/src/services/provider-repository.ts +589 -0
  281. package/modules/agents/src/services/provider-service.ts +968 -0
  282. package/modules/agents/src/services/repository.ts +294 -0
  283. package/modules/agents/src/services/run-grant.ts +305 -0
  284. package/modules/agents/src/services/usage-service.ts +119 -0
  285. package/modules/agents/src/services/worker.ts +442 -0
  286. package/modules/agents/src/settings.ts +244 -0
  287. package/modules/agents/translations/en.json +403 -0
  288. package/modules/agents/translations/pl.json +403 -0
  289. package/modules/auth/README.md +215 -0
  290. package/modules/auth/migrations/0001_auth_core.up.sql +108 -0
  291. package/modules/auth/migrations/0002_bundled_module_scopes.up.sql +26 -0
  292. package/modules/auth/migrations/0003_agent_core_scopes.up.sql +12 -0
  293. package/modules/auth/migrations/0004_owner_only_development_scopes.up.sql +8 -0
  294. package/modules/auth/migrations/0005_agent_provider_scopes.up.sql +13 -0
  295. package/modules/auth/migrations/0006_tenant_slug.up.sql +6 -0
  296. package/modules/auth/migrations/0007_sandbox_scopes.up.sql +13 -0
  297. package/modules/auth/migrations/0008_api_tokens.up.sql +43 -0
  298. package/modules/auth/migrations/0009_module_settings.up.sql +24 -0
  299. package/modules/auth/migrations/0010_sign_in_lockout.up.sql +11 -0
  300. package/modules/auth/migrations/0011_session_ids_and_password_reset.up.sql +7 -0
  301. package/modules/auth/migrations/0012_roles.up.sql +31 -0
  302. package/modules/auth/migrations/0013_audit.up.sql +27 -0
  303. package/modules/auth/migrations/0014_audit_actor.up.sql +4 -0
  304. package/modules/auth/migrations/0015_account_security.up.sql +81 -0
  305. package/modules/auth/module.json +26 -0
  306. package/modules/auth/package.json +61 -0
  307. package/modules/auth/spec/module.yaml +190 -0
  308. package/modules/auth/src/acl/scopes.ts +86 -0
  309. package/modules/auth/src/api/attempt-limiter.ts +42 -0
  310. package/modules/auth/src/api/client-address.ts +14 -0
  311. package/modules/auth/src/api/cookies.ts +52 -0
  312. package/modules/auth/src/api/origin.ts +40 -0
  313. package/modules/auth/src/cli/commands.json +74 -0
  314. package/modules/auth/src/cli/database.ts +51 -0
  315. package/modules/auth/src/cli/greenfield.ts +173 -0
  316. package/modules/auth/src/cli/index.ts +184 -0
  317. package/modules/auth/src/cli/provisioning.ts +211 -0
  318. package/modules/auth/src/client/AuthBackdrop.tsrx +137 -0
  319. package/modules/auth/src/client/AuthenticationCore.tsrx +630 -0
  320. package/modules/auth/src/client/PasswordChangeRequired.tsrx +145 -0
  321. package/modules/auth/src/client/PublicAuthFlows.tsrx +379 -0
  322. package/modules/auth/src/client/api.ts +464 -0
  323. package/modules/auth/src/client/audit/AuditView.tsrx +357 -0
  324. package/modules/auth/src/client/audit/api.ts +164 -0
  325. package/modules/auth/src/client/audit/audit-columns.tsrx +47 -0
  326. package/modules/auth/src/client/audit/audit-row.ts +38 -0
  327. package/modules/auth/src/client/auth.css +483 -0
  328. package/modules/auth/src/client/contribution.tsrx +100 -0
  329. package/modules/auth/src/client/index.ts +32 -0
  330. package/modules/auth/src/client/security/SecurityView.tsrx +253 -0
  331. package/modules/auth/src/client/settings/PreferencesCard.tsrx +65 -0
  332. package/modules/auth/src/client/settings/SettingsView.tsrx +212 -0
  333. package/modules/auth/src/client/settings/WorkspaceCard.tsrx +59 -0
  334. package/modules/auth/src/client/settings/api.ts +115 -0
  335. package/modules/auth/src/client/slug.ts +26 -0
  336. package/modules/auth/src/client/state.ts +115 -0
  337. package/modules/auth/src/client/tokens/ApiTokenForm.tsrx +153 -0
  338. package/modules/auth/src/client/tokens/ApiTokenSecret.tsrx +35 -0
  339. package/modules/auth/src/client/tokens/ApiTokensView.tsrx +216 -0
  340. package/modules/auth/src/client/tokens/api.ts +59 -0
  341. package/modules/auth/src/client/tokens/state.ts +26 -0
  342. package/modules/auth/src/client/tokens/token-columns.tsrx +64 -0
  343. package/modules/auth/src/client/translations.ts +23 -0
  344. package/modules/auth/src/domain/types.ts +152 -0
  345. package/modules/auth/src/index.ts +42 -0
  346. package/modules/auth/src/middleware/authentication.ts +97 -0
  347. package/modules/auth/src/platform.ts +0 -0
  348. package/modules/auth/src/server/actor.ts +12 -0
  349. package/modules/auth/src/server/audit-endpoints.ts +51 -0
  350. package/modules/auth/src/server/composition.ts +9 -0
  351. package/modules/auth/src/server/endpoints.ts +893 -0
  352. package/modules/auth/src/server/http.ts +153 -0
  353. package/modules/auth/src/server/index.ts +37 -0
  354. package/modules/auth/src/server/roles-endpoints.ts +120 -0
  355. package/modules/auth/src/server/runtime.ts +705 -0
  356. package/modules/auth/src/server/session-endpoints.ts +86 -0
  357. package/modules/auth/src/server/session-security.ts +62 -0
  358. package/modules/auth/src/server/token-endpoints.ts +106 -0
  359. package/modules/auth/src/services/auth-service-error.ts +11 -0
  360. package/modules/auth/src/services/auth-service.ts +2309 -0
  361. package/modules/auth/src/services/database-repository.ts +1825 -0
  362. package/modules/auth/src/services/mail-delivery.ts +27 -0
  363. package/modules/auth/src/services/migration.ts +591 -0
  364. package/modules/auth/src/services/password.ts +115 -0
  365. package/modules/auth/src/services/repository.ts +395 -0
  366. package/modules/auth/src/services/settings-store.ts +92 -0
  367. package/modules/auth/src/services/totp.ts +135 -0
  368. package/modules/auth/src/services/validation.ts +186 -0
  369. package/modules/auth/src/settings.ts +150 -0
  370. package/modules/auth/translations/en.json +263 -0
  371. package/modules/auth/translations/pl.json +263 -0
  372. package/modules/automations/migrations/0001_automations_core.down.sql +10 -0
  373. package/modules/automations/migrations/0001_automations_core.up.sql +76 -0
  374. package/modules/automations/migrations/0002_automations_targets.down.sql +9 -0
  375. package/modules/automations/migrations/0002_automations_targets.up.sql +37 -0
  376. package/modules/automations/migrations/0003_automations_scheduler_role.down.sql +3 -0
  377. package/modules/automations/migrations/0003_automations_scheduler_role.up.sql +19 -0
  378. package/modules/automations/migrations/0004_automations_trigger_routing_role.down.sql +3 -0
  379. package/modules/automations/migrations/0004_automations_trigger_routing_role.up.sql +18 -0
  380. package/modules/automations/migrations/README.md +3 -0
  381. package/modules/automations/module.json +31 -0
  382. package/modules/automations/package.json +55 -0
  383. package/modules/automations/spec/module.yaml +93 -0
  384. package/modules/automations/src/acl/permissions.ts +10 -0
  385. package/modules/automations/src/api/endpoints.ts +480 -0
  386. package/modules/automations/src/client/AutomationScheduleForm.tsrx +218 -0
  387. package/modules/automations/src/client/AutomationSchedulesView.tsrx +339 -0
  388. package/modules/automations/src/client/AutomationTriggerForm.tsrx +190 -0
  389. package/modules/automations/src/client/AutomationTriggersView.tsrx +329 -0
  390. package/modules/automations/src/client/api.ts +148 -0
  391. package/modules/automations/src/client/contribution.tsrx +70 -0
  392. package/modules/automations/src/client/index.ts +20 -0
  393. package/modules/automations/src/client/presentation.ts +38 -0
  394. package/modules/automations/src/client/state.ts +40 -0
  395. package/modules/automations/src/client/target-selection.ts +44 -0
  396. package/modules/automations/src/domain/cadence.ts +44 -0
  397. package/modules/automations/src/domain/types.ts +110 -0
  398. package/modules/automations/src/domain/variables.ts +178 -0
  399. package/modules/automations/src/index.ts +39 -0
  400. package/modules/automations/src/platform.ts +73 -0
  401. package/modules/automations/src/server/execution.ts +77 -0
  402. package/modules/automations/src/server/index.ts +39 -0
  403. package/modules/automations/src/server/runtime.ts +218 -0
  404. package/modules/automations/src/server/targets.ts +116 -0
  405. package/modules/automations/src/services/automations-service.ts +10 -0
  406. package/modules/automations/src/services/database-repository.ts +763 -0
  407. package/modules/automations/src/services/index.ts +5 -0
  408. package/modules/automations/src/services/migration.ts +223 -0
  409. package/modules/automations/src/services/repository.ts +119 -0
  410. package/modules/automations/src/services/schedule-service.ts +837 -0
  411. package/modules/automations/src/services/secret-vault.ts +124 -0
  412. package/modules/automations/src/services/trigger-service.ts +648 -0
  413. package/modules/automations/src/settings.ts +69 -0
  414. package/modules/automations/translations/en.json +126 -0
  415. package/modules/automations/translations/pl.json +126 -0
  416. package/modules/automations-workflows-integration/module.json +27 -0
  417. package/modules/automations-workflows-integration/package.json +48 -0
  418. package/modules/automations-workflows-integration/spec/module.yaml +93 -0
  419. package/modules/automations-workflows-integration/src/index.ts +14 -0
  420. package/modules/automations-workflows-integration/src/platform.ts +24 -0
  421. package/modules/automations-workflows-integration/src/server/adapter.ts +272 -0
  422. package/modules/automations-workflows-integration/src/server/index.ts +11 -0
  423. package/modules/automations-workflows-integration/src/server/tools.ts +0 -0
  424. package/modules/automations-workflows-integration/translations/en.json +21 -0
  425. package/modules/automations-workflows-integration/translations/pl.json +21 -0
  426. package/modules/profile/migrations/0001_profile_core.down.sql +1 -0
  427. package/modules/profile/migrations/0001_profile_core.up.sql +13 -0
  428. package/modules/profile/migrations/0002_profile_language.down.sql +1 -0
  429. package/modules/profile/migrations/0002_profile_language.up.sql +12 -0
  430. package/modules/profile/migrations/README.md +3 -0
  431. package/modules/profile/module.json +23 -0
  432. package/modules/profile/package.json +55 -0
  433. package/modules/profile/spec/module.yaml +136 -0
  434. package/modules/profile/src/acl/permissions.ts +5 -0
  435. package/modules/profile/src/api/endpoints.ts +184 -0
  436. package/modules/profile/src/client/DisplayNameCard.tsrx +59 -0
  437. package/modules/profile/src/client/LanguageCard.tsrx +89 -0
  438. package/modules/profile/src/client/PasswordCard.tsrx +79 -0
  439. package/modules/profile/src/client/ProfileView.tsrx +192 -0
  440. package/modules/profile/src/client/SessionsCard.tsrx +158 -0
  441. package/modules/profile/src/client/api.ts +134 -0
  442. package/modules/profile/src/client/contribution.tsrx +61 -0
  443. package/modules/profile/src/client/index.ts +15 -0
  444. package/modules/profile/src/client/password.ts +41 -0
  445. package/modules/profile/src/client/sessions-api.ts +52 -0
  446. package/modules/profile/src/client/state.ts +23 -0
  447. package/modules/profile/src/domain/types.ts +21 -0
  448. package/modules/profile/src/index.ts +21 -0
  449. package/modules/profile/src/platform.ts +23 -0
  450. package/modules/profile/src/server/index.ts +7 -0
  451. package/modules/profile/src/server/runtime.ts +84 -0
  452. package/modules/profile/src/services/database-repository.ts +154 -0
  453. package/modules/profile/src/services/index.ts +6 -0
  454. package/modules/profile/src/services/migration.ts +58 -0
  455. package/modules/profile/src/services/profile-service.ts +114 -0
  456. package/modules/profile/src/services/repository.ts +13 -0
  457. package/modules/profile/translations/en.json +58 -0
  458. package/modules/profile/translations/pl.json +58 -0
  459. package/modules/sandbox/migrations/0001_sandbox_core.down.sql +6 -0
  460. package/modules/sandbox/migrations/0001_sandbox_core.up.sql +70 -0
  461. package/modules/sandbox/migrations/0002_sandbox_session_lifecycle.down.sql +36 -0
  462. package/modules/sandbox/migrations/0002_sandbox_session_lifecycle.up.sql +37 -0
  463. package/modules/sandbox/migrations/README.md +7 -0
  464. package/modules/sandbox/module.json +31 -0
  465. package/modules/sandbox/package.json +56 -0
  466. package/modules/sandbox/spec/module.yaml +111 -0
  467. package/modules/sandbox/src/acl/permissions.ts +18 -0
  468. package/modules/sandbox/src/api/endpoints.ts +439 -0
  469. package/modules/sandbox/src/cli/commands.json +84 -0
  470. package/modules/sandbox/src/cli/index.ts +450 -0
  471. package/modules/sandbox/src/client/SandboxAccessView.tsrx +191 -0
  472. package/modules/sandbox/src/client/SandboxGrantForm.tsrx +134 -0
  473. package/modules/sandbox/src/client/SandboxGrantList.tsrx +83 -0
  474. package/modules/sandbox/src/client/SandboxSessionList.tsrx +56 -0
  475. package/modules/sandbox/src/client/api.ts +87 -0
  476. package/modules/sandbox/src/client/contribution.tsrx +47 -0
  477. package/modules/sandbox/src/client/index.ts +18 -0
  478. package/modules/sandbox/src/client/presentation.ts +47 -0
  479. package/modules/sandbox/src/client/state.ts +20 -0
  480. package/modules/sandbox/src/domain/types.ts +125 -0
  481. package/modules/sandbox/src/index.ts +33 -0
  482. package/modules/sandbox/src/platform.ts +28 -0
  483. package/modules/sandbox/src/server/index.ts +20 -0
  484. package/modules/sandbox/src/server/runtime.ts +119 -0
  485. package/modules/sandbox/src/services/database-repository.ts +567 -0
  486. package/modules/sandbox/src/services/directory.ts +33 -0
  487. package/modules/sandbox/src/services/index.ts +19 -0
  488. package/modules/sandbox/src/services/migration.ts +145 -0
  489. package/modules/sandbox/src/services/repository.ts +55 -0
  490. package/modules/sandbox/src/services/sandbox-service-error.ts +10 -0
  491. package/modules/sandbox/src/services/sandbox-service.ts +533 -0
  492. package/modules/sandbox/translations/en.json +77 -0
  493. package/modules/sandbox/translations/pl.json +77 -0
  494. package/modules/system/module.json +18 -0
  495. package/modules/system/package.json +56 -0
  496. package/modules/system/spec/module.yaml +104 -0
  497. package/modules/system/src/acl/permissions.ts +10 -0
  498. package/modules/system/src/client/ModuleSettingRow.tsrx +191 -0
  499. package/modules/system/src/client/ModuleSettingsSection.tsrx +155 -0
  500. package/modules/system/src/client/ModulesView.tsrx +241 -0
  501. package/modules/system/src/client/api.ts +36 -0
  502. package/modules/system/src/client/contribution.tsrx +48 -0
  503. package/modules/system/src/client/index.ts +26 -0
  504. package/modules/system/src/client/module-columns.tsrx +66 -0
  505. package/modules/system/src/client/overview-api.ts +30 -0
  506. package/modules/system/src/client/setting-copy.ts +30 -0
  507. package/modules/system/src/client/settings-api.ts +57 -0
  508. package/modules/system/src/client/state.ts +16 -0
  509. package/modules/system/src/index.ts +11 -0
  510. package/modules/system/src/platform.ts +11 -0
  511. package/modules/system/src/server/endpoints.ts +372 -0
  512. package/modules/system/src/server/index.ts +11 -0
  513. package/modules/system/src/server/module-catalog.ts +117 -0
  514. package/modules/system/translations/en.json +64 -0
  515. package/modules/system/translations/pl.json +64 -0
  516. package/modules/users/migrations/README.md +3 -0
  517. package/modules/users/module.json +27 -0
  518. package/modules/users/package.json +54 -0
  519. package/modules/users/spec/module.yaml +79 -0
  520. package/modules/users/src/acl/permissions.ts +6 -0
  521. package/modules/users/src/api/endpoints.ts +222 -0
  522. package/modules/users/src/client/InviteMemberForm.tsrx +62 -0
  523. package/modules/users/src/client/MemberDrawer.tsrx +298 -0
  524. package/modules/users/src/client/RoleForm.tsrx +140 -0
  525. package/modules/users/src/client/RolesView.tsrx +288 -0
  526. package/modules/users/src/client/UserAccountForm.tsrx +86 -0
  527. package/modules/users/src/client/UsersView.tsrx +486 -0
  528. package/modules/users/src/client/api.ts +172 -0
  529. package/modules/users/src/client/contribution.tsrx +92 -0
  530. package/modules/users/src/client/index.ts +19 -0
  531. package/modules/users/src/client/member-columns.tsrx +52 -0
  532. package/modules/users/src/client/roles-api.ts +90 -0
  533. package/modules/users/src/client/state.ts +42 -0
  534. package/modules/users/src/index.ts +27 -0
  535. package/modules/users/src/platform.ts +11 -0
  536. package/modules/users/src/services/index.ts +2 -0
  537. package/modules/users/src/services/users-service.ts +142 -0
  538. package/modules/users/translations/en.json +133 -0
  539. package/modules/users/translations/pl.json +133 -0
  540. package/modules/workflows/migrations/0001_workflows_core.down.sql +19 -0
  541. package/modules/workflows/migrations/0001_workflows_core.up.sql +228 -0
  542. package/modules/workflows/migrations/0002_workflows_authorization_subject.down.sql +1 -0
  543. package/modules/workflows/migrations/0002_workflows_authorization_subject.up.sql +9 -0
  544. package/modules/workflows/migrations/0003_workflows_worker_role.up.sql +34 -0
  545. package/modules/workflows/migrations/README.md +7 -0
  546. package/modules/workflows/module.json +31 -0
  547. package/modules/workflows/package.json +55 -0
  548. package/modules/workflows/spec/module.yaml +227 -0
  549. package/modules/workflows/src/acl/permissions.ts +10 -0
  550. package/modules/workflows/src/api/endpoints.ts +852 -0
  551. package/modules/workflows/src/client/WorkflowCanvas.tsrx +819 -0
  552. package/modules/workflows/src/client/WorkflowCreateForm.tsrx +111 -0
  553. package/modules/workflows/src/client/WorkflowEditor.tsrx +443 -0
  554. package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +169 -0
  555. package/modules/workflows/src/client/WorkflowFixtureDrawer.tsrx +206 -0
  556. package/modules/workflows/src/client/WorkflowInputForm.tsrx +125 -0
  557. package/modules/workflows/src/client/WorkflowInputSchemaEditor.tsrx +185 -0
  558. package/modules/workflows/src/client/WorkflowInspector.tsrx +471 -0
  559. package/modules/workflows/src/client/WorkflowLiveConfirmation.tsrx +67 -0
  560. package/modules/workflows/src/client/WorkflowMappingEditor.tsrx +283 -0
  561. package/modules/workflows/src/client/WorkflowNodePalette.tsrx +50 -0
  562. package/modules/workflows/src/client/WorkflowPointerField.tsrx +178 -0
  563. package/modules/workflows/src/client/WorkflowRevisionReview.tsrx +49 -0
  564. package/modules/workflows/src/client/WorkflowRunInspector.tsrx +143 -0
  565. package/modules/workflows/src/client/WorkflowTestPanel.tsrx +109 -0
  566. package/modules/workflows/src/client/WorkflowValidationPanel.tsrx +139 -0
  567. package/modules/workflows/src/client/WorkflowsView.tsrx +1060 -0
  568. package/modules/workflows/src/client/api.ts +312 -0
  569. package/modules/workflows/src/client/canvas-model.ts +472 -0
  570. package/modules/workflows/src/client/connected-node.ts +71 -0
  571. package/modules/workflows/src/client/contribution.tsrx +57 -0
  572. package/modules/workflows/src/client/create-model.ts +15 -0
  573. package/modules/workflows/src/client/draft-repair.ts +86 -0
  574. package/modules/workflows/src/client/index.ts +19 -0
  575. package/modules/workflows/src/client/input-model.ts +124 -0
  576. package/modules/workflows/src/client/mapping-model.ts +110 -0
  577. package/modules/workflows/src/client/pointer-model.ts +123 -0
  578. package/modules/workflows/src/client/presentation.ts +62 -0
  579. package/modules/workflows/src/client/run-actions.ts +19 -0
  580. package/modules/workflows/src/client/state.ts +57 -0
  581. package/modules/workflows/src/client/viewport.ts +107 -0
  582. package/modules/workflows/src/client/workflows.css +899 -0
  583. package/modules/workflows/src/domain/events.ts +399 -0
  584. package/modules/workflows/src/domain/graph.ts +1136 -0
  585. package/modules/workflows/src/domain/types.ts +563 -0
  586. package/modules/workflows/src/index.ts +27 -0
  587. package/modules/workflows/src/platform.ts +49 -0
  588. package/modules/workflows/src/server/index.ts +13 -0
  589. package/modules/workflows/src/server/runtime.ts +266 -0
  590. package/modules/workflows/src/services/cursor-codec.ts +62 -0
  591. package/modules/workflows/src/services/database-repository.ts +2407 -0
  592. package/modules/workflows/src/services/index.ts +6 -0
  593. package/modules/workflows/src/services/migration.ts +375 -0
  594. package/modules/workflows/src/services/payload-codec.ts +264 -0
  595. package/modules/workflows/src/services/repository.ts +285 -0
  596. package/modules/workflows/src/services/simulation.ts +359 -0
  597. package/modules/workflows/src/services/worker.ts +1196 -0
  598. package/modules/workflows/src/services/workflows-service.ts +1395 -0
  599. package/modules/workflows/translations/en.json +348 -0
  600. package/modules/workflows/translations/pl.json +348 -0
  601. package/modules.json +41 -0
  602. package/package.json +125 -0
  603. package/packages/ai-provider/package.json +46 -0
  604. package/packages/ai-provider/src/catalog.ts +102 -0
  605. package/packages/ai-provider/src/errors.ts +110 -0
  606. package/packages/ai-provider/src/index.ts +33 -0
  607. package/packages/ai-provider/src/model.ts +198 -0
  608. package/packages/ai-provider/src/pricing.ts +73 -0
  609. package/packages/cli-protocol/package.json +40 -0
  610. package/packages/cli-protocol/src/index.ts +113 -0
  611. package/packages/client/package.json +46 -0
  612. package/packages/client/src/ApplicationShell.tsrx +374 -0
  613. package/packages/client/src/contributions.ts +172 -0
  614. package/packages/client/src/i18n/index.ts +30 -0
  615. package/packages/client/src/i18n/locale.ts +25 -0
  616. package/packages/client/src/i18n/locales/en.json +66 -0
  617. package/packages/client/src/i18n/locales/pl.json +66 -0
  618. package/packages/client/src/i18n/runtime.ts +201 -0
  619. package/packages/client/src/i18n/translations.ts +88 -0
  620. package/packages/client/src/index.ts +55 -0
  621. package/packages/client/src/routing.ts +9 -0
  622. package/packages/client/src/shell/BrandMark.tsrx +10 -0
  623. package/packages/client/src/shell/CommandPalette.tsrx +79 -0
  624. package/packages/client/src/shell/ContributionOutlets.tsrx +31 -0
  625. package/packages/client/src/shell/DashboardView.tsrx +60 -0
  626. package/packages/client/src/shell/MobileHeader.tsrx +24 -0
  627. package/packages/client/src/shell/OrganizationSwitcher.tsrx +100 -0
  628. package/packages/client/src/shell/OverviewCharts.tsrx +167 -0
  629. package/packages/client/src/shell/PrimaryNavigation.tsrx +75 -0
  630. package/packages/client/src/shell/Sidebar.tsrx +76 -0
  631. package/packages/client/src/shell/Topbar.tsrx +44 -0
  632. package/packages/client/src/shell/UserMenu.tsrx +103 -0
  633. package/packages/client/src/shell/WorkspaceContent.tsrx +30 -0
  634. package/packages/client/src/shell/identity.ts +12 -0
  635. package/packages/client/src/shell/navigation.ts +68 -0
  636. package/packages/client/src/shell/shell.css +734 -0
  637. package/packages/client/src/shell/types.ts +29 -0
  638. package/packages/client/src/state.ts +63 -0
  639. package/packages/client/src/web.ts +39 -0
  640. package/packages/coding-agent/package.json +44 -0
  641. package/packages/coding-agent/src/drivers/byok.ts +322 -0
  642. package/packages/coding-agent/src/drivers/claude-code.ts +290 -0
  643. package/packages/coding-agent/src/drivers/codex.ts +281 -0
  644. package/packages/coding-agent/src/index.ts +56 -0
  645. package/packages/coding-agent/src/registry.ts +119 -0
  646. package/packages/coding-agent/src/roles/contract.ts +60 -0
  647. package/packages/coding-agent/src/roles/defaults.ts +134 -0
  648. package/packages/coding-agent/src/roles/handoff.ts +41 -0
  649. package/packages/coding-agent/src/roles/registry.ts +156 -0
  650. package/packages/coding-agent/src/roles/skills.ts +72 -0
  651. package/packages/coding-agent/src/roles/sync.ts +88 -0
  652. package/packages/coding-agent/src/types.ts +102 -0
  653. package/packages/coding-agent/src/workspace.ts +275 -0
  654. package/packages/contracts/package.json +38 -0
  655. package/packages/contracts/schemas/blueprint.schema.json +48 -0
  656. package/packages/contracts/schemas/cli-extension.schema.json +90 -0
  657. package/packages/contracts/schemas/module-artifact.schema.json +82 -0
  658. package/packages/contracts/schemas/module-catalog.schema.json +48 -0
  659. package/packages/contracts/schemas/module-spec.schema.json +111 -0
  660. package/packages/contracts/schemas/module.schema.json +156 -0
  661. package/packages/contracts/schemas/platform-spec.schema.json +17 -0
  662. package/packages/contracts/schemas/project.schema.json +270 -0
  663. package/packages/contracts/src/index.ts +191 -0
  664. package/packages/contracts/src/module-distribution.ts +47 -0
  665. package/packages/contracts/src/schemas.ts +18 -0
  666. package/packages/contracts/src/variables.ts +156 -0
  667. package/packages/contracts/src/web.ts +60 -0
  668. package/packages/database/package.json +43 -0
  669. package/packages/database/src/contracts.ts +399 -0
  670. package/packages/database/src/index.ts +112 -0
  671. package/packages/database/src/migrations.ts +444 -0
  672. package/packages/database/src/postgresql.ts +517 -0
  673. package/packages/database/src/provider.ts +639 -0
  674. package/packages/database/src/record-history.ts +201 -0
  675. package/packages/database/src/registry.ts +265 -0
  676. package/packages/database/src/reset.ts +87 -0
  677. package/packages/database-pglite/package.json +42 -0
  678. package/packages/database-pglite/src/driver.ts +168 -0
  679. package/packages/database-pglite/src/index.ts +2 -0
  680. package/packages/database-testing/package.json +44 -0
  681. package/packages/database-testing/src/index.ts +8 -0
  682. package/packages/database-testing/src/pglite.ts +124 -0
  683. package/packages/database-testing/src/postgres.ts +184 -0
  684. package/packages/database-testing/src/provider.ts +43 -0
  685. package/packages/database-testing/src/vitest-preset.ts +23 -0
  686. package/packages/dev-console/README.md +6 -0
  687. package/packages/dev-console/package.json +38 -0
  688. package/packages/dev-console/src/index.mjs +220 -0
  689. package/packages/harness/package.json +47 -0
  690. package/packages/harness/schemas/task-packet.schema.json +48 -0
  691. package/packages/harness/src/catalog.ts +24 -0
  692. package/packages/harness/src/errors.ts +9 -0
  693. package/packages/harness/src/index.ts +53 -0
  694. package/packages/harness/src/preamble.ts +31 -0
  695. package/packages/harness/src/runtime.ts +833 -0
  696. package/packages/harness/src/task-packet.ts +86 -0
  697. package/packages/harness/src/tool-adapters.ts +106 -0
  698. package/packages/harness/src/tool-contract.ts +242 -0
  699. package/packages/harness/src/vercel-ai-provider.ts +198 -0
  700. package/packages/kernel/package.json +44 -0
  701. package/packages/kernel/src/acl.ts +20 -0
  702. package/packages/kernel/src/actor.ts +139 -0
  703. package/packages/kernel/src/agent-registry.ts +81 -0
  704. package/packages/kernel/src/capability-registry.ts +28 -0
  705. package/packages/kernel/src/errors.ts +9 -0
  706. package/packages/kernel/src/index.ts +90 -0
  707. package/packages/kernel/src/legacy-local-state.ts +103 -0
  708. package/packages/kernel/src/module-compatibility.ts +70 -0
  709. package/packages/kernel/src/module-registry.ts +124 -0
  710. package/packages/kernel/src/module-settings.ts +403 -0
  711. package/packages/kernel/src/record-history.ts +146 -0
  712. package/packages/kernel/src/runtime-config.ts +51 -0
  713. package/packages/kernel/src/tool-registry.ts +25 -0
  714. package/packages/kernel/src/variable-registry.ts +339 -0
  715. package/packages/sandbox/README.md +691 -0
  716. package/packages/sandbox/bin/flowdular-sandbox.mjs +393 -0
  717. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +11 -0
  718. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +1 -0
  719. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +3 -0
  720. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  721. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  722. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  723. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  724. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  725. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  726. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  727. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  728. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  729. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  730. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  731. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  732. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  733. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  734. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  735. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  736. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  737. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  738. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  739. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  740. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  741. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  742. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  743. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  744. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  745. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  746. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +4 -0
  747. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +3 -0
  748. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +1 -0
  749. package/packages/sandbox/dist/client/favicon.svg +1 -0
  750. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +12785 -0
  751. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +4125 -0
  752. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +24 -0
  753. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +388 -0
  754. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +55 -0
  755. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +3 -0
  756. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +494 -0
  757. package/packages/sandbox/dist/server/entry.js +91671 -0
  758. package/packages/sandbox/dist/server/favicon.svg +1 -0
  759. package/packages/sandbox/dist/server/index.html +161 -0
  760. package/packages/sandbox/index.html +159 -0
  761. package/packages/sandbox/octane.config.ts +36 -0
  762. package/packages/sandbox/package.json +83 -0
  763. package/packages/sandbox/public/favicon.svg +1 -0
  764. package/packages/sandbox/public/og.png +0 -0
  765. package/packages/sandbox/src/App.tsrx +924 -0
  766. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +39 -0
  767. package/packages/sandbox/src/client/ChatPane.tsrx +694 -0
  768. package/packages/sandbox/src/client/ConnectView.tsrx +86 -0
  769. package/packages/sandbox/src/client/DiffPane.tsrx +150 -0
  770. package/packages/sandbox/src/client/EjectModal.tsrx +571 -0
  771. package/packages/sandbox/src/client/GateResults.tsrx +60 -0
  772. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +219 -0
  773. package/packages/sandbox/src/client/NewSessionForm.tsrx +214 -0
  774. package/packages/sandbox/src/client/SessionBar.tsrx +151 -0
  775. package/packages/sandbox/src/client/SessionPicker.tsrx +472 -0
  776. package/packages/sandbox/src/client/SpecEditor.tsrx +109 -0
  777. package/packages/sandbox/src/client/SpecReviewCard.tsrx +321 -0
  778. package/packages/sandbox/src/client/WorkCard.tsrx +128 -0
  779. package/packages/sandbox/src/client/WorkbenchModal.tsrx +161 -0
  780. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +100 -0
  781. package/packages/sandbox/src/client/api.ts +797 -0
  782. package/packages/sandbox/src/client/attachments.ts +73 -0
  783. package/packages/sandbox/src/client/dashboard.ts +46 -0
  784. package/packages/sandbox/src/client/github-settings.ts +48 -0
  785. package/packages/sandbox/src/client/highlight.ts +105 -0
  786. package/packages/sandbox/src/client/i18n.ts +32 -0
  787. package/packages/sandbox/src/client/locales/en.json +466 -0
  788. package/packages/sandbox/src/client/locales/pl.json +466 -0
  789. package/packages/sandbox/src/client/session-labels.ts +77 -0
  790. package/packages/sandbox/src/client/state.ts +108 -0
  791. package/packages/sandbox/src/index.ts +10 -0
  792. package/packages/sandbox/src/preview/PreviewHost.tsrx +373 -0
  793. package/packages/sandbox/src/preview/load-module.ts +16 -0
  794. package/packages/sandbox/src/server/attachments.ts +308 -0
  795. package/packages/sandbox/src/server/auto-review.ts +181 -0
  796. package/packages/sandbox/src/server/checkpoints.ts +90 -0
  797. package/packages/sandbox/src/server/config.ts +460 -0
  798. package/packages/sandbox/src/server/dashboard.ts +245 -0
  799. package/packages/sandbox/src/server/delivery/configuration.ts +193 -0
  800. package/packages/sandbox/src/server/delivery/git-pr.ts +1473 -0
  801. package/packages/sandbox/src/server/delivery/index.ts +83 -0
  802. package/packages/sandbox/src/server/delivery/local.ts +151 -0
  803. package/packages/sandbox/src/server/delivery/official-modules.ts +516 -0
  804. package/packages/sandbox/src/server/delivery/plan.ts +218 -0
  805. package/packages/sandbox/src/server/delivery/policies.ts +239 -0
  806. package/packages/sandbox/src/server/delivery/record.ts +41 -0
  807. package/packages/sandbox/src/server/delivery/steps.ts +464 -0
  808. package/packages/sandbox/src/server/delivery/types.ts +158 -0
  809. package/packages/sandbox/src/server/dependencies.ts +88 -0
  810. package/packages/sandbox/src/server/diff.ts +230 -0
  811. package/packages/sandbox/src/server/gates.ts +388 -0
  812. package/packages/sandbox/src/server/index.ts +139 -0
  813. package/packages/sandbox/src/server/path-guard.ts +282 -0
  814. package/packages/sandbox/src/server/planning.ts +749 -0
  815. package/packages/sandbox/src/server/platform-client.ts +189 -0
  816. package/packages/sandbox/src/server/preview-database-host.ts +340 -0
  817. package/packages/sandbox/src/server/preview-database-protocol.ts +163 -0
  818. package/packages/sandbox/src/server/preview-database-proxy.ts +356 -0
  819. package/packages/sandbox/src/server/preview-database.ts +109 -0
  820. package/packages/sandbox/src/server/preview-ipc.ts +15 -0
  821. package/packages/sandbox/src/server/preview-modules.ts +83 -0
  822. package/packages/sandbox/src/server/preview-revision.ts +44 -0
  823. package/packages/sandbox/src/server/preview-runtime.ts +467 -0
  824. package/packages/sandbox/src/server/preview-worker-manager.ts +436 -0
  825. package/packages/sandbox/src/server/preview-worker.ts +168 -0
  826. package/packages/sandbox/src/server/reference.ts +251 -0
  827. package/packages/sandbox/src/server/routes.ts +2258 -0
  828. package/packages/sandbox/src/server/runtime.ts +188 -0
  829. package/packages/sandbox/src/server/session-owner.ts +23 -0
  830. package/packages/sandbox/src/server/sessions.ts +849 -0
  831. package/packages/sandbox/src/server/spec.ts +471 -0
  832. package/packages/sandbox/src/server/turn-lifecycle.ts +56 -0
  833. package/packages/sandbox/src/server/turns.ts +993 -0
  834. package/packages/sandbox/src/server/workspace-install.ts +266 -0
  835. package/packages/sandbox/src/server/workspace-root.ts +73 -0
  836. package/packages/sandbox/src/styles.css +1573 -0
  837. package/packages/sandbox/vite.config.ts +133 -0
  838. package/packages/server/package.json +43 -0
  839. package/packages/server/src/application-routes.ts +21 -0
  840. package/packages/server/src/composition.ts +48 -0
  841. package/packages/server/src/endpoint.ts +142 -0
  842. package/packages/server/src/http.ts +128 -0
  843. package/packages/server/src/index.ts +46 -0
  844. package/packages/server/src/response-lifetime.ts +56 -0
  845. package/packages/server/src/security-headers.ts +130 -0
  846. package/packages/server/src/web-html.ts +55 -0
  847. package/packages/server/src/web.ts +361 -0
  848. package/packages/ui/package.json +46 -0
  849. package/packages/ui/src/brand/BrandMark.tsrx +66 -0
  850. package/packages/ui/src/brand/mark.ts +44 -0
  851. package/packages/ui/src/components/Alert.tsrx +17 -0
  852. package/packages/ui/src/components/Avatar.tsrx +18 -0
  853. package/packages/ui/src/components/Button.tsrx +29 -0
  854. package/packages/ui/src/components/Chart.tsrx +274 -0
  855. package/packages/ui/src/components/CheckGrid.tsrx +93 -0
  856. package/packages/ui/src/components/ConfirmDialog.tsrx +63 -0
  857. package/packages/ui/src/components/Drawer.tsrx +71 -0
  858. package/packages/ui/src/components/EmptyState.tsrx +26 -0
  859. package/packages/ui/src/components/Filters.tsrx +39 -0
  860. package/packages/ui/src/components/FormField.tsrx +27 -0
  861. package/packages/ui/src/components/Kpi.tsrx +36 -0
  862. package/packages/ui/src/components/PageHeader.tsrx +26 -0
  863. package/packages/ui/src/components/ScopeSummary.tsrx +76 -0
  864. package/packages/ui/src/components/SearchField.tsrx +24 -0
  865. package/packages/ui/src/components/SettingRow.tsrx +50 -0
  866. package/packages/ui/src/components/Switch.tsrx +21 -0
  867. package/packages/ui/src/components/Table.tsrx +267 -0
  868. package/packages/ui/src/components/TableCard.tsrx +52 -0
  869. package/packages/ui/src/components/Tag.tsrx +28 -0
  870. package/packages/ui/src/components/VariableField.tsrx +284 -0
  871. package/packages/ui/src/components/VariableInput.tsrx +7 -0
  872. package/packages/ui/src/components/VariableSelect.tsrx +72 -0
  873. package/packages/ui/src/components/VariableTextarea.tsrx +9 -0
  874. package/packages/ui/src/components/variable-field.ts +61 -0
  875. package/packages/ui/src/components/variable-select.ts +69 -0
  876. package/packages/ui/src/icons/Icon.tsrx +56 -0
  877. package/packages/ui/src/index.ts +81 -0
  878. package/packages/ui/src/initials.ts +10 -0
  879. package/packages/ui/src/styles/base.css +71 -0
  880. package/packages/ui/src/styles/components.css +1859 -0
  881. package/packages/ui/src/styles/index.css +3 -0
  882. package/packages/ui/src/styles/tokens.css +124 -0
  883. package/tsconfig.base.json +18 -0
@@ -0,0 +1,2786 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ runDatabaseMigrations,
4
+ type DatabaseHandle,
5
+ type DatabaseTransaction,
6
+ } from '@flowdular/sdk/database';
7
+ import { normalizeActor, type Actor } from '@flowdular/sdk/kernel';
8
+ import type {
9
+ AgentExecutionDefinition,
10
+ AgentExecutionEvent,
11
+ AgentExecutionResult,
12
+ AgentOutputContract,
13
+ AgentUsage,
14
+ JsonValue,
15
+ } from '@flowdular/sdk/harness';
16
+ import { usageCostMicros } from '@flowdular/sdk/harness/catalog';
17
+ import type {
18
+ AgentAuditEvent,
19
+ AgentAuditPage,
20
+ AgentDefinition,
21
+ AgentDefinitionRevision,
22
+ AgentActionInvocation,
23
+ AgentRevisionProcedure,
24
+ ModuleAgentBinding,
25
+ ModuleAgentDefinition,
26
+ AgentProcedure,
27
+ AgentProcedureSnapshot,
28
+ AgentRun,
29
+ AgentRunDetail,
30
+ AgentRunExecution,
31
+ AgentUsageAgent,
32
+ AgentUsageBucket,
33
+ AgentUsageDay,
34
+ AuditChainVerification,
35
+ } from '../domain/types.ts';
36
+ import { databaseMigrations } from './migration.ts';
37
+ import type { EncryptedCredential } from './credential-vault.ts';
38
+ import {
39
+ DuplicateAgentKeyError,
40
+ DuplicateAgentProcedureKeyError,
41
+ DuplicateActionIdempotencyKeyError,
42
+ DuplicateRunIdempotencyKeyError,
43
+ ModuleAgentBindingConflictError,
44
+ type AgentRepository,
45
+ type PendingAgentAuditEvent,
46
+ type RecoverableRun,
47
+ } from './repository.ts';
48
+ import { usageDay } from './usage-service.ts';
49
+ import { moduleAgentDefinitionHash } from '../server/define-agent.ts';
50
+
51
+ interface AgentRow {
52
+ id: string;
53
+ tenant_id: string;
54
+ agent_key: string;
55
+ name: string;
56
+ description: string;
57
+ instructions: string;
58
+ provider: string;
59
+ model: string;
60
+ allowed_tools_json: string;
61
+ max_steps: number;
62
+ timeout_ms: number | string;
63
+ temperature_milli: number;
64
+ max_output_tokens: number | string;
65
+ status: AgentDefinition['status'];
66
+ revision: number;
67
+ created_by: string;
68
+ created_at: number | string;
69
+ updated_by: string;
70
+ updated_at: number | string;
71
+ }
72
+
73
+ interface RunRow {
74
+ id: string;
75
+ tenant_id: string;
76
+ agent_id: string;
77
+ agent_name: string;
78
+ agent_revision: number;
79
+ instructions_snapshot: string;
80
+ provider: string;
81
+ model: string;
82
+ allowed_tools_json: string;
83
+ max_steps: number;
84
+ timeout_ms: number | string;
85
+ temperature_milli: number;
86
+ max_output_tokens: number | string;
87
+ trigger: AgentRun['trigger'];
88
+ status: AgentRun['status'];
89
+ input: string;
90
+ output: string | null;
91
+ structured_output_json: string | null;
92
+ output_contract_json: string;
93
+ workflow_run_id: string | null;
94
+ request_hash: string;
95
+ requested_by: string;
96
+ requested_actor_json: string;
97
+ authorization_subject_json: string | null;
98
+ permission_snapshot_json: string;
99
+ tool_grants_json: string;
100
+ usage_json: string | null;
101
+ failure_code: string | null;
102
+ failure_message: string | null;
103
+ attempt: number;
104
+ queued_at: number | string;
105
+ started_at: number | string | null;
106
+ completed_at: number | string | null;
107
+ lease_expires_at: number | string | null;
108
+ }
109
+
110
+ interface AgentRevisionRow {
111
+ tenant_id: string;
112
+ agent_id: string;
113
+ revision: number;
114
+ agent_key: string;
115
+ name: string;
116
+ description: string;
117
+ instructions: string;
118
+ provider: string;
119
+ model: string;
120
+ allowed_tools_json: string;
121
+ skills_json: string;
122
+ max_steps: number;
123
+ timeout_ms: number | string;
124
+ temperature_milli: number;
125
+ max_output_tokens: number | string;
126
+ status: AgentDefinition['status'];
127
+ retained_by: string;
128
+ retained_at: number | string;
129
+ module_id: string | null;
130
+ module_definition_revision: number | null;
131
+ }
132
+
133
+ interface ModuleAgentDefinitionRow {
134
+ agent_id: string;
135
+ module_id: string;
136
+ agent_key: string;
137
+ definition_revision: number;
138
+ content_hash: string;
139
+ name: string;
140
+ description: string;
141
+ instructions: string;
142
+ allowed_tools_json: string;
143
+ max_steps: number;
144
+ timeout_ms: number | string;
145
+ temperature_milli: number;
146
+ max_output_tokens: number | string;
147
+ registered_at: number | string;
148
+ }
149
+
150
+ interface ModuleAgentBindingRow {
151
+ tenant_id: string;
152
+ agent_id: string;
153
+ provider: string;
154
+ model: string;
155
+ enabled_tools_json: string;
156
+ status: ModuleAgentBinding['status'];
157
+ module_definition_revision: number;
158
+ executable_revision: number;
159
+ revision: number;
160
+ updated_by: string;
161
+ updated_at: number | string;
162
+ }
163
+
164
+ interface ActionRow {
165
+ id: string;
166
+ tenant_id: string;
167
+ workflow_run_id: string;
168
+ node_run_id: string;
169
+ action_id: string;
170
+ contract_version: number;
171
+ actor_json: string;
172
+ authorization_subject_json: string | null;
173
+ permission_snapshot_json: string;
174
+ input_json: string;
175
+ idempotency_key: string;
176
+ request_hash: string;
177
+ status: AgentActionInvocation['status'];
178
+ output_json: string | null;
179
+ failure_code: string | null;
180
+ attempt: number;
181
+ queued_at: number | string;
182
+ started_at: number | string | null;
183
+ completed_at: number | string | null;
184
+ lease_expires_at: number | string | null;
185
+ }
186
+
187
+ interface RunEventRow {
188
+ sequence: number | string;
189
+ event_type: AgentExecutionEvent['type'];
190
+ message: string;
191
+ metadata_json: string;
192
+ occurred_at: number | string;
193
+ }
194
+
195
+ interface AuditRow {
196
+ id: string;
197
+ tenant_id: string;
198
+ sequence: number | string;
199
+ actor_id: string;
200
+ action: string;
201
+ subject_type: AgentAuditEvent['subjectType'];
202
+ subject_id: string;
203
+ metadata_json: string;
204
+ occurred_at: number | string;
205
+ previous_hash: string | null;
206
+ event_hash: string;
207
+ }
208
+
209
+ interface RecoverableRow {
210
+ tenant_id: string;
211
+ id: string;
212
+ }
213
+
214
+ interface SkillRow {
215
+ id: string;
216
+ tenant_id: string;
217
+ skill_key: string;
218
+ name: string;
219
+ description: string;
220
+ instructions: string;
221
+ required_tools_json: string;
222
+ status: AgentProcedure['status'];
223
+ revision: number;
224
+ created_by: string;
225
+ created_at: number | string;
226
+ updated_by: string;
227
+ updated_at: number | string;
228
+ }
229
+
230
+ interface SkillSnapshotRow {
231
+ skill_id: string;
232
+ skill_key: string;
233
+ skill_name: string;
234
+ skill_revision: number;
235
+ required_tools_json: string;
236
+ }
237
+
238
+ interface UsageRow {
239
+ day: string;
240
+ agent_id: string;
241
+ agent_name: string;
242
+ runs: number | string;
243
+ input_tokens: number | string;
244
+ output_tokens: number | string;
245
+ cost_micro_usd: number | string | null;
246
+ unpriced_runs: number | string;
247
+ }
248
+
249
+ function stringArray(value: string): readonly string[] {
250
+ const parsed: unknown = JSON.parse(value);
251
+ if (
252
+ !Array.isArray(parsed) ||
253
+ parsed.some((item) => typeof item !== 'string')
254
+ ) {
255
+ throw new Error('Stored string array is invalid.');
256
+ }
257
+ return parsed as string[];
258
+ }
259
+
260
+ function usage(value: string | null): AgentUsage | null {
261
+ return value === null ? null : (JSON.parse(value) as AgentUsage);
262
+ }
263
+
264
+ function jsonValue(value: string | null): JsonValue | null {
265
+ return value === null ? null : (JSON.parse(value) as JsonValue);
266
+ }
267
+
268
+ function metadata(
269
+ value: string,
270
+ ): Readonly<Record<string, string | number | boolean>> {
271
+ return JSON.parse(value) as Record<string, string | number | boolean>;
272
+ }
273
+
274
+ function stableMetadata(
275
+ value: Readonly<Record<string, string | number | boolean>>,
276
+ ): string {
277
+ return JSON.stringify(
278
+ Object.fromEntries(
279
+ Object.entries(value).sort(([left], [right]) =>
280
+ left.localeCompare(right),
281
+ ),
282
+ ),
283
+ );
284
+ }
285
+
286
+ function fromAgentRow(row: AgentRow): AgentDefinition {
287
+ return {
288
+ id: row.id,
289
+ tenantId: row.tenant_id,
290
+ key: row.agent_key,
291
+ name: row.name,
292
+ description: row.description,
293
+ instructions: row.instructions,
294
+ provider: row.provider,
295
+ model: row.model,
296
+ allowedTools: stringArray(row.allowed_tools_json),
297
+ procedureIds: [],
298
+ maxSteps: row.max_steps,
299
+ timeoutMs: integer(row.timeout_ms),
300
+ temperature: row.temperature_milli / 1_000,
301
+ maxOutputTokens: integer(row.max_output_tokens),
302
+ status: row.status,
303
+ revision: row.revision,
304
+ createdBy: row.created_by,
305
+ createdAt: integer(row.created_at),
306
+ updatedBy: row.updated_by,
307
+ updatedAt: integer(row.updated_at),
308
+ };
309
+ }
310
+
311
+ function fromRunRow(row: RunRow): AgentRun {
312
+ const requestedActor = normalizeActor(
313
+ JSON.parse(row.requested_actor_json) as Actor,
314
+ );
315
+ if (!requestedActor) throw new Error('Stored agent run actor is invalid.');
316
+ return {
317
+ id: row.id,
318
+ tenantId: row.tenant_id,
319
+ agentId: row.agent_id,
320
+ agentName: row.agent_name,
321
+ agentRevision: row.agent_revision,
322
+ trigger: row.trigger,
323
+ status: row.status,
324
+ input: row.input,
325
+ output: row.output,
326
+ structuredOutput: jsonValue(row.structured_output_json),
327
+ outputContract: JSON.parse(row.output_contract_json) as AgentOutputContract,
328
+ workflowRunId: row.workflow_run_id,
329
+ provider: row.provider,
330
+ model: row.model,
331
+ requestedBy: row.requested_by,
332
+ requestedActor,
333
+ authorizationSubject:
334
+ row.authorization_subject_json === null
335
+ ? null
336
+ : (JSON.parse(
337
+ row.authorization_subject_json,
338
+ ) as AgentRun['authorizationSubject']),
339
+ permissionSnapshot: stringArray(row.permission_snapshot_json),
340
+ toolGrants: stringArray(row.tool_grants_json),
341
+ procedureSnapshots: [],
342
+ usage: usage(row.usage_json),
343
+ failureCode: row.failure_code,
344
+ failureMessage: row.failure_message,
345
+ attempt: row.attempt,
346
+ queuedAt: integer(row.queued_at),
347
+ startedAt: integerOrNull(row.started_at),
348
+ completedAt: integerOrNull(row.completed_at),
349
+ leaseExpiresAt: integerOrNull(row.lease_expires_at),
350
+ };
351
+ }
352
+
353
+ function fromRevisionRow(row: AgentRevisionRow): AgentDefinitionRevision {
354
+ return {
355
+ tenantId: row.tenant_id,
356
+ agentId: row.agent_id,
357
+ revision: row.revision,
358
+ key: row.agent_key,
359
+ name: row.name,
360
+ description: row.description,
361
+ instructions: row.instructions,
362
+ provider: row.provider,
363
+ model: row.model,
364
+ allowedTools: stringArray(row.allowed_tools_json),
365
+ procedures: JSON.parse(row.skills_json) as AgentRevisionProcedure[],
366
+ maxSteps: row.max_steps,
367
+ timeoutMs: integer(row.timeout_ms),
368
+ temperature: row.temperature_milli / 1_000,
369
+ maxOutputTokens: integer(row.max_output_tokens),
370
+ status: row.status,
371
+ ownership:
372
+ row.module_id === null
373
+ ? { kind: 'tenant' }
374
+ : {
375
+ kind: 'module',
376
+ moduleId: row.module_id,
377
+ definitionRevision: row.module_definition_revision!,
378
+ },
379
+ moduleDefinitionRevision: row.module_definition_revision,
380
+ retainedBy: row.retained_by,
381
+ retainedAt: integer(row.retained_at),
382
+ };
383
+ }
384
+
385
+ function fromModuleBindingRow(row: ModuleAgentBindingRow): ModuleAgentBinding {
386
+ return {
387
+ tenantId: row.tenant_id,
388
+ agentId: row.agent_id,
389
+ provider: row.provider,
390
+ model: row.model,
391
+ enabledTools: stringArray(row.enabled_tools_json),
392
+ status: row.status,
393
+ moduleDefinitionRevision: row.module_definition_revision,
394
+ executableRevision: row.executable_revision,
395
+ revision: row.revision,
396
+ updatedBy: row.updated_by,
397
+ updatedAt: integer(row.updated_at),
398
+ };
399
+ }
400
+
401
+ function fromActionRow(row: ActionRow): AgentActionInvocation {
402
+ return {
403
+ id: row.id,
404
+ tenantId: row.tenant_id,
405
+ workflowRunId: row.workflow_run_id,
406
+ nodeRunId: row.node_run_id,
407
+ actionId: row.action_id,
408
+ contractVersion: row.contract_version,
409
+ actor: JSON.parse(row.actor_json) as AgentActionInvocation['actor'],
410
+ authorizationSubject:
411
+ row.authorization_subject_json === null
412
+ ? null
413
+ : (JSON.parse(
414
+ row.authorization_subject_json,
415
+ ) as AgentActionInvocation['authorizationSubject']),
416
+ permissionSnapshot: stringArray(row.permission_snapshot_json),
417
+ input: JSON.parse(row.input_json) as JsonValue,
418
+ idempotencyKey: row.idempotency_key,
419
+ requestHash: row.request_hash,
420
+ status: row.status,
421
+ output: jsonValue(row.output_json),
422
+ code: row.failure_code,
423
+ attempt: row.attempt,
424
+ queuedAt: integer(row.queued_at),
425
+ startedAt: integerOrNull(row.started_at),
426
+ completedAt: integerOrNull(row.completed_at),
427
+ leaseExpiresAt: integerOrNull(row.lease_expires_at),
428
+ };
429
+ }
430
+
431
+ function fromSkillRow(row: SkillRow): AgentProcedure {
432
+ return {
433
+ id: row.id,
434
+ tenantId: row.tenant_id,
435
+ key: row.skill_key,
436
+ name: row.name,
437
+ description: row.description,
438
+ instructions: row.instructions,
439
+ requiredTools: stringArray(row.required_tools_json),
440
+ status: row.status,
441
+ revision: row.revision,
442
+ createdBy: row.created_by,
443
+ createdAt: integer(row.created_at),
444
+ updatedBy: row.updated_by,
445
+ updatedAt: integer(row.updated_at),
446
+ };
447
+ }
448
+
449
+ function executionFromRow(row: RunRow): AgentRunExecution {
450
+ const definition: AgentExecutionDefinition = {
451
+ id: row.agent_id,
452
+ name: row.agent_name,
453
+ revision: row.agent_revision,
454
+ instructions: row.instructions_snapshot,
455
+ provider: row.provider,
456
+ model: row.model,
457
+ allowedTools: stringArray(row.allowed_tools_json),
458
+ maxSteps: row.max_steps,
459
+ timeoutMs: integer(row.timeout_ms),
460
+ temperature: row.temperature_milli / 1_000,
461
+ maxOutputTokens: integer(row.max_output_tokens),
462
+ };
463
+ return { run: fromRunRow(row), definition };
464
+ }
465
+
466
+ /* SUM() over BIGINT is NUMERIC in PostgreSQL, and the driver hands NUMERIC
467
+ back as a string. Aggregates leave this repository as numbers. */
468
+ function integer(value: number | string | null): number {
469
+ return typeof value === 'number' ? value : Number(value ?? 0);
470
+ }
471
+
472
+ /* A nullable BIGINT must stay null: coercing it to 0 would turn "never started"
473
+ into "started at the epoch". */
474
+ function integerOrNull(value: number | string | null): number | null {
475
+ return value === null ? null : integer(value);
476
+ }
477
+
478
+ function fromUsageRow(row: UsageRow): AgentUsageBucket {
479
+ return {
480
+ runs: integer(row.runs),
481
+ inputTokens: integer(row.input_tokens),
482
+ outputTokens: integer(row.output_tokens),
483
+ costMicroUsd: integer(row.cost_micro_usd),
484
+ unpricedRuns: integer(row.unpriced_runs),
485
+ };
486
+ }
487
+
488
+ const USAGE_COLUMNS = `COUNT(*) AS runs,
489
+ COALESCE(SUM(input_tokens), 0) AS input_tokens,
490
+ COALESCE(SUM(output_tokens), 0) AS output_tokens,
491
+ COALESCE(SUM(cost_micro_usd), 0) AS cost_micro_usd,
492
+ SUM(CASE WHEN cost_micro_usd IS NULL THEN 1 ELSE 0 END) AS unpriced_runs`;
493
+
494
+ function fromAuditRow(row: AuditRow): AgentAuditEvent {
495
+ return {
496
+ id: row.id,
497
+ tenantId: row.tenant_id,
498
+ sequence: integer(row.sequence),
499
+ actorId: row.actor_id,
500
+ action: row.action,
501
+ subjectType: row.subject_type,
502
+ subjectId: row.subject_id,
503
+ metadata: metadata(row.metadata_json),
504
+ occurredAt: integer(row.occurred_at),
505
+ previousHash: row.previous_hash,
506
+ eventHash: row.event_hash,
507
+ };
508
+ }
509
+
510
+ function auditHash(value: {
511
+ tenantId: string;
512
+ sequence: number;
513
+ actorId: string;
514
+ action: string;
515
+ subjectType: AgentAuditEvent['subjectType'];
516
+ subjectId: string;
517
+ metadataJson: string;
518
+ occurredAt: number;
519
+ previousHash: string | null;
520
+ }): string {
521
+ return createHash('sha256')
522
+ .update(
523
+ JSON.stringify([
524
+ value.tenantId,
525
+ value.sequence,
526
+ value.actorId,
527
+ value.action,
528
+ value.subjectType,
529
+ value.subjectId,
530
+ value.metadataJson,
531
+ value.occurredAt,
532
+ value.previousHash,
533
+ ]),
534
+ )
535
+ .digest('hex');
536
+ }
537
+
538
+ const DEFAULT_MAX_OUTPUT_TOKENS = 4_096;
539
+
540
+ const RUN_COLUMNS = `agent_runs.id, agent_runs.tenant_id, agent_id, agent_name, agent_revision,
541
+ instructions_snapshot, provider, model, allowed_tools_json, max_steps,
542
+ COALESCE(agent_run_execution_limits.timeout_ms, agent_runs.timeout_ms) AS timeout_ms,
543
+ temperature_milli,
544
+ COALESCE(agent_run_output_limits.max_output_tokens, ${DEFAULT_MAX_OUTPUT_TOKENS}) AS max_output_tokens,
545
+ trigger, status, input, output, requested_by,
546
+ permission_snapshot_json, tool_grants_json, usage_json, failure_code,
547
+ failure_message, attempt, queued_at, started_at, completed_at, lease_expires_at,
548
+ agent_run_contracts.structured_output_json,
549
+ COALESCE(agent_run_contracts.output_contract_json, '{"kind":"text"}') AS output_contract_json,
550
+ agent_run_contracts.workflow_run_id,
551
+ COALESCE(agent_run_contracts.request_hash, '') AS request_hash,
552
+ agent_run_actors.actor_json AS requested_actor_json,
553
+ agent_run_actors.authorization_subject_json`;
554
+ const RUN_FROM = `agent_runs LEFT JOIN agent_run_execution_limits
555
+ ON agent_run_execution_limits.run_id = agent_runs.id
556
+ LEFT JOIN agent_run_output_limits
557
+ ON agent_run_output_limits.run_id = agent_runs.id
558
+ LEFT JOIN agent_run_contracts ON agent_run_contracts.run_id = agent_runs.id
559
+ JOIN agent_run_actors ON agent_run_actors.run_id = agent_runs.id
560
+ AND agent_run_actors.tenant_id = agent_runs.tenant_id`;
561
+ const AGENT_SELECT = `SELECT agent_definitions.*,
562
+ COALESCE(agent_definition_execution_limits.timeout_ms,
563
+ agent_definitions.timeout_ms) AS timeout_ms,
564
+ COALESCE(agent_definition_output_limits.max_output_tokens,
565
+ ${DEFAULT_MAX_OUTPUT_TOKENS}) AS max_output_tokens
566
+ FROM agent_definitions LEFT JOIN agent_definition_execution_limits
567
+ ON agent_definition_execution_limits.agent_id = agent_definitions.id
568
+ LEFT JOIN agent_definition_output_limits
569
+ ON agent_definition_output_limits.agent_id = agent_definitions.id`;
570
+ const AGENT_REVISION_SELECT = `SELECT agent_definition_revisions.*,
571
+ agent_revision_ownership.module_id,
572
+ agent_revision_ownership.module_definition_revision
573
+ FROM agent_definition_revisions
574
+ LEFT JOIN agent_revision_ownership
575
+ ON agent_revision_ownership.tenant_id = agent_definition_revisions.tenant_id
576
+ AND agent_revision_ownership.agent_id = agent_definition_revisions.agent_id
577
+ AND agent_revision_ownership.revision = agent_definition_revisions.revision`;
578
+
579
+ export interface AgentsPersistenceStatements {
580
+ readonly procedureIds: string;
581
+ readonly procedureSnapshots: string;
582
+ readonly setAgentSkills1: string;
583
+ readonly setAgentSkills2: string;
584
+ readonly revisionSkills: string;
585
+ readonly retainAgentRevision1: string;
586
+ readonly retainAgentRevision2: string;
587
+ readonly listAgentsForRevisionAdoption: string;
588
+ readonly retainModuleAgentRevision1: string;
589
+ readonly retainModuleAgentRevision2: string;
590
+ readonly reconcileModuleAgents1: string;
591
+ readonly reconcileModuleAgents2: string;
592
+ readonly reconcileModuleAgents3: string;
593
+ readonly reconcileModuleAgents4: string;
594
+ readonly reconcileModuleAgents5: string;
595
+ readonly reconcileModuleAgents6: string;
596
+ readonly listModuleAgentBindings: string;
597
+ readonly getModuleAgentBinding: string;
598
+ readonly saveModuleAgentBinding1: string;
599
+ readonly saveModuleAgentBinding2: string;
600
+ readonly saveModuleAgentBinding3: string;
601
+ readonly listAgents: string;
602
+ readonly providerUsage1: string;
603
+ readonly providerUsage2: string;
604
+ readonly agentUsage: string;
605
+ readonly getAgent: string;
606
+ readonly getAgentRevision: string;
607
+ readonly listAgentRevisions: string;
608
+ readonly createAgent1: string;
609
+ readonly createAgent2: string;
610
+ readonly createAgent3: string;
611
+ readonly updateAgent1: string;
612
+ readonly updateAgent2: string;
613
+ readonly updateAgent3: string;
614
+ readonly deleteAgent: string;
615
+ readonly listProcedures: string;
616
+ readonly getProcedure: string;
617
+ readonly createProcedure: string;
618
+ readonly updateProcedure: string;
619
+ readonly procedureUsage: string;
620
+ readonly deleteProcedure: string;
621
+ readonly listRuns: string;
622
+ readonly getRun: string;
623
+ readonly listRunEvents: string;
624
+ readonly findRunByIdempotencyKey: string;
625
+ readonly enqueueRun1: string;
626
+ readonly enqueueRun2: string;
627
+ readonly enqueueRun3: string;
628
+ readonly enqueueRun4: string;
629
+ readonly enqueueRun5: string;
630
+ readonly enqueueRun6: string;
631
+ readonly listRecoverableRuns: string;
632
+ readonly claimRun1: string;
633
+ readonly claimRun2: string;
634
+ readonly renewLease: string;
635
+ readonly consumeRunGrant1: string;
636
+ readonly consumeRunGrant2: string;
637
+ readonly appendRunEvent: string;
638
+ readonly completeRun1: string;
639
+ readonly completeRun2: string;
640
+ readonly completeRun3: string;
641
+ readonly completeRun4: string;
642
+ readonly failRun: string;
643
+ readonly cancelRun1: string;
644
+ readonly cancelRun2: string;
645
+ readonly enqueueAction: string;
646
+ readonly getAction: string;
647
+ readonly findActionByIdempotencyKey: string;
648
+ readonly listRecoverableActions: string;
649
+ readonly claimAction: string;
650
+ readonly renewActionLease: string;
651
+ readonly completeAction: string;
652
+ readonly failAction: string;
653
+ readonly cancelAction: string;
654
+ readonly appendAuditEvent1: string;
655
+ readonly appendAuditEvent2: string;
656
+ readonly listAuditEvents: string;
657
+ readonly pageAuditEvents1: string;
658
+ readonly pageAuditEvents2: string;
659
+ readonly verifyAuditChainDetailed: string;
660
+ readonly usageByDay: string;
661
+ readonly usageByAgent: string;
662
+ readonly usageTotal1: string;
663
+ readonly usageTotal2: string;
664
+ }
665
+
666
+ /* Every statement is PostgreSQL; the platform has no second dialect. */
667
+ export const AGENTS_SQL: AgentsPersistenceStatements = Object.freeze({
668
+ procedureIds: `SELECT skill_id FROM agent_skill_assignments
669
+ WHERE tenant_id = $1 AND agent_id = $2 ORDER BY skill_id`,
670
+ procedureSnapshots: `SELECT * FROM agent_run_skill_snapshots
671
+ WHERE tenant_id = $1 AND run_id = $2 ORDER BY skill_key, skill_id`,
672
+ setAgentSkills1: `DELETE FROM agent_skill_assignments WHERE agent_id = $1`,
673
+ setAgentSkills2: `INSERT INTO agent_skill_assignments (agent_id, skill_id, tenant_id)
674
+ VALUES ($1, $2, $3)`,
675
+ revisionSkills: `SELECT agent_skills.id, agent_skills.skill_key, agent_skills.name,
676
+ agent_skills.revision, agent_skills.instructions,
677
+ agent_skills.required_tools_json
678
+ FROM agent_skill_assignments JOIN agent_skills
679
+ ON agent_skills.id = agent_skill_assignments.skill_id
680
+ AND agent_skills.tenant_id = agent_skill_assignments.tenant_id
681
+ WHERE agent_skill_assignments.tenant_id = $1
682
+ AND agent_skill_assignments.agent_id = $2
683
+ ORDER BY agent_skills.skill_key, agent_skills.id`,
684
+ retainAgentRevision1: `INSERT INTO agent_definition_revisions
685
+ (tenant_id, agent_id, revision, agent_key, name, description,
686
+ instructions, provider, model, allowed_tools_json, skills_json,
687
+ max_steps, timeout_ms, temperature_milli, max_output_tokens,
688
+ status, retained_by, retained_at)
689
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
690
+ ON CONFLICT DO NOTHING`,
691
+ retainAgentRevision2: `INSERT INTO agent_definition_revisions
692
+ (tenant_id, agent_id, revision, agent_key, name, description,
693
+ instructions, provider, model, allowed_tools_json, skills_json,
694
+ max_steps, timeout_ms, temperature_milli, max_output_tokens,
695
+ status, retained_by, retained_at)
696
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`,
697
+ listAgentsForRevisionAdoption: `${AGENT_SELECT}
698
+ WHERE agent_definitions.tenant_id = $1
699
+ ORDER BY agent_definitions.tenant_id, agent_definitions.id`,
700
+ retainModuleAgentRevision1: `INSERT INTO agent_definition_revisions
701
+ (tenant_id, agent_id, revision, agent_key, name, description,
702
+ instructions, provider, model, allowed_tools_json, skills_json,
703
+ max_steps, timeout_ms, temperature_milli, max_output_tokens,
704
+ status, retained_by, retained_at)
705
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, '[]', $11, $12, $13, $14, 'active', $15, $16)`,
706
+ retainModuleAgentRevision2: `INSERT INTO agent_revision_ownership
707
+ (tenant_id, agent_id, revision, module_id, module_definition_revision)
708
+ VALUES ($1, $2, $3, $4, $5)`,
709
+ reconcileModuleAgents1: `SELECT * FROM module_agent_definitions WHERE agent_id = $1`,
710
+ reconcileModuleAgents2: `INSERT INTO agent_definitions
711
+ (id, tenant_id, agent_key, name, description, instructions,
712
+ provider, model, allowed_tools_json, max_steps, timeout_ms,
713
+ temperature_milli, status, revision, created_by, created_at,
714
+ updated_by, updated_at)
715
+ VALUES ($1, '__flowdular_module_agents__', $2, $3, $4, $5,
716
+ 'local-simulation', 'deterministic-v1', $6, $7, $8, $9, 'archived',
717
+ $10, $11, $12, $13, $14)`,
718
+ reconcileModuleAgents3: `UPDATE agent_definitions SET name = $1, description = $2,
719
+ instructions = $3, allowed_tools_json = $4, max_steps = $5,
720
+ timeout_ms = $6, temperature_milli = $7, revision = $8,
721
+ updated_by = $9, updated_at = $10 WHERE id = $11`,
722
+ reconcileModuleAgents4: `INSERT INTO module_agent_definitions
723
+ (agent_id, module_id, agent_key, definition_revision, content_hash,
724
+ name, description, instructions, allowed_tools_json, max_steps,
725
+ timeout_ms, temperature_milli, max_output_tokens, registered_at)
726
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
727
+ ON CONFLICT(agent_id) DO UPDATE SET
728
+ definition_revision = excluded.definition_revision,
729
+ content_hash = excluded.content_hash, name = excluded.name,
730
+ description = excluded.description, instructions = excluded.instructions,
731
+ allowed_tools_json = excluded.allowed_tools_json,
732
+ max_steps = excluded.max_steps, timeout_ms = excluded.timeout_ms,
733
+ temperature_milli = excluded.temperature_milli,
734
+ max_output_tokens = excluded.max_output_tokens,
735
+ registered_at = excluded.registered_at`,
736
+ reconcileModuleAgents5: `SELECT * FROM module_agent_bindings WHERE agent_id = $1`,
737
+ reconcileModuleAgents6: `UPDATE module_agent_bindings SET enabled_tools_json = $1,
738
+ module_definition_revision = $2, executable_revision = $3,
739
+ revision = $4, updated_by = $5, updated_at = $6
740
+ WHERE tenant_id = $7 AND agent_id = $8`,
741
+ listModuleAgentBindings: `SELECT * FROM module_agent_bindings WHERE tenant_id = $1
742
+ ORDER BY agent_id`,
743
+ getModuleAgentBinding: `SELECT * FROM module_agent_bindings WHERE tenant_id = $1 AND agent_id = $2`,
744
+ saveModuleAgentBinding1: `INSERT INTO module_agent_bindings
745
+ (tenant_id, agent_id, provider, model, enabled_tools_json, status,
746
+ module_definition_revision, executable_revision, revision,
747
+ updated_by, updated_at)
748
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
749
+ saveModuleAgentBinding2: `UPDATE module_agent_bindings SET provider = $1, model = $2,
750
+ enabled_tools_json = $3, status = $4, module_definition_revision = $5,
751
+ executable_revision = $6, revision = $7, updated_by = $8, updated_at = $9
752
+ WHERE tenant_id = $10 AND agent_id = $11 AND revision = $12`,
753
+ saveModuleAgentBinding3: `SELECT 1 FROM agent_definition_revisions
754
+ WHERE tenant_id = $1 AND agent_id = $2 AND revision = $3`,
755
+ listAgents: `${AGENT_SELECT} WHERE agent_definitions.tenant_id = $1
756
+ ORDER BY lower(agent_definitions.name), agent_definitions.id`,
757
+ providerUsage1: `SELECT
758
+ (SELECT COUNT(*) FROM agent_definitions
759
+ WHERE tenant_id = $1 AND provider = $2 AND status <> 'archived') +
760
+ (SELECT COUNT(*) FROM module_agent_bindings
761
+ WHERE tenant_id = $3 AND provider = $4) AS count`,
762
+ providerUsage2: `SELECT COUNT(*) AS count FROM agent_runs
763
+ WHERE tenant_id = $1 AND provider = $2 AND status IN ('queued', 'running')`,
764
+ agentUsage: `SELECT
765
+ (SELECT COUNT(*) FROM agent_runs
766
+ WHERE tenant_id = $1 AND agent_id = $2) AS runs,
767
+ (SELECT COUNT(*) FROM agent_runs
768
+ WHERE tenant_id = $3 AND agent_id = $4
769
+ AND status IN ('queued', 'running')) AS pending_runs,
770
+ (SELECT COUNT(*) FROM agent_skill_assignments
771
+ WHERE tenant_id = $5 AND agent_id = $6) AS assignments`,
772
+ getAgent: `${AGENT_SELECT} WHERE agent_definitions.tenant_id = $1
773
+ AND agent_definitions.id = $2`,
774
+ getAgentRevision: `${AGENT_REVISION_SELECT}
775
+ WHERE agent_definition_revisions.tenant_id = $1
776
+ AND agent_definition_revisions.agent_id = $2
777
+ AND agent_definition_revisions.revision = $3`,
778
+ listAgentRevisions: `${AGENT_REVISION_SELECT}
779
+ WHERE agent_definition_revisions.tenant_id = $1
780
+ ORDER BY lower(agent_definition_revisions.name),
781
+ agent_definition_revisions.agent_id,
782
+ agent_definition_revisions.revision DESC`,
783
+ createAgent1: `INSERT INTO agent_definitions
784
+ (id, tenant_id, agent_key, name, description, instructions,
785
+ provider, model, allowed_tools_json, max_steps, timeout_ms,
786
+ temperature_milli, status, revision, created_by, created_at,
787
+ updated_by, updated_at)
788
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`,
789
+ createAgent2: `INSERT INTO agent_definition_execution_limits
790
+ (agent_id, tenant_id, timeout_ms) VALUES ($1, $2, $3)`,
791
+ createAgent3: `INSERT INTO agent_definition_output_limits
792
+ (agent_id, tenant_id, max_output_tokens) VALUES ($1, $2, $3)`,
793
+ updateAgent1: `UPDATE agent_definitions SET agent_key = $1, name = $2,
794
+ description = $3, instructions = $4, provider = $5, model = $6,
795
+ allowed_tools_json = $7, max_steps = $8, timeout_ms = $9,
796
+ temperature_milli = $10, status = $11, revision = $12, updated_by = $13,
797
+ updated_at = $14 WHERE tenant_id = $15 AND id = $16`,
798
+ updateAgent2: `INSERT INTO agent_definition_execution_limits
799
+ (agent_id, tenant_id, timeout_ms) VALUES ($1, $2, $3)
800
+ ON CONFLICT(agent_id) DO UPDATE SET timeout_ms = excluded.timeout_ms`,
801
+ updateAgent3: `INSERT INTO agent_definition_output_limits
802
+ (agent_id, tenant_id, max_output_tokens) VALUES ($1, $2, $3)
803
+ ON CONFLICT(agent_id) DO UPDATE SET max_output_tokens = excluded.max_output_tokens`,
804
+ deleteAgent: `DELETE FROM agent_definitions WHERE tenant_id = $1 AND id = $2`,
805
+ listProcedures: `SELECT * FROM agent_skills WHERE tenant_id = $1
806
+ ORDER BY lower(name), id`,
807
+ getProcedure: `SELECT * FROM agent_skills WHERE tenant_id = $1 AND id = $2`,
808
+ createProcedure: `INSERT INTO agent_skills
809
+ (id, tenant_id, skill_key, name, description, instructions,
810
+ required_tools_json, status, revision, created_by, created_at,
811
+ updated_by, updated_at)
812
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
813
+ updateProcedure: `UPDATE agent_skills SET skill_key = $1, name = $2, description = $3,
814
+ instructions = $4, required_tools_json = $5, status = $6, revision = $7,
815
+ updated_by = $8, updated_at = $9 WHERE tenant_id = $10 AND id = $11`,
816
+ procedureUsage: `SELECT COUNT(*) AS assignments,
817
+ SUM(CASE WHEN agent_definitions.status = 'active' THEN 1 ELSE 0 END) AS active_definitions
818
+ FROM agent_skill_assignments
819
+ JOIN agent_definitions
820
+ ON agent_definitions.id = agent_skill_assignments.agent_id
821
+ AND agent_definitions.tenant_id = agent_skill_assignments.tenant_id
822
+ WHERE agent_skill_assignments.tenant_id = $1
823
+ AND agent_skill_assignments.skill_id = $2`,
824
+ deleteProcedure: `DELETE FROM agent_skills WHERE tenant_id = $1 AND id = $2`,
825
+ listRuns: `SELECT ${RUN_COLUMNS} FROM ${RUN_FROM} WHERE agent_runs.tenant_id = $1
826
+ ORDER BY queued_at DESC, agent_runs.id DESC LIMIT $2`,
827
+ getRun: `SELECT ${RUN_COLUMNS} FROM ${RUN_FROM} WHERE agent_runs.tenant_id = $1 AND agent_runs.id = $2`,
828
+ listRunEvents: `SELECT sequence, event_type, message, metadata_json, occurred_at
829
+ FROM agent_run_events WHERE tenant_id = $1 AND run_id = $2
830
+ AND sequence > $3 ORDER BY sequence`,
831
+ findRunByIdempotencyKey: `SELECT ${RUN_COLUMNS} FROM ${RUN_FROM}
832
+ WHERE agent_runs.tenant_id = $1 AND idempotency_key = $2`,
833
+ enqueueRun1: `INSERT INTO agent_runs
834
+ (id, tenant_id, agent_id, agent_name, agent_revision,
835
+ instructions_snapshot, provider, model, allowed_tools_json,
836
+ max_steps, timeout_ms, temperature_milli, trigger, status, input,
837
+ output, requested_by, permission_snapshot_json, tool_grants_json,
838
+ usage_json, failure_code, failure_message, idempotency_key, attempt,
839
+ queued_at, started_at, completed_at, lease_owner, lease_expires_at)
840
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'queued', $14, NULL,
841
+ $15, $16, $17, NULL, NULL, NULL, $18, 0, $19, NULL, NULL, NULL, NULL)`,
842
+ enqueueRun2: `INSERT INTO agent_run_execution_limits (run_id, tenant_id, timeout_ms)
843
+ VALUES ($1, $2, $3)`,
844
+ enqueueRun3: `INSERT INTO agent_run_output_limits (run_id, tenant_id, max_output_tokens)
845
+ VALUES ($1, $2, $3)`,
846
+ enqueueRun4: `INSERT INTO agent_run_skill_snapshots
847
+ (run_id, tenant_id, skill_id, skill_key, skill_name, skill_revision,
848
+ required_tools_json)
849
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
850
+ enqueueRun5: `INSERT INTO agent_run_contracts
851
+ (run_id, tenant_id, workflow_run_id, output_contract_json,
852
+ structured_output_json, request_hash)
853
+ VALUES ($1, $2, $3, $4, NULL, $5)`,
854
+ enqueueRun6: `INSERT INTO agent_run_actors
855
+ (run_id, tenant_id, actor_json, authorization_subject_json)
856
+ VALUES ($1, $2, $3, $4)`,
857
+ listRecoverableRuns: `SELECT tenant_id, id FROM agent_runs
858
+ WHERE status = 'queued'
859
+ OR (status = 'running' AND lease_expires_at < $1)
860
+ ORDER BY queued_at, id LIMIT $2`,
861
+ claimRun1: `UPDATE agent_runs SET status = 'running', lease_owner = $1,
862
+ lease_expires_at = $2, started_at = COALESCE(started_at, $3),
863
+ attempt = attempt + 1
864
+ WHERE tenant_id = $4 AND id = $5 AND
865
+ (status = 'queued' OR (status = 'running' AND lease_expires_at < $6))`,
866
+ claimRun2: `SELECT ${RUN_COLUMNS} FROM ${RUN_FROM} WHERE agent_runs.tenant_id = $1 AND agent_runs.id = $2`,
867
+ renewLease: `UPDATE agent_runs SET lease_expires_at = $1 WHERE tenant_id = $2
868
+ AND id = $3 AND status = 'running' AND lease_owner = $4`,
869
+ consumeRunGrant1: `DELETE FROM agent_run_grant_uses WHERE expires_at < $1`,
870
+ consumeRunGrant2: `INSERT INTO agent_run_grant_uses
871
+ (grant_id, token_hash, tenant_id, run_id, worker_id, provider_id,
872
+ model_id, issued_at, expires_at, consumed_at)
873
+ SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10
874
+ FROM agent_runs
875
+ WHERE tenant_id = $11 AND id = $12 AND status = 'running'
876
+ AND lease_owner = $13 AND lease_expires_at >= $14
877
+ ON CONFLICT DO NOTHING`,
878
+ appendRunEvent: `INSERT INTO agent_run_events
879
+ (run_id, tenant_id, sequence, event_type, message, metadata_json,
880
+ occurred_at)
881
+ SELECT $1, $2, COALESCE(MAX(sequence), 0) + 1, $3, $4, $5, $6
882
+ FROM agent_run_events WHERE run_id = $7 AND tenant_id = $8`,
883
+ completeRun1: `UPDATE agent_runs SET status = 'succeeded', output = $1, usage_json = $2,
884
+ completed_at = $3, lease_owner = NULL, lease_expires_at = NULL
885
+ WHERE tenant_id = $4 AND id = $5 AND status = 'running'
886
+ AND lease_owner = $6`,
887
+ completeRun2: `UPDATE agent_run_contracts SET structured_output_json = $1
888
+ WHERE tenant_id = $2 AND run_id = $3`,
889
+ completeRun3: `SELECT agent_id, agent_name, model FROM agent_runs
890
+ WHERE tenant_id = $1 AND id = $2`,
891
+ completeRun4: `INSERT INTO agent_run_costs
892
+ (run_id, tenant_id, agent_id, agent_name, model, day, input_tokens,
893
+ output_tokens, cost_micro_usd, completed_at)
894
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
895
+ ON CONFLICT(run_id) DO UPDATE SET
896
+ input_tokens = excluded.input_tokens,
897
+ output_tokens = excluded.output_tokens,
898
+ cost_micro_usd = excluded.cost_micro_usd,
899
+ day = excluded.day, completed_at = excluded.completed_at`,
900
+ failRun: `UPDATE agent_runs SET status = 'failed', failure_code = $1,
901
+ failure_message = $2, completed_at = $3, lease_owner = NULL,
902
+ lease_expires_at = NULL WHERE tenant_id = $4 AND id = $5
903
+ AND status = 'running' AND lease_owner = $6`,
904
+ cancelRun1: `SELECT status FROM agent_runs WHERE tenant_id = $1 AND id = $2`,
905
+ cancelRun2: `UPDATE agent_runs SET status = 'cancelled', failure_code = 'RUN_CANCELLED',
906
+ failure_message = $1, completed_at = $2, lease_owner = NULL,
907
+ lease_expires_at = NULL WHERE tenant_id = $3 AND id = $4`,
908
+ enqueueAction: `INSERT INTO agent_action_invocations
909
+ (id, tenant_id, workflow_run_id, node_run_id, action_id,
910
+ contract_version, actor_json, authorization_subject_json, permission_snapshot_json,
911
+ input_json, idempotency_key, request_hash, status, output_json,
912
+ failure_code, attempt, queued_at, started_at, completed_at,
913
+ lease_owner, lease_expires_at)
914
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, 'queued', NULL, NULL,
915
+ 0, $13, NULL, NULL, NULL, NULL)`,
916
+ getAction: `SELECT * FROM agent_action_invocations
917
+ WHERE tenant_id = $1 AND id = $2`,
918
+ findActionByIdempotencyKey: `SELECT * FROM agent_action_invocations
919
+ WHERE tenant_id = $1 AND idempotency_key = $2`,
920
+ listRecoverableActions: `SELECT tenant_id, id FROM agent_action_invocations
921
+ WHERE status = 'queued'
922
+ OR (status = 'running' AND lease_expires_at < $1)
923
+ ORDER BY queued_at, id LIMIT $2`,
924
+ claimAction: `UPDATE agent_action_invocations SET status = 'running',
925
+ lease_owner = $1, lease_expires_at = $2,
926
+ started_at = COALESCE(started_at, $3), attempt = attempt + 1
927
+ WHERE tenant_id = $4 AND id = $5
928
+ AND (status = 'queued' OR
929
+ (status = 'running' AND lease_expires_at < $6))`,
930
+ renewActionLease: `UPDATE agent_action_invocations SET lease_expires_at = $1
931
+ WHERE tenant_id = $2 AND id = $3 AND status = 'running'
932
+ AND lease_owner = $4`,
933
+ completeAction: `UPDATE agent_action_invocations SET status = 'succeeded',
934
+ output_json = $1, completed_at = $2, lease_owner = NULL,
935
+ lease_expires_at = NULL WHERE tenant_id = $3 AND id = $4
936
+ AND status = 'running' AND lease_owner = $5`,
937
+ failAction: `UPDATE agent_action_invocations SET status = 'failed',
938
+ failure_code = $1, completed_at = $2, lease_owner = NULL,
939
+ lease_expires_at = NULL WHERE tenant_id = $3 AND id = $4
940
+ AND status = 'running' AND lease_owner = $5`,
941
+ cancelAction: `UPDATE agent_action_invocations SET status = 'cancelled',
942
+ failure_code = 'ACTION_CANCELLED', completed_at = $1,
943
+ lease_owner = NULL, lease_expires_at = NULL
944
+ WHERE tenant_id = $2 AND id = $3 AND status IN ('queued', 'running')`,
945
+ appendAuditEvent1: `SELECT sequence, event_hash FROM agent_audit_events_v4
946
+ WHERE tenant_id = $1 ORDER BY sequence DESC LIMIT 1`,
947
+ appendAuditEvent2: `INSERT INTO agent_audit_events_v4
948
+ (id, tenant_id, sequence, actor_id, action, subject_type,
949
+ subject_id, metadata_json, occurred_at, previous_hash, event_hash)
950
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
951
+ listAuditEvents: `SELECT * FROM agent_audit_events_v4 WHERE tenant_id = $1
952
+ ORDER BY sequence DESC LIMIT $2`,
953
+ pageAuditEvents1: `SELECT * FROM agent_audit_events_v4 WHERE tenant_id = $1
954
+ AND (occurred_at < $2 OR (occurred_at = $3 AND sequence < $4))
955
+ ORDER BY occurred_at DESC, sequence DESC LIMIT $5`,
956
+ pageAuditEvents2: `SELECT * FROM agent_audit_events_v4 WHERE tenant_id = $1
957
+ ORDER BY occurred_at DESC, sequence DESC LIMIT $2`,
958
+ verifyAuditChainDetailed: `SELECT * FROM agent_audit_events_v4 WHERE tenant_id = $1
959
+ ORDER BY sequence`,
960
+ usageByDay: `SELECT day, ${USAGE_COLUMNS} FROM agent_run_costs
961
+ WHERE tenant_id = $1 AND day BETWEEN $2 AND $3
962
+ GROUP BY day ORDER BY day`,
963
+ usageByAgent: `SELECT agent_id, MAX(agent_name) AS agent_name, ${USAGE_COLUMNS}
964
+ FROM agent_run_costs WHERE tenant_id = $1 AND day BETWEEN $2 AND $3
965
+ GROUP BY agent_id ORDER BY cost_micro_usd DESC, agent_id`,
966
+ usageTotal1: `SELECT ${USAGE_COLUMNS} FROM agent_run_costs
967
+ WHERE tenant_id = $1 AND day >= $2`,
968
+ usageTotal2: `SELECT ${USAGE_COLUMNS} FROM agent_run_costs
969
+ WHERE tenant_id = $1 AND agent_id = $2 AND day >= $3`,
970
+ });
971
+
972
+ export async function migrateAgentsDatabase(
973
+ database: DatabaseHandle,
974
+ ): Promise<void> {
975
+ await runDatabaseMigrations(database, 'agents.core', databaseMigrations);
976
+ }
977
+
978
+ export interface AgentsDatabaseHandles {
979
+ /** Tenant-scoped handle used by every request-time read and write. */
980
+ readonly runtime: DatabaseHandle;
981
+ /**
982
+ * Cross-tenant read handle for the worker recovery polls. It reads only what
983
+ * those tables' own FOR SELECT policy grants and writes nothing; the claim
984
+ * that follows runs under the tenant of the row it returned.
985
+ */
986
+ readonly background: DatabaseHandle;
987
+ }
988
+
989
+ /** A dialect-neutral repository over platform-owned database handles. */
990
+ export class DatabaseAgentRepository implements AgentRepository {
991
+ constructor(
992
+ private readonly handles: AgentsDatabaseHandles,
993
+ private readonly readyPromise: Promise<void> = Promise.resolve(),
994
+ ) {}
995
+
996
+ /* One unit of work under one tenant. Every multi-statement operation runs
997
+ inside a single transaction, so a partially written run is impossible. */
998
+ async #tx<T>(
999
+ tenantId: string,
1000
+ access: 'read' | 'write',
1001
+ body: (transaction: DatabaseTransaction) => Promise<T>,
1002
+ ): Promise<T> {
1003
+ await this.readyPromise;
1004
+ return this.handles.runtime.transaction(body, { access, tenantId });
1005
+ }
1006
+
1007
+ /* The recovery polls have to find work before they know whose it is. They
1008
+ read routing columns on the read-only background handle; the claim that
1009
+ follows runs under the tenant the row named. */
1010
+ async #crossTenant<T>(
1011
+ body: (transaction: DatabaseTransaction) => Promise<T>,
1012
+ ): Promise<T> {
1013
+ await this.readyPromise;
1014
+ return this.handles.background.transaction(body, { access: 'read' });
1015
+ }
1016
+
1017
+ async #query<Row extends object>(
1018
+ transaction: DatabaseTransaction,
1019
+ text: string,
1020
+ parameters: readonly unknown[],
1021
+ ): Promise<readonly Row[]> {
1022
+ const result = await transaction.query<Row>({
1023
+ text,
1024
+ parameters: parameters as never,
1025
+ });
1026
+ return result.rows;
1027
+ }
1028
+
1029
+ async #exec(
1030
+ transaction: DatabaseTransaction,
1031
+ text: string,
1032
+ parameters: readonly unknown[],
1033
+ ): Promise<number> {
1034
+ const result = await transaction.execute({
1035
+ text,
1036
+ parameters: parameters as never,
1037
+ });
1038
+ return result.affectedRows;
1039
+ }
1040
+
1041
+ async #procedureIds(
1042
+ transaction: DatabaseTransaction,
1043
+ tenantId: string,
1044
+ agentId: string,
1045
+ ): Promise<readonly string[]> {
1046
+ return (
1047
+ (await this.#query(transaction, AGENTS_SQL.procedureIds, [
1048
+ tenantId,
1049
+ agentId,
1050
+ ])) as unknown as { skill_id: string }[]
1051
+ ).map((row) => row.skill_id);
1052
+ }
1053
+
1054
+ async #procedureSnapshots(
1055
+ transaction: DatabaseTransaction,
1056
+ tenantId: string,
1057
+ runId: string,
1058
+ ): Promise<readonly AgentProcedureSnapshot[]> {
1059
+ return (
1060
+ (await this.#query(transaction, AGENTS_SQL.procedureSnapshots, [
1061
+ tenantId,
1062
+ runId,
1063
+ ])) as unknown as SkillSnapshotRow[]
1064
+ ).map((row) => ({
1065
+ id: row.skill_id,
1066
+ key: row.skill_key,
1067
+ name: row.skill_name,
1068
+ revision: row.skill_revision,
1069
+ requiredTools: stringArray(row.required_tools_json),
1070
+ }));
1071
+ }
1072
+
1073
+ async #setAgentSkills(
1074
+ transaction: DatabaseTransaction,
1075
+ agent: AgentDefinition,
1076
+ ): Promise<void> {
1077
+ await this.#exec(transaction, AGENTS_SQL.setAgentSkills1, [agent.id]);
1078
+ for (const procedureId of agent.procedureIds) {
1079
+ await this.#exec(transaction, AGENTS_SQL.setAgentSkills2, [
1080
+ agent.id,
1081
+ procedureId,
1082
+ agent.tenantId,
1083
+ ]);
1084
+ }
1085
+ }
1086
+
1087
+ async #revisionSkills(
1088
+ transaction: DatabaseTransaction,
1089
+
1090
+ tenantId: string,
1091
+ agentId: string,
1092
+ ): Promise<readonly AgentRevisionProcedure[]> {
1093
+ return (
1094
+ (await this.#query(transaction, AGENTS_SQL.revisionSkills, [
1095
+ tenantId,
1096
+ agentId,
1097
+ ])) as unknown as {
1098
+ id: string;
1099
+ skill_key: string;
1100
+ name: string;
1101
+ revision: number;
1102
+ instructions: string;
1103
+ required_tools_json: string;
1104
+ }[]
1105
+ ).map((row) => ({
1106
+ id: row.id,
1107
+ key: row.skill_key,
1108
+ name: row.name,
1109
+ revision: row.revision,
1110
+ instructions: row.instructions,
1111
+ requiredTools: stringArray(row.required_tools_json),
1112
+ }));
1113
+ }
1114
+
1115
+ async #retainAgentRevision(
1116
+ transaction: DatabaseTransaction,
1117
+ agent: AgentDefinition,
1118
+ ignoreExisting = false,
1119
+ ): Promise<void> {
1120
+ await this.#exec(
1121
+ transaction,
1122
+ ignoreExisting
1123
+ ? AGENTS_SQL.retainAgentRevision1
1124
+ : AGENTS_SQL.retainAgentRevision2,
1125
+ [
1126
+ agent.tenantId,
1127
+ agent.id,
1128
+ agent.revision,
1129
+ agent.key,
1130
+ agent.name,
1131
+ agent.description,
1132
+ agent.instructions,
1133
+ agent.provider,
1134
+ agent.model,
1135
+ JSON.stringify(agent.allowedTools),
1136
+ JSON.stringify(
1137
+ await this.#revisionSkills(transaction, agent.tenantId, agent.id),
1138
+ ),
1139
+ agent.maxSteps,
1140
+ agent.timeoutMs,
1141
+ Math.round(agent.temperature * 1_000),
1142
+ agent.maxOutputTokens,
1143
+ agent.status,
1144
+ agent.updatedBy,
1145
+ agent.updatedAt,
1146
+ ],
1147
+ );
1148
+ }
1149
+
1150
+ async adoptCurrentAgentRevisions(): Promise<void> {
1151
+ let afterTenant = '';
1152
+ for (;;) {
1153
+ const page = await this.#crossTenant((transaction) =>
1154
+ this.#query<{ tenant_id: string }>(
1155
+ transaction,
1156
+ `SELECT DISTINCT tenant_id FROM agent_definitions
1157
+ WHERE tenant_id > $1 AND tenant_id <> '__flowdular_module_agents__'
1158
+ ORDER BY tenant_id LIMIT 100`,
1159
+ [afterTenant],
1160
+ ),
1161
+ );
1162
+ if (page.length === 0) return;
1163
+ for (const { tenant_id: tenantId } of page)
1164
+ await this.#tx(tenantId, 'write', async (transaction) => {
1165
+ for (const agent of await this.#agentsForRevisionAdoption(
1166
+ transaction,
1167
+ tenantId,
1168
+ )) {
1169
+ await this.#retainAgentRevision(transaction, agent, true);
1170
+ }
1171
+ });
1172
+ afterTenant = page[page.length - 1]!.tenant_id;
1173
+ }
1174
+ }
1175
+
1176
+ /* Constructor-only helper. It avoids calling a public method while the
1177
+ repository is still adopting the pre-ledger current definitions. */
1178
+ async #agentsForRevisionAdoption(
1179
+ transaction: DatabaseTransaction,
1180
+ tenantId: string,
1181
+ ): Promise<readonly AgentDefinition[]> {
1182
+ const rows = (await this.#query(
1183
+ transaction,
1184
+ AGENTS_SQL.listAgentsForRevisionAdoption,
1185
+ [tenantId],
1186
+ )) as unknown as AgentRow[];
1187
+ const agents: AgentDefinition[] = [];
1188
+ for (const row of rows) {
1189
+ const agent = fromAgentRow(row);
1190
+ agents.push({
1191
+ ...agent,
1192
+ procedureIds: await this.#procedureIds(
1193
+ transaction,
1194
+ agent.tenantId,
1195
+ agent.id,
1196
+ ),
1197
+ });
1198
+ }
1199
+ return agents;
1200
+ }
1201
+
1202
+ async #retainModuleAgentRevision(
1203
+ transaction: DatabaseTransaction,
1204
+
1205
+ binding: ModuleAgentBinding,
1206
+ definition: ModuleAgentDefinition,
1207
+ ): Promise<void> {
1208
+ await this.#exec(transaction, AGENTS_SQL.retainModuleAgentRevision1, [
1209
+ binding.tenantId,
1210
+ definition.id,
1211
+ binding.executableRevision,
1212
+ definition.key,
1213
+ definition.name,
1214
+ definition.description,
1215
+ definition.instructions,
1216
+ binding.provider,
1217
+ binding.model,
1218
+ JSON.stringify(binding.enabledTools),
1219
+ definition.limits.maxSteps,
1220
+ definition.limits.timeoutMs,
1221
+ Math.round(definition.limits.temperature * 1_000),
1222
+ definition.limits.maxOutputTokens,
1223
+ binding.updatedBy,
1224
+ binding.updatedAt,
1225
+ ]);
1226
+ await this.#exec(transaction, AGENTS_SQL.retainModuleAgentRevision2, [
1227
+ binding.tenantId,
1228
+ definition.id,
1229
+ binding.executableRevision,
1230
+ definition.moduleId,
1231
+ definition.definitionRevision,
1232
+ ]);
1233
+ }
1234
+
1235
+ async reconcileModuleAgents(
1236
+ definitions: readonly ModuleAgentDefinition[],
1237
+ reconciledAt: number,
1238
+ ): Promise<void> {
1239
+ await this.#tx(
1240
+ '__flowdular_module_agents__',
1241
+ 'write',
1242
+ async (transaction) => {
1243
+ await transaction.query({
1244
+ text: "SELECT pg_advisory_xact_lock(hashtextextended('agents.core.module-catalog', 0))",
1245
+ });
1246
+ try {
1247
+ const seen = new Set<string>();
1248
+ for (const definition of definitions) {
1249
+ if (seen.has(definition.id)) {
1250
+ throw new Error(`MODULE_AGENT_DUPLICATE: ${definition.id}`);
1251
+ }
1252
+ seen.add(definition.id);
1253
+ const contentHash = moduleAgentDefinitionHash(definition);
1254
+ const stored = (
1255
+ await this.#query(
1256
+ transaction,
1257
+ AGENTS_SQL.reconcileModuleAgents1,
1258
+ [definition.id],
1259
+ )
1260
+ )[0] as unknown as ModuleAgentDefinitionRow | undefined;
1261
+ if (stored) {
1262
+ if (definition.definitionRevision < stored.definition_revision) {
1263
+ throw new Error(
1264
+ `MODULE_AGENT_REVISION_DOWNGRADE: ${definition.id} registered revision ${definition.definitionRevision} after ${stored.definition_revision}.`,
1265
+ );
1266
+ }
1267
+ if (
1268
+ definition.definitionRevision === stored.definition_revision
1269
+ ) {
1270
+ if (contentHash !== stored.content_hash) {
1271
+ throw new Error(
1272
+ `MODULE_AGENT_REVISION_DRIFT: ${definition.id} changed without a definition revision bump.`,
1273
+ );
1274
+ }
1275
+ continue;
1276
+ }
1277
+ } else {
1278
+ /* The legacy run table references agent_definitions by id only. A
1279
+ retained internal parent keeps that foreign key valid across all
1280
+ tenant bindings without pretending the behavior is tenant-owned. */
1281
+ await this.#exec(transaction, AGENTS_SQL.reconcileModuleAgents2, [
1282
+ definition.id,
1283
+ definition.id,
1284
+ definition.name,
1285
+ definition.description,
1286
+ definition.instructions,
1287
+ JSON.stringify(definition.allowedTools),
1288
+ definition.limits.maxSteps,
1289
+ Math.min(definition.limits.timeoutMs, 300_000),
1290
+ Math.round(definition.limits.temperature * 1_000),
1291
+ definition.definitionRevision,
1292
+ `module:${definition.moduleId}`,
1293
+ reconciledAt,
1294
+ `module:${definition.moduleId}`,
1295
+ reconciledAt,
1296
+ ]);
1297
+ }
1298
+
1299
+ if (stored) {
1300
+ await this.#exec(transaction, AGENTS_SQL.reconcileModuleAgents3, [
1301
+ definition.name,
1302
+ definition.description,
1303
+ definition.instructions,
1304
+ JSON.stringify(definition.allowedTools),
1305
+ definition.limits.maxSteps,
1306
+ Math.min(definition.limits.timeoutMs, 300_000),
1307
+ Math.round(definition.limits.temperature * 1_000),
1308
+ definition.definitionRevision,
1309
+ `module:${definition.moduleId}`,
1310
+ reconciledAt,
1311
+ definition.id,
1312
+ ]);
1313
+ }
1314
+
1315
+ await this.#exec(transaction, AGENTS_SQL.reconcileModuleAgents4, [
1316
+ definition.id,
1317
+ definition.moduleId,
1318
+ definition.key,
1319
+ definition.definitionRevision,
1320
+ contentHash,
1321
+ definition.name,
1322
+ definition.description,
1323
+ definition.instructions,
1324
+ JSON.stringify(definition.allowedTools),
1325
+ definition.limits.maxSteps,
1326
+ definition.limits.timeoutMs,
1327
+ Math.round(definition.limits.temperature * 1_000),
1328
+ definition.limits.maxOutputTokens,
1329
+ reconciledAt,
1330
+ ]);
1331
+ }
1332
+ } catch (error) {
1333
+ throw error;
1334
+ }
1335
+ },
1336
+ );
1337
+ // Retry stale bindings even when the durable catalog already has this revision.
1338
+ for (const definition of definitions)
1339
+ await this.#reconcileModuleBindings(definition, reconciledAt);
1340
+ }
1341
+
1342
+ async #reconcileModuleBindings(
1343
+ definition: ModuleAgentDefinition,
1344
+ reconciledAt: number,
1345
+ ): Promise<void> {
1346
+ let afterTenant = '';
1347
+ for (;;) {
1348
+ const page = await this.#crossTenant((transaction) =>
1349
+ this.#query<{ tenant_id: string }>(
1350
+ transaction,
1351
+ 'SELECT tenant_id FROM module_agent_bindings WHERE agent_id = $1 AND tenant_id > $2 ORDER BY tenant_id LIMIT 100',
1352
+ [definition.id, afterTenant],
1353
+ ),
1354
+ );
1355
+ if (page.length === 0) return;
1356
+ for (const { tenant_id: tenantId } of page)
1357
+ await this.#tx(tenantId, 'write', async (transaction) => {
1358
+ const catalog = (
1359
+ await this.#query<ModuleAgentDefinitionRow>(
1360
+ transaction,
1361
+ AGENTS_SQL.reconcileModuleAgents1 + ' FOR SHARE',
1362
+ [definition.id],
1363
+ )
1364
+ )[0];
1365
+ if (
1366
+ catalog?.definition_revision !== definition.definitionRevision ||
1367
+ catalog.content_hash !== moduleAgentDefinitionHash(definition)
1368
+ ) {
1369
+ throw new Error(
1370
+ 'MODULE_AGENT_REVISION_DRIFT: catalog changed during reconciliation.',
1371
+ );
1372
+ }
1373
+ const row = (
1374
+ await this.#query<ModuleAgentBindingRow>(
1375
+ transaction,
1376
+ AGENTS_SQL.getModuleAgentBinding + ' FOR UPDATE',
1377
+ [tenantId, definition.id],
1378
+ )
1379
+ )[0];
1380
+ if (
1381
+ !row ||
1382
+ row.module_definition_revision >= definition.definitionRevision
1383
+ )
1384
+ return;
1385
+ const previous = fromModuleBindingRow(row);
1386
+ const binding: ModuleAgentBinding = {
1387
+ ...previous,
1388
+ enabledTools: previous.enabledTools.filter((tool) =>
1389
+ definition.allowedTools.includes(tool),
1390
+ ),
1391
+ moduleDefinitionRevision: definition.definitionRevision,
1392
+ executableRevision: previous.executableRevision + 1,
1393
+ revision: previous.revision + 1,
1394
+ updatedBy: `module:${definition.moduleId}`,
1395
+ updatedAt: reconciledAt,
1396
+ };
1397
+ await this.#exec(transaction, AGENTS_SQL.reconcileModuleAgents6, [
1398
+ JSON.stringify(binding.enabledTools),
1399
+ binding.moduleDefinitionRevision,
1400
+ binding.executableRevision,
1401
+ binding.revision,
1402
+ binding.updatedBy,
1403
+ binding.updatedAt,
1404
+ binding.tenantId,
1405
+ binding.agentId,
1406
+ ]);
1407
+ await this.#retainModuleAgentRevision(
1408
+ transaction,
1409
+ binding,
1410
+ definition,
1411
+ );
1412
+ await this.#appendAuditEvent(transaction, {
1413
+ tenantId: binding.tenantId,
1414
+ actorId: binding.updatedBy,
1415
+ action: 'module-agent.definition-reconciled',
1416
+ subjectType: 'agent',
1417
+ subjectId: definition.id,
1418
+ metadata: {
1419
+ moduleId: definition.moduleId,
1420
+ previousDefinitionRevision: previous.moduleDefinitionRevision,
1421
+ definitionRevision: definition.definitionRevision,
1422
+ executableRevision: binding.executableRevision,
1423
+ bindingRevision: binding.revision,
1424
+ removedTools:
1425
+ previous.enabledTools.length - binding.enabledTools.length,
1426
+ },
1427
+ occurredAt: reconciledAt,
1428
+ });
1429
+ });
1430
+ afterTenant = page[page.length - 1]!.tenant_id;
1431
+ }
1432
+ }
1433
+
1434
+ async listModuleAgentBindings(
1435
+ tenantId: string,
1436
+ ): Promise<readonly ModuleAgentBinding[]> {
1437
+ return this.#tx(tenantId, 'read', async (transaction) => {
1438
+ return (
1439
+ (await this.#query(transaction, AGENTS_SQL.listModuleAgentBindings, [
1440
+ tenantId,
1441
+ ])) as unknown as ModuleAgentBindingRow[]
1442
+ ).map(fromModuleBindingRow);
1443
+ });
1444
+ }
1445
+
1446
+ async getModuleAgentBinding(
1447
+ tenantId: string,
1448
+ agentId: string,
1449
+ ): Promise<ModuleAgentBinding | null> {
1450
+ return this.#tx(tenantId, 'read', async (transaction) => {
1451
+ const row = (
1452
+ await this.#query(transaction, AGENTS_SQL.getModuleAgentBinding, [
1453
+ tenantId,
1454
+ agentId,
1455
+ ])
1456
+ )[0] as unknown as ModuleAgentBindingRow | undefined;
1457
+ return row ? fromModuleBindingRow(row) : null;
1458
+ });
1459
+ }
1460
+
1461
+ async saveModuleAgentBinding(
1462
+ binding: ModuleAgentBinding,
1463
+ definition: ModuleAgentDefinition,
1464
+ expectedRevision: number,
1465
+ audit: PendingAgentAuditEvent,
1466
+ ): Promise<ModuleAgentBinding> {
1467
+ return this.#tx(binding.tenantId, 'write', async (transaction) => {
1468
+ try {
1469
+ if (expectedRevision === 0) {
1470
+ await this.#exec(transaction, AGENTS_SQL.saveModuleAgentBinding1, [
1471
+ binding.tenantId,
1472
+ binding.agentId,
1473
+ binding.provider,
1474
+ binding.model,
1475
+ JSON.stringify(binding.enabledTools),
1476
+ binding.status,
1477
+ binding.moduleDefinitionRevision,
1478
+ binding.executableRevision,
1479
+ binding.revision,
1480
+ binding.updatedBy,
1481
+ binding.updatedAt,
1482
+ ]);
1483
+ } else {
1484
+ const result = await this.#exec(
1485
+ transaction,
1486
+ AGENTS_SQL.saveModuleAgentBinding2,
1487
+ [
1488
+ binding.provider,
1489
+ binding.model,
1490
+ JSON.stringify(binding.enabledTools),
1491
+ binding.status,
1492
+ binding.moduleDefinitionRevision,
1493
+ binding.executableRevision,
1494
+ binding.revision,
1495
+ binding.updatedBy,
1496
+ binding.updatedAt,
1497
+ binding.tenantId,
1498
+ binding.agentId,
1499
+ expectedRevision,
1500
+ ],
1501
+ );
1502
+ if (result !== 1) throw new ModuleAgentBindingConflictError();
1503
+ }
1504
+ const existingRevision = (
1505
+ await this.#query(transaction, AGENTS_SQL.saveModuleAgentBinding3, [
1506
+ binding.tenantId,
1507
+ binding.agentId,
1508
+ binding.executableRevision,
1509
+ ])
1510
+ )[0];
1511
+ if (!existingRevision)
1512
+ await this.#retainModuleAgentRevision(
1513
+ transaction,
1514
+ binding,
1515
+ definition,
1516
+ );
1517
+ await this.#appendAuditEvent(transaction, audit);
1518
+ } catch (error) {
1519
+ if (
1520
+ expectedRevision === 0 &&
1521
+ String(error).includes('module_agent_bindings_pkey')
1522
+ ) {
1523
+ throw new ModuleAgentBindingConflictError();
1524
+ }
1525
+ throw error;
1526
+ }
1527
+ return binding;
1528
+ });
1529
+ }
1530
+
1531
+ async listAgents(tenantId: string): Promise<readonly AgentDefinition[]> {
1532
+ return this.#tx(tenantId, 'read', async (transaction) => {
1533
+ const rows = (await this.#query(transaction, AGENTS_SQL.listAgents, [
1534
+ tenantId,
1535
+ ])) as unknown as AgentRow[];
1536
+ const agents: AgentDefinition[] = [];
1537
+ for (const row of rows) {
1538
+ const agent = fromAgentRow(row);
1539
+ agents.push({
1540
+ ...agent,
1541
+ procedureIds: await this.#procedureIds(
1542
+ transaction,
1543
+ agent.tenantId,
1544
+ agent.id,
1545
+ ),
1546
+ });
1547
+ }
1548
+ return agents;
1549
+ });
1550
+ }
1551
+
1552
+ providerUsage(
1553
+ tenantId: string,
1554
+ providerId: string,
1555
+ ): Promise<{ readonly definitions: number; readonly pendingRuns: number }> {
1556
+ return this.#tx(tenantId, 'read', async (transaction) => {
1557
+ const definitions = (
1558
+ await this.#query(transaction, AGENTS_SQL.providerUsage1, [
1559
+ tenantId,
1560
+ providerId,
1561
+ tenantId,
1562
+ providerId,
1563
+ ])
1564
+ )[0] as unknown as { count: number | string };
1565
+ const pendingRuns = (
1566
+ await this.#query(transaction, AGENTS_SQL.providerUsage2, [
1567
+ tenantId,
1568
+ providerId,
1569
+ ])
1570
+ )[0] as unknown as { count: number | string };
1571
+ return {
1572
+ definitions: integer(definitions.count),
1573
+ pendingRuns: integer(pendingRuns.count),
1574
+ };
1575
+ });
1576
+ }
1577
+
1578
+ agentUsage(
1579
+ tenantId: string,
1580
+ agentId: string,
1581
+ ): Promise<{
1582
+ readonly runs: number;
1583
+ readonly pendingRuns: number;
1584
+ readonly assignments: number;
1585
+ }> {
1586
+ return this.#tx(tenantId, 'read', async (transaction) => {
1587
+ const counts = (
1588
+ await this.#query(transaction, AGENTS_SQL.agentUsage, [
1589
+ tenantId,
1590
+ agentId,
1591
+ tenantId,
1592
+ agentId,
1593
+ tenantId,
1594
+ agentId,
1595
+ ])
1596
+ )[0] as unknown as {
1597
+ runs: number;
1598
+ pending_runs: number;
1599
+ assignments: number;
1600
+ };
1601
+ return {
1602
+ runs: counts.runs,
1603
+ pendingRuns: counts.pending_runs,
1604
+ assignments: counts.assignments,
1605
+ };
1606
+ });
1607
+ }
1608
+
1609
+ async getAgent(
1610
+ tenantId: string,
1611
+ agentId: string,
1612
+ ): Promise<AgentDefinition | null> {
1613
+ return this.#tx(tenantId, 'read', async (transaction) => {
1614
+ const row = (
1615
+ await this.#query(transaction, AGENTS_SQL.getAgent, [tenantId, agentId])
1616
+ )[0] as unknown as AgentRow | undefined;
1617
+ if (!row) return null;
1618
+ const agent = fromAgentRow(row);
1619
+ return {
1620
+ ...agent,
1621
+ procedureIds: await this.#procedureIds(
1622
+ transaction,
1623
+ agent.tenantId,
1624
+ agent.id,
1625
+ ),
1626
+ };
1627
+ });
1628
+ }
1629
+
1630
+ async getAgentRevision(
1631
+ tenantId: string,
1632
+ agentId: string,
1633
+ revision: number,
1634
+ ): Promise<AgentDefinitionRevision | null> {
1635
+ return this.#tx(tenantId, 'read', async (transaction) => {
1636
+ const row = (
1637
+ await this.#query(transaction, AGENTS_SQL.getAgentRevision, [
1638
+ tenantId,
1639
+ agentId,
1640
+ revision,
1641
+ ])
1642
+ )[0] as unknown as AgentRevisionRow | undefined;
1643
+ return row ? fromRevisionRow(row) : null;
1644
+ });
1645
+ }
1646
+
1647
+ async listAgentRevisions(
1648
+ tenantId: string,
1649
+ ): Promise<readonly AgentDefinitionRevision[]> {
1650
+ return this.#tx(tenantId, 'read', async (transaction) => {
1651
+ return (
1652
+ (await this.#query(transaction, AGENTS_SQL.listAgentRevisions, [
1653
+ tenantId,
1654
+ ])) as unknown as AgentRevisionRow[]
1655
+ ).map(fromRevisionRow);
1656
+ });
1657
+ }
1658
+
1659
+ async createAgent(agent: AgentDefinition): Promise<AgentDefinition> {
1660
+ return this.#tx(agent.tenantId, 'write', async (transaction) => {
1661
+ try {
1662
+ await this.#exec(transaction, AGENTS_SQL.createAgent1, [
1663
+ agent.id,
1664
+ agent.tenantId,
1665
+ agent.key,
1666
+ agent.name,
1667
+ agent.description,
1668
+ agent.instructions,
1669
+ agent.provider,
1670
+ agent.model,
1671
+ JSON.stringify(agent.allowedTools),
1672
+ agent.maxSteps,
1673
+ Math.min(agent.timeoutMs, 300_000),
1674
+ Math.round(agent.temperature * 1_000),
1675
+ agent.status,
1676
+ agent.revision,
1677
+ agent.createdBy,
1678
+ agent.createdAt,
1679
+ agent.updatedBy,
1680
+ agent.updatedAt,
1681
+ ]);
1682
+ await this.#exec(transaction, AGENTS_SQL.createAgent2, [
1683
+ agent.id,
1684
+ agent.tenantId,
1685
+ agent.timeoutMs,
1686
+ ]);
1687
+ await this.#exec(transaction, AGENTS_SQL.createAgent3, [
1688
+ agent.id,
1689
+ agent.tenantId,
1690
+ agent.maxOutputTokens,
1691
+ ]);
1692
+ await this.#setAgentSkills(transaction, agent);
1693
+ await this.#retainAgentRevision(transaction, agent);
1694
+ } catch (error) {
1695
+ if (
1696
+ String(error).includes('agent_definitions_tenant_id_agent_key_key')
1697
+ ) {
1698
+ throw new DuplicateAgentKeyError();
1699
+ }
1700
+ throw error;
1701
+ }
1702
+ return agent;
1703
+ });
1704
+ }
1705
+
1706
+ async updateAgent(agent: AgentDefinition): Promise<AgentDefinition> {
1707
+ return this.#tx(agent.tenantId, 'write', async (transaction) => {
1708
+ try {
1709
+ const result = await this.#exec(transaction, AGENTS_SQL.updateAgent1, [
1710
+ agent.key,
1711
+ agent.name,
1712
+ agent.description,
1713
+ agent.instructions,
1714
+ agent.provider,
1715
+ agent.model,
1716
+ JSON.stringify(agent.allowedTools),
1717
+ agent.maxSteps,
1718
+ Math.min(agent.timeoutMs, 300_000),
1719
+ Math.round(agent.temperature * 1_000),
1720
+ agent.status,
1721
+ agent.revision,
1722
+ agent.updatedBy,
1723
+ agent.updatedAt,
1724
+ agent.tenantId,
1725
+ agent.id,
1726
+ ]);
1727
+ if (result !== 1) throw new Error('Agent definition not found.');
1728
+ await this.#exec(transaction, AGENTS_SQL.updateAgent2, [
1729
+ agent.id,
1730
+ agent.tenantId,
1731
+ agent.timeoutMs,
1732
+ ]);
1733
+ await this.#exec(transaction, AGENTS_SQL.updateAgent3, [
1734
+ agent.id,
1735
+ agent.tenantId,
1736
+ agent.maxOutputTokens,
1737
+ ]);
1738
+ await this.#setAgentSkills(transaction, agent);
1739
+ await this.#retainAgentRevision(transaction, agent);
1740
+ } catch (error) {
1741
+ if (
1742
+ String(error).includes('agent_definitions_tenant_id_agent_key_key')
1743
+ ) {
1744
+ throw new DuplicateAgentKeyError();
1745
+ }
1746
+ throw error;
1747
+ }
1748
+ return agent;
1749
+ });
1750
+ }
1751
+
1752
+ async deleteAgent(tenantId: string, agentId: string): Promise<boolean> {
1753
+ return this.#tx(tenantId, 'write', async (transaction) => {
1754
+ return (
1755
+ (await this.#exec(transaction, AGENTS_SQL.deleteAgent, [
1756
+ tenantId,
1757
+ agentId,
1758
+ ])) === 1
1759
+ );
1760
+ });
1761
+ }
1762
+
1763
+ async listProcedures(tenantId: string): Promise<readonly AgentProcedure[]> {
1764
+ return this.#tx(tenantId, 'read', async (transaction) => {
1765
+ return (
1766
+ (await this.#query(transaction, AGENTS_SQL.listProcedures, [
1767
+ tenantId,
1768
+ ])) as unknown as SkillRow[]
1769
+ ).map(fromSkillRow);
1770
+ });
1771
+ }
1772
+
1773
+ async getProcedure(
1774
+ tenantId: string,
1775
+ skillId: string,
1776
+ ): Promise<AgentProcedure | null> {
1777
+ return this.#tx(tenantId, 'read', async (transaction) => {
1778
+ const row = (
1779
+ await this.#query(transaction, AGENTS_SQL.getProcedure, [
1780
+ tenantId,
1781
+ skillId,
1782
+ ])
1783
+ )[0] as unknown as SkillRow | undefined;
1784
+ return row ? fromSkillRow(row) : null;
1785
+ });
1786
+ }
1787
+
1788
+ async createProcedure(skill: AgentProcedure): Promise<AgentProcedure> {
1789
+ return this.#tx(skill.tenantId, 'write', async (transaction) => {
1790
+ try {
1791
+ await this.#exec(transaction, AGENTS_SQL.createProcedure, [
1792
+ skill.id,
1793
+ skill.tenantId,
1794
+ skill.key,
1795
+ skill.name,
1796
+ skill.description,
1797
+ skill.instructions,
1798
+ JSON.stringify(skill.requiredTools),
1799
+ skill.status,
1800
+ skill.revision,
1801
+ skill.createdBy,
1802
+ skill.createdAt,
1803
+ skill.updatedBy,
1804
+ skill.updatedAt,
1805
+ ]);
1806
+ } catch (error) {
1807
+ if (String(error).includes('agent_skills_tenant_id_skill_key_key')) {
1808
+ throw new DuplicateAgentProcedureKeyError();
1809
+ }
1810
+ throw error;
1811
+ }
1812
+ return skill;
1813
+ });
1814
+ }
1815
+
1816
+ async updateProcedure(skill: AgentProcedure): Promise<AgentProcedure> {
1817
+ return this.#tx(skill.tenantId, 'write', async (transaction) => {
1818
+ try {
1819
+ const result = await this.#exec(
1820
+ transaction,
1821
+ AGENTS_SQL.updateProcedure,
1822
+ [
1823
+ skill.key,
1824
+ skill.name,
1825
+ skill.description,
1826
+ skill.instructions,
1827
+ JSON.stringify(skill.requiredTools),
1828
+ skill.status,
1829
+ skill.revision,
1830
+ skill.updatedBy,
1831
+ skill.updatedAt,
1832
+ skill.tenantId,
1833
+ skill.id,
1834
+ ],
1835
+ );
1836
+ if (result !== 1) throw new Error('Agent skill not found.');
1837
+ } catch (error) {
1838
+ if (String(error).includes('agent_skills_tenant_id_skill_key_key')) {
1839
+ throw new DuplicateAgentProcedureKeyError();
1840
+ }
1841
+ throw error;
1842
+ }
1843
+ return skill;
1844
+ });
1845
+ }
1846
+
1847
+ procedureUsage(
1848
+ tenantId: string,
1849
+ skillId: string,
1850
+ ): Promise<{
1851
+ readonly assignments: number;
1852
+ readonly activeDefinitions: number;
1853
+ }> {
1854
+ return this.#tx(tenantId, 'read', async (transaction) => {
1855
+ const counts = (
1856
+ await this.#query(transaction, AGENTS_SQL.procedureUsage, [
1857
+ tenantId,
1858
+ skillId,
1859
+ ])
1860
+ )[0] as unknown as {
1861
+ assignments: number;
1862
+ active_definitions: number | null;
1863
+ };
1864
+ return {
1865
+ assignments: counts.assignments,
1866
+ activeDefinitions: counts.active_definitions ?? 0,
1867
+ };
1868
+ });
1869
+ }
1870
+
1871
+ async deleteProcedure(tenantId: string, skillId: string): Promise<boolean> {
1872
+ return this.#tx(tenantId, 'write', async (transaction) => {
1873
+ return (
1874
+ (await this.#exec(transaction, AGENTS_SQL.deleteProcedure, [
1875
+ tenantId,
1876
+ skillId,
1877
+ ])) === 1
1878
+ );
1879
+ });
1880
+ }
1881
+
1882
+ async listRuns(
1883
+ tenantId: string,
1884
+ limit: number,
1885
+ ): Promise<readonly AgentRun[]> {
1886
+ return this.#tx(tenantId, 'read', async (transaction) => {
1887
+ const rows = (await this.#query(transaction, AGENTS_SQL.listRuns, [
1888
+ tenantId,
1889
+ limit,
1890
+ ])) as unknown as RunRow[];
1891
+ const runs: AgentRun[] = [];
1892
+ for (const row of rows) {
1893
+ const run = fromRunRow(row);
1894
+ runs.push({
1895
+ ...run,
1896
+ procedureSnapshots: await this.#procedureSnapshots(
1897
+ transaction,
1898
+ tenantId,
1899
+ run.id,
1900
+ ),
1901
+ });
1902
+ }
1903
+ return runs;
1904
+ });
1905
+ }
1906
+
1907
+ async getRun(
1908
+ tenantId: string,
1909
+ runId: string,
1910
+ ): Promise<AgentRunDetail | null> {
1911
+ return this.#tx(tenantId, 'read', async (transaction) => {
1912
+ const row = (
1913
+ await this.#query(transaction, AGENTS_SQL.getRun, [tenantId, runId])
1914
+ )[0] as unknown as RunRow | undefined;
1915
+ if (!row) return null;
1916
+ const events = await this.#runEventsIn(transaction, tenantId, runId, 0);
1917
+ return {
1918
+ ...fromRunRow(row),
1919
+ procedureSnapshots: await this.#procedureSnapshots(
1920
+ transaction,
1921
+ tenantId,
1922
+ row.id,
1923
+ ),
1924
+ events,
1925
+ };
1926
+ });
1927
+ }
1928
+
1929
+ async #runEventsIn(
1930
+ transaction: DatabaseTransaction,
1931
+ tenantId: string,
1932
+ runId: string,
1933
+ afterSequence: number,
1934
+ ): Promise<readonly AgentExecutionEvent[]> {
1935
+ {
1936
+ return (
1937
+ (await this.#query(transaction, AGENTS_SQL.listRunEvents, [
1938
+ tenantId,
1939
+ runId,
1940
+ afterSequence,
1941
+ ])) as unknown as RunEventRow[]
1942
+ ).map(
1943
+ (event): AgentExecutionEvent => ({
1944
+ sequence: integer(event.sequence),
1945
+ type: event.event_type,
1946
+ timestamp: integer(event.occurred_at),
1947
+ message: event.message,
1948
+ metadata: metadata(event.metadata_json),
1949
+ }),
1950
+ );
1951
+ }
1952
+ }
1953
+
1954
+ async listRunEvents(
1955
+ tenantId: string,
1956
+ runId: string,
1957
+ afterSequence: number,
1958
+ ): Promise<readonly AgentExecutionEvent[]> {
1959
+ return this.#tx(tenantId, 'read', (transaction) =>
1960
+ this.#runEventsIn(transaction, tenantId, runId, afterSequence),
1961
+ );
1962
+ }
1963
+
1964
+ async findRunByIdempotencyKey(
1965
+ tenantId: string,
1966
+ idempotencyKey: string,
1967
+ ): Promise<AgentRun | null> {
1968
+ return this.#tx(tenantId, 'read', async (transaction) => {
1969
+ const row = (
1970
+ await this.#query(transaction, AGENTS_SQL.findRunByIdempotencyKey, [
1971
+ tenantId,
1972
+ idempotencyKey,
1973
+ ])
1974
+ )[0] as unknown as RunRow | undefined;
1975
+ if (!row) return null;
1976
+ const run = fromRunRow(row);
1977
+ return {
1978
+ ...run,
1979
+ procedureSnapshots: await this.#procedureSnapshots(
1980
+ transaction,
1981
+ tenantId,
1982
+ run.id,
1983
+ ),
1984
+ };
1985
+ });
1986
+ }
1987
+
1988
+ async enqueueRun(
1989
+ execution: AgentRunExecution,
1990
+ idempotencyKey: string | null,
1991
+ audit: PendingAgentAuditEvent,
1992
+ ): Promise<AgentRun> {
1993
+ return this.#tx(execution.run.tenantId, 'write', async (transaction) => {
1994
+ const { run, definition } = execution;
1995
+ try {
1996
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun1, [
1997
+ run.id,
1998
+ run.tenantId,
1999
+ run.agentId,
2000
+ run.agentName,
2001
+ run.agentRevision,
2002
+ definition.instructions,
2003
+ run.provider,
2004
+ run.model,
2005
+ JSON.stringify(definition.allowedTools),
2006
+ definition.maxSteps,
2007
+ Math.min(definition.timeoutMs, 300_000),
2008
+ Math.round(definition.temperature * 1_000),
2009
+ run.trigger,
2010
+ run.input,
2011
+ run.requestedBy,
2012
+ JSON.stringify(run.permissionSnapshot),
2013
+ JSON.stringify(run.toolGrants),
2014
+ idempotencyKey,
2015
+ run.queuedAt,
2016
+ ]);
2017
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun2, [
2018
+ run.id,
2019
+ run.tenantId,
2020
+ definition.timeoutMs,
2021
+ ]);
2022
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun3, [
2023
+ run.id,
2024
+ run.tenantId,
2025
+ definition.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
2026
+ ]);
2027
+ for (const skill of run.procedureSnapshots) {
2028
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun4, [
2029
+ run.id,
2030
+ run.tenantId,
2031
+ skill.id,
2032
+ skill.key,
2033
+ skill.name,
2034
+ skill.revision,
2035
+ JSON.stringify(skill.requiredTools),
2036
+ ]);
2037
+ }
2038
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun5, [
2039
+ run.id,
2040
+ run.tenantId,
2041
+ run.workflowRunId,
2042
+ JSON.stringify(run.outputContract),
2043
+ createHash('sha256')
2044
+ .update(
2045
+ JSON.stringify([
2046
+ run.agentId,
2047
+ run.agentRevision,
2048
+ run.input,
2049
+ run.outputContract,
2050
+ run.workflowRunId,
2051
+ ]),
2052
+ )
2053
+ .digest('hex'),
2054
+ ]);
2055
+ await this.#exec(transaction, AGENTS_SQL.enqueueRun6, [
2056
+ run.id,
2057
+ run.tenantId,
2058
+ JSON.stringify(run.requestedActor),
2059
+ run.authorizationSubject
2060
+ ? JSON.stringify(run.authorizationSubject)
2061
+ : null,
2062
+ ]);
2063
+ await this.#appendAuditEvent(transaction, audit);
2064
+ } catch (error) {
2065
+ if (
2066
+ String(error).includes('agent_runs_tenant_id_idempotency_key_key')
2067
+ ) {
2068
+ throw new DuplicateRunIdempotencyKeyError();
2069
+ }
2070
+ throw error;
2071
+ }
2072
+ return run;
2073
+ });
2074
+ }
2075
+
2076
+ async listRecoverableRuns(
2077
+ now: number,
2078
+ limit: number,
2079
+ ): Promise<readonly RecoverableRun[]> {
2080
+ return this.#crossTenant(async (transaction) => {
2081
+ return (
2082
+ (await this.#query(transaction, AGENTS_SQL.listRecoverableRuns, [
2083
+ now,
2084
+ limit,
2085
+ ])) as unknown as RecoverableRow[]
2086
+ ).map((row) => ({ tenantId: row.tenant_id, runId: row.id }));
2087
+ });
2088
+ }
2089
+
2090
+ async claimRun(
2091
+ tenantId: string,
2092
+ runId: string,
2093
+ workerId: string,
2094
+ now: number,
2095
+ leaseExpiresAt: number,
2096
+ audit: PendingAgentAuditEvent,
2097
+ ): Promise<AgentRunExecution | null> {
2098
+ return this.#tx(tenantId, 'write', async (transaction) => {
2099
+ try {
2100
+ const result = await this.#exec(transaction, AGENTS_SQL.claimRun1, [
2101
+ workerId,
2102
+ leaseExpiresAt,
2103
+ now,
2104
+ tenantId,
2105
+ runId,
2106
+ now,
2107
+ ]);
2108
+ if (result !== 1) {
2109
+ return null;
2110
+ }
2111
+ const row = (
2112
+ await this.#query(transaction, AGENTS_SQL.claimRun2, [
2113
+ tenantId,
2114
+ runId,
2115
+ ])
2116
+ )[0] as unknown as RunRow;
2117
+ const execution = executionFromRow(row);
2118
+ await this.#appendAuditEvent(transaction, {
2119
+ ...audit,
2120
+ metadata: { ...audit.metadata, attempt: execution.run.attempt },
2121
+ });
2122
+ return {
2123
+ ...execution,
2124
+ run: {
2125
+ ...execution.run,
2126
+ procedureSnapshots: await this.#procedureSnapshots(
2127
+ transaction,
2128
+ execution.run.tenantId,
2129
+ execution.run.id,
2130
+ ),
2131
+ },
2132
+ };
2133
+ } catch (error) {
2134
+ throw error;
2135
+ }
2136
+ });
2137
+ }
2138
+
2139
+ async renewLease(
2140
+ tenantId: string,
2141
+ runId: string,
2142
+ workerId: string,
2143
+ leaseExpiresAt: number,
2144
+ ): Promise<boolean> {
2145
+ return this.#tx(tenantId, 'write', async (transaction) => {
2146
+ return (
2147
+ (await this.#exec(transaction, AGENTS_SQL.renewLease, [
2148
+ leaseExpiresAt,
2149
+ tenantId,
2150
+ runId,
2151
+ workerId,
2152
+ ])) === 1
2153
+ );
2154
+ });
2155
+ }
2156
+
2157
+ async consumeRunGrant(input: {
2158
+ readonly grantId: string;
2159
+ readonly tokenHash: string;
2160
+ readonly tenantId: string;
2161
+ readonly runId: string;
2162
+ readonly workerId: string;
2163
+ readonly providerId: string;
2164
+ readonly modelId: string;
2165
+ readonly issuedAt: number;
2166
+ readonly expiresAt: number;
2167
+ readonly consumedAt: number;
2168
+ }): Promise<boolean> {
2169
+ return this.#tx(input.tenantId, 'write', async (transaction) => {
2170
+ await this.#exec(transaction, AGENTS_SQL.consumeRunGrant1, [
2171
+ input.consumedAt,
2172
+ ]);
2173
+ const result = await this.#exec(
2174
+ transaction,
2175
+ AGENTS_SQL.consumeRunGrant2,
2176
+ [
2177
+ input.grantId,
2178
+ input.tokenHash,
2179
+ input.tenantId,
2180
+ input.runId,
2181
+ input.workerId,
2182
+ input.providerId,
2183
+ input.modelId,
2184
+ input.issuedAt,
2185
+ input.expiresAt,
2186
+ input.consumedAt,
2187
+ input.tenantId,
2188
+ input.runId,
2189
+ input.workerId,
2190
+ input.consumedAt,
2191
+ ],
2192
+ );
2193
+ return result === 1;
2194
+ });
2195
+ }
2196
+
2197
+ async appendRunEvent(
2198
+ tenantId: string,
2199
+ runId: string,
2200
+ event: AgentExecutionEvent,
2201
+ ): Promise<void> {
2202
+ return this.#tx(tenantId, 'write', async (transaction) => {
2203
+ // A server has multiple connections; MAX(sequence) needs a per-run lock.
2204
+ await transaction.query({
2205
+ text: 'SELECT id FROM agent_runs WHERE tenant_id = $1 AND id = $2 FOR UPDATE',
2206
+ parameters: [tenantId, runId],
2207
+ });
2208
+ await this.#exec(transaction, AGENTS_SQL.appendRunEvent, [
2209
+ runId,
2210
+ tenantId,
2211
+ event.type,
2212
+ event.message,
2213
+ stableMetadata(event.metadata ?? {}),
2214
+ event.timestamp,
2215
+ runId,
2216
+ tenantId,
2217
+ ]);
2218
+ });
2219
+ }
2220
+
2221
+ /* Settling the run and projecting its cost share one transaction: a run that
2222
+ succeeded but is missing from the rollup would understate every budget
2223
+ check that follows it. */
2224
+ async completeRun(
2225
+ tenantId: string,
2226
+ runId: string,
2227
+ workerId: string,
2228
+ result: AgentExecutionResult,
2229
+ audit: PendingAgentAuditEvent,
2230
+ ): Promise<void> {
2231
+ return this.#tx(tenantId, 'write', async (transaction) => {
2232
+ try {
2233
+ const update = await this.#exec(transaction, AGENTS_SQL.completeRun1, [
2234
+ result.output,
2235
+ JSON.stringify(result.usage),
2236
+ result.completedAt,
2237
+ tenantId,
2238
+ runId,
2239
+ workerId,
2240
+ ]);
2241
+ if (update !== 1) throw new Error('Agent run lease was lost.');
2242
+ await this.#exec(transaction, AGENTS_SQL.completeRun2, [
2243
+ result.structuredOutput === undefined
2244
+ ? null
2245
+ : JSON.stringify(result.structuredOutput),
2246
+ tenantId,
2247
+ runId,
2248
+ ]);
2249
+ const run = (
2250
+ await this.#query(transaction, AGENTS_SQL.completeRun3, [
2251
+ tenantId,
2252
+ runId,
2253
+ ])
2254
+ )[0] as unknown as {
2255
+ agent_id: string;
2256
+ agent_name: string;
2257
+ model: string;
2258
+ };
2259
+ await this.#exec(transaction, AGENTS_SQL.completeRun4, [
2260
+ runId,
2261
+ tenantId,
2262
+ run.agent_id,
2263
+ run.agent_name,
2264
+ run.model,
2265
+ usageDay(result.completedAt),
2266
+ Math.max(0, Math.trunc(result.usage.inputTokens)),
2267
+ Math.max(0, Math.trunc(result.usage.outputTokens)),
2268
+ usageCostMicros(run.model, result.usage),
2269
+ result.completedAt,
2270
+ ]);
2271
+ await this.#appendAuditEvent(transaction, audit);
2272
+ } catch (error) {
2273
+ throw error;
2274
+ }
2275
+ });
2276
+ }
2277
+
2278
+ async failRun(
2279
+ tenantId: string,
2280
+ runId: string,
2281
+ workerId: string,
2282
+ code: string,
2283
+ message: string,
2284
+ completedAt: number,
2285
+ audit: PendingAgentAuditEvent,
2286
+ ): Promise<void> {
2287
+ return this.#tx(tenantId, 'write', async (transaction) => {
2288
+ try {
2289
+ const update = await this.#exec(transaction, AGENTS_SQL.failRun, [
2290
+ code,
2291
+ message,
2292
+ completedAt,
2293
+ tenantId,
2294
+ runId,
2295
+ workerId,
2296
+ ]);
2297
+ if (update !== 1) throw new Error('Agent run lease was lost.');
2298
+ await this.#appendAuditEvent(transaction, audit);
2299
+ } catch (error) {
2300
+ throw error;
2301
+ }
2302
+ });
2303
+ }
2304
+
2305
+ async cancelRun(
2306
+ tenantId: string,
2307
+ runId: string,
2308
+ message: string,
2309
+ completedAt: number,
2310
+ audit: PendingAgentAuditEvent,
2311
+ ): Promise<AgentRun['status'] | null> {
2312
+ return this.#tx(tenantId, 'write', async (transaction) => {
2313
+ try {
2314
+ const current = (
2315
+ await this.#query(transaction, AGENTS_SQL.cancelRun1, [
2316
+ tenantId,
2317
+ runId,
2318
+ ])
2319
+ )[0] as unknown as { status: AgentRun['status'] } | undefined;
2320
+ if (!current || !['queued', 'running'].includes(current.status)) {
2321
+ return null;
2322
+ }
2323
+ await this.#exec(transaction, AGENTS_SQL.cancelRun2, [
2324
+ message,
2325
+ completedAt,
2326
+ tenantId,
2327
+ runId,
2328
+ ]);
2329
+ await this.#appendAuditEvent(transaction, {
2330
+ ...audit,
2331
+ metadata: { ...audit.metadata, previousStatus: current.status },
2332
+ });
2333
+ return current.status;
2334
+ } catch (error) {
2335
+ throw error;
2336
+ }
2337
+ });
2338
+ }
2339
+
2340
+ async enqueueAction(
2341
+ invocation: AgentActionInvocation,
2342
+ audit: PendingAgentAuditEvent,
2343
+ ): Promise<AgentActionInvocation> {
2344
+ return this.#tx(invocation.tenantId, 'write', async (transaction) => {
2345
+ try {
2346
+ await this.#exec(transaction, AGENTS_SQL.enqueueAction, [
2347
+ invocation.id,
2348
+ invocation.tenantId,
2349
+ invocation.workflowRunId,
2350
+ invocation.nodeRunId,
2351
+ invocation.actionId,
2352
+ invocation.contractVersion,
2353
+ JSON.stringify(invocation.actor),
2354
+ JSON.stringify(invocation.authorizationSubject),
2355
+ JSON.stringify(invocation.permissionSnapshot),
2356
+ JSON.stringify(invocation.input),
2357
+ invocation.idempotencyKey,
2358
+ invocation.requestHash,
2359
+ invocation.queuedAt,
2360
+ ]);
2361
+ await this.#appendAuditEvent(transaction, audit);
2362
+ } catch (error) {
2363
+ if (
2364
+ String(error).includes(
2365
+ 'agent_action_invocations_tenant_id_idempotency_key_key',
2366
+ )
2367
+ ) {
2368
+ throw new DuplicateActionIdempotencyKeyError();
2369
+ }
2370
+ throw error;
2371
+ }
2372
+ return invocation;
2373
+ });
2374
+ }
2375
+
2376
+ async #actionIn(
2377
+ transaction: DatabaseTransaction,
2378
+ tenantId: string,
2379
+ invocationId: string,
2380
+ ): Promise<AgentActionInvocation | null> {
2381
+ const row = (
2382
+ await this.#query(transaction, AGENTS_SQL.getAction, [
2383
+ tenantId,
2384
+ invocationId,
2385
+ ])
2386
+ )[0] as unknown as ActionRow | undefined;
2387
+ return row ? fromActionRow(row) : null;
2388
+ }
2389
+
2390
+ async getAction(
2391
+ tenantId: string,
2392
+ invocationId: string,
2393
+ ): Promise<AgentActionInvocation | null> {
2394
+ return this.#tx(tenantId, 'read', (transaction) =>
2395
+ this.#actionIn(transaction, tenantId, invocationId),
2396
+ );
2397
+ }
2398
+
2399
+ async findActionByIdempotencyKey(
2400
+ tenantId: string,
2401
+ idempotencyKey: string,
2402
+ ): Promise<AgentActionInvocation | null> {
2403
+ return this.#tx(tenantId, 'read', async (transaction) => {
2404
+ const row = (
2405
+ await this.#query(transaction, AGENTS_SQL.findActionByIdempotencyKey, [
2406
+ tenantId,
2407
+ idempotencyKey,
2408
+ ])
2409
+ )[0] as unknown as ActionRow | undefined;
2410
+ return row ? fromActionRow(row) : null;
2411
+ });
2412
+ }
2413
+
2414
+ listRecoverableActions(
2415
+ now: number,
2416
+ limit: number,
2417
+ ): Promise<
2418
+ readonly { readonly tenantId: string; readonly invocationId: string }[]
2419
+ > {
2420
+ return this.#crossTenant(async (transaction) =>
2421
+ (
2422
+ (await this.#query(transaction, AGENTS_SQL.listRecoverableActions, [
2423
+ now,
2424
+ limit,
2425
+ ])) as unknown as { tenant_id: string; id: string }[]
2426
+ ).map((row) => ({ tenantId: row.tenant_id, invocationId: row.id })),
2427
+ );
2428
+ }
2429
+
2430
+ async claimAction(
2431
+ tenantId: string,
2432
+ invocationId: string,
2433
+ workerId: string,
2434
+ now: number,
2435
+ leaseExpiresAt: number,
2436
+ audit: PendingAgentAuditEvent,
2437
+ ): Promise<AgentActionInvocation | null> {
2438
+ return this.#tx(tenantId, 'write', async (transaction) => {
2439
+ try {
2440
+ const updated = await this.#exec(transaction, AGENTS_SQL.claimAction, [
2441
+ workerId,
2442
+ leaseExpiresAt,
2443
+ now,
2444
+ tenantId,
2445
+ invocationId,
2446
+ now,
2447
+ ]);
2448
+ if (updated !== 1) {
2449
+ return null;
2450
+ }
2451
+ const invocation = (await this.#actionIn(
2452
+ transaction,
2453
+ tenantId,
2454
+ invocationId,
2455
+ ))!;
2456
+ await this.#appendAuditEvent(transaction, {
2457
+ ...audit,
2458
+ metadata: { ...audit.metadata, attempt: invocation.attempt },
2459
+ });
2460
+ return invocation;
2461
+ } catch (error) {
2462
+ throw error;
2463
+ }
2464
+ });
2465
+ }
2466
+
2467
+ async renewActionLease(
2468
+ tenantId: string,
2469
+ invocationId: string,
2470
+ workerId: string,
2471
+ leaseExpiresAt: number,
2472
+ ): Promise<boolean> {
2473
+ return this.#tx(tenantId, 'write', async (transaction) => {
2474
+ return (
2475
+ (await this.#exec(transaction, AGENTS_SQL.renewActionLease, [
2476
+ leaseExpiresAt,
2477
+ tenantId,
2478
+ invocationId,
2479
+ workerId,
2480
+ ])) === 1
2481
+ );
2482
+ });
2483
+ }
2484
+
2485
+ async completeAction(
2486
+ tenantId: string,
2487
+ invocationId: string,
2488
+ workerId: string,
2489
+ output: JsonValue,
2490
+ completedAt: number,
2491
+ audit: PendingAgentAuditEvent,
2492
+ ): Promise<boolean> {
2493
+ return this.#tx(tenantId, 'write', async (transaction) => {
2494
+ try {
2495
+ const changed =
2496
+ (await this.#exec(transaction, AGENTS_SQL.completeAction, [
2497
+ JSON.stringify(output),
2498
+ completedAt,
2499
+ tenantId,
2500
+ invocationId,
2501
+ workerId,
2502
+ ])) === 1;
2503
+ if (changed) await this.#appendAuditEvent(transaction, audit);
2504
+ return changed;
2505
+ } catch (error) {
2506
+ throw error;
2507
+ }
2508
+ });
2509
+ }
2510
+
2511
+ async failAction(
2512
+ tenantId: string,
2513
+ invocationId: string,
2514
+ workerId: string,
2515
+ code: string,
2516
+ completedAt: number,
2517
+ audit: PendingAgentAuditEvent,
2518
+ ): Promise<boolean> {
2519
+ return this.#tx(tenantId, 'write', async (transaction) => {
2520
+ try {
2521
+ const changed =
2522
+ (await this.#exec(transaction, AGENTS_SQL.failAction, [
2523
+ code,
2524
+ completedAt,
2525
+ tenantId,
2526
+ invocationId,
2527
+ workerId,
2528
+ ])) === 1;
2529
+ if (changed) await this.#appendAuditEvent(transaction, audit);
2530
+ return changed;
2531
+ } catch (error) {
2532
+ throw error;
2533
+ }
2534
+ });
2535
+ }
2536
+
2537
+ async cancelAction(
2538
+ tenantId: string,
2539
+ invocationId: string,
2540
+ completedAt: number,
2541
+ audit: PendingAgentAuditEvent,
2542
+ ): Promise<AgentActionInvocation['status'] | null> {
2543
+ return this.#tx(tenantId, 'write', async (transaction) => {
2544
+ try {
2545
+ const current = await this.#actionIn(
2546
+ transaction,
2547
+ tenantId,
2548
+ invocationId,
2549
+ );
2550
+ if (!current || !['queued', 'running'].includes(current.status)) {
2551
+ return null;
2552
+ }
2553
+ const changed = await this.#exec(transaction, AGENTS_SQL.cancelAction, [
2554
+ completedAt,
2555
+ tenantId,
2556
+ invocationId,
2557
+ ]);
2558
+ if (changed !== 1) {
2559
+ return null;
2560
+ }
2561
+ await this.#appendAuditEvent(transaction, {
2562
+ ...audit,
2563
+ metadata: { ...audit.metadata, previousStatus: current.status },
2564
+ });
2565
+ return current.status;
2566
+ } catch (error) {
2567
+ throw error;
2568
+ }
2569
+ });
2570
+ }
2571
+
2572
+ async #appendAuditEvent(
2573
+ transaction: DatabaseTransaction,
2574
+ event: PendingAgentAuditEvent,
2575
+ ): Promise<AgentAuditEvent> {
2576
+ await transaction.query({
2577
+ text: 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
2578
+ parameters: [`agents.core.audit:${event.tenantId}`],
2579
+ });
2580
+ const previous = (
2581
+ await this.#query(transaction, AGENTS_SQL.appendAuditEvent1, [
2582
+ event.tenantId,
2583
+ ])
2584
+ )[0] as unknown as
2585
+ | { sequence: number | string; event_hash: string }
2586
+ | undefined;
2587
+ /* count and every BIGINT arrive as a string from the server driver, so the
2588
+ next sequence must be added as a number. Without this the chain reads
2589
+ 1, then "11", then "111". */
2590
+ const sequence = integer(previous?.sequence ?? 0) + 1;
2591
+ const previousHash = previous?.event_hash ?? null;
2592
+ const metadataJson = stableMetadata(event.metadata);
2593
+ const eventHash = auditHash({
2594
+ tenantId: event.tenantId,
2595
+ sequence,
2596
+ actorId: event.actorId,
2597
+ action: event.action,
2598
+ subjectType: event.subjectType,
2599
+ subjectId: event.subjectId,
2600
+ metadataJson,
2601
+ occurredAt: event.occurredAt,
2602
+ previousHash,
2603
+ });
2604
+ const created: AgentAuditEvent = {
2605
+ ...event,
2606
+ id: randomUUID(),
2607
+ sequence,
2608
+ previousHash,
2609
+ eventHash,
2610
+ };
2611
+ await this.#exec(transaction, AGENTS_SQL.appendAuditEvent2, [
2612
+ created.id,
2613
+ created.tenantId,
2614
+ created.sequence,
2615
+ created.actorId,
2616
+ created.action,
2617
+ created.subjectType,
2618
+ created.subjectId,
2619
+ metadataJson,
2620
+ created.occurredAt,
2621
+ created.previousHash,
2622
+ created.eventHash,
2623
+ ]);
2624
+ return created;
2625
+ }
2626
+
2627
+ async appendAuditEvent(
2628
+ event: PendingAgentAuditEvent,
2629
+ ): Promise<AgentAuditEvent> {
2630
+ return this.#tx(event.tenantId, 'write', (transaction) =>
2631
+ this.#appendAuditEvent(transaction, event),
2632
+ );
2633
+ }
2634
+
2635
+ async listAuditEvents(
2636
+ tenantId: string,
2637
+ limit: number,
2638
+ ): Promise<readonly AgentAuditEvent[]> {
2639
+ return this.#tx(tenantId, 'read', async (transaction) => {
2640
+ return (
2641
+ (await this.#query(transaction, AGENTS_SQL.listAuditEvents, [
2642
+ tenantId,
2643
+ limit,
2644
+ ])) as unknown as AuditRow[]
2645
+ ).map(fromAuditRow);
2646
+ });
2647
+ }
2648
+
2649
+ async pageAuditEvents(
2650
+ tenantId: string,
2651
+ cursor: { readonly occurredAt: number; readonly sequence: number } | null,
2652
+ limit: number,
2653
+ ): Promise<AgentAuditPage> {
2654
+ return this.#tx(tenantId, 'read', async (transaction) => {
2655
+ const rows = (cursor
2656
+ ? await this.#query(transaction, AGENTS_SQL.pageAuditEvents1, [
2657
+ tenantId,
2658
+ cursor.occurredAt,
2659
+ cursor.occurredAt,
2660
+ cursor.sequence,
2661
+ limit + 1,
2662
+ ])
2663
+ : await this.#query(transaction, AGENTS_SQL.pageAuditEvents2, [
2664
+ tenantId,
2665
+ limit + 1,
2666
+ ])) as unknown as AuditRow[];
2667
+ const page = rows.slice(0, limit).map(fromAuditRow);
2668
+ const last = page[page.length - 1];
2669
+ return {
2670
+ events: page,
2671
+ nextCursor:
2672
+ rows.length > limit && last
2673
+ ? `${last.occurredAt}:${last.sequence}`
2674
+ : null,
2675
+ };
2676
+ });
2677
+ }
2678
+
2679
+ async verifyAuditChain(tenantId: string): Promise<boolean> {
2680
+ return (await this.verifyAuditChainDetailed(tenantId)).verified;
2681
+ }
2682
+
2683
+ async verifyAuditChainDetailed(
2684
+ tenantId: string,
2685
+ ): Promise<AuditChainVerification> {
2686
+ return this.#tx(tenantId, 'read', async (transaction) => {
2687
+ const events = (
2688
+ (await this.#query(transaction, AGENTS_SQL.verifyAuditChainDetailed, [
2689
+ tenantId,
2690
+ ])) as unknown as AuditRow[]
2691
+ ).map(fromAuditRow);
2692
+ let previousHash: string | null = null;
2693
+ for (const event of events) {
2694
+ if (event.previousHash !== previousHash) {
2695
+ return { verified: false, brokenAt: event.id };
2696
+ }
2697
+ const expected = auditHash({
2698
+ tenantId: event.tenantId,
2699
+ sequence: event.sequence,
2700
+ actorId: event.actorId,
2701
+ action: event.action,
2702
+ subjectType: event.subjectType,
2703
+ subjectId: event.subjectId,
2704
+ metadataJson: stableMetadata(event.metadata),
2705
+ occurredAt: event.occurredAt,
2706
+ previousHash,
2707
+ });
2708
+ if (expected !== event.eventHash) {
2709
+ return { verified: false, brokenAt: event.id };
2710
+ }
2711
+ previousHash = event.eventHash;
2712
+ }
2713
+ return { verified: true, brokenAt: null };
2714
+ });
2715
+ }
2716
+
2717
+ async usageByDay(
2718
+ tenantId: string,
2719
+ fromDay: string,
2720
+ toDay: string,
2721
+ ): Promise<readonly AgentUsageDay[]> {
2722
+ return this.#tx(tenantId, 'read', async (transaction) => {
2723
+ return (
2724
+ (await this.#query(transaction, AGENTS_SQL.usageByDay, [
2725
+ tenantId,
2726
+ fromDay,
2727
+ toDay,
2728
+ ])) as unknown as UsageRow[]
2729
+ ).map((row) => ({ day: row.day, ...fromUsageRow(row) }));
2730
+ });
2731
+ }
2732
+
2733
+ async usageByAgent(
2734
+ tenantId: string,
2735
+ fromDay: string,
2736
+ toDay: string,
2737
+ ): Promise<readonly AgentUsageAgent[]> {
2738
+ return this.#tx(tenantId, 'read', async (transaction) => {
2739
+ return (
2740
+ (await this.#query(transaction, AGENTS_SQL.usageByAgent, [
2741
+ tenantId,
2742
+ fromDay,
2743
+ toDay,
2744
+ ])) as unknown as UsageRow[]
2745
+ ).map((row) => ({
2746
+ agentId: row.agent_id,
2747
+ agentName: row.agent_name,
2748
+ ...fromUsageRow(row),
2749
+ }));
2750
+ });
2751
+ }
2752
+
2753
+ async usageTotal(
2754
+ tenantId: string,
2755
+ fromDay: string,
2756
+ agentId: string | null,
2757
+ ): Promise<AgentUsageBucket> {
2758
+ return this.#tx(tenantId, 'read', async (transaction) => {
2759
+ const row = (agentId === null
2760
+ ? (
2761
+ await this.#query(transaction, AGENTS_SQL.usageTotal1, [
2762
+ tenantId,
2763
+ fromDay,
2764
+ ])
2765
+ )[0]
2766
+ : (
2767
+ await this.#query(transaction, AGENTS_SQL.usageTotal2, [
2768
+ tenantId,
2769
+ agentId,
2770
+ fromDay,
2771
+ ])
2772
+ )[0]) as unknown as UsageRow | undefined;
2773
+ return row
2774
+ ? fromUsageRow(row)
2775
+ : {
2776
+ runs: 0,
2777
+ inputTokens: 0,
2778
+ outputTokens: 0,
2779
+ costMicroUsd: 0,
2780
+ unpricedRuns: 0,
2781
+ };
2782
+ });
2783
+ }
2784
+
2785
+ async close(): Promise<void> {}
2786
+ }