@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,17 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260419100521_ai_assistant extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`create table "ai_agent_prompt_overrides" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid null, "agent_id" text not null, "version" int not null, "sections" jsonb not null, "notes" text null, "created_by_user_id" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "ai_agent_prompt_overrides_pkey" primary key ("id"));`);
7
+ this.addSql(`create unique index "ai_agent_prompt_overrides_tenant_org_agent_version_uq" on "ai_agent_prompt_overrides" ("tenant_id", "organization_id", "agent_id", "version") where "organization_id" is not null;`);
8
+ this.addSql(`create unique index "ai_agent_prompt_overrides_tenant_agent_version_null_org_uq" on "ai_agent_prompt_overrides" ("tenant_id", "agent_id", "version") where "organization_id" is null;`);
9
+ this.addSql(`create index "ai_agent_prompt_overrides_tenant_org_agent_version_idx" on "ai_agent_prompt_overrides" ("tenant_id", "organization_id", "agent_id", "version" desc);`);
10
+ this.addSql(`create index "ai_agent_prompt_overrides_tenant_agent_idx" on "ai_agent_prompt_overrides" ("tenant_id", "agent_id");`);
11
+ }
12
+
13
+ override async down(): Promise<void> {
14
+ this.addSql(`drop table if exists "ai_agent_prompt_overrides" cascade;`);
15
+ }
16
+
17
+ }
@@ -0,0 +1,16 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260419132948_ai_assistant extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`create table "ai_agent_mutation_policy_overrides" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid null, "agent_id" text not null, "mutation_policy" text not null, "notes" text null, "created_by_user_id" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "ai_agent_mutation_policy_overrides_pkey" primary key ("id"));`);
7
+ this.addSql(`create unique index "ai_agent_mutation_policy_overrides_tenant_org_agent_uq" on "ai_agent_mutation_policy_overrides" ("tenant_id", "organization_id", "agent_id") where "organization_id" is not null;`);
8
+ this.addSql(`create unique index "ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq" on "ai_agent_mutation_policy_overrides" ("tenant_id", "agent_id") where "organization_id" is null;`);
9
+ this.addSql(`create index "ai_agent_mutation_policy_overrides_tenant_agent_idx" on "ai_agent_mutation_policy_overrides" ("tenant_id", "agent_id");`);
10
+ }
11
+
12
+ override async down(): Promise<void> {
13
+ this.addSql(`drop table if exists "ai_agent_mutation_policy_overrides" cascade;`);
14
+ }
15
+
16
+ }
@@ -0,0 +1,17 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260419134235_ai_assistant extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`create table "ai_pending_actions" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid null, "agent_id" text not null, "tool_name" text not null, "conversation_id" text null, "target_entity_type" text null, "target_record_id" text null, "normalized_input" jsonb not null, "field_diff" jsonb not null default '[]', "records" jsonb null, "failed_records" jsonb null, "side_effects_summary" text null, "record_version" text null, "attachment_ids" jsonb not null default '[]', "idempotency_key" text not null, "created_by_user_id" uuid not null, "status" text not null, "queue_mode" text not null default 'inline', "execution_result" jsonb null, "created_at" timestamptz not null, "expires_at" timestamptz not null, "resolved_at" timestamptz null, "resolved_by_user_id" uuid null, constraint "ai_pending_actions_pkey" primary key ("id"));`);
7
+ this.addSql(`create unique index "ai_pending_actions_tenant_org_idempotency_uq" on "ai_pending_actions" ("tenant_id", "organization_id", "idempotency_key") where "organization_id" is not null;`);
8
+ this.addSql(`create unique index "ai_pending_actions_tenant_idem_null_org_uq" on "ai_pending_actions" ("tenant_id", "idempotency_key") where "organization_id" is null;`);
9
+ this.addSql(`create index "ai_pending_actions_tenant_org_agent_status_idx" on "ai_pending_actions" ("tenant_id", "organization_id", "agent_id", "status");`);
10
+ this.addSql(`create index "ai_pending_actions_tenant_org_status_expires_idx" on "ai_pending_actions" ("tenant_id", "organization_id", "status", "expires_at");`);
11
+ }
12
+
13
+ override async down(): Promise<void> {
14
+ this.addSql(`drop table if exists "ai_pending_actions" cascade;`);
15
+ }
16
+
17
+ }
@@ -1,5 +1,54 @@
1
1
  import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
2
 
3
+ const PENDING_ACTION_CLEANUP_SCHEDULE_ID = 'ai_assistant:pending-action-cleanup'
4
+
5
+ /**
6
+ * System-scoped recurring schedule: every 5 minutes, enqueue a job to the
7
+ * `ai-pending-action-cleanup` queue so the worker can sweep rows whose TTL
8
+ * elapsed without any confirm/cancel activity (Step 5.12). The schedule id
9
+ * is stable and `scheduler.register()` is an upsert, so calling this from
10
+ * every tenant bootstrap stays idempotent.
11
+ */
12
+ async function ensurePendingActionCleanupSchedule(
13
+ container: import('awilix').AwilixContainer | undefined,
14
+ ): Promise<void> {
15
+ if (!container) return
16
+ let schedulerService:
17
+ | {
18
+ register: (registration: Record<string, unknown>) => Promise<void>
19
+ }
20
+ | undefined
21
+ try {
22
+ schedulerService = container.resolve('schedulerService')
23
+ } catch {
24
+ schedulerService = undefined
25
+ }
26
+ if (!schedulerService) return
27
+ try {
28
+ await schedulerService.register({
29
+ id: PENDING_ACTION_CLEANUP_SCHEDULE_ID,
30
+ name: 'AI pending-action cleanup',
31
+ description:
32
+ 'Sweep pending AI mutation approvals whose TTL elapsed without confirm/cancel and flip them to expired.',
33
+ scopeType: 'system',
34
+ scheduleType: 'interval',
35
+ scheduleValue: '5m',
36
+ timezone: 'UTC',
37
+ targetType: 'queue',
38
+ targetQueue: 'ai-pending-action-cleanup',
39
+ targetPayload: {},
40
+ sourceType: 'module',
41
+ sourceModule: 'ai_assistant',
42
+ isEnabled: true,
43
+ })
44
+ } catch (error) {
45
+ console.warn(
46
+ '[ai_assistant] Failed to register pending-action cleanup schedule:',
47
+ error instanceof Error ? error.message : error,
48
+ )
49
+ }
50
+ }
51
+
3
52
  export const setup: ModuleSetupConfig = {
4
53
  defaultRoleFeatures: {
5
54
  admin: [
@@ -12,6 +61,10 @@ export const setup: ModuleSetupConfig = {
12
61
  ],
13
62
  employee: ['ai_assistant.view'],
14
63
  },
64
+
65
+ async seedDefaults({ container }) {
66
+ await ensurePendingActionCleanupSchedule(container)
67
+ },
15
68
  }
16
69
 
17
70
  export default setup
@@ -0,0 +1,333 @@
1
+ import { runPendingActionCleanup } from '../ai-pending-action-cleanup'
2
+ import type { AiPendingAction } from '../../data/entities'
3
+ import {
4
+ AiPendingActionStateError,
5
+ type AiPendingActionStatus,
6
+ } from '../../lib/pending-action-types'
7
+
8
+ type Row = AiPendingAction & Record<string, unknown>
9
+
10
+ function makeRow(overrides: Partial<Row> = {}): Row {
11
+ return {
12
+ id: overrides.id ?? 'pa_1',
13
+ tenantId: overrides.tenantId ?? 'tenant-alpha',
14
+ organizationId: (overrides as { organizationId?: string | null }).organizationId ?? 'org-alpha',
15
+ agentId: overrides.agentId ?? 'catalog.merchandising_assistant',
16
+ toolName: overrides.toolName ?? 'catalog.update_product',
17
+ status: (overrides.status ?? 'pending') as AiPendingActionStatus,
18
+ fieldDiff: [],
19
+ records: null,
20
+ failedRecords: null,
21
+ sideEffectsSummary: null,
22
+ recordVersion: 'v-1',
23
+ attachmentIds: [],
24
+ normalizedInput: {},
25
+ queueMode: 'inline',
26
+ executionResult: null,
27
+ targetEntityType: 'product',
28
+ targetRecordId: 'p-1',
29
+ conversationId: null,
30
+ idempotencyKey: overrides.idempotencyKey ?? 'idem-1',
31
+ createdByUserId: 'user-1',
32
+ createdAt: overrides.createdAt ?? new Date('2026-04-18T09:00:00.000Z'),
33
+ expiresAt:
34
+ (overrides as { expiresAt?: Date }).expiresAt ?? new Date('2026-04-18T09:15:00.000Z'),
35
+ resolvedAt: null,
36
+ resolvedByUserId: null,
37
+ ...overrides,
38
+ } as Row
39
+ }
40
+
41
+ interface RepoStubOptions {
42
+ seeds: Row[]
43
+ /** ids that should throw AiPendingActionStateError when setStatus is invoked. */
44
+ raceIds?: string[]
45
+ /** ids that should throw a generic error from setStatus. */
46
+ errorIds?: string[]
47
+ }
48
+
49
+ function makeRepoStub(options: RepoStubOptions) {
50
+ const store = new Map<string, Row>()
51
+ for (const row of options.seeds) {
52
+ store.set(row.id, { ...row })
53
+ }
54
+ const raceIds = new Set(options.raceIds ?? [])
55
+ const errorIds = new Set(options.errorIds ?? [])
56
+
57
+ const listExpired = jest.fn(
58
+ async (
59
+ ctx: { tenantId: string; organizationId?: string | null },
60
+ now: Date,
61
+ limit: number,
62
+ ) => {
63
+ const all = Array.from(store.values()).filter((row) => {
64
+ if (row.tenantId !== ctx.tenantId) return false
65
+ const expected = ctx.organizationId ?? null
66
+ if ((row.organizationId ?? null) !== expected) return false
67
+ if (row.status !== 'pending') return false
68
+ return row.expiresAt.getTime() < now.getTime()
69
+ })
70
+ all.sort((a, b) => a.expiresAt.getTime() - b.expiresAt.getTime())
71
+ return all.slice(0, limit)
72
+ },
73
+ )
74
+
75
+ const setStatus = jest.fn(
76
+ async (
77
+ id: string,
78
+ next: AiPendingActionStatus,
79
+ _ctx: { tenantId: string; organizationId?: string | null },
80
+ extra?: { now?: Date; resolvedByUserId?: string | null },
81
+ ) => {
82
+ const existing = store.get(id)
83
+ if (!existing) throw new Error(`row ${id} not found`)
84
+ if (raceIds.has(id)) {
85
+ throw new AiPendingActionStateError(existing.status, next)
86
+ }
87
+ if (errorIds.has(id)) {
88
+ throw new Error(`boom-${id}`)
89
+ }
90
+ if (existing.status === next) return existing
91
+ existing.status = next
92
+ existing.resolvedAt = extra?.now ?? new Date()
93
+ if (extra && 'resolvedByUserId' in extra) {
94
+ existing.resolvedByUserId = extra.resolvedByUserId ?? null
95
+ }
96
+ return existing
97
+ },
98
+ )
99
+
100
+ return {
101
+ repo: { listExpired, setStatus } as unknown as import('../../data/repositories/AiPendingActionRepository').AiPendingActionRepository,
102
+ listExpired,
103
+ setStatus,
104
+ store,
105
+ }
106
+ }
107
+
108
+ describe('runPendingActionCleanup', () => {
109
+ const clock = new Date('2026-04-18T10:00:00.000Z')
110
+ const em = {} as import('@mikro-orm/postgresql').EntityManager
111
+
112
+ beforeEach(() => {
113
+ jest.clearAllMocks()
114
+ })
115
+
116
+ it('happy path: flips 3 expired rows and emits one ai.action.expired per row', async () => {
117
+ const seeds = [
118
+ makeRow({ id: 'pa-1', idempotencyKey: 'k1' }),
119
+ makeRow({ id: 'pa-2', idempotencyKey: 'k2' }),
120
+ makeRow({ id: 'pa-3', idempotencyKey: 'k3' }),
121
+ ]
122
+ const { repo, setStatus } = makeRepoStub({ seeds })
123
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
124
+
125
+ const summary = await runPendingActionCleanup({
126
+ em,
127
+ repo,
128
+ emitEvent,
129
+ now: clock,
130
+ discoverTenants: async () => [
131
+ { tenantId: 'tenant-alpha', organizationId: 'org-alpha' },
132
+ ],
133
+ })
134
+
135
+ expect(setStatus).toHaveBeenCalledTimes(3)
136
+ for (const call of setStatus.mock.calls) {
137
+ expect(call[1]).toBe('expired')
138
+ expect(call[3]).toMatchObject({ resolvedByUserId: null })
139
+ }
140
+ expect(emitEvent).toHaveBeenCalledTimes(3)
141
+ for (const [eventId, payload] of emitEvent.mock.calls) {
142
+ expect(eventId).toBe('ai.action.expired')
143
+ expect(payload.resolvedByUserId).toBeNull()
144
+ expect(payload.status).toBe('expired')
145
+ expect(payload.tenantId).toBe('tenant-alpha')
146
+ expect(payload.organizationId).toBe('org-alpha')
147
+ }
148
+ expect(summary).toEqual({
149
+ tenantsScanned: 1,
150
+ rowsProcessed: 3,
151
+ rowsExpired: 3,
152
+ rowsSkipped: 0,
153
+ rowsErrored: 0,
154
+ })
155
+ })
156
+
157
+ it('race-safe: rows flipped under us are caught, logged, and skipped (no emit)', async () => {
158
+ const seeds = [
159
+ makeRow({ id: 'pa-1' }),
160
+ makeRow({ id: 'pa-raced' }),
161
+ makeRow({ id: 'pa-3' }),
162
+ ]
163
+ const { repo, setStatus } = makeRepoStub({
164
+ seeds,
165
+ raceIds: ['pa-raced'],
166
+ })
167
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
168
+
169
+ const summary = await runPendingActionCleanup({
170
+ em,
171
+ repo,
172
+ emitEvent,
173
+ now: clock,
174
+ discoverTenants: async () => [
175
+ { tenantId: 'tenant-alpha', organizationId: 'org-alpha' },
176
+ ],
177
+ })
178
+
179
+ expect(setStatus).toHaveBeenCalledTimes(3)
180
+ // Only successful rows produce events
181
+ const emittedIds = emitEvent.mock.calls.map(([, payload]) => payload.pendingActionId)
182
+ expect(emittedIds.sort()).toEqual(['pa-1', 'pa-3'])
183
+ expect(summary.rowsExpired).toBe(2)
184
+ expect(summary.rowsSkipped).toBe(1)
185
+ expect(summary.rowsErrored).toBe(0)
186
+ })
187
+
188
+ it('paginates: more than pageSize expired rows take multiple fetch cycles', async () => {
189
+ const seeds: Row[] = []
190
+ for (let i = 0; i < 5; i += 1) {
191
+ seeds.push(
192
+ makeRow({
193
+ id: `pa-${i}`,
194
+ idempotencyKey: `k-${i}`,
195
+ expiresAt: new Date(clock.getTime() - (10 - i) * 1000),
196
+ }),
197
+ )
198
+ }
199
+ const { repo, listExpired, setStatus } = makeRepoStub({ seeds })
200
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
201
+
202
+ const summary = await runPendingActionCleanup({
203
+ em,
204
+ repo,
205
+ emitEvent,
206
+ now: clock,
207
+ pageSize: 2,
208
+ discoverTenants: async () => [
209
+ { tenantId: 'tenant-alpha', organizationId: 'org-alpha' },
210
+ ],
211
+ })
212
+
213
+ // 5 rows, pageSize 2 → pages of 2,2,1 → 3 listExpired calls
214
+ expect(listExpired).toHaveBeenCalledTimes(3)
215
+ expect(setStatus).toHaveBeenCalledTimes(5)
216
+ expect(emitEvent).toHaveBeenCalledTimes(5)
217
+ expect(summary.rowsProcessed).toBe(5)
218
+ expect(summary.rowsExpired).toBe(5)
219
+ })
220
+
221
+ it('cross-tenant: rows from tenant A and tenant B both get processed', async () => {
222
+ const seeds = [
223
+ makeRow({ id: 'pa-a1', tenantId: 'tenant-alpha', organizationId: 'org-a' }),
224
+ makeRow({ id: 'pa-b1', tenantId: 'tenant-beta', organizationId: 'org-b' }),
225
+ makeRow({ id: 'pa-b2', tenantId: 'tenant-beta', organizationId: 'org-b' }),
226
+ ]
227
+ const { repo, setStatus } = makeRepoStub({ seeds })
228
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
229
+
230
+ const summary = await runPendingActionCleanup({
231
+ em,
232
+ repo,
233
+ emitEvent,
234
+ now: clock,
235
+ discoverTenants: async () => [
236
+ { tenantId: 'tenant-alpha', organizationId: 'org-a' },
237
+ { tenantId: 'tenant-beta', organizationId: 'org-b' },
238
+ ],
239
+ })
240
+
241
+ expect(setStatus).toHaveBeenCalledTimes(3)
242
+ const tenantIds = emitEvent.mock.calls.map(([, payload]) => payload.tenantId)
243
+ expect(tenantIds.sort()).toEqual(['tenant-alpha', 'tenant-beta', 'tenant-beta'])
244
+ expect(summary.tenantsScanned).toBe(2)
245
+ expect(summary.rowsExpired).toBe(3)
246
+ })
247
+
248
+ it('zero-expired: empty tenant list emits no events and completes cleanly', async () => {
249
+ const { repo, setStatus, listExpired } = makeRepoStub({ seeds: [] })
250
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
251
+
252
+ const summary = await runPendingActionCleanup({
253
+ em,
254
+ repo,
255
+ emitEvent,
256
+ now: clock,
257
+ discoverTenants: async () => [],
258
+ })
259
+
260
+ expect(listExpired).not.toHaveBeenCalled()
261
+ expect(setStatus).not.toHaveBeenCalled()
262
+ expect(emitEvent).not.toHaveBeenCalled()
263
+ expect(summary).toEqual({
264
+ tenantsScanned: 0,
265
+ rowsProcessed: 0,
266
+ rowsExpired: 0,
267
+ rowsSkipped: 0,
268
+ rowsErrored: 0,
269
+ })
270
+ })
271
+
272
+ it('single-row error does not abort the batch', async () => {
273
+ const seeds = [
274
+ makeRow({ id: 'pa-good-1' }),
275
+ makeRow({ id: 'pa-boom' }),
276
+ makeRow({ id: 'pa-good-2' }),
277
+ ]
278
+ const { repo, setStatus } = makeRepoStub({
279
+ seeds,
280
+ errorIds: ['pa-boom'],
281
+ })
282
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
283
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
284
+
285
+ try {
286
+ const summary = await runPendingActionCleanup({
287
+ em,
288
+ repo,
289
+ emitEvent,
290
+ now: clock,
291
+ discoverTenants: async () => [
292
+ { tenantId: 'tenant-alpha', organizationId: 'org-alpha' },
293
+ ],
294
+ })
295
+
296
+ expect(setStatus).toHaveBeenCalledTimes(3)
297
+ // Only the two good rows emit
298
+ const emittedIds = emitEvent.mock.calls.map(([, payload]) => payload.pendingActionId)
299
+ expect(emittedIds.sort()).toEqual(['pa-good-1', 'pa-good-2'])
300
+ expect(summary.rowsExpired).toBe(2)
301
+ expect(summary.rowsErrored).toBe(1)
302
+ expect(summary.rowsSkipped).toBe(0)
303
+ } finally {
304
+ errorSpy.mockRestore()
305
+ }
306
+ })
307
+
308
+ it('already-expired row is a no-op on subsequent sweep (idempotency)', async () => {
309
+ // Two rows, both already expired by status — listExpired filters them out,
310
+ // so setStatus is never called and no events are emitted.
311
+ const seeds = [
312
+ makeRow({ id: 'pa-1', status: 'expired' }),
313
+ makeRow({ id: 'pa-2', status: 'expired' }),
314
+ ]
315
+ const { repo, listExpired, setStatus } = makeRepoStub({ seeds })
316
+ const emitEvent = jest.fn().mockResolvedValue(undefined)
317
+
318
+ const summary = await runPendingActionCleanup({
319
+ em,
320
+ repo,
321
+ emitEvent,
322
+ now: clock,
323
+ discoverTenants: async () => [
324
+ { tenantId: 'tenant-alpha', organizationId: 'org-alpha' },
325
+ ],
326
+ })
327
+
328
+ expect(listExpired).toHaveBeenCalledTimes(1)
329
+ expect(setStatus).not.toHaveBeenCalled()
330
+ expect(emitEvent).not.toHaveBeenCalled()
331
+ expect(summary.rowsExpired).toBe(0)
332
+ })
333
+ })