@open-mercato/ai-assistant 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (273) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +361 -0
  3. package/README.md +5 -0
  4. package/dist/index.js +154 -0
  5. package/dist/index.js.map +2 -2
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js +73 -0
  7. package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js.map +7 -0
  8. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js +484 -0
  9. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js.map +7 -0
  10. package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js +251 -0
  11. package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js.map +7 -0
  12. package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js +91 -0
  13. package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js.map +7 -0
  14. package/dist/modules/ai_assistant/ai-tools/attachments-pack.js +202 -0
  15. package/dist/modules/ai_assistant/ai-tools/attachments-pack.js.map +7 -0
  16. package/dist/modules/ai_assistant/ai-tools/meta-pack.js +121 -0
  17. package/dist/modules/ai_assistant/ai-tools/meta-pack.js.map +7 -0
  18. package/dist/modules/ai_assistant/ai-tools/search-pack.js +94 -0
  19. package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +7 -0
  20. package/dist/modules/ai_assistant/ai-tools.js +14 -0
  21. package/dist/modules/ai_assistant/ai-tools.js.map +7 -0
  22. package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js +175 -0
  23. package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js.map +7 -0
  24. package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js +174 -0
  25. package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js.map +7 -0
  26. package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js +101 -0
  27. package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js.map +7 -0
  28. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js +311 -0
  29. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js.map +7 -0
  30. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js +246 -0
  31. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js.map +7 -0
  32. package/dist/modules/ai_assistant/api/ai/agents/route.js +94 -0
  33. package/dist/modules/ai_assistant/api/ai/agents/route.js.map +7 -0
  34. package/dist/modules/ai_assistant/api/ai/chat/route.js +173 -0
  35. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +7 -0
  36. package/dist/modules/ai_assistant/api/ai/run-object/route.js +167 -0
  37. package/dist/modules/ai_assistant/api/ai/run-object/route.js.map +7 -0
  38. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +1111 -0
  39. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +7 -0
  40. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js +10 -0
  41. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js.map +7 -0
  42. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js +28 -0
  43. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js.map +7 -0
  44. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +10 -0
  45. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +7 -0
  46. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js +30 -0
  47. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js.map +7 -0
  48. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +4 -6
  49. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +2 -2
  50. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +1 -21
  51. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +2 -2
  52. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +462 -0
  53. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +7 -0
  54. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js +10 -0
  55. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js.map +7 -0
  56. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js +28 -0
  57. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js.map +7 -0
  58. package/dist/modules/ai_assistant/cli.js +78 -12
  59. package/dist/modules/ai_assistant/cli.js.map +2 -2
  60. package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js +5 -0
  61. package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js.map +7 -0
  62. package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js +5 -0
  63. package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js.map +7 -0
  64. package/dist/modules/ai_assistant/data/entities/AiPendingAction.js +5 -0
  65. package/dist/modules/ai_assistant/data/entities/AiPendingAction.js.map +7 -0
  66. package/dist/modules/ai_assistant/data/entities.js +228 -0
  67. package/dist/modules/ai_assistant/data/entities.js.map +7 -0
  68. package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js +95 -0
  69. package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js.map +7 -0
  70. package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js +95 -0
  71. package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js.map +7 -0
  72. package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js +223 -0
  73. package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js.map +7 -0
  74. package/dist/modules/ai_assistant/events.js +33 -0
  75. package/dist/modules/ai_assistant/events.js.map +7 -0
  76. package/dist/modules/ai_assistant/i18n/de.json +252 -0
  77. package/dist/modules/ai_assistant/i18n/en.json +252 -0
  78. package/dist/modules/ai_assistant/i18n/es.json +252 -0
  79. package/dist/modules/ai_assistant/i18n/pl.json +252 -0
  80. package/dist/modules/ai_assistant/lib/agent-policy.js +168 -0
  81. package/dist/modules/ai_assistant/lib/agent-policy.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/agent-registry.js +195 -0
  83. package/dist/modules/ai_assistant/lib/agent-registry.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/agent-runtime.js +451 -0
  85. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/agent-tools.js +223 -0
  87. package/dist/modules/ai_assistant/lib/agent-tools.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/agent-transport.js +25 -0
  89. package/dist/modules/ai_assistant/lib/agent-transport.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/ai-agent-definition.js +11 -0
  91. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js +1 -0
  93. package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js +239 -0
  95. package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/ai-overrides.js +189 -0
  97. package/dist/modules/ai_assistant/lib/ai-overrides.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/ai-tool-definition.js +7 -0
  99. package/dist/modules/ai_assistant/lib/ai-tool-definition.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js +1 -0
  101. package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/api-backed-tool.js +48 -0
  103. package/dist/modules/ai_assistant/lib/api-backed-tool.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/attachment-bridge-types.js +1 -0
  105. package/dist/modules/ai_assistant/lib/attachment-bridge-types.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/attachment-parts.js +276 -0
  107. package/dist/modules/ai_assistant/lib/attachment-parts.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/model-factory.js +68 -0
  109. package/dist/modules/ai_assistant/lib/model-factory.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/pending-action-cancel.js +86 -0
  111. package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/pending-action-client.js +35 -0
  113. package/dist/modules/ai_assistant/lib/pending-action-client.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/pending-action-executor.js +243 -0
  115. package/dist/modules/ai_assistant/lib/pending-action-executor.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/pending-action-recheck.js +246 -0
  117. package/dist/modules/ai_assistant/lib/pending-action-recheck.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/pending-action-types.js +70 -0
  119. package/dist/modules/ai_assistant/lib/pending-action-types.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/prepare-mutation.js +315 -0
  121. package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/prompt-composition-types.js +7 -0
  123. package/dist/modules/ai_assistant/lib/prompt-composition-types.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/prompt-override-merge.js +175 -0
  125. package/dist/modules/ai_assistant/lib/prompt-override-merge.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/schema-utils.js +5 -1
  127. package/dist/modules/ai_assistant/lib/schema-utils.js.map +2 -2
  128. package/dist/modules/ai_assistant/lib/tool-executor.js +13 -2
  129. package/dist/modules/ai_assistant/lib/tool-executor.js.map +2 -2
  130. package/dist/modules/ai_assistant/lib/tool-loader.js +86 -11
  131. package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
  132. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js +120 -0
  133. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js.map +7 -0
  134. package/dist/modules/ai_assistant/lib/tool-test-runner.js +418 -0
  135. package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +7 -0
  136. package/dist/modules/ai_assistant/migrations/Migration20260419100521.js +17 -0
  137. package/dist/modules/ai_assistant/migrations/Migration20260419100521.js.map +7 -0
  138. package/dist/modules/ai_assistant/migrations/Migration20260419132948.js +16 -0
  139. package/dist/modules/ai_assistant/migrations/Migration20260419132948.js.map +7 -0
  140. package/dist/modules/ai_assistant/migrations/Migration20260419134235.js +17 -0
  141. package/dist/modules/ai_assistant/migrations/Migration20260419134235.js.map +7 -0
  142. package/dist/modules/ai_assistant/setup.js +36 -0
  143. package/dist/modules/ai_assistant/setup.js.map +2 -2
  144. package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js +161 -0
  145. package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js.map +7 -0
  146. package/generated/entities/ai_agent_mutation_policy_override/index.ts +9 -0
  147. package/generated/entities/ai_agent_prompt_override/index.ts +10 -0
  148. package/generated/entities/ai_pending_action/index.ts +24 -0
  149. package/generated/entities.ids.generated.ts +13 -0
  150. package/generated/entity-fields-registry.ts +57 -0
  151. package/jest.config.cjs +7 -0
  152. package/package.json +4 -4
  153. package/src/index.ts +215 -0
  154. package/src/modules/ai_assistant/__integration__/README.md +5 -0
  155. package/src/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.ts +115 -0
  156. package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.ts +574 -0
  157. package/src/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.ts +333 -0
  158. package/src/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.ts +135 -0
  159. package/src/modules/ai_assistant/__tests__/events.test.ts +145 -0
  160. package/src/modules/ai_assistant/__tests__/integration/pending-action-contract.test.ts +1015 -0
  161. package/src/modules/ai_assistant/__tests__/integration/ws-c-attachment-bridge.test.ts +235 -0
  162. package/src/modules/ai_assistant/__tests__/integration/ws-c-policy-and-tools.test.ts +330 -0
  163. package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +285 -0
  164. package/src/modules/ai_assistant/ai-tools/__tests__/attachments-pack.test.ts +322 -0
  165. package/src/modules/ai_assistant/ai-tools/__tests__/meta-pack.test.ts +218 -0
  166. package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +192 -0
  167. package/src/modules/ai_assistant/ai-tools/attachments-pack.ts +269 -0
  168. package/src/modules/ai_assistant/ai-tools/meta-pack.ts +140 -0
  169. package/src/modules/ai_assistant/ai-tools/search-pack.ts +122 -0
  170. package/src/modules/ai_assistant/ai-tools.ts +21 -0
  171. package/src/modules/ai_assistant/api/ai/actions/[id]/__tests__/route.test.ts +222 -0
  172. package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/__tests__/route.test.ts +286 -0
  173. package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/route.ts +237 -0
  174. package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/__tests__/route.test.ts +339 -0
  175. package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/route.ts +229 -0
  176. package/src/modules/ai_assistant/api/ai/actions/[id]/route.ts +142 -0
  177. package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/__tests__/route.test.ts +367 -0
  178. package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.ts +380 -0
  179. package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/__tests__/route.test.ts +333 -0
  180. package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.ts +307 -0
  181. package/src/modules/ai_assistant/api/ai/agents/route.ts +107 -0
  182. package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +282 -0
  183. package/src/modules/ai_assistant/api/ai/chat/route.ts +207 -0
  184. package/src/modules/ai_assistant/api/ai/run-object/__tests__/route.test.ts +282 -0
  185. package/src/modules/ai_assistant/api/ai/run-object/route.ts +204 -0
  186. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +1419 -0
  187. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.ts +26 -0
  188. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.tsx +12 -0
  189. package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.ts +28 -0
  190. package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +12 -0
  191. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +8 -23
  192. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +15 -10
  193. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +604 -0
  194. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.ts +26 -0
  195. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.tsx +12 -0
  196. package/src/modules/ai_assistant/cli.ts +99 -24
  197. package/src/modules/ai_assistant/data/__tests__/schema-unique-indexes.test.ts +69 -0
  198. package/src/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.ts +7 -0
  199. package/src/modules/ai_assistant/data/entities/AiAgentPromptOverride.ts +7 -0
  200. package/src/modules/ai_assistant/data/entities/AiPendingAction.ts +7 -0
  201. package/src/modules/ai_assistant/data/entities.ts +270 -0
  202. package/src/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.ts +129 -0
  203. package/src/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.ts +132 -0
  204. package/src/modules/ai_assistant/data/repositories/AiPendingActionRepository.ts +334 -0
  205. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentMutationPolicyOverrideRepository.test.ts +195 -0
  206. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentPromptOverrideRepository.test.ts +197 -0
  207. package/src/modules/ai_assistant/data/repositories/__tests__/AiPendingActionRepository.test.ts +357 -0
  208. package/src/modules/ai_assistant/events.ts +112 -0
  209. package/src/modules/ai_assistant/i18n/de.json +252 -0
  210. package/src/modules/ai_assistant/i18n/en.json +252 -0
  211. package/src/modules/ai_assistant/i18n/es.json +252 -0
  212. package/src/modules/ai_assistant/i18n/pl.json +252 -0
  213. package/src/modules/ai_assistant/lib/__tests__/agent-policy.mutation-override.test.ts +203 -0
  214. package/src/modules/ai_assistant/lib/__tests__/agent-policy.test.ts +385 -0
  215. package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +217 -0
  216. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-object.test.ts +329 -0
  217. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-parity.test.ts +573 -0
  218. package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +291 -0
  219. package/src/modules/ai_assistant/lib/__tests__/agent-tools.test.ts +172 -0
  220. package/src/modules/ai_assistant/lib/__tests__/agent-transport.test.ts +41 -0
  221. package/src/modules/ai_assistant/lib/__tests__/ai-agent-definition.test.ts +183 -0
  222. package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +432 -0
  223. package/src/modules/ai_assistant/lib/__tests__/ai-overrides.test.ts +308 -0
  224. package/src/modules/ai_assistant/lib/__tests__/api-backed-tool.test.ts +302 -0
  225. package/src/modules/ai_assistant/lib/__tests__/attachment-bridge-and-prompt-types.test.ts +188 -0
  226. package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +531 -0
  227. package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +263 -0
  228. package/src/modules/ai_assistant/lib/__tests__/model-factory.integration.test.ts +183 -0
  229. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +168 -0
  230. package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +235 -0
  231. package/src/modules/ai_assistant/lib/__tests__/pending-action-client.test.ts +148 -0
  232. package/src/modules/ai_assistant/lib/__tests__/pending-action-executor.test.ts +348 -0
  233. package/src/modules/ai_assistant/lib/__tests__/pending-action-recheck.test.ts +378 -0
  234. package/src/modules/ai_assistant/lib/__tests__/phase-0-additive-contract.test.ts +299 -0
  235. package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +610 -0
  236. package/src/modules/ai_assistant/lib/__tests__/prompt-override-merge.test.ts +136 -0
  237. package/src/modules/ai_assistant/lib/__tests__/tool-loader.test.ts +125 -0
  238. package/src/modules/ai_assistant/lib/agent-policy.ts +270 -0
  239. package/src/modules/ai_assistant/lib/agent-registry.ts +277 -0
  240. package/src/modules/ai_assistant/lib/agent-runtime.ts +751 -0
  241. package/src/modules/ai_assistant/lib/agent-tools.ts +396 -0
  242. package/src/modules/ai_assistant/lib/agent-transport.ts +51 -0
  243. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +86 -0
  244. package/src/modules/ai_assistant/lib/ai-agents-generated.d.ts +18 -0
  245. package/src/modules/ai_assistant/lib/ai-api-operation-runner.ts +333 -0
  246. package/src/modules/ai_assistant/lib/ai-overrides.ts +389 -0
  247. package/src/modules/ai_assistant/lib/ai-tool-definition.ts +7 -0
  248. package/src/modules/ai_assistant/lib/ai-tools-generated.d.ts +7 -0
  249. package/src/modules/ai_assistant/lib/api-backed-tool.ts +85 -0
  250. package/src/modules/ai_assistant/lib/attachment-bridge-types.ts +24 -0
  251. package/src/modules/ai_assistant/lib/attachment-parts.ts +433 -0
  252. package/src/modules/ai_assistant/lib/model-factory.ts +212 -0
  253. package/src/modules/ai_assistant/lib/pending-action-cancel.ts +179 -0
  254. package/src/modules/ai_assistant/lib/pending-action-client.ts +126 -0
  255. package/src/modules/ai_assistant/lib/pending-action-executor.ts +424 -0
  256. package/src/modules/ai_assistant/lib/pending-action-recheck.ts +410 -0
  257. package/src/modules/ai_assistant/lib/pending-action-types.ts +194 -0
  258. package/src/modules/ai_assistant/lib/prepare-mutation.ts +448 -0
  259. package/src/modules/ai_assistant/lib/prompt-composition-types.ts +24 -0
  260. package/src/modules/ai_assistant/lib/prompt-override-merge.ts +253 -0
  261. package/src/modules/ai_assistant/lib/schema-utils.ts +14 -2
  262. package/src/modules/ai_assistant/lib/tool-executor.ts +25 -3
  263. package/src/modules/ai_assistant/lib/tool-loader.ts +159 -13
  264. package/src/modules/ai_assistant/lib/tool-test-fixtures.ts +160 -0
  265. package/src/modules/ai_assistant/lib/tool-test-runner.ts +596 -0
  266. package/src/modules/ai_assistant/lib/types.ts +105 -2
  267. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +871 -0
  268. package/src/modules/ai_assistant/migrations/Migration20260419100521.ts +17 -0
  269. package/src/modules/ai_assistant/migrations/Migration20260419132948.ts +16 -0
  270. package/src/modules/ai_assistant/migrations/Migration20260419134235.ts +17 -0
  271. package/src/modules/ai_assistant/setup.ts +53 -0
  272. package/src/modules/ai_assistant/workers/__tests__/ai-pending-action-cleanup.test.ts +333 -0
  273. package/src/modules/ai_assistant/workers/ai-pending-action-cleanup.ts +269 -0
@@ -0,0 +1,339 @@
1
+ import { z } from 'zod'
2
+
3
+ const authMock = jest.fn()
4
+ const loadAclMock = jest.fn()
5
+ const createRequestContainerMock = jest.fn()
6
+ const repoGetByIdMock = jest.fn()
7
+ const repoSetStatusMock = jest.fn()
8
+ const policyOverrideGetMock = jest.fn()
9
+ const loadAgentRegistryMock = jest.fn()
10
+ const loadAllModuleToolsMock = jest.fn()
11
+ const getAgentMock = jest.fn()
12
+ const getToolMock = jest.fn()
13
+ const attachmentFindMock = jest.fn()
14
+
15
+ jest.mock('@open-mercato/shared/lib/auth/server', () => ({
16
+ getAuthFromRequest: (...args: unknown[]) => authMock(...args),
17
+ }))
18
+
19
+ jest.mock('@open-mercato/shared/lib/di/container', () => ({
20
+ createRequestContainer: (...args: unknown[]) => createRequestContainerMock(...args),
21
+ }))
22
+
23
+ jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
24
+ findWithDecryption: (...args: unknown[]) => attachmentFindMock(...args),
25
+ findOneWithDecryption: jest.fn(),
26
+ }))
27
+
28
+ jest.mock('@open-mercato/core/modules/attachments/data/entities', () => ({
29
+ Attachment: class Attachment {},
30
+ AttachmentPartition: class AttachmentPartition {},
31
+ }))
32
+
33
+ jest.mock('../../../../../../data/repositories/AiPendingActionRepository', () => ({
34
+ AiPendingActionRepository: jest.fn().mockImplementation(() => ({
35
+ getById: repoGetByIdMock,
36
+ setStatus: repoSetStatusMock,
37
+ })),
38
+ }))
39
+
40
+ jest.mock('../../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository', () => ({
41
+ AiAgentMutationPolicyOverrideRepository: jest.fn().mockImplementation(() => ({
42
+ get: policyOverrideGetMock,
43
+ })),
44
+ }))
45
+
46
+ jest.mock('../../../../../../lib/agent-registry', () => ({
47
+ getAgent: (...args: unknown[]) => getAgentMock(...args),
48
+ loadAgentRegistry: (...args: unknown[]) => loadAgentRegistryMock(...args),
49
+ }))
50
+
51
+ jest.mock('../../../../../../lib/tool-registry', () => ({
52
+ toolRegistry: {
53
+ getTool: (...args: unknown[]) => getToolMock(...args),
54
+ },
55
+ }))
56
+
57
+ jest.mock('../../../../../../lib/tool-loader', () => ({
58
+ loadAllModuleTools: (...args: unknown[]) => loadAllModuleToolsMock(...args),
59
+ }))
60
+
61
+ import { POST } from '../route'
62
+
63
+ function buildRequest(): Request {
64
+ return new Request('http://localhost/api/ai_assistant/ai/actions/pa_123/confirm', {
65
+ method: 'POST',
66
+ headers: { 'content-type': 'application/json' },
67
+ })
68
+ }
69
+
70
+ function buildContext(id: string) {
71
+ return { params: Promise.resolve({ id }) }
72
+ }
73
+
74
+ function makeRow(overrides: Record<string, unknown> = {}) {
75
+ return {
76
+ id: 'pa_123',
77
+ tenantId: 'tenant-1',
78
+ organizationId: 'org-1',
79
+ agentId: 'catalog.merchandising_assistant',
80
+ toolName: 'catalog.update_product',
81
+ status: 'pending',
82
+ fieldDiff: [{ field: 'title', before: 'Old', after: 'New' }],
83
+ records: null,
84
+ failedRecords: null,
85
+ sideEffectsSummary: null,
86
+ recordVersion: 'v-1',
87
+ attachmentIds: [],
88
+ normalizedInput: { productId: 'p-1', patch: { title: 'New' } },
89
+ queueMode: 'inline',
90
+ executionResult: null,
91
+ targetEntityType: 'product',
92
+ targetRecordId: 'p-1',
93
+ conversationId: null,
94
+ idempotencyKey: 'idem_1',
95
+ createdByUserId: 'user-1',
96
+ createdAt: new Date(Date.now() - 60_000),
97
+ expiresAt: new Date(Date.now() + 3_600_000),
98
+ resolvedAt: null,
99
+ resolvedByUserId: null,
100
+ ...overrides,
101
+ }
102
+ }
103
+
104
+ function makeAgent(overrides: Record<string, unknown> = {}) {
105
+ return {
106
+ id: 'catalog.merchandising_assistant',
107
+ moduleId: 'catalog',
108
+ label: 'Catalog',
109
+ description: '...',
110
+ systemPrompt: '...',
111
+ allowedTools: ['catalog.update_product'],
112
+ readOnly: false,
113
+ mutationPolicy: 'confirm-required',
114
+ ...overrides,
115
+ }
116
+ }
117
+
118
+ function makeTool(overrides: Record<string, unknown> = {}) {
119
+ return {
120
+ name: 'catalog.update_product',
121
+ description: 'Update product',
122
+ inputSchema: z.object({
123
+ productId: z.string(),
124
+ patch: z.object({ title: z.string() }).partial(),
125
+ }),
126
+ handler: jest.fn().mockResolvedValue({ recordId: 'p-1', commandName: 'catalog.product.update' }),
127
+ isMutation: true,
128
+ loadBeforeRecord: jest.fn().mockResolvedValue({
129
+ recordId: 'p-1',
130
+ entityType: 'catalog.product',
131
+ recordVersion: 'v-1',
132
+ before: { title: 'Old' },
133
+ }),
134
+ ...overrides,
135
+ }
136
+ }
137
+
138
+ describe('POST /api/ai/actions/:id/confirm route (Step 5.8)', () => {
139
+ let consoleErrorSpy: jest.SpyInstance
140
+
141
+ beforeEach(() => {
142
+ jest.clearAllMocks()
143
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
144
+
145
+ authMock.mockResolvedValue({
146
+ sub: 'user-1',
147
+ tenantId: 'tenant-1',
148
+ orgId: 'org-1',
149
+ })
150
+ loadAclMock.mockResolvedValue({
151
+ features: ['ai_assistant.view', 'catalog.view', 'catalog.manage'],
152
+ isSuperAdmin: false,
153
+ })
154
+ createRequestContainerMock.mockResolvedValue({
155
+ resolve: (name: string) => {
156
+ if (name === 'rbacService') {
157
+ return {
158
+ loadAcl: loadAclMock,
159
+ hasAllFeatures: (required: string[], granted: string[]) =>
160
+ required.every((feature) => granted.includes(feature)),
161
+ }
162
+ }
163
+ if (name === 'em') return {}
164
+ if (name === 'eventBus') return { emitEvent: jest.fn().mockResolvedValue(undefined) }
165
+ return null
166
+ },
167
+ })
168
+ policyOverrideGetMock.mockResolvedValue(null)
169
+ loadAgentRegistryMock.mockResolvedValue(undefined)
170
+ loadAllModuleToolsMock.mockResolvedValue(undefined)
171
+ getAgentMock.mockReturnValue(makeAgent())
172
+ getToolMock.mockReturnValue(makeTool())
173
+ attachmentFindMock.mockResolvedValue([])
174
+
175
+ repoSetStatusMock.mockImplementation(async (id: string, status: string, _scope: unknown, extra?: any) => {
176
+ return { ...makeRow({ id, status }), ...(extra?.executionResult ? { executionResult: extra.executionResult } : {}) }
177
+ })
178
+ })
179
+
180
+ afterEach(() => {
181
+ consoleErrorSpy.mockRestore()
182
+ })
183
+
184
+ it('returns 401 when unauthenticated', async () => {
185
+ authMock.mockResolvedValueOnce(null)
186
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
187
+ expect(response.status).toBe(401)
188
+ })
189
+
190
+ it('happy path: returns 200 with pendingAction.status === confirmed', async () => {
191
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
192
+
193
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
194
+ expect(response.status).toBe(200)
195
+ const body = await response.json()
196
+ expect(body.ok).toBe(true)
197
+ expect(body.pendingAction.status).toBe('confirmed')
198
+ expect(body.mutationResult).toEqual({ recordId: 'p-1', commandName: 'catalog.product.update' })
199
+ })
200
+
201
+ it('409 invalid_status: already cancelled', async () => {
202
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ status: 'cancelled' }))
203
+
204
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
205
+ expect(response.status).toBe(409)
206
+ const body = await response.json()
207
+ expect(body.code).toBe('invalid_status')
208
+ })
209
+
210
+ it('409 expired: expiresAt in the past', async () => {
211
+ repoGetByIdMock.mockResolvedValueOnce(
212
+ makeRow({ expiresAt: new Date('2020-01-01T00:00:00.000Z') }),
213
+ )
214
+
215
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
216
+ expect(response.status).toBe(409)
217
+ const body = await response.json()
218
+ expect(body.code).toBe('expired')
219
+ })
220
+
221
+ it('412 stale_version: record version drift on single-record action', async () => {
222
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ recordVersion: 'v-1' }))
223
+ getToolMock.mockReturnValueOnce(
224
+ makeTool({
225
+ loadBeforeRecord: jest.fn().mockResolvedValue({
226
+ recordId: 'p-1',
227
+ entityType: 'catalog.product',
228
+ recordVersion: 'v-2',
229
+ before: { title: 'x' },
230
+ }),
231
+ }),
232
+ )
233
+
234
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
235
+ expect(response.status).toBe(412)
236
+ const body = await response.json()
237
+ expect(body.code).toBe('stale_version')
238
+ })
239
+
240
+ it('403 read_only_agent when policy override downgrades to read-only', async () => {
241
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
242
+ policyOverrideGetMock.mockResolvedValueOnce({ mutationPolicy: 'read-only' })
243
+
244
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
245
+ expect(response.status).toBe(403)
246
+ const body = await response.json()
247
+ expect(body.code).toBe('read_only_agent')
248
+ })
249
+
250
+ it('403 tool_not_whitelisted when agent no longer whitelists the tool', async () => {
251
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
252
+ getAgentMock.mockReturnValueOnce(makeAgent({ allowedTools: [] }))
253
+
254
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
255
+ expect(response.status).toBe(403)
256
+ const body = await response.json()
257
+ expect(body.code).toBe('tool_not_whitelisted')
258
+ })
259
+
260
+ it('403 agent_features_denied when caller lacks agent features', async () => {
261
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
262
+ getAgentMock.mockReturnValueOnce(makeAgent({ requiredFeatures: ['catalog.restricted.manage'] }))
263
+
264
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
265
+ expect(response.status).toBe(403)
266
+ const body = await response.json()
267
+ expect(body.code).toBe('agent_features_denied')
268
+ })
269
+
270
+ it('403 attachment_cross_tenant when an attachment belongs to a different tenant', async () => {
271
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ attachmentIds: ['a-1'] }))
272
+ attachmentFindMock.mockResolvedValueOnce([
273
+ { id: 'a-1', tenantId: 'tenant-OTHER', organizationId: 'org-OTHER' },
274
+ ])
275
+
276
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
277
+ expect(response.status).toBe(403)
278
+ const body = await response.json()
279
+ expect(body.code).toBe('attachment_cross_tenant')
280
+ })
281
+
282
+ it('404 agent_unknown when agent has been removed from the registry', async () => {
283
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
284
+ getAgentMock.mockReturnValueOnce(undefined)
285
+
286
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
287
+ expect(response.status).toBe(404)
288
+ const body = await response.json()
289
+ expect(body.code).toBe('agent_unknown')
290
+ })
291
+
292
+ it('404 pending_action_not_found when the row does not exist for the caller', async () => {
293
+ repoGetByIdMock.mockResolvedValueOnce(null)
294
+
295
+ const response = await POST(buildRequest() as any, buildContext('pa_missing'))
296
+ expect(response.status).toBe(404)
297
+ const body = await response.json()
298
+ expect(body.code).toBe('pending_action_not_found')
299
+ })
300
+
301
+ it('403 forbidden when caller lacks ai_assistant.view', async () => {
302
+ loadAclMock.mockResolvedValueOnce({ features: ['catalog.view'], isSuperAdmin: false })
303
+
304
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
305
+ expect(response.status).toBe(403)
306
+ const body = await response.json()
307
+ expect(body.code).toBe('forbidden')
308
+ })
309
+
310
+ it('idempotent: second confirm on already-confirmed row returns 200 + prior executionResult without invoking handler', async () => {
311
+ const priorResult = { recordId: 'p-1', commandName: 'catalog.product.update' }
312
+ const priorRow = makeRow({
313
+ status: 'confirmed',
314
+ executionResult: priorResult,
315
+ resolvedAt: new Date('2026-04-18T10:30:00.000Z'),
316
+ resolvedByUserId: 'user-1',
317
+ })
318
+ repoGetByIdMock.mockResolvedValueOnce(priorRow)
319
+ const handlerSpy = jest.fn()
320
+ getToolMock.mockReturnValueOnce(makeTool({ handler: handlerSpy }))
321
+
322
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
323
+ expect(response.status).toBe(200)
324
+ const body = await response.json()
325
+ expect(body.ok).toBe(true)
326
+ expect(body.mutationResult).toEqual(priorResult)
327
+ expect(handlerSpy).not.toHaveBeenCalled()
328
+ expect(repoSetStatusMock).not.toHaveBeenCalled()
329
+ })
330
+
331
+ it('500 confirm_internal_error when the repo throws unexpectedly', async () => {
332
+ repoGetByIdMock.mockRejectedValueOnce(new Error('db down'))
333
+
334
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
335
+ expect(response.status).toBe(500)
336
+ const body = await response.json()
337
+ expect(body.code).toBe('confirm_internal_error')
338
+ })
339
+ })
@@ -0,0 +1,229 @@
1
+ import { NextResponse, type NextRequest } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
5
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
7
+ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
8
+ import { AiPendingActionRepository } from '../../../../../data/repositories/AiPendingActionRepository'
9
+ import { hasRequiredFeatures } from '../../../../../lib/auth'
10
+ import { serializePendingActionForClient } from '../../../../../lib/pending-action-client'
11
+ import { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'
12
+ import { toolRegistry } from '../../../../../lib/tool-registry'
13
+ import { loadAllModuleTools } from '../../../../../lib/tool-loader'
14
+ import type { AiToolDefinition } from '../../../../../lib/types'
15
+ import {
16
+ runPendingActionRechecks,
17
+ type PendingActionRecheckResult,
18
+ } from '../../../../../lib/pending-action-recheck'
19
+ import {
20
+ executePendingActionConfirm,
21
+ type PendingActionExecuteContext,
22
+ } from '../../../../../lib/pending-action-executor'
23
+ import { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'
24
+ import type { AiAgentMutationPolicy } from '../../../../../lib/ai-agent-definition'
25
+ import { isKnownMutationPolicy } from '../../../../../lib/agent-policy'
26
+
27
+ /**
28
+ * POST `/api/ai/actions/:id/confirm` — mutation approval gate confirm
29
+ * endpoint (spec §9.4, Step 5.8).
30
+ *
31
+ * Invariant: every check from spec §9.4 is re-run on the server before the
32
+ * tool handler executes. The original model-proposed tool arguments are
33
+ * re-parsed through the CURRENT tool `inputSchema` so that a schema
34
+ * migration between propose and confirm surfaces as `schema_drift`
35
+ * (412) instead of silently passing through invalid data.
36
+ *
37
+ * Idempotency: calling this endpoint twice on the same pending-action id
38
+ * returns the prior execution result without re-invoking the handler.
39
+ */
40
+
41
+ const REQUIRED_FEATURE = 'ai_assistant.view'
42
+
43
+ const idParamSchema = z.object({
44
+ id: z
45
+ .string()
46
+ .trim()
47
+ .min(1, 'id must be a non-empty string')
48
+ .max(128, 'id exceeds the maximum length of 128 characters'),
49
+ })
50
+
51
+ export const openApi: OpenApiRouteDoc = {
52
+ tag: 'AI Assistant',
53
+ summary: 'Pending action (mutation approval gate) confirm',
54
+ methods: {
55
+ POST: {
56
+ operationId: 'aiAssistantConfirmPendingAction',
57
+ summary: 'Confirm an AI pending action, re-running every server-side check before execution.',
58
+ description:
59
+ 'Re-verifies the full contract from spec §9.4 (status, expiry, agent registration, ' +
60
+ 'required features, mutation policy, tool whitelist, attachment tenant scope, record ' +
61
+ 'version, and schema drift), flips the pending-action state machine to `executing`, ' +
62
+ 'invokes the wrapped tool handler, and persists `executionResult`. Idempotent: a ' +
63
+ 'second call on a row already in `confirmed` state returns the prior result without ' +
64
+ 're-executing the handler.',
65
+ responses: [
66
+ {
67
+ status: 200,
68
+ description: 'Confirmation complete; body includes the serialized pending action and the mutation result.',
69
+ mediaType: 'application/json',
70
+ },
71
+ ],
72
+ errors: [
73
+ { status: 401, description: 'Unauthenticated caller.' },
74
+ { status: 403, description: 'Caller lacks `ai_assistant.view`, a required agent feature, tool whitelist, or accesses attachments outside their tenant.' },
75
+ { status: 404, description: 'Pending action or agent not found in the caller scope.' },
76
+ { status: 409, description: 'Pending action is not in `pending` status or has expired.' },
77
+ { status: 412, description: 'Record version changed between propose and confirm, or the input schema no longer accepts the stored payload.' },
78
+ { status: 500, description: 'Unexpected server failure during confirm.' },
79
+ ],
80
+ },
81
+ },
82
+ }
83
+
84
+ export const metadata = {
85
+ POST: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },
86
+ }
87
+
88
+ interface RouteContext {
89
+ params: Promise<{ id: string }>
90
+ }
91
+
92
+ function jsonError(
93
+ status: number,
94
+ message: string,
95
+ code: string,
96
+ extra?: Record<string, unknown>,
97
+ ): NextResponse {
98
+ return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })
99
+ }
100
+
101
+ function fromRecheckFailure(result: PendingActionRecheckResult & { ok: false }): NextResponse {
102
+ return jsonError(result.status, result.message, result.code, result.extra)
103
+ }
104
+
105
+ export async function POST(req: NextRequest, context: RouteContext): Promise<Response> {
106
+ const auth = await getAuthFromRequest(req)
107
+ if (!auth) {
108
+ return jsonError(401, 'Unauthorized', 'unauthenticated')
109
+ }
110
+
111
+ const rawParams = await context.params
112
+ const paramResult = idParamSchema.safeParse(rawParams)
113
+ if (!paramResult.success) {
114
+ return jsonError(400, 'Invalid pending action id.', 'validation_error', {
115
+ issues: paramResult.error.issues,
116
+ })
117
+ }
118
+ const pendingActionId = paramResult.data.id
119
+
120
+ try {
121
+ const container = await createRequestContainer()
122
+ const rbacService = container.resolve<RbacService>('rbacService')
123
+ const acl = await rbacService.loadAcl(auth.sub, {
124
+ tenantId: auth.tenantId,
125
+ organizationId: auth.orgId,
126
+ })
127
+
128
+ if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {
129
+ return jsonError(403, `Caller lacks required feature "${REQUIRED_FEATURE}".`, 'forbidden')
130
+ }
131
+
132
+ if (!auth.tenantId) {
133
+ return jsonError(
134
+ 404,
135
+ `No pending action "${pendingActionId}" accessible to the caller.`,
136
+ 'pending_action_not_found',
137
+ )
138
+ }
139
+
140
+ const em = container.resolve<EntityManager>('em')
141
+ const repo = new AiPendingActionRepository(em)
142
+ const row = await repo.getById(pendingActionId, {
143
+ tenantId: auth.tenantId,
144
+ organizationId: auth.orgId ?? null,
145
+ userId: auth.sub,
146
+ })
147
+ if (!row) {
148
+ return jsonError(
149
+ 404,
150
+ `No pending action "${pendingActionId}" accessible to the caller.`,
151
+ 'pending_action_not_found',
152
+ )
153
+ }
154
+
155
+ // Idempotency short-circuit — a prior confirm already ran.
156
+ if (row.status === 'confirmed' || row.status === 'failed') {
157
+ const executionResult = row.executionResult ?? null
158
+ return NextResponse.json({
159
+ ok: row.status === 'confirmed',
160
+ pendingAction: serializePendingActionForClient(row),
161
+ mutationResult: executionResult,
162
+ })
163
+ }
164
+
165
+ await loadAgentRegistry()
166
+ await loadAllModuleTools()
167
+ const agent = getAgent(row.agentId)
168
+ const tool = toolRegistry.getTool(row.toolName) as AiToolDefinition | undefined
169
+
170
+ const policyOverrideRepo = new AiAgentMutationPolicyOverrideRepository(em)
171
+ const overrideRow = await policyOverrideRepo.get(row.agentId, {
172
+ tenantId: auth.tenantId,
173
+ organizationId: auth.orgId ?? null,
174
+ })
175
+ const rawOverridePolicy = overrideRow?.mutationPolicy ?? null
176
+ const mutationPolicyOverride: AiAgentMutationPolicy | null =
177
+ rawOverridePolicy && isKnownMutationPolicy(rawOverridePolicy) ? rawOverridePolicy : null
178
+
179
+ const recheckResult = await runPendingActionRechecks({
180
+ action: row,
181
+ agent,
182
+ tool,
183
+ ctx: {
184
+ tenantId: auth.tenantId,
185
+ organizationId: auth.orgId ?? null,
186
+ userId: auth.sub,
187
+ userFeatures: acl.features,
188
+ isSuperAdmin: acl.isSuperAdmin,
189
+ container,
190
+ em,
191
+ },
192
+ mutationPolicyOverride,
193
+ })
194
+ if (!recheckResult.ok) {
195
+ return fromRecheckFailure(recheckResult)
196
+ }
197
+
198
+ const executeCtx: PendingActionExecuteContext = {
199
+ tenantId: auth.tenantId,
200
+ organizationId: auth.orgId ?? null,
201
+ userId: auth.sub,
202
+ userFeatures: acl.features,
203
+ isSuperAdmin: acl.isSuperAdmin,
204
+ container,
205
+ }
206
+
207
+ const executed = await executePendingActionConfirm({
208
+ action: row,
209
+ agent: agent!,
210
+ tool: tool!,
211
+ ctx: executeCtx,
212
+ repo,
213
+ failedRecords: recheckResult.failedRecords ?? null,
214
+ })
215
+
216
+ return NextResponse.json({
217
+ ok: executed.ok,
218
+ pendingAction: serializePendingActionForClient(executed.action),
219
+ mutationResult: executed.executionResult,
220
+ })
221
+ } catch (error) {
222
+ console.error('[AI Pending Action CONFIRM] Failure:', error)
223
+ return jsonError(
224
+ 500,
225
+ error instanceof Error ? error.message : 'Failed to confirm pending action.',
226
+ 'confirm_internal_error',
227
+ )
228
+ }
229
+ }