@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,2167 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { resolveTemplate } from '@flowdular/sdk/contracts';
3
+ import {
4
+ validateJsonValue,
5
+ type AgentHarness,
6
+ type AgentOutputContract,
7
+ type AgentRunTrigger,
8
+ } from '@flowdular/sdk/harness';
9
+ import {
10
+ actorsEqual,
11
+ normalizeActor,
12
+ type Actor,
13
+ type UserActor,
14
+ } from '@flowdular/sdk/kernel';
15
+ import { AGENT_PERMISSIONS } from '../acl/permissions.ts';
16
+ import {
17
+ agentContextValues,
18
+ type AgentRunContext,
19
+ } from '../domain/context-variables.ts';
20
+ import { groupRunTimeline } from '../domain/run-timeline.ts';
21
+ import type {
22
+ AgentAuditPage,
23
+ AgentDefinition,
24
+ AgentDefinitionRevision,
25
+ ModuleAgentBinding,
26
+ ModuleAgentDefinition,
27
+ ModuleAgentView,
28
+ AgentProviderConnection,
29
+ AgentRun,
30
+ AgentRunDetail,
31
+ AgentRunTimeline,
32
+ AgentProcedure,
33
+ AgentProcedureSnapshot,
34
+ AgentWorkerStatus,
35
+ AuditChainVerification,
36
+ CreateAgentProcedureInput,
37
+ CreateAgentInput,
38
+ EnqueueAgentRunInput,
39
+ UpdateAgentProcedureInput,
40
+ UpdateAgentInput,
41
+ UpdateModuleAgentBindingInput,
42
+ } from '../domain/types.ts';
43
+ import type { AgentSettingsReader } from '../settings.ts';
44
+ import {
45
+ DuplicateAgentKeyError,
46
+ DuplicateAgentProcedureKeyError,
47
+ DuplicateRunIdempotencyKeyError,
48
+ ModuleAgentBindingConflictError,
49
+ type AgentRepository,
50
+ } from './repository.ts';
51
+ import type { AgentProviderService } from './provider-service.ts';
52
+ import type { AgentWorker } from './worker.ts';
53
+ import { normalizeModuleAgentDefinitions } from '../server/define-agent.ts';
54
+
55
+ const DEFAULT_MAX_OUTPUT_TOKENS = 4_096;
56
+ const DEFAULT_AUDIT_PAGE = 50;
57
+ const MAX_AUDIT_PAGE = 100;
58
+ const TERMINAL_STATUSES: readonly AgentRun['status'][] = [
59
+ 'succeeded',
60
+ 'failed',
61
+ 'cancelled',
62
+ ];
63
+
64
+ function outputContract(value: AgentOutputContract): AgentOutputContract {
65
+ if (value.kind === 'text') return { kind: 'text' };
66
+ const name = bounded(value.name, 'outputContract.name', 1, 64);
67
+ if (
68
+ !value.schema ||
69
+ Array.isArray(value.schema) ||
70
+ typeof value.schema !== 'object'
71
+ ) {
72
+ throw new AgentServiceError(
73
+ 'INVALID_OUTPUT_CONTRACT',
74
+ 'outputContract.schema must be a JSON object no larger than 32768 characters.',
75
+ );
76
+ }
77
+ let serialized: string;
78
+ try {
79
+ validateJsonValue(
80
+ value.schema,
81
+ 'INVALID_OUTPUT_CONTRACT',
82
+ 'outputContract.schema',
83
+ );
84
+ serialized = JSON.stringify(value.schema);
85
+ } catch {
86
+ throw new AgentServiceError(
87
+ 'INVALID_OUTPUT_CONTRACT',
88
+ 'outputContract.schema must be JSON serializable.',
89
+ );
90
+ }
91
+ if (serialized.length > 32_768) {
92
+ throw new AgentServiceError(
93
+ 'INVALID_OUTPUT_CONTRACT',
94
+ 'outputContract.schema must be a JSON object no larger than 32768 characters.',
95
+ );
96
+ }
97
+ return { kind: 'json-schema', name, schema: value.schema };
98
+ }
99
+
100
+ /* `occurredAt:sequence` of the last row of the previous page. Both halves are
101
+ non-negative integers; anything else is a client error, never a silent
102
+ first-page fallback. */
103
+ function auditCursor(
104
+ raw: string | null,
105
+ ): { readonly occurredAt: number; readonly sequence: number } | null {
106
+ if (raw === null || raw === '') return null;
107
+ const match = /^(\d+):(\d+)$/.exec(raw);
108
+ const occurredAt = match ? Number(match[1]) : NaN;
109
+ const sequence = match ? Number(match[2]) : NaN;
110
+ if (!Number.isSafeInteger(occurredAt) || !Number.isSafeInteger(sequence)) {
111
+ throw new AgentServiceError('INVALID_CURSOR', 'cursor is malformed.', 400);
112
+ }
113
+ return { occurredAt, sequence };
114
+ }
115
+
116
+ export class AgentServiceError extends Error {
117
+ constructor(
118
+ readonly code: string,
119
+ message: string,
120
+ readonly status = 400,
121
+ ) {
122
+ super(message);
123
+ this.name = 'AgentServiceError';
124
+ }
125
+ }
126
+
127
+ function bounded(
128
+ value: string,
129
+ field: string,
130
+ minimum: number,
131
+ maximum: number,
132
+ ): string {
133
+ const normalized = value.trim();
134
+ if (normalized.length < minimum || normalized.length > maximum) {
135
+ throw new AgentServiceError(
136
+ 'INVALID_INPUT',
137
+ `${field} must contain between ${minimum} and ${maximum} characters.`,
138
+ );
139
+ }
140
+ if (normalized.includes('\u0000')) {
141
+ throw new AgentServiceError(
142
+ 'INVALID_INPUT',
143
+ `${field} contains an unsupported character.`,
144
+ );
145
+ }
146
+ return normalized;
147
+ }
148
+
149
+ function requestedActor(value: Actor | string): Actor {
150
+ if (typeof value === 'string') {
151
+ const id = bounded(value, 'actorId', 1, 128);
152
+ return { kind: 'user', id, label: id };
153
+ }
154
+ const actor = normalizeActor(value);
155
+ if (!actor) throw new AgentServiceError('INVALID_ACTOR', 'Actor is invalid.');
156
+ return actor;
157
+ }
158
+
159
+ function agentKey(value: string): string {
160
+ const normalized = value.trim().toLowerCase();
161
+ if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(normalized)) {
162
+ throw new AgentServiceError(
163
+ 'INVALID_AGENT_KEY',
164
+ 'key must contain 3 to 64 lowercase letters, numbers, or hyphens.',
165
+ );
166
+ }
167
+ return normalized;
168
+ }
169
+
170
+ function identifier(value: string, field: string): string {
171
+ const normalized = value.trim();
172
+ if (!/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/.test(normalized)) {
173
+ throw new AgentServiceError(
174
+ 'INVALID_IDENTIFIER',
175
+ `${field} must be a lowercase dot-separated identifier.`,
176
+ );
177
+ }
178
+ return normalized;
179
+ }
180
+
181
+ function toolList(value: readonly string[]): readonly string[] {
182
+ if (value.length > 32) {
183
+ throw new AgentServiceError(
184
+ 'TOOL_LIMIT_EXCEEDED',
185
+ 'An agent can allow at most 32 tools.',
186
+ );
187
+ }
188
+ return [...new Set(value.map((tool) => identifier(tool, 'tool')))].sort();
189
+ }
190
+
191
+ function procedureIdList(value: readonly string[]): readonly string[] {
192
+ if (value.length > 8) {
193
+ throw new AgentServiceError(
194
+ 'SKILL_LIMIT_EXCEEDED',
195
+ 'An agent can attach at most 8 procedures.',
196
+ );
197
+ }
198
+ return [
199
+ ...new Set(value.map((id) => bounded(id, 'procedure id', 1, 128))),
200
+ ].sort();
201
+ }
202
+
203
+ function procedureStatus(
204
+ value: CreateAgentProcedureInput['status'],
205
+ ): AgentProcedure['status'] {
206
+ if (!['draft', 'active', 'archived'].includes(value)) {
207
+ throw new AgentServiceError(
208
+ 'INVALID_SKILL_STATUS',
209
+ 'Agent procedure status is not supported.',
210
+ );
211
+ }
212
+ return value;
213
+ }
214
+
215
+ function executionLimits(
216
+ input: CreateAgentInput,
217
+ defaultMaxOutputTokens: number,
218
+ ): {
219
+ maxSteps: number;
220
+ timeoutMs: number;
221
+ temperature: number;
222
+ maxOutputTokens: number;
223
+ } {
224
+ if (
225
+ !Number.isSafeInteger(input.maxSteps) ||
226
+ input.maxSteps < 1 ||
227
+ input.maxSteps > 32
228
+ ) {
229
+ throw new AgentServiceError(
230
+ 'INVALID_MAX_STEPS',
231
+ 'maxSteps must be between 1 and 32.',
232
+ );
233
+ }
234
+ if (
235
+ !Number.isSafeInteger(input.timeoutMs) ||
236
+ input.timeoutMs < 250 ||
237
+ input.timeoutMs > 86_400_000
238
+ ) {
239
+ throw new AgentServiceError(
240
+ 'INVALID_TIMEOUT',
241
+ 'timeoutMs must be between 250 and 86400000.',
242
+ );
243
+ }
244
+ if (
245
+ !Number.isFinite(input.temperature) ||
246
+ input.temperature < 0 ||
247
+ input.temperature > 2
248
+ ) {
249
+ throw new AgentServiceError(
250
+ 'INVALID_TEMPERATURE',
251
+ 'temperature must be between 0 and 2.',
252
+ );
253
+ }
254
+ const maxOutputTokens = input.maxOutputTokens ?? defaultMaxOutputTokens;
255
+ if (
256
+ !Number.isSafeInteger(maxOutputTokens) ||
257
+ maxOutputTokens < 256 ||
258
+ maxOutputTokens > 65_536
259
+ ) {
260
+ throw new AgentServiceError(
261
+ 'INVALID_MAX_OUTPUT_TOKENS',
262
+ 'maxOutputTokens must be between 256 and 65536.',
263
+ );
264
+ }
265
+ return {
266
+ maxSteps: input.maxSteps,
267
+ timeoutMs: input.timeoutMs,
268
+ temperature: input.temperature,
269
+ maxOutputTokens,
270
+ };
271
+ }
272
+
273
+ function status(value: CreateAgentInput['status']): CreateAgentInput['status'] {
274
+ if (!['draft', 'active', 'paused', 'archived'].includes(value)) {
275
+ throw new AgentServiceError(
276
+ 'INVALID_AGENT_STATUS',
277
+ 'Agent status is not supported.',
278
+ );
279
+ }
280
+ return value;
281
+ }
282
+
283
+ function trigger(value: AgentRunTrigger): AgentRunTrigger {
284
+ if (!['playground', 'workflow', 'service', 'schedule'].includes(value)) {
285
+ throw new AgentServiceError(
286
+ 'INVALID_TRIGGER',
287
+ 'Agent run trigger is not supported.',
288
+ );
289
+ }
290
+ return value;
291
+ }
292
+
293
+ /* Absent means no cap is configured. Returning a refusal instead of throwing
294
+ keeps the budget reader free of any dependency on this module. */
295
+ export interface AgentBudgetGuard {
296
+ check(
297
+ tenantId: string,
298
+ agentId: string,
299
+ now: number,
300
+ ): Promise<{ readonly code: string; readonly message: string } | null>;
301
+ }
302
+
303
+ export class AgentService {
304
+ readonly #moduleAgents = new Map<string, ModuleAgentDefinition>();
305
+
306
+ constructor(
307
+ private readonly repository: AgentRepository,
308
+ private readonly harness: AgentHarness,
309
+ private readonly worker: AgentWorker,
310
+ private readonly providerService?: AgentProviderService,
311
+ private readonly now: () => number = Date.now,
312
+ private readonly settings?: AgentSettingsReader,
313
+ private readonly budget?: AgentBudgetGuard,
314
+ ) {}
315
+
316
+ workerStatus(): AgentWorkerStatus {
317
+ return this.worker.status();
318
+ }
319
+
320
+ async providers(
321
+ tenantId: string,
322
+ ): Promise<readonly AgentProviderConnection[]> {
323
+ if (this.providerService) return this.providerService.list(tenantId);
324
+ return this.harness.providers().map((id) => ({
325
+ id,
326
+ tenantId,
327
+ key: id,
328
+ name: id,
329
+ kind: 'local-simulation' as const,
330
+ enabled: true,
331
+ resourceName: null,
332
+ baseURL: null,
333
+ models: [],
334
+ credentialConfigured: false,
335
+ credentialRevision: 0,
336
+ readiness: {
337
+ status: 'healthy' as const,
338
+ model: null,
339
+ latencyMs: 0,
340
+ errorCode: null,
341
+ checkedAt: 0,
342
+ },
343
+ revision: 1,
344
+ createdBy: 'system',
345
+ createdAt: 0,
346
+ updatedBy: 'system',
347
+ updatedAt: 0,
348
+ }));
349
+ }
350
+
351
+ tools(): readonly string[] {
352
+ return this.harness.tools();
353
+ }
354
+
355
+ async reconcileModuleAgents(
356
+ definitions: readonly ModuleAgentDefinition[],
357
+ ): Promise<void> {
358
+ const normalized = normalizeModuleAgentDefinitions(definitions);
359
+ await this.repository.reconcileModuleAgents(normalized, this.now());
360
+ this.#moduleAgents.clear();
361
+ for (const definition of normalized) {
362
+ this.#moduleAgents.set(definition.id, definition);
363
+ }
364
+ }
365
+
366
+ async listModuleAgents(
367
+ tenantId: string,
368
+ ): Promise<readonly ModuleAgentView[]> {
369
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
370
+ const bindings = new Map(
371
+ (await this.repository.listModuleAgentBindings(trustedTenantId)).map(
372
+ (binding) => [binding.agentId, binding] as const,
373
+ ),
374
+ );
375
+ const views: ModuleAgentView[] = [];
376
+ for (const definition of [...this.#moduleAgents.values()].sort(
377
+ (left, right) => left.id.localeCompare(right.id),
378
+ )) {
379
+ views.push(
380
+ await this.#moduleAgentView(
381
+ trustedTenantId,
382
+ definition,
383
+ bindings.get(definition.id) ?? null,
384
+ ),
385
+ );
386
+ }
387
+ return views;
388
+ }
389
+
390
+ async configureModuleAgent(
391
+ tenantId: string,
392
+ actorId: string,
393
+ input: UpdateModuleAgentBindingInput,
394
+ ): Promise<ModuleAgentView> {
395
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
396
+ const id = bounded(input.agentId, 'agentId', 1, 128);
397
+ const definition = this.#moduleAgents.get(id);
398
+ if (!definition) {
399
+ throw new AgentServiceError(
400
+ 'MODULE_AGENT_UNAVAILABLE',
401
+ 'The module-owned agent is not registered by an enabled module.',
402
+ 409,
403
+ );
404
+ }
405
+ if (
406
+ !Number.isSafeInteger(input.expectedRevision) ||
407
+ input.expectedRevision < 0
408
+ ) {
409
+ throw new AgentServiceError(
410
+ 'MODULE_AGENT_BINDING_REVISION_INVALID',
411
+ 'expectedRevision must be a non-negative integer.',
412
+ );
413
+ }
414
+ if (!['active', 'paused'].includes(input.status)) {
415
+ throw new AgentServiceError(
416
+ 'INVALID_AGENT_STATUS',
417
+ 'Module agent binding status must be active or paused.',
418
+ );
419
+ }
420
+ const provider = bounded(input.provider, 'provider', 1, 128);
421
+ const model = bounded(input.model, 'model', 1, 160);
422
+ await this.assertProviderConfigured(trustedTenantId, provider, model);
423
+ if (input.status === 'active' && this.providerService) {
424
+ await this.providerService.assertUsable(trustedTenantId, provider, model);
425
+ }
426
+ const enabledTools = toolList(input.enabledTools);
427
+ const outsideMaximum = enabledTools.find(
428
+ (tool) => !definition.allowedTools.includes(tool),
429
+ );
430
+ if (outsideMaximum) {
431
+ throw new AgentServiceError(
432
+ 'MODULE_AGENT_TOOL_NOT_ALLOWED',
433
+ `Tool ${outsideMaximum} is outside the module-owned allowlist.`,
434
+ 403,
435
+ );
436
+ }
437
+ this.assertToolsRegistered(enabledTools);
438
+ const existing = await this.repository.getModuleAgentBinding(
439
+ trustedTenantId,
440
+ id,
441
+ );
442
+ if ((existing?.revision ?? 0) !== input.expectedRevision) {
443
+ throw new AgentServiceError(
444
+ 'MODULE_AGENT_BINDING_REVISION_CONFLICT',
445
+ 'The module agent binding changed. Reload before saving.',
446
+ 409,
447
+ );
448
+ }
449
+ const configurationChanged =
450
+ !existing ||
451
+ existing.provider !== provider ||
452
+ existing.model !== model ||
453
+ JSON.stringify(existing.enabledTools) !== JSON.stringify(enabledTools) ||
454
+ existing.moduleDefinitionRevision !== definition.definitionRevision;
455
+ if (existing && !configurationChanged && existing.status === input.status) {
456
+ return await this.#moduleAgentView(trustedTenantId, definition, existing);
457
+ }
458
+ const updatedAt = this.now();
459
+ const identity = bounded(actorId, 'actorId', 1, 128);
460
+ const binding: ModuleAgentBinding = {
461
+ tenantId: trustedTenantId,
462
+ agentId: definition.id,
463
+ provider,
464
+ model,
465
+ enabledTools,
466
+ status: input.status,
467
+ moduleDefinitionRevision: definition.definitionRevision,
468
+ executableRevision: existing
469
+ ? existing.executableRevision + (configurationChanged ? 1 : 0)
470
+ : 1,
471
+ revision: (existing?.revision ?? 0) + 1,
472
+ updatedBy: identity,
473
+ updatedAt,
474
+ };
475
+ try {
476
+ await this.repository.saveModuleAgentBinding(
477
+ binding,
478
+ definition,
479
+ input.expectedRevision,
480
+ {
481
+ tenantId: trustedTenantId,
482
+ actorId: identity,
483
+ action: existing
484
+ ? 'module-agent.binding-updated'
485
+ : 'module-agent.binding-created',
486
+ subjectType: 'agent',
487
+ subjectId: definition.id,
488
+ metadata: {
489
+ moduleId: definition.moduleId,
490
+ definitionRevision: definition.definitionRevision,
491
+ executableRevision: binding.executableRevision,
492
+ bindingRevision: binding.revision,
493
+ status: binding.status,
494
+ },
495
+ occurredAt: updatedAt,
496
+ },
497
+ );
498
+ } catch (error) {
499
+ if (error instanceof ModuleAgentBindingConflictError) {
500
+ throw new AgentServiceError(
501
+ 'MODULE_AGENT_BINDING_REVISION_CONFLICT',
502
+ error.message,
503
+ 409,
504
+ );
505
+ }
506
+ throw error;
507
+ }
508
+ return await this.#moduleAgentView(trustedTenantId, definition, binding);
509
+ }
510
+
511
+ async listAgents(tenantId: string): Promise<readonly AgentDefinition[]> {
512
+ return await this.repository.listAgents(
513
+ bounded(tenantId, 'tenantId', 1, 128),
514
+ );
515
+ }
516
+
517
+ async listProcedures(tenantId: string): Promise<readonly AgentProcedure[]> {
518
+ return await this.repository.listProcedures(
519
+ bounded(tenantId, 'tenantId', 1, 128),
520
+ );
521
+ }
522
+
523
+ async pageAuditEvents(
524
+ tenantId: string,
525
+ cursor: string | null,
526
+ limit: number,
527
+ ): Promise<AgentAuditPage> {
528
+ const size = Number.isSafeInteger(limit)
529
+ ? Math.min(Math.max(1, Math.trunc(limit)), MAX_AUDIT_PAGE)
530
+ : DEFAULT_AUDIT_PAGE;
531
+ return await this.repository.pageAuditEvents(
532
+ bounded(tenantId, 'tenantId', 1, 128),
533
+ auditCursor(cursor),
534
+ size,
535
+ );
536
+ }
537
+
538
+ async verifyAudit(tenantId: string): Promise<AuditChainVerification> {
539
+ return await this.repository.verifyAuditChainDetailed(
540
+ bounded(tenantId, 'tenantId', 1, 128),
541
+ );
542
+ }
543
+
544
+ async createProcedure(
545
+ tenantId: string,
546
+ actorId: string,
547
+ input: CreateAgentProcedureInput,
548
+ ): Promise<AgentProcedure> {
549
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
550
+ const actor = bounded(actorId, 'actorId', 1, 128);
551
+ const requiredTools = toolList(input.requiredTools);
552
+ this.assertToolsRegistered(requiredTools);
553
+ const now = this.now();
554
+ const procedure: AgentProcedure = {
555
+ id: randomUUID(),
556
+ tenantId: trustedTenantId,
557
+ key: agentKey(input.key),
558
+ name: bounded(input.name, 'name', 2, 120),
559
+ description: bounded(input.description, 'description', 2, 500),
560
+ instructions: bounded(input.instructions, 'instructions', 8, 8_000),
561
+ requiredTools,
562
+ status: 'draft',
563
+ revision: 1,
564
+ createdBy: actor,
565
+ createdAt: now,
566
+ updatedBy: actor,
567
+ updatedAt: now,
568
+ };
569
+ try {
570
+ const created = await this.repository.createProcedure(procedure);
571
+ await this.repository.appendAuditEvent({
572
+ tenantId: trustedTenantId,
573
+ actorId: actor,
574
+ /* Audit action and subject ids are evidence. 0.8 renames the product
575
+ term, never the history it would have to rewrite. */
576
+ action: 'agent-skill.created',
577
+ subjectType: 'agent-skill',
578
+ subjectId: created.id,
579
+ metadata: { key: created.key, revision: created.revision },
580
+ occurredAt: now,
581
+ });
582
+ return created;
583
+ } catch (error) {
584
+ if (error instanceof DuplicateAgentProcedureKeyError) {
585
+ throw new AgentServiceError('DUPLICATE_SKILL_KEY', error.message, 409);
586
+ }
587
+ throw error;
588
+ }
589
+ }
590
+
591
+ async updateProcedure(
592
+ tenantId: string,
593
+ procedureId: string,
594
+ actorId: string,
595
+ input: UpdateAgentProcedureInput,
596
+ ): Promise<AgentProcedure> {
597
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
598
+ const existing = await this.repository.getProcedure(
599
+ trustedTenantId,
600
+ bounded(procedureId, 'procedureId', 1, 128),
601
+ );
602
+ if (!existing) {
603
+ throw new AgentServiceError(
604
+ 'SKILL_NOT_FOUND',
605
+ 'Agent procedure not found.',
606
+ 404,
607
+ );
608
+ }
609
+ if (input.expectedRevision !== existing.revision) {
610
+ throw new AgentServiceError(
611
+ 'SKILL_REVISION_CONFLICT',
612
+ 'The procedure was changed by another request. Reload before saving.',
613
+ 409,
614
+ );
615
+ }
616
+ if (input.status === 'archived' && existing.status !== 'archived') {
617
+ await this.assertSkillCanArchive(trustedTenantId, existing.id);
618
+ }
619
+ const requiredTools = toolList(input.requiredTools);
620
+ this.assertToolsRegistered(requiredTools);
621
+ const actor = bounded(actorId, 'actorId', 1, 128);
622
+ const now = this.now();
623
+ const updated: AgentProcedure = {
624
+ ...existing,
625
+ key: agentKey(input.key),
626
+ name: bounded(input.name, 'name', 2, 120),
627
+ description: bounded(input.description, 'description', 2, 500),
628
+ instructions: bounded(input.instructions, 'instructions', 8, 8_000),
629
+ requiredTools,
630
+ status: procedureStatus(input.status),
631
+ revision: existing.revision + 1,
632
+ updatedBy: actor,
633
+ updatedAt: now,
634
+ };
635
+ try {
636
+ const result = await this.repository.updateProcedure(updated);
637
+ await this.repository.appendAuditEvent({
638
+ tenantId: trustedTenantId,
639
+ actorId: actor,
640
+ action:
641
+ input.status === 'archived' && existing.status !== 'archived'
642
+ ? 'agent-skill.archived'
643
+ : 'agent-skill.updated',
644
+ subjectType: 'agent-skill',
645
+ subjectId: result.id,
646
+ metadata: { revision: result.revision, status: result.status },
647
+ occurredAt: now,
648
+ });
649
+ return result;
650
+ } catch (error) {
651
+ if (error instanceof DuplicateAgentProcedureKeyError) {
652
+ throw new AgentServiceError('DUPLICATE_SKILL_KEY', error.message, 409);
653
+ }
654
+ throw error;
655
+ }
656
+ }
657
+
658
+ async archiveProcedure(
659
+ tenantId: string,
660
+ procedureId: string,
661
+ actorId: string,
662
+ expectedRevision: number,
663
+ ): Promise<AgentProcedure> {
664
+ const existing = await this.requireSkill(tenantId, procedureId);
665
+ return await this.updateProcedure(tenantId, existing.id, actorId, {
666
+ key: existing.key,
667
+ name: existing.name,
668
+ description: existing.description,
669
+ instructions: existing.instructions,
670
+ requiredTools: existing.requiredTools,
671
+ status: 'archived',
672
+ expectedRevision,
673
+ });
674
+ }
675
+
676
+ async deleteProcedure(
677
+ tenantId: string,
678
+ procedureId: string,
679
+ actorId: string,
680
+ expectedRevision: number,
681
+ ): Promise<void> {
682
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
683
+ const existing = await this.requireSkill(trustedTenantId, procedureId);
684
+ if (existing.revision !== expectedRevision) {
685
+ throw new AgentServiceError(
686
+ 'SKILL_REVISION_CONFLICT',
687
+ 'The procedure was changed by another request. Reload before deleting.',
688
+ 409,
689
+ );
690
+ }
691
+ if (existing.status !== 'archived') {
692
+ throw new AgentServiceError(
693
+ 'SKILL_NOT_ARCHIVED',
694
+ 'Archive the procedure before deleting it.',
695
+ 409,
696
+ );
697
+ }
698
+ const usage = await this.repository.procedureUsage(
699
+ trustedTenantId,
700
+ existing.id,
701
+ );
702
+ if (usage.assignments > 0) {
703
+ throw new AgentServiceError(
704
+ 'SKILL_IN_USE',
705
+ 'Remove this procedure from every agent before deleting it.',
706
+ 409,
707
+ );
708
+ }
709
+ if (
710
+ !(await this.repository.deleteProcedure(trustedTenantId, existing.id))
711
+ ) {
712
+ throw new AgentServiceError(
713
+ 'SKILL_NOT_FOUND',
714
+ 'Agent procedure not found.',
715
+ 404,
716
+ );
717
+ }
718
+ await this.repository.appendAuditEvent({
719
+ tenantId: trustedTenantId,
720
+ actorId: bounded(actorId, 'actorId', 1, 128),
721
+ action: 'agent-skill.deleted',
722
+ subjectType: 'agent-skill',
723
+ subjectId: existing.id,
724
+ metadata: { key: existing.key, revision: existing.revision },
725
+ occurredAt: this.now(),
726
+ });
727
+ }
728
+
729
+ async createAgent(
730
+ tenantId: string,
731
+ actorId: string,
732
+ input: CreateAgentInput,
733
+ ): Promise<AgentDefinition> {
734
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
735
+ // Provider references are stored connection IDs, not tool identifiers.
736
+ const provider = bounded(
737
+ input.provider || (this.settings?.defaultProvider(trustedTenantId) ?? ''),
738
+ 'provider',
739
+ 1,
740
+ 128,
741
+ );
742
+ const model = bounded(
743
+ input.model || (this.settings?.defaultModel(trustedTenantId) ?? ''),
744
+ 'model',
745
+ 1,
746
+ 160,
747
+ );
748
+ await this.assertProviderConfigured(trustedTenantId, provider, model);
749
+ const allowedTools = toolList(input.allowedTools);
750
+ this.assertToolsRegistered(allowedTools);
751
+ const procedureIds = procedureIdList(input.procedureIds);
752
+ await this.resolveSkills(trustedTenantId, procedureIds, false);
753
+ const createdAt = this.now();
754
+ const identity = bounded(actorId, 'actorId', 1, 128);
755
+ const agent: AgentDefinition = {
756
+ id: randomUUID(),
757
+ tenantId: trustedTenantId,
758
+ key: agentKey(input.key),
759
+ name: bounded(input.name, 'name', 2, 120),
760
+ description: bounded(input.description, 'description', 2, 500),
761
+ instructions: bounded(input.instructions, 'instructions', 8, 40_000),
762
+ provider,
763
+ model,
764
+ allowedTools,
765
+ procedureIds,
766
+ ...executionLimits(
767
+ input,
768
+ this.settings?.defaultMaxOutputTokens(trustedTenantId) ??
769
+ DEFAULT_MAX_OUTPUT_TOKENS,
770
+ ),
771
+ status: 'draft',
772
+ revision: 1,
773
+ createdBy: identity,
774
+ createdAt,
775
+ updatedBy: identity,
776
+ updatedAt: createdAt,
777
+ };
778
+ try {
779
+ const created = await this.repository.createAgent(agent);
780
+ await this.repository.appendAuditEvent({
781
+ tenantId: created.tenantId,
782
+ actorId: identity,
783
+ action: 'agent.created',
784
+ subjectType: 'agent',
785
+ subjectId: created.id,
786
+ metadata: { key: created.key, revision: created.revision },
787
+ occurredAt: createdAt,
788
+ });
789
+ return created;
790
+ } catch (error) {
791
+ if (error instanceof DuplicateAgentKeyError) {
792
+ throw new AgentServiceError('DUPLICATE_AGENT_KEY', error.message, 409);
793
+ }
794
+ throw error;
795
+ }
796
+ }
797
+
798
+ async updateAgent(
799
+ tenantId: string,
800
+ agentId: string,
801
+ actorId: string,
802
+ input: UpdateAgentInput,
803
+ ): Promise<AgentDefinition> {
804
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
805
+ const id = bounded(agentId, 'agentId', 1, 128);
806
+ this.#refuseModuleAgentMutation(id);
807
+ const existing = await this.repository.getAgent(trustedTenantId, id);
808
+ if (!existing) {
809
+ throw new AgentServiceError('AGENT_NOT_FOUND', 'Agent not found.', 404);
810
+ }
811
+ if (input.expectedRevision !== existing.revision) {
812
+ throw new AgentServiceError(
813
+ 'AGENT_REVISION_CONFLICT',
814
+ 'The agent was changed by another request. Reload before saving.',
815
+ 409,
816
+ );
817
+ }
818
+ const provider = bounded(
819
+ input.provider || existing.provider,
820
+ 'provider',
821
+ 1,
822
+ 128,
823
+ );
824
+ const model = bounded(input.model || existing.model, 'model', 1, 160);
825
+ await this.assertProviderConfigured(trustedTenantId, provider, model);
826
+ if (input.status === 'active' && this.providerService) {
827
+ await this.providerService.assertUsable(trustedTenantId, provider, model);
828
+ }
829
+ const allowedTools = toolList(input.allowedTools);
830
+ this.assertToolsRegistered(allowedTools);
831
+ const procedureIds = procedureIdList(input.procedureIds);
832
+ const skills = await this.resolveSkills(
833
+ trustedTenantId,
834
+ procedureIds,
835
+ input.status === 'active',
836
+ );
837
+ if (input.status === 'active') {
838
+ this.assertSkillToolsAllowed(skills, allowedTools);
839
+ }
840
+ const updatedAt = this.now();
841
+ const identity = bounded(actorId, 'actorId', 1, 128);
842
+ const updated: AgentDefinition = {
843
+ ...existing,
844
+ key: agentKey(input.key),
845
+ name: bounded(input.name, 'name', 2, 120),
846
+ description: bounded(input.description, 'description', 2, 500),
847
+ instructions: bounded(input.instructions, 'instructions', 8, 40_000),
848
+ provider,
849
+ model,
850
+ allowedTools,
851
+ procedureIds,
852
+ ...executionLimits(input, existing.maxOutputTokens),
853
+ status: status(input.status),
854
+ revision: existing.revision + 1,
855
+ updatedBy: identity,
856
+ updatedAt,
857
+ };
858
+ try {
859
+ const result = await this.repository.updateAgent(updated);
860
+ await this.repository.appendAuditEvent({
861
+ tenantId: result.tenantId,
862
+ actorId: identity,
863
+ action:
864
+ input.status === 'archived' && existing.status !== 'archived'
865
+ ? 'agent.archived'
866
+ : 'agent.updated',
867
+ subjectType: 'agent',
868
+ subjectId: result.id,
869
+ metadata: { revision: result.revision, status: result.status },
870
+ occurredAt: updatedAt,
871
+ });
872
+ return result;
873
+ } catch (error) {
874
+ if (error instanceof DuplicateAgentKeyError) {
875
+ throw new AgentServiceError('DUPLICATE_AGENT_KEY', error.message, 409);
876
+ }
877
+ throw error;
878
+ }
879
+ }
880
+
881
+ async archiveAgent(
882
+ tenantId: string,
883
+ agentId: string,
884
+ actorId: string,
885
+ expectedRevision: number,
886
+ ): Promise<AgentDefinition> {
887
+ this.#refuseModuleAgentMutation(bounded(agentId, 'agentId', 1, 128));
888
+ const existing = await this.requireAgent(tenantId, agentId);
889
+ return await this.updateAgent(tenantId, existing.id, actorId, {
890
+ key: existing.key,
891
+ name: existing.name,
892
+ description: existing.description,
893
+ instructions: existing.instructions,
894
+ provider: existing.provider,
895
+ model: existing.model,
896
+ allowedTools: existing.allowedTools,
897
+ procedureIds: existing.procedureIds,
898
+ maxSteps: existing.maxSteps,
899
+ timeoutMs: existing.timeoutMs,
900
+ temperature: existing.temperature,
901
+ maxOutputTokens: existing.maxOutputTokens,
902
+ status: 'archived',
903
+ expectedRevision,
904
+ });
905
+ }
906
+
907
+ async deleteAgent(
908
+ tenantId: string,
909
+ agentId: string,
910
+ actorId: string,
911
+ expectedRevision: number,
912
+ ): Promise<void> {
913
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
914
+ this.#refuseModuleAgentMutation(bounded(agentId, 'agentId', 1, 128));
915
+ const existing = await this.requireAgent(trustedTenantId, agentId);
916
+ if (existing.revision !== expectedRevision) {
917
+ throw new AgentServiceError(
918
+ 'AGENT_REVISION_CONFLICT',
919
+ 'The agent was changed by another request. Reload before deleting.',
920
+ 409,
921
+ );
922
+ }
923
+ if (existing.status !== 'archived') {
924
+ throw new AgentServiceError(
925
+ 'AGENT_NOT_ARCHIVED',
926
+ 'Archive the agent before deleting it.',
927
+ 409,
928
+ );
929
+ }
930
+ const usage = await this.repository.agentUsage(
931
+ trustedTenantId,
932
+ existing.id,
933
+ );
934
+ if (usage.runs > 0) {
935
+ throw new AgentServiceError(
936
+ 'AGENT_IN_USE',
937
+ 'Delete is unavailable while this agent has run history.',
938
+ 409,
939
+ );
940
+ }
941
+ if (usage.assignments > 0) {
942
+ throw new AgentServiceError(
943
+ 'AGENT_IN_USE',
944
+ 'Remove every attached procedure before deleting this agent.',
945
+ 409,
946
+ );
947
+ }
948
+ if (!(await this.repository.deleteAgent(trustedTenantId, existing.id))) {
949
+ throw new AgentServiceError('AGENT_NOT_FOUND', 'Agent not found.', 404);
950
+ }
951
+ await this.repository.appendAuditEvent({
952
+ tenantId: trustedTenantId,
953
+ actorId: bounded(actorId, 'actorId', 1, 128),
954
+ action: 'agent.deleted',
955
+ subjectType: 'agent',
956
+ subjectId: existing.id,
957
+ metadata: { key: existing.key, revision: existing.revision },
958
+ occurredAt: this.now(),
959
+ });
960
+ }
961
+
962
+ async listRuns(tenantId: string, limit = 100): Promise<readonly AgentRun[]> {
963
+ const boundedLimit = Math.max(1, Math.min(250, Math.trunc(limit)));
964
+ return await this.repository.listRuns(
965
+ bounded(tenantId, 'tenantId', 1, 128),
966
+ boundedLimit,
967
+ );
968
+ }
969
+
970
+ async getRun(tenantId: string, runId: string): Promise<AgentRunDetail> {
971
+ const run = await this.repository.getRun(
972
+ bounded(tenantId, 'tenantId', 1, 128),
973
+ bounded(runId, 'runId', 1, 128),
974
+ );
975
+ if (!run) {
976
+ throw new AgentServiceError('RUN_NOT_FOUND', 'Agent run not found.', 404);
977
+ }
978
+ return run;
979
+ }
980
+
981
+ async getRevisionReference(
982
+ tenantId: string,
983
+ agentId: string,
984
+ revision: number,
985
+ ): Promise<{
986
+ readonly agentId: string;
987
+ readonly revision: number;
988
+ readonly name: string;
989
+ readonly status: Exclude<AgentDefinition['status'], 'draft'>;
990
+ readonly supportsStructuredOutput: boolean;
991
+ readonly allowedTools: readonly string[];
992
+ } | null> {
993
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
994
+ if (!Number.isSafeInteger(revision) || revision < 1) {
995
+ throw new AgentServiceError(
996
+ 'INVALID_AGENT_REVISION',
997
+ 'revision must be a positive integer.',
998
+ );
999
+ }
1000
+ const id = bounded(agentId, 'agentId', 1, 128);
1001
+ const retained = await this.repository.getAgentRevision(
1002
+ trustedTenantId,
1003
+ id,
1004
+ revision,
1005
+ );
1006
+ if (!retained || retained.status === 'draft') return null;
1007
+ if (retained.ownership.kind === 'module') {
1008
+ const definition = this.#moduleAgents.get(id);
1009
+ const binding = await this.repository.getModuleAgentBinding(
1010
+ trustedTenantId,
1011
+ id,
1012
+ );
1013
+ if (
1014
+ !definition ||
1015
+ !binding ||
1016
+ (await this.#moduleAgentRevisionUnavailableReason(
1017
+ trustedTenantId,
1018
+ retained,
1019
+ binding,
1020
+ ))
1021
+ ) {
1022
+ return null;
1023
+ }
1024
+ return {
1025
+ agentId: retained.agentId,
1026
+ revision: retained.revision,
1027
+ name: retained.name,
1028
+ status: binding.status,
1029
+ supportsStructuredOutput:
1030
+ (await this.providerService?.supportsStructuredOutput(
1031
+ trustedTenantId,
1032
+ retained.provider,
1033
+ retained.model,
1034
+ )) ??
1035
+ this.harness.providerSupportsStructuredOutput(retained.provider),
1036
+ allowedTools: retained.allowedTools,
1037
+ };
1038
+ }
1039
+ const current = await this.repository.getAgent(trustedTenantId, id);
1040
+ if (!current || current.status === 'draft') return null;
1041
+ return {
1042
+ agentId: retained.agentId,
1043
+ revision: retained.revision,
1044
+ name: retained.name,
1045
+ status: current.status === 'active' ? retained.status : current.status,
1046
+ supportsStructuredOutput:
1047
+ (await this.providerService?.supportsStructuredOutput(
1048
+ trustedTenantId,
1049
+ retained.provider,
1050
+ retained.model,
1051
+ )) ?? this.harness.providerSupportsStructuredOutput(retained.provider),
1052
+ allowedTools: retained.allowedTools,
1053
+ };
1054
+ }
1055
+
1056
+ async listRevisionReferences(tenantId: string): Promise<
1057
+ readonly {
1058
+ readonly agentId: string;
1059
+ readonly revision: number;
1060
+ readonly name: string;
1061
+ readonly status: Exclude<AgentDefinition['status'], 'draft'>;
1062
+ readonly supportsStructuredOutput: boolean;
1063
+ readonly allowedTools: readonly string[];
1064
+ }[]
1065
+ > {
1066
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
1067
+ const current = new Map(
1068
+ (await this.repository.listAgents(trustedTenantId)).map(
1069
+ (agent) => [agent.id, agent] as const,
1070
+ ),
1071
+ );
1072
+ const moduleBindings = new Map(
1073
+ (await this.repository.listModuleAgentBindings(trustedTenantId)).map(
1074
+ (binding) => [binding.agentId, binding] as const,
1075
+ ),
1076
+ );
1077
+ const references = [];
1078
+ for (const retained of await this.repository.listAgentRevisions(
1079
+ trustedTenantId,
1080
+ )) {
1081
+ references.push(
1082
+ ...(await (async () => {
1083
+ if (retained.ownership.kind === 'module') {
1084
+ const definition = this.#moduleAgents.get(retained.agentId);
1085
+ const binding = moduleBindings.get(retained.agentId);
1086
+ if (
1087
+ !definition ||
1088
+ !binding ||
1089
+ (await this.#moduleAgentRevisionUnavailableReason(
1090
+ trustedTenantId,
1091
+ retained,
1092
+ binding,
1093
+ ))
1094
+ ) {
1095
+ return [];
1096
+ }
1097
+ return [
1098
+ {
1099
+ agentId: retained.agentId,
1100
+ revision: retained.revision,
1101
+ name: retained.name,
1102
+ status: binding.status,
1103
+ supportsStructuredOutput:
1104
+ (await this.providerService?.supportsStructuredOutput(
1105
+ trustedTenantId,
1106
+ retained.provider,
1107
+ retained.model,
1108
+ )) ??
1109
+ this.harness.providerSupportsStructuredOutput(
1110
+ retained.provider,
1111
+ ),
1112
+ allowedTools: retained.allowedTools,
1113
+ },
1114
+ ];
1115
+ }
1116
+ const agent = current.get(retained.agentId);
1117
+ if (
1118
+ !agent ||
1119
+ agent.status === 'draft' ||
1120
+ retained.status === 'draft'
1121
+ ) {
1122
+ return [];
1123
+ }
1124
+ return [
1125
+ {
1126
+ agentId: retained.agentId,
1127
+ revision: retained.revision,
1128
+ name: retained.name,
1129
+ status:
1130
+ agent.status === 'active' ? retained.status : agent.status,
1131
+ supportsStructuredOutput:
1132
+ (await this.providerService?.supportsStructuredOutput(
1133
+ trustedTenantId,
1134
+ retained.provider,
1135
+ retained.model,
1136
+ )) ??
1137
+ this.harness.providerSupportsStructuredOutput(
1138
+ retained.provider,
1139
+ ),
1140
+ allowedTools: retained.allowedTools,
1141
+ },
1142
+ ];
1143
+ })()),
1144
+ );
1145
+ }
1146
+ return references;
1147
+ }
1148
+
1149
+ async enqueueRevisionRun(
1150
+ context: {
1151
+ readonly tenantId: string;
1152
+ readonly workflowRunId: string;
1153
+ readonly actor: Actor;
1154
+ readonly authorizationSubject?: UserActor;
1155
+ readonly permissionSnapshot: readonly string[];
1156
+ },
1157
+ input: {
1158
+ readonly agentId: string;
1159
+ readonly revision: number;
1160
+ readonly input: string;
1161
+ readonly toolGrants: readonly string[];
1162
+ readonly outputContract: AgentOutputContract;
1163
+ readonly idempotencyKey: string;
1164
+ },
1165
+ ): Promise<{ readonly runId: string; readonly created: boolean }> {
1166
+ const trustedTenantId = bounded(context.tenantId, 'tenantId', 1, 128);
1167
+ const workflowRunId = bounded(
1168
+ context.workflowRunId,
1169
+ 'workflowRunId',
1170
+ 1,
1171
+ 128,
1172
+ );
1173
+ const actor = normalizeActor(context.actor);
1174
+ if (!actor) {
1175
+ throw new AgentServiceError(
1176
+ 'INVALID_ACTOR',
1177
+ 'The workflow actor is invalid.',
1178
+ );
1179
+ }
1180
+ if (!Number.isSafeInteger(input.revision) || input.revision < 1) {
1181
+ throw new AgentServiceError(
1182
+ 'INVALID_AGENT_REVISION',
1183
+ 'revision must be a positive integer.',
1184
+ );
1185
+ }
1186
+ const agentId = bounded(input.agentId, 'agentId', 1, 128);
1187
+ const contract = outputContract(input.outputContract);
1188
+ const idempotencyKey = bounded(
1189
+ input.idempotencyKey,
1190
+ 'idempotencyKey',
1191
+ 8,
1192
+ 128,
1193
+ );
1194
+ const prompt = bounded(input.input, 'input', 1, 100_000);
1195
+ const suppliedSubject = context.authorizationSubject
1196
+ ? normalizeActor(context.authorizationSubject)
1197
+ : undefined;
1198
+ const derivedSubject =
1199
+ actor.kind === 'user'
1200
+ ? actor
1201
+ : actor.kind === 'service'
1202
+ ? actor.configuredBy
1203
+ : undefined;
1204
+ const authorizationSubject = suppliedSubject ?? derivedSubject;
1205
+ if (
1206
+ !authorizationSubject ||
1207
+ authorizationSubject.kind !== 'user' ||
1208
+ (derivedSubject && authorizationSubject.id !== derivedSubject.id)
1209
+ ) {
1210
+ throw new AgentServiceError(
1211
+ 'INVALID_AUTHORIZATION_SUBJECT',
1212
+ 'The workflow authorization subject is invalid.',
1213
+ );
1214
+ }
1215
+ const permissionSnapshot = [...new Set(context.permissionSnapshot)].sort();
1216
+ const requestedToolGrants = toolList(input.toolGrants);
1217
+ const requestMatches = (candidate: AgentRun): boolean =>
1218
+ candidate.agentId === agentId &&
1219
+ candidate.agentRevision === input.revision &&
1220
+ candidate.input === prompt &&
1221
+ candidate.workflowRunId === workflowRunId &&
1222
+ actorsEqual(candidate.requestedActor, actor) &&
1223
+ candidate.authorizationSubject?.id === authorizationSubject.id &&
1224
+ JSON.stringify(candidate.permissionSnapshot) ===
1225
+ JSON.stringify(permissionSnapshot) &&
1226
+ JSON.stringify(candidate.toolGrants) ===
1227
+ JSON.stringify(requestedToolGrants) &&
1228
+ JSON.stringify(candidate.outputContract) === JSON.stringify(contract);
1229
+ const existing = await this.repository.findRunByIdempotencyKey(
1230
+ trustedTenantId,
1231
+ idempotencyKey,
1232
+ );
1233
+ if (existing) {
1234
+ if (!requestMatches(existing)) {
1235
+ throw new AgentServiceError(
1236
+ 'AGENT_RUN_IDEMPOTENCY_CONFLICT',
1237
+ 'The idempotency key is already bound to another agent request.',
1238
+ 409,
1239
+ );
1240
+ }
1241
+ return { runId: existing.id, created: false };
1242
+ }
1243
+ const retained = await this.repository.getAgentRevision(
1244
+ trustedTenantId,
1245
+ agentId,
1246
+ input.revision,
1247
+ );
1248
+ if (!retained) {
1249
+ throw new AgentServiceError(
1250
+ 'AGENT_REVISION_NOT_FOUND',
1251
+ 'Agent revision not found.',
1252
+ 404,
1253
+ );
1254
+ }
1255
+ let currentStatus: AgentDefinition['status'];
1256
+ if (retained.ownership.kind === 'module') {
1257
+ const definition = this.#moduleAgents.get(agentId);
1258
+ const binding = await this.repository.getModuleAgentBinding(
1259
+ trustedTenantId,
1260
+ agentId,
1261
+ );
1262
+ if (!definition || !binding) {
1263
+ throw new AgentServiceError(
1264
+ 'AGENT_REVISION_NOT_FOUND',
1265
+ 'Agent revision not found.',
1266
+ 404,
1267
+ );
1268
+ }
1269
+ const unavailable = await this.#moduleAgentRevisionUnavailableReason(
1270
+ trustedTenantId,
1271
+ retained,
1272
+ binding,
1273
+ );
1274
+ if (unavailable) {
1275
+ throw new AgentServiceError(
1276
+ 'MODULE_AGENT_UNAVAILABLE',
1277
+ unavailable,
1278
+ 409,
1279
+ );
1280
+ }
1281
+ currentStatus = binding.status;
1282
+ } else {
1283
+ const current = await this.repository.getAgent(trustedTenantId, agentId);
1284
+ if (!current) {
1285
+ throw new AgentServiceError(
1286
+ 'AGENT_REVISION_NOT_FOUND',
1287
+ 'Agent revision not found.',
1288
+ 404,
1289
+ );
1290
+ }
1291
+ currentStatus = current.status;
1292
+ }
1293
+ if (
1294
+ (retained.ownership.kind === 'tenant' && retained.status !== 'active') ||
1295
+ currentStatus !== 'active'
1296
+ ) {
1297
+ throw new AgentServiceError(
1298
+ 'AGENT_REVISION_NOT_ACTIVE',
1299
+ 'Only an active retained agent revision can accept workflow runs.',
1300
+ 409,
1301
+ );
1302
+ }
1303
+ const supportsStructuredOutput =
1304
+ (await this.providerService?.supportsStructuredOutput(
1305
+ trustedTenantId,
1306
+ retained.provider,
1307
+ retained.model,
1308
+ )) ?? this.harness.providerSupportsStructuredOutput(retained.provider);
1309
+ if (contract.kind === 'json-schema' && !supportsStructuredOutput) {
1310
+ throw new AgentServiceError(
1311
+ 'STRUCTURED_OUTPUT_UNSUPPORTED',
1312
+ 'The pinned agent model cannot guarantee structured output.',
1313
+ 409,
1314
+ );
1315
+ }
1316
+ this.assertToolsRegistered(retained.allowedTools);
1317
+ const invalidGrant = requestedToolGrants.find(
1318
+ (tool) => !retained.allowedTools.includes(tool),
1319
+ );
1320
+ if (invalidGrant) {
1321
+ throw new AgentServiceError(
1322
+ 'TOOL_NOT_ALLOWED',
1323
+ `Tool ${invalidGrant} is not allowed by the pinned agent revision.`,
1324
+ 403,
1325
+ );
1326
+ }
1327
+ const effectiveToolGrants = this.harness.effectiveToolGrants(
1328
+ retained.allowedTools,
1329
+ requestedToolGrants,
1330
+ permissionSnapshot,
1331
+ );
1332
+ const missingProcedureGrant = retained.procedures
1333
+ .flatMap((procedure) => procedure.requiredTools)
1334
+ .find((tool) => !effectiveToolGrants.includes(tool));
1335
+ if (missingProcedureGrant) {
1336
+ /* The code is a stable API identifier and keeps its procedure-era spelling
1337
+ for the same reason the permission ids do. */
1338
+ throw new AgentServiceError(
1339
+ 'SKILL_TOOL_GRANT_REQUIRED',
1340
+ `Procedure requires permission for tool ${missingProcedureGrant}.`,
1341
+ 403,
1342
+ );
1343
+ }
1344
+ const refusal = await this.budget?.check(
1345
+ trustedTenantId,
1346
+ retained.agentId,
1347
+ this.now(),
1348
+ );
1349
+ if (refusal) {
1350
+ throw new AgentServiceError(refusal.code, refusal.message, 409);
1351
+ }
1352
+ await this.providerService?.ensureUsable(
1353
+ trustedTenantId,
1354
+ retained.provider,
1355
+ retained.model,
1356
+ actor.id,
1357
+ );
1358
+ const resolvedInstructions = resolveTemplate(
1359
+ retained.instructions,
1360
+ agentContextValues({
1361
+ tenantName: trustedTenantId,
1362
+ userDisplayName: actor.label,
1363
+ userEmail: actor.id,
1364
+ now: this.now(),
1365
+ }),
1366
+ );
1367
+ const instructions = [
1368
+ resolvedInstructions,
1369
+ /* Model-facing wording is deliberately unchanged: the rename is a product
1370
+ and API contract change, not a change to what an agent reads. */
1371
+ ...retained.procedures.map(
1372
+ (procedure) =>
1373
+ `\n[Approved procedure: ${procedure.key} revision ${procedure.revision}]\n${procedure.instructions}`,
1374
+ ),
1375
+ ].join('\n');
1376
+ if (instructions.length > 40_000) {
1377
+ throw new AgentServiceError(
1378
+ 'AGENT_INSTRUCTIONS_TOO_LARGE',
1379
+ 'Agent and procedure instructions exceed the 40000 character execution limit.',
1380
+ 409,
1381
+ );
1382
+ }
1383
+ const queuedAt = this.now();
1384
+ const run: AgentRun = {
1385
+ id: randomUUID(),
1386
+ tenantId: trustedTenantId,
1387
+ agentId: retained.agentId,
1388
+ agentName: retained.name,
1389
+ agentRevision: retained.revision,
1390
+ trigger: 'workflow',
1391
+ status: 'queued',
1392
+ input: prompt,
1393
+ output: null,
1394
+ structuredOutput: null,
1395
+ outputContract: contract,
1396
+ workflowRunId,
1397
+ provider: retained.provider,
1398
+ model: retained.model,
1399
+ requestedBy: actor.id,
1400
+ requestedActor: actor,
1401
+ authorizationSubject,
1402
+ permissionSnapshot,
1403
+ toolGrants: requestedToolGrants,
1404
+ procedureSnapshots: retained.procedures.map((procedure) => ({
1405
+ id: procedure.id,
1406
+ key: procedure.key,
1407
+ name: procedure.name,
1408
+ revision: procedure.revision,
1409
+ requiredTools: procedure.requiredTools,
1410
+ })),
1411
+ usage: null,
1412
+ failureCode: null,
1413
+ failureMessage: null,
1414
+ attempt: 0,
1415
+ queuedAt,
1416
+ startedAt: null,
1417
+ completedAt: null,
1418
+ leaseExpiresAt: null,
1419
+ };
1420
+ try {
1421
+ await this.repository.enqueueRun(
1422
+ {
1423
+ run,
1424
+ definition: {
1425
+ id: retained.agentId,
1426
+ name: retained.name,
1427
+ revision: retained.revision,
1428
+ instructions,
1429
+ provider: retained.provider,
1430
+ model: retained.model,
1431
+ allowedTools: retained.allowedTools,
1432
+ maxSteps: retained.maxSteps,
1433
+ timeoutMs: retained.timeoutMs,
1434
+ temperature: retained.temperature,
1435
+ maxOutputTokens: retained.maxOutputTokens,
1436
+ },
1437
+ },
1438
+ idempotencyKey,
1439
+ {
1440
+ tenantId: trustedTenantId,
1441
+ actorId: actor.id,
1442
+ action: 'agent-run.queued',
1443
+ subjectType: 'agent-run',
1444
+ subjectId: run.id,
1445
+ metadata: {
1446
+ agentId: run.agentId,
1447
+ agentRevision: run.agentRevision,
1448
+ trigger: 'workflow',
1449
+ workflowRunId,
1450
+ },
1451
+ occurredAt: queuedAt,
1452
+ },
1453
+ );
1454
+ } catch (error) {
1455
+ if (error instanceof DuplicateRunIdempotencyKeyError) {
1456
+ const raced = await this.repository.findRunByIdempotencyKey(
1457
+ trustedTenantId,
1458
+ idempotencyKey,
1459
+ );
1460
+ if (raced && requestMatches(raced)) {
1461
+ return { runId: raced.id, created: false };
1462
+ }
1463
+ throw new AgentServiceError(
1464
+ 'AGENT_RUN_IDEMPOTENCY_CONFLICT',
1465
+ 'The idempotency key is already bound to another agent request.',
1466
+ 409,
1467
+ );
1468
+ }
1469
+ throw error;
1470
+ }
1471
+ this.worker.kick();
1472
+ return { runId: run.id, created: true };
1473
+ }
1474
+
1475
+ async getWorkflowRun(
1476
+ tenantId: string,
1477
+ workflowRunId: string,
1478
+ runId: string,
1479
+ ): Promise<AgentRunDetail | null> {
1480
+ const run = await this.repository.getRun(
1481
+ bounded(tenantId, 'tenantId', 1, 128),
1482
+ bounded(runId, 'runId', 1, 128),
1483
+ );
1484
+ return run?.workflowRunId ===
1485
+ bounded(workflowRunId, 'workflowRunId', 1, 128)
1486
+ ? run
1487
+ : null;
1488
+ }
1489
+
1490
+ async readWorkflowRunEvents(
1491
+ tenantId: string,
1492
+ workflowRunId: string,
1493
+ runId: string,
1494
+ afterSequence: number,
1495
+ ) {
1496
+ if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
1497
+ throw new AgentServiceError(
1498
+ 'INVALID_EVENT_SEQUENCE',
1499
+ 'afterSequence must be a non-negative integer.',
1500
+ );
1501
+ }
1502
+ const run = await this.getWorkflowRun(tenantId, workflowRunId, runId);
1503
+ return run
1504
+ ? await this.repository.listRunEvents(run.tenantId, run.id, afterSequence)
1505
+ : [];
1506
+ }
1507
+
1508
+ async cancelWorkflowRun(
1509
+ context: {
1510
+ readonly tenantId: string;
1511
+ readonly workflowRunId: string;
1512
+ readonly actor: Actor;
1513
+ readonly permissionSnapshot: readonly string[];
1514
+ },
1515
+ runId: string,
1516
+ ): Promise<boolean> {
1517
+ const actor = normalizeActor(context.actor);
1518
+ if (!actor) {
1519
+ throw new AgentServiceError('INVALID_ACTOR', 'Actor is invalid.');
1520
+ }
1521
+ const run = await this.getWorkflowRun(
1522
+ context.tenantId,
1523
+ context.workflowRunId,
1524
+ runId,
1525
+ );
1526
+ if (!run) return false;
1527
+ if (TERMINAL_STATUSES.includes(run.status)) return false;
1528
+ await this.cancelRun(
1529
+ context.tenantId,
1530
+ actor,
1531
+ context.permissionSnapshot,
1532
+ run.id,
1533
+ );
1534
+ return true;
1535
+ }
1536
+
1537
+ /* The served shape of one run: stored events folded into timeline entries.
1538
+ Every reader goes through here, so the run screen and any other consumer
1539
+ see the same grouping and the same ceiling. */
1540
+ async getRunTimeline(
1541
+ tenantId: string,
1542
+ runId: string,
1543
+ ): Promise<AgentRunTimeline> {
1544
+ const { events, ...run } = await this.getRun(tenantId, runId);
1545
+ return { ...run, timeline: groupRunTimeline(events) };
1546
+ }
1547
+
1548
+ async enqueueRun(
1549
+ tenantId: string,
1550
+ actorInput: Actor | string,
1551
+ permissionSnapshot: readonly string[],
1552
+ input: EnqueueAgentRunInput,
1553
+ context?: AgentRunContext,
1554
+ ): Promise<AgentRun> {
1555
+ return (
1556
+ await this.enqueueRunWithOutcome(
1557
+ tenantId,
1558
+ actorInput,
1559
+ permissionSnapshot,
1560
+ input,
1561
+ context,
1562
+ )
1563
+ ).run;
1564
+ }
1565
+
1566
+ async enqueueRunWithOutcome(
1567
+ tenantId: string,
1568
+ actorInput: Actor | string,
1569
+ permissionSnapshot: readonly string[],
1570
+ input: EnqueueAgentRunInput,
1571
+ context?: AgentRunContext,
1572
+ ): Promise<{ readonly run: AgentRun; readonly created: boolean }> {
1573
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
1574
+ const actor = requestedActor(actorInput);
1575
+ const identity = actor.id;
1576
+ const agentId = bounded(input.agentId, 'agentId', 1, 128);
1577
+ const runTrigger = trigger(input.trigger);
1578
+ const prompt = bounded(input.input, 'input', 1, 100_000);
1579
+ const permissions = [...new Set(permissionSnapshot)].sort();
1580
+ const requestedToolGrants = toolList(input.toolGrants);
1581
+ const idempotencyKey = input.idempotencyKey
1582
+ ? bounded(input.idempotencyKey, 'idempotencyKey', 8, 128)
1583
+ : null;
1584
+ const requestMatches = (candidate: AgentRun): boolean =>
1585
+ candidate.agentId === agentId &&
1586
+ candidate.trigger === runTrigger &&
1587
+ candidate.input === prompt &&
1588
+ candidate.workflowRunId === null &&
1589
+ actorsEqual(candidate.requestedActor, actor) &&
1590
+ JSON.stringify(candidate.permissionSnapshot) ===
1591
+ JSON.stringify(permissions) &&
1592
+ JSON.stringify(candidate.toolGrants) ===
1593
+ JSON.stringify(requestedToolGrants) &&
1594
+ candidate.outputContract.kind === 'text';
1595
+ if (idempotencyKey) {
1596
+ const existing = await this.repository.findRunByIdempotencyKey(
1597
+ trustedTenantId,
1598
+ idempotencyKey,
1599
+ );
1600
+ if (existing) {
1601
+ if (!requestMatches(existing)) {
1602
+ throw new AgentServiceError(
1603
+ 'AGENT_RUN_IDEMPOTENCY_CONFLICT',
1604
+ 'The idempotency key is already bound to another agent request.',
1605
+ 409,
1606
+ );
1607
+ }
1608
+ return { run: existing, created: false };
1609
+ }
1610
+ }
1611
+ const agent = await this.#currentExecutionAgent(trustedTenantId, agentId);
1612
+ if (!agent) {
1613
+ throw new AgentServiceError('AGENT_NOT_FOUND', 'Agent not found.', 404);
1614
+ }
1615
+ if (agent.status !== 'active') {
1616
+ throw new AgentServiceError(
1617
+ 'AGENT_NOT_ACTIVE',
1618
+ 'Only an active agent can accept runs.',
1619
+ 409,
1620
+ );
1621
+ }
1622
+ this.assertToolsRegistered(agent.allowedTools);
1623
+ const skills = await this.resolveSkills(
1624
+ trustedTenantId,
1625
+ agent.procedureIds,
1626
+ true,
1627
+ );
1628
+ this.assertSkillToolsAllowed(skills, agent.allowedTools);
1629
+ const invalidGrant = requestedToolGrants.find(
1630
+ (tool) => !agent.allowedTools.includes(tool),
1631
+ );
1632
+ if (invalidGrant) {
1633
+ throw new AgentServiceError(
1634
+ 'TOOL_NOT_ALLOWED',
1635
+ `Tool ${invalidGrant} is not allowed by the agent definition.`,
1636
+ 403,
1637
+ );
1638
+ }
1639
+ const effectiveToolGrants = this.harness.effectiveToolGrants(
1640
+ agent.allowedTools,
1641
+ requestedToolGrants,
1642
+ permissions,
1643
+ );
1644
+ const missingSkillGrant = skills
1645
+ .flatMap((procedure) => procedure.requiredTools)
1646
+ .find((tool) => !effectiveToolGrants.includes(tool));
1647
+ if (missingSkillGrant) {
1648
+ throw new AgentServiceError(
1649
+ 'SKILL_TOOL_GRANT_REQUIRED',
1650
+ `Skill requires explicit run grant for tool ${missingSkillGrant}.`,
1651
+ 403,
1652
+ );
1653
+ }
1654
+ const refusal = await this.budget?.check(
1655
+ trustedTenantId,
1656
+ agent.id,
1657
+ this.now(),
1658
+ );
1659
+ if (refusal) {
1660
+ throw new AgentServiceError(refusal.code, refusal.message, 409);
1661
+ }
1662
+ await this.providerService?.ensureUsable(
1663
+ trustedTenantId,
1664
+ agent.provider,
1665
+ agent.model,
1666
+ identity,
1667
+ );
1668
+ const procedureSnapshots: readonly AgentProcedureSnapshot[] = skills.map(
1669
+ (procedure) => ({
1670
+ id: procedure.id,
1671
+ key: procedure.key,
1672
+ name: procedure.name,
1673
+ revision: procedure.revision,
1674
+ requiredTools: procedure.requiredTools,
1675
+ }),
1676
+ );
1677
+ /* The stored definition keeps the raw {{ }} template; the run snapshot
1678
+ carries the text resolved against the run context. Unknown tokens are
1679
+ kept verbatim so a later cross-module resolver can fill them. */
1680
+ const resolvedInstructions = resolveTemplate(
1681
+ agent.instructions,
1682
+ agentContextValues({
1683
+ tenantName: context?.tenantName ?? trustedTenantId,
1684
+ userDisplayName: context?.userDisplayName ?? identity,
1685
+ userEmail: context?.userEmail ?? identity,
1686
+ now: this.now(),
1687
+ }),
1688
+ );
1689
+ const instructions = [
1690
+ resolvedInstructions,
1691
+ ...skills.map(
1692
+ (procedure) =>
1693
+ `\n[Approved procedure: ${procedure.key} revision ${procedure.revision}]\n${procedure.instructions}`,
1694
+ ),
1695
+ ].join('\n');
1696
+ if (instructions.length > 40_000) {
1697
+ throw new AgentServiceError(
1698
+ 'AGENT_INSTRUCTIONS_TOO_LARGE',
1699
+ 'Agent and procedure instructions exceed the 40000 character execution limit.',
1700
+ 409,
1701
+ );
1702
+ }
1703
+ const queuedAt = this.now();
1704
+ const run: AgentRun = {
1705
+ id: randomUUID(),
1706
+ tenantId: trustedTenantId,
1707
+ agentId: agent.id,
1708
+ agentName: agent.name,
1709
+ agentRevision: agent.revision,
1710
+ trigger: runTrigger,
1711
+ status: 'queued',
1712
+ input: prompt,
1713
+ output: null,
1714
+ structuredOutput: null,
1715
+ outputContract: { kind: 'text' },
1716
+ workflowRunId: null,
1717
+ provider: agent.provider,
1718
+ model: agent.model,
1719
+ requestedBy: identity,
1720
+ requestedActor: actor,
1721
+ authorizationSubject:
1722
+ actor.kind === 'user'
1723
+ ? actor
1724
+ : actor.kind === 'service'
1725
+ ? actor.configuredBy
1726
+ : null,
1727
+ permissionSnapshot: permissions,
1728
+ toolGrants: requestedToolGrants,
1729
+ procedureSnapshots,
1730
+ usage: null,
1731
+ failureCode: null,
1732
+ failureMessage: null,
1733
+ attempt: 0,
1734
+ queuedAt,
1735
+ startedAt: null,
1736
+ completedAt: null,
1737
+ leaseExpiresAt: null,
1738
+ };
1739
+ let queued: AgentRun;
1740
+ try {
1741
+ queued = await this.repository.enqueueRun(
1742
+ {
1743
+ run,
1744
+ definition: {
1745
+ id: agent.id,
1746
+ name: agent.name,
1747
+ revision: agent.revision,
1748
+ instructions,
1749
+ provider: agent.provider,
1750
+ model: agent.model,
1751
+ allowedTools: agent.allowedTools,
1752
+ maxSteps: agent.maxSteps,
1753
+ timeoutMs: agent.timeoutMs,
1754
+ temperature: agent.temperature,
1755
+ maxOutputTokens: agent.maxOutputTokens,
1756
+ },
1757
+ },
1758
+ idempotencyKey,
1759
+ {
1760
+ tenantId: run.tenantId,
1761
+ actorId: actor.id,
1762
+ action: 'agent-run.queued',
1763
+ subjectType: 'agent-run',
1764
+ subjectId: run.id,
1765
+ metadata: {
1766
+ agentId: run.agentId,
1767
+ agentRevision: run.agentRevision,
1768
+ trigger: run.trigger,
1769
+ },
1770
+ occurredAt: queuedAt,
1771
+ },
1772
+ );
1773
+ } catch (error) {
1774
+ /* Two callers raced past the lookup with the same key; the row that
1775
+ won is the run they both asked for. */
1776
+ if (error instanceof DuplicateRunIdempotencyKeyError && idempotencyKey) {
1777
+ const existing = await this.repository.findRunByIdempotencyKey(
1778
+ trustedTenantId,
1779
+ idempotencyKey,
1780
+ );
1781
+ if (existing && requestMatches(existing)) {
1782
+ return { run: existing, created: false };
1783
+ }
1784
+ throw new AgentServiceError(
1785
+ 'AGENT_RUN_IDEMPOTENCY_CONFLICT',
1786
+ 'The idempotency key is already bound to another agent request.',
1787
+ 409,
1788
+ );
1789
+ }
1790
+ throw error;
1791
+ }
1792
+ this.worker.kick();
1793
+ return { run: queued, created: true };
1794
+ }
1795
+
1796
+ /* The requester may cancel their own run; a definitions manager may cancel
1797
+ any run in the tenant. */
1798
+ async cancelRun(
1799
+ tenantId: string,
1800
+ actorInput: Actor | string,
1801
+ permissionSnapshot: readonly string[],
1802
+ runId: string,
1803
+ ): Promise<AgentRunTimeline> {
1804
+ const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
1805
+ const actor = requestedActor(actorInput);
1806
+ const identity = actor.id;
1807
+ const id = bounded(runId, 'runId', 1, 128);
1808
+ const run = await this.getRun(trustedTenantId, id);
1809
+ if (
1810
+ !actorsEqual(run.requestedActor, actor) &&
1811
+ !permissionSnapshot.includes(AGENT_PERMISSIONS.definitionsManage)
1812
+ ) {
1813
+ throw new AgentServiceError(
1814
+ 'RUN_CANCEL_FORBIDDEN',
1815
+ 'Only the requester or an agent manager can cancel this run.',
1816
+ 403,
1817
+ );
1818
+ }
1819
+ if (TERMINAL_STATUSES.includes(run.status)) {
1820
+ throw new AgentServiceError(
1821
+ 'RUN_ALREADY_FINISHED',
1822
+ `The run already finished with status ${run.status}.`,
1823
+ 409,
1824
+ );
1825
+ }
1826
+ const cancelledAt = this.now();
1827
+ const previousStatus = await this.repository.cancelRun(
1828
+ trustedTenantId,
1829
+ id,
1830
+ `Cancelled by ${identity}.`,
1831
+ cancelledAt,
1832
+ {
1833
+ tenantId: trustedTenantId,
1834
+ actorId: identity,
1835
+ action: 'agent-run.cancelled',
1836
+ subjectType: 'agent-run',
1837
+ subjectId: id,
1838
+ metadata: {},
1839
+ occurredAt: cancelledAt,
1840
+ },
1841
+ );
1842
+ if (previousStatus === null) {
1843
+ throw new AgentServiceError(
1844
+ 'RUN_ALREADY_FINISHED',
1845
+ 'The run finished before it could be cancelled.',
1846
+ 409,
1847
+ );
1848
+ }
1849
+ this.worker.cancel(id);
1850
+ return await this.getRunTimeline(trustedTenantId, id);
1851
+ }
1852
+
1853
+ private async assertProviderConfigured(
1854
+ tenantId: string,
1855
+ providerId: string,
1856
+ modelId: string,
1857
+ ): Promise<void> {
1858
+ if (!this.providerService) {
1859
+ if (!this.harness.providers().includes(providerId)) {
1860
+ throw new AgentServiceError(
1861
+ 'PROVIDER_NOT_AVAILABLE',
1862
+ 'Provider is not registered in the harness.',
1863
+ 409,
1864
+ );
1865
+ }
1866
+ return;
1867
+ }
1868
+ const provider = await this.providerService.get(tenantId, providerId);
1869
+ if (!provider) {
1870
+ throw new AgentServiceError(
1871
+ 'PROVIDER_NOT_AVAILABLE',
1872
+ 'Provider connection is not available in the active tenant.',
1873
+ 409,
1874
+ );
1875
+ }
1876
+ if (
1877
+ !provider.models.some((model) => model.enabled && model.id === modelId)
1878
+ ) {
1879
+ throw new AgentServiceError(
1880
+ 'MODEL_NOT_CONFIGURED',
1881
+ 'Model is not enabled for the selected provider.',
1882
+ 409,
1883
+ );
1884
+ }
1885
+ }
1886
+
1887
+ #refuseModuleAgentMutation(agentId: string): void {
1888
+ if (agentId.startsWith('module-agent:')) {
1889
+ throw new AgentServiceError(
1890
+ 'MODULE_AGENT_READ_ONLY',
1891
+ 'Module-owned behavior is read-only. Update its tenant binding instead.',
1892
+ 409,
1893
+ );
1894
+ }
1895
+ }
1896
+
1897
+ async #moduleAgentUnavailableReason(
1898
+ tenantId: string,
1899
+ definition: ModuleAgentDefinition,
1900
+ binding: ModuleAgentBinding,
1901
+ ): Promise<string | null> {
1902
+ const missingTool = binding.enabledTools.find(
1903
+ (tool) =>
1904
+ !definition.allowedTools.includes(tool) ||
1905
+ !this.harness.tools().includes(tool),
1906
+ );
1907
+ if (missingTool) return `Tool ${missingTool} is not available.`;
1908
+ try {
1909
+ await this.assertProviderConfigured(
1910
+ tenantId,
1911
+ binding.provider,
1912
+ binding.model,
1913
+ );
1914
+ if (binding.status === 'active' && this.providerService) {
1915
+ await this.providerService.assertUsable(
1916
+ tenantId,
1917
+ binding.provider,
1918
+ binding.model,
1919
+ );
1920
+ }
1921
+ return null;
1922
+ } catch (error) {
1923
+ return error instanceof Error
1924
+ ? error.message
1925
+ : 'The provider binding is unavailable.';
1926
+ }
1927
+ }
1928
+
1929
+ /* Exact workflow revisions use their retained provider, model and tools.
1930
+ The mutable binding contributes availability only. Checking its current
1931
+ execution configuration here would silently move an old workflow to a
1932
+ newer binding and break the exact-revision contract. */
1933
+ async #moduleAgentRevisionUnavailableReason(
1934
+ tenantId: string,
1935
+ revision: AgentDefinitionRevision,
1936
+ binding: ModuleAgentBinding,
1937
+ ): Promise<string | null> {
1938
+ const missingTool = revision.allowedTools.find(
1939
+ (tool) => !this.harness.tools().includes(tool),
1940
+ );
1941
+ if (missingTool) return `Tool ${missingTool} is not available.`;
1942
+ try {
1943
+ await this.assertProviderConfigured(
1944
+ tenantId,
1945
+ revision.provider,
1946
+ revision.model,
1947
+ );
1948
+ if (binding.status === 'active' && this.providerService) {
1949
+ await this.providerService.assertUsable(
1950
+ tenantId,
1951
+ revision.provider,
1952
+ revision.model,
1953
+ );
1954
+ }
1955
+ return null;
1956
+ } catch (error) {
1957
+ return error instanceof Error
1958
+ ? error.message
1959
+ : 'The retained module agent revision is unavailable.';
1960
+ }
1961
+ }
1962
+
1963
+ async #moduleAgentView(
1964
+ tenantId: string,
1965
+ definition: ModuleAgentDefinition,
1966
+ binding: ModuleAgentBinding | null,
1967
+ ): Promise<ModuleAgentView> {
1968
+ if (!binding) {
1969
+ return {
1970
+ id: definition.id,
1971
+ tenantId,
1972
+ key: definition.key,
1973
+ name: definition.name,
1974
+ description: definition.description,
1975
+ instructions: definition.instructions,
1976
+ provider: null,
1977
+ model: null,
1978
+ allowedTools: definition.allowedTools,
1979
+ enabledTools: [],
1980
+ limits: definition.limits,
1981
+ status: 'unconfigured',
1982
+ revision: null,
1983
+ bindingRevision: null,
1984
+ unavailableReason: null,
1985
+ ownership: definition.ownership,
1986
+ };
1987
+ }
1988
+ const unavailableReason = await this.#moduleAgentUnavailableReason(
1989
+ tenantId,
1990
+ definition,
1991
+ binding,
1992
+ );
1993
+ return {
1994
+ id: definition.id,
1995
+ tenantId,
1996
+ key: definition.key,
1997
+ name: definition.name,
1998
+ description: definition.description,
1999
+ instructions: definition.instructions,
2000
+ provider: binding.provider,
2001
+ model: binding.model,
2002
+ allowedTools: definition.allowedTools,
2003
+ enabledTools: binding.enabledTools,
2004
+ limits: definition.limits,
2005
+ status: unavailableReason ? 'unavailable' : binding.status,
2006
+ revision: binding.executableRevision,
2007
+ bindingRevision: binding.revision,
2008
+ unavailableReason,
2009
+ ownership: definition.ownership,
2010
+ };
2011
+ }
2012
+
2013
+ async #currentExecutionAgent(
2014
+ tenantId: string,
2015
+ agentId: string,
2016
+ ): Promise<AgentDefinition | null> {
2017
+ const tenantAgent = await this.repository.getAgent(tenantId, agentId);
2018
+ if (tenantAgent) return tenantAgent;
2019
+ const definition = this.#moduleAgents.get(agentId);
2020
+ if (!definition) return null;
2021
+ const binding = await this.repository.getModuleAgentBinding(
2022
+ tenantId,
2023
+ agentId,
2024
+ );
2025
+ if (!binding) {
2026
+ throw new AgentServiceError(
2027
+ 'MODULE_AGENT_UNCONFIGURED',
2028
+ 'Configure a provider and model before running this module agent.',
2029
+ 409,
2030
+ );
2031
+ }
2032
+ const unavailable = await this.#moduleAgentUnavailableReason(
2033
+ tenantId,
2034
+ definition,
2035
+ binding,
2036
+ );
2037
+ if (unavailable) {
2038
+ throw new AgentServiceError('MODULE_AGENT_UNAVAILABLE', unavailable, 409);
2039
+ }
2040
+ return {
2041
+ id: definition.id,
2042
+ tenantId,
2043
+ key: definition.key,
2044
+ name: definition.name,
2045
+ description: definition.description,
2046
+ instructions: definition.instructions,
2047
+ provider: binding.provider,
2048
+ model: binding.model,
2049
+ allowedTools: binding.enabledTools,
2050
+ procedureIds: [],
2051
+ maxSteps: definition.limits.maxSteps,
2052
+ timeoutMs: definition.limits.timeoutMs,
2053
+ temperature: definition.limits.temperature,
2054
+ maxOutputTokens: definition.limits.maxOutputTokens,
2055
+ status: binding.status,
2056
+ revision: binding.executableRevision,
2057
+ createdBy: `module:${definition.moduleId}`,
2058
+ createdAt: binding.updatedAt,
2059
+ updatedBy: binding.updatedBy,
2060
+ updatedAt: binding.updatedAt,
2061
+ };
2062
+ }
2063
+
2064
+ private async requireAgent(
2065
+ tenantId: string,
2066
+ agentId: string,
2067
+ ): Promise<AgentDefinition> {
2068
+ const agent = await this.repository.getAgent(
2069
+ bounded(tenantId, 'tenantId', 1, 128),
2070
+ bounded(agentId, 'agentId', 1, 128),
2071
+ );
2072
+ if (!agent) {
2073
+ throw new AgentServiceError('AGENT_NOT_FOUND', 'Agent not found.', 404);
2074
+ }
2075
+ return agent;
2076
+ }
2077
+
2078
+ private async requireSkill(
2079
+ tenantId: string,
2080
+ procedureId: string,
2081
+ ): Promise<AgentProcedure> {
2082
+ const procedure = await this.repository.getProcedure(
2083
+ bounded(tenantId, 'tenantId', 1, 128),
2084
+ bounded(procedureId, 'procedureId', 1, 128),
2085
+ );
2086
+ if (!procedure) {
2087
+ throw new AgentServiceError(
2088
+ 'SKILL_NOT_FOUND',
2089
+ 'Agent procedure not found.',
2090
+ 404,
2091
+ );
2092
+ }
2093
+ return procedure;
2094
+ }
2095
+
2096
+ private async assertSkillCanArchive(
2097
+ tenantId: string,
2098
+ procedureId: string,
2099
+ ): Promise<void> {
2100
+ const usage = await this.repository.procedureUsage(tenantId, procedureId);
2101
+ if (usage.activeDefinitions > 0) {
2102
+ throw new AgentServiceError(
2103
+ 'SKILL_IN_USE',
2104
+ 'Remove this procedure from every active agent before archiving it.',
2105
+ 409,
2106
+ );
2107
+ }
2108
+ }
2109
+
2110
+ private assertToolsRegistered(tools: readonly string[]): void {
2111
+ const unavailable = tools.find(
2112
+ (tool) => !this.harness.tools().includes(tool),
2113
+ );
2114
+ if (unavailable) {
2115
+ throw new AgentServiceError(
2116
+ 'TOOL_NOT_AVAILABLE',
2117
+ `Tool ${unavailable} is not registered.`,
2118
+ );
2119
+ }
2120
+ }
2121
+
2122
+ private async resolveSkills(
2123
+ tenantId: string,
2124
+ procedureIds: readonly string[],
2125
+ requireActive: boolean,
2126
+ ): Promise<readonly AgentProcedure[]> {
2127
+ const procedures: AgentProcedure[] = [];
2128
+ for (const procedureId of procedureIds) {
2129
+ const procedure = await this.repository.getProcedure(
2130
+ tenantId,
2131
+ procedureId,
2132
+ );
2133
+ if (!procedure) {
2134
+ throw new AgentServiceError(
2135
+ 'SKILL_NOT_FOUND',
2136
+ 'Attached agent procedure was not found in the active tenant.',
2137
+ 409,
2138
+ );
2139
+ }
2140
+ if (requireActive && procedure.status !== 'active') {
2141
+ throw new AgentServiceError(
2142
+ 'SKILL_NOT_ACTIVE',
2143
+ `Skill ${procedure.key} must be active before the agent can run.`,
2144
+ 409,
2145
+ );
2146
+ }
2147
+ procedures.push(procedure);
2148
+ }
2149
+ return procedures;
2150
+ }
2151
+
2152
+ private assertSkillToolsAllowed(
2153
+ skills: readonly AgentProcedure[],
2154
+ allowedTools: readonly string[],
2155
+ ): void {
2156
+ const unavailable = skills
2157
+ .flatMap((procedure) => procedure.requiredTools)
2158
+ .find((tool) => !allowedTools.includes(tool));
2159
+ if (unavailable) {
2160
+ throw new AgentServiceError(
2161
+ 'SKILL_TOOL_NOT_ALLOWED',
2162
+ `Attached procedure requires ${unavailable}, which the agent does not allow.`,
2163
+ 409,
2164
+ );
2165
+ }
2166
+ }
2167
+ }