@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,286 @@
1
+ const authMock = jest.fn()
2
+ const loadAclMock = jest.fn()
3
+ const createRequestContainerMock = jest.fn()
4
+ const repoGetByIdMock = jest.fn()
5
+ const repoSetStatusMock = jest.fn()
6
+ const emitEventMock = jest.fn()
7
+
8
+ jest.mock('@open-mercato/shared/lib/auth/server', () => ({
9
+ getAuthFromRequest: (...args: unknown[]) => authMock(...args),
10
+ }))
11
+
12
+ jest.mock('@open-mercato/shared/lib/di/container', () => ({
13
+ createRequestContainer: (...args: unknown[]) => createRequestContainerMock(...args),
14
+ }))
15
+
16
+ jest.mock('../../../../../../data/repositories/AiPendingActionRepository', () => ({
17
+ AiPendingActionRepository: jest.fn().mockImplementation(() => ({
18
+ getById: repoGetByIdMock,
19
+ setStatus: repoSetStatusMock,
20
+ })),
21
+ }))
22
+
23
+ import { setGlobalEventBus } from '@open-mercato/shared/modules/events'
24
+ import { POST } from '../route'
25
+
26
+ function buildRequest(body?: unknown): Request {
27
+ const init: RequestInit = {
28
+ method: 'POST',
29
+ headers: { 'content-type': 'application/json' },
30
+ }
31
+ if (body !== undefined) {
32
+ init.body = typeof body === 'string' ? body : JSON.stringify(body)
33
+ }
34
+ return new Request('http://localhost/api/ai_assistant/ai/actions/pa_123/cancel', init)
35
+ }
36
+
37
+ function buildContext(id: string) {
38
+ return { params: Promise.resolve({ id }) }
39
+ }
40
+
41
+ function makeRow(overrides: Record<string, unknown> = {}) {
42
+ return {
43
+ id: 'pa_123',
44
+ tenantId: 'tenant-1',
45
+ organizationId: 'org-1',
46
+ agentId: 'catalog.merchandising_assistant',
47
+ toolName: 'catalog.update_product',
48
+ status: 'pending',
49
+ fieldDiff: [{ field: 'title', before: 'Old', after: 'New' }],
50
+ records: null,
51
+ failedRecords: null,
52
+ sideEffectsSummary: null,
53
+ recordVersion: 'v-1',
54
+ attachmentIds: [],
55
+ normalizedInput: { productId: 'p-1', patch: { title: 'New' } },
56
+ queueMode: 'inline',
57
+ executionResult: null,
58
+ targetEntityType: 'product',
59
+ targetRecordId: 'p-1',
60
+ conversationId: null,
61
+ idempotencyKey: 'idem_1',
62
+ createdByUserId: 'user-1',
63
+ createdAt: new Date(Date.now() - 60_000),
64
+ expiresAt: new Date(Date.now() + 3_600_000),
65
+ resolvedAt: null,
66
+ resolvedByUserId: null,
67
+ ...overrides,
68
+ }
69
+ }
70
+
71
+ describe('POST /api/ai/actions/:id/cancel route (Step 5.9)', () => {
72
+ let consoleErrorSpy: jest.SpyInstance
73
+ let consoleWarnSpy: jest.SpyInstance
74
+
75
+ beforeEach(() => {
76
+ jest.clearAllMocks()
77
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
78
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
79
+
80
+ authMock.mockResolvedValue({
81
+ sub: 'user-1',
82
+ tenantId: 'tenant-1',
83
+ orgId: 'org-1',
84
+ })
85
+ loadAclMock.mockResolvedValue({
86
+ features: ['ai_assistant.view'],
87
+ isSuperAdmin: false,
88
+ })
89
+ createRequestContainerMock.mockResolvedValue({
90
+ resolve: (name: string) => {
91
+ if (name === 'rbacService') {
92
+ return {
93
+ loadAcl: loadAclMock,
94
+ hasAllFeatures: (required: string[], granted: string[]) =>
95
+ required.every((feature) => granted.includes(feature)),
96
+ }
97
+ }
98
+ if (name === 'em') return {}
99
+ if (name === 'eventBus') return { emitEvent: emitEventMock }
100
+ return null
101
+ },
102
+ })
103
+
104
+ emitEventMock.mockResolvedValue(undefined)
105
+ setGlobalEventBus({
106
+ emit: (eventId, payload, options) => emitEventMock(eventId, payload, options),
107
+ })
108
+ repoSetStatusMock.mockImplementation(
109
+ async (id: string, status: string, _scope: unknown, extra?: any) => {
110
+ return {
111
+ ...makeRow({
112
+ id,
113
+ status,
114
+ executionResult: extra?.executionResult ?? null,
115
+ resolvedAt: extra?.now ?? new Date(),
116
+ resolvedByUserId: extra?.resolvedByUserId ?? null,
117
+ }),
118
+ }
119
+ },
120
+ )
121
+ })
122
+
123
+ afterEach(() => {
124
+ consoleErrorSpy.mockRestore()
125
+ consoleWarnSpy.mockRestore()
126
+ })
127
+
128
+ it('returns 401 when unauthenticated', async () => {
129
+ authMock.mockResolvedValueOnce(null)
130
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
131
+ expect(response.status).toBe(401)
132
+ })
133
+
134
+ it('happy path: pending → cancelled returns 200 with pendingAction.status === cancelled', async () => {
135
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
136
+
137
+ const response = await POST(buildRequest({ reason: 'Wrong price' }) as any, buildContext('pa_123'))
138
+ expect(response.status).toBe(200)
139
+ const body = await response.json()
140
+ expect(body.ok).toBe(true)
141
+ expect(body.pendingAction.status).toBe('cancelled')
142
+ expect(body.pendingAction.executionResult).toEqual({
143
+ error: { code: 'cancelled_by_user', message: 'Wrong price' },
144
+ })
145
+ expect(repoSetStatusMock).toHaveBeenCalledTimes(1)
146
+ const [, nextStatus, , extra] = repoSetStatusMock.mock.calls[0]
147
+ expect(nextStatus).toBe('cancelled')
148
+ expect(extra).toMatchObject({ resolvedByUserId: 'user-1' })
149
+ expect(emitEventMock).toHaveBeenCalledTimes(1)
150
+ expect(emitEventMock.mock.calls[0][0]).toBe('ai.action.cancelled')
151
+ })
152
+
153
+ it('idempotent: second cancel on cancelled row returns 200 + same row without re-emitting event', async () => {
154
+ const cancelledRow = makeRow({
155
+ status: 'cancelled',
156
+ resolvedAt: new Date('2026-04-18T10:30:00.000Z'),
157
+ resolvedByUserId: 'user-1',
158
+ executionResult: { error: { code: 'cancelled_by_user', message: 'Cancelled by user' } },
159
+ })
160
+ repoGetByIdMock.mockResolvedValueOnce(cancelledRow)
161
+
162
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
163
+ expect(response.status).toBe(200)
164
+ const body = await response.json()
165
+ expect(body.ok).toBe(true)
166
+ expect(body.pendingAction.status).toBe('cancelled')
167
+ expect(repoSetStatusMock).not.toHaveBeenCalled()
168
+ expect(emitEventMock).not.toHaveBeenCalled()
169
+ })
170
+
171
+ it('409 expired: expiresAt in the past flips to expired and returns 409', async () => {
172
+ const expiredRow = makeRow({ expiresAt: new Date('2020-01-01T00:00:00.000Z') })
173
+ repoGetByIdMock.mockResolvedValueOnce(expiredRow)
174
+
175
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
176
+ expect(response.status).toBe(409)
177
+ const body = await response.json()
178
+ expect(body.code).toBe('expired')
179
+ // expired branch performs a setStatus('expired', ...) + emits ai.action.expired
180
+ expect(repoSetStatusMock).toHaveBeenCalledTimes(1)
181
+ const [, nextStatus] = repoSetStatusMock.mock.calls[0]
182
+ expect(nextStatus).toBe('expired')
183
+ expect(emitEventMock).toHaveBeenCalledTimes(1)
184
+ expect(emitEventMock.mock.calls[0][0]).toBe('ai.action.expired')
185
+ })
186
+
187
+ it('409 invalid_status: already confirmed', async () => {
188
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ status: 'confirmed' }))
189
+
190
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
191
+ expect(response.status).toBe(409)
192
+ const body = await response.json()
193
+ expect(body.code).toBe('invalid_status')
194
+ expect(repoSetStatusMock).not.toHaveBeenCalled()
195
+ expect(emitEventMock).not.toHaveBeenCalled()
196
+ })
197
+
198
+ it('409 invalid_status: already executing', async () => {
199
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ status: 'executing' }))
200
+
201
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
202
+ expect(response.status).toBe(409)
203
+ const body = await response.json()
204
+ expect(body.code).toBe('invalid_status')
205
+ })
206
+
207
+ it('409 invalid_status: already failed', async () => {
208
+ repoGetByIdMock.mockResolvedValueOnce(makeRow({ status: 'failed' }))
209
+
210
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
211
+ expect(response.status).toBe(409)
212
+ const body = await response.json()
213
+ expect(body.code).toBe('invalid_status')
214
+ })
215
+
216
+ it('404 pending_action_not_found for cross-tenant / unknown id', async () => {
217
+ repoGetByIdMock.mockResolvedValueOnce(null)
218
+
219
+ const response = await POST(buildRequest() as any, buildContext('pa_missing'))
220
+ expect(response.status).toBe(404)
221
+ const body = await response.json()
222
+ expect(body.code).toBe('pending_action_not_found')
223
+ expect(repoSetStatusMock).not.toHaveBeenCalled()
224
+ })
225
+
226
+ it('403 forbidden when caller lacks ai_assistant.view', async () => {
227
+ loadAclMock.mockResolvedValueOnce({ features: ['catalog.view'], isSuperAdmin: false })
228
+
229
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
230
+ expect(response.status).toBe(403)
231
+ const body = await response.json()
232
+ expect(body.code).toBe('forbidden')
233
+ expect(repoGetByIdMock).not.toHaveBeenCalled()
234
+ })
235
+
236
+ it('whitespace-only reason becomes empty → default "Cancelled by user" message', async () => {
237
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
238
+
239
+ const response = await POST(buildRequest({ reason: ' \t\n ' }) as any, buildContext('pa_123'))
240
+ expect(response.status).toBe(200)
241
+ const body = await response.json()
242
+ expect(body.pendingAction.executionResult).toEqual({
243
+ error: { code: 'cancelled_by_user', message: 'Cancelled by user' },
244
+ })
245
+ })
246
+
247
+ it('400 validation_error when reason exceeds 500 characters', async () => {
248
+ const longReason = 'x'.repeat(501)
249
+ const response = await POST(buildRequest({ reason: longReason }) as any, buildContext('pa_123'))
250
+ expect(response.status).toBe(400)
251
+ const body = await response.json()
252
+ expect(body.code).toBe('validation_error')
253
+ expect(repoGetByIdMock).not.toHaveBeenCalled()
254
+ })
255
+
256
+ it('400 validation_error when body contains unknown fields', async () => {
257
+ const response = await POST(
258
+ buildRequest({ reason: 'ok', evil: 'payload' }) as any,
259
+ buildContext('pa_123'),
260
+ )
261
+ expect(response.status).toBe(400)
262
+ const body = await response.json()
263
+ expect(body.code).toBe('validation_error')
264
+ })
265
+
266
+ it('accepts an empty body (no reason)', async () => {
267
+ repoGetByIdMock.mockResolvedValueOnce(makeRow())
268
+
269
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
270
+ expect(response.status).toBe(200)
271
+ const body = await response.json()
272
+ expect(body.ok).toBe(true)
273
+ expect(body.pendingAction.executionResult).toEqual({
274
+ error: { code: 'cancelled_by_user', message: 'Cancelled by user' },
275
+ })
276
+ })
277
+
278
+ it('500 cancel_internal_error when the repo throws unexpectedly', async () => {
279
+ repoGetByIdMock.mockRejectedValueOnce(new Error('db down'))
280
+
281
+ const response = await POST(buildRequest() as any, buildContext('pa_123'))
282
+ expect(response.status).toBe(500)
283
+ const body = await response.json()
284
+ expect(body.code).toBe('cancel_internal_error')
285
+ })
286
+ })
@@ -0,0 +1,237 @@
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 { checkStatusAndExpiry } from '../../../../../lib/pending-action-recheck'
12
+ import { executePendingActionCancel } from '../../../../../lib/pending-action-cancel'
13
+
14
+ /**
15
+ * POST `/api/ai/actions/:id/cancel` — mutation approval gate cancel
16
+ * endpoint (spec §9.4, Step 5.9).
17
+ *
18
+ * Siblings the Step 5.8 confirm route: flips `pending → cancelled` and
19
+ * emits `ai.action.cancelled`. The tool handler is NEVER invoked.
20
+ *
21
+ * The route re-uses only the `status + expiry + tenant-scope` guards
22
+ * from `pending-action-recheck.ts` — the agent / tool / attachment /
23
+ * record-version guards are confirm-only. A caller may cancel even when
24
+ * they'd be blocked from confirming; cancelling does not touch data.
25
+ *
26
+ * Idempotency: calling this endpoint twice on an already-`cancelled`
27
+ * row returns 200 with the current row without re-emitting the event.
28
+ */
29
+
30
+ const REQUIRED_FEATURE = 'ai_assistant.view'
31
+
32
+ const idParamSchema = z.object({
33
+ id: z
34
+ .string()
35
+ .trim()
36
+ .min(1, 'id must be a non-empty string')
37
+ .max(128, 'id exceeds the maximum length of 128 characters'),
38
+ })
39
+
40
+ const bodySchema = z
41
+ .object({
42
+ reason: z
43
+ .string()
44
+ .max(500, 'reason must be at most 500 characters')
45
+ .optional(),
46
+ })
47
+ .strict()
48
+ .optional()
49
+
50
+ export const openApi: OpenApiRouteDoc = {
51
+ tag: 'AI Assistant',
52
+ summary: 'Pending action (mutation approval gate) cancel',
53
+ methods: {
54
+ POST: {
55
+ operationId: 'aiAssistantCancelPendingAction',
56
+ summary: 'Cancel an AI pending action without executing the wrapped tool.',
57
+ description:
58
+ 'Flips a pending AI action from `pending` to `cancelled` and emits the ' +
59
+ '`ai.action.cancelled` event. The tool handler is never invoked. Idempotent: ' +
60
+ 'a second call on a row already in `cancelled` status returns 200 with the ' +
61
+ 'current row without re-emitting the event. Rows whose `expiresAt` is in the ' +
62
+ 'past are flipped to `expired` and returned as 409 `expired` so the client can ' +
63
+ 'surface the TTL loss instead of silently masking it as a cancellation.',
64
+ responses: [
65
+ {
66
+ status: 200,
67
+ description: 'Cancellation complete (or idempotent replay); body includes the serialized pending action with status `cancelled`.',
68
+ mediaType: 'application/json',
69
+ },
70
+ ],
71
+ errors: [
72
+ { status: 400, description: 'Invalid cancel request body (unknown field, reason exceeds 500 chars, wrong type).' },
73
+ { status: 401, description: 'Unauthenticated caller.' },
74
+ { status: 403, description: 'Caller lacks `ai_assistant.view`.' },
75
+ { status: 404, description: 'Pending action not found in the caller scope.' },
76
+ { status: 409, description: 'Pending action is not in `pending` status (already confirmed/failed/executing) or has expired.' },
77
+ { status: 500, description: 'Unexpected server failure during cancel.' },
78
+ ],
79
+ },
80
+ },
81
+ }
82
+
83
+ export const metadata = {
84
+ POST: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },
85
+ }
86
+
87
+ interface RouteContext {
88
+ params: Promise<{ id: string }>
89
+ }
90
+
91
+ function jsonError(
92
+ status: number,
93
+ message: string,
94
+ code: string,
95
+ extra?: Record<string, unknown>,
96
+ ): NextResponse {
97
+ return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })
98
+ }
99
+
100
+ async function readRequestBody(req: NextRequest): Promise<unknown> {
101
+ try {
102
+ const text = await req.text()
103
+ if (!text || text.trim().length === 0) return undefined
104
+ return JSON.parse(text)
105
+ } catch {
106
+ return Symbol.for('ai_assistant.cancel.bad_json')
107
+ }
108
+ }
109
+
110
+ export async function POST(req: NextRequest, context: RouteContext): Promise<Response> {
111
+ const auth = await getAuthFromRequest(req)
112
+ if (!auth) {
113
+ return jsonError(401, 'Unauthorized', 'unauthenticated')
114
+ }
115
+
116
+ const rawParams = await context.params
117
+ const paramResult = idParamSchema.safeParse(rawParams)
118
+ if (!paramResult.success) {
119
+ return jsonError(400, 'Invalid pending action id.', 'validation_error', {
120
+ issues: paramResult.error.issues,
121
+ })
122
+ }
123
+ const pendingActionId = paramResult.data.id
124
+
125
+ const rawBody = await readRequestBody(req)
126
+ if (rawBody === Symbol.for('ai_assistant.cancel.bad_json')) {
127
+ return jsonError(400, 'Invalid JSON body.', 'validation_error')
128
+ }
129
+ const bodyResult = bodySchema.safeParse(rawBody)
130
+ if (!bodyResult.success) {
131
+ return jsonError(400, 'Invalid cancel body.', 'validation_error', {
132
+ issues: bodyResult.error.issues,
133
+ })
134
+ }
135
+ const parsedBody = bodyResult.data ?? {}
136
+
137
+ try {
138
+ const container = await createRequestContainer()
139
+ const rbacService = container.resolve<RbacService>('rbacService')
140
+ const acl = await rbacService.loadAcl(auth.sub, {
141
+ tenantId: auth.tenantId,
142
+ organizationId: auth.orgId,
143
+ })
144
+
145
+ if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {
146
+ return jsonError(403, `Caller lacks required feature "${REQUIRED_FEATURE}".`, 'forbidden')
147
+ }
148
+
149
+ if (!auth.tenantId) {
150
+ return jsonError(
151
+ 404,
152
+ `No pending action "${pendingActionId}" accessible to the caller.`,
153
+ 'pending_action_not_found',
154
+ )
155
+ }
156
+
157
+ const em = container.resolve<EntityManager>('em')
158
+ const repo = new AiPendingActionRepository(em)
159
+ const row = await repo.getById(pendingActionId, {
160
+ tenantId: auth.tenantId,
161
+ organizationId: auth.orgId ?? null,
162
+ userId: auth.sub,
163
+ })
164
+ if (!row) {
165
+ return jsonError(
166
+ 404,
167
+ `No pending action "${pendingActionId}" accessible to the caller.`,
168
+ 'pending_action_not_found',
169
+ )
170
+ }
171
+
172
+ // Idempotent replay: second cancel on an already-cancelled row returns
173
+ // 200 with the current row and does NOT emit a second event.
174
+ if (row.status === 'cancelled') {
175
+ return NextResponse.json({
176
+ ok: true,
177
+ pendingAction: serializePendingActionForClient(row),
178
+ })
179
+ }
180
+
181
+ const statusCheck = checkStatusAndExpiry(row)
182
+ if (!statusCheck.ok) {
183
+ // Expired short-circuit: flip to `expired` + emit `ai.action.expired`
184
+ // so Step 5.12 does not race to do it. Return 409 so the client
185
+ // surfaces the TTL loss rather than silently cancelling.
186
+ if (statusCheck.code === 'expired') {
187
+ const cancelResult = await executePendingActionCancel({
188
+ action: row,
189
+ ctx: {
190
+ tenantId: auth.tenantId,
191
+ organizationId: auth.orgId ?? null,
192
+ userId: auth.sub,
193
+ container,
194
+ },
195
+ repo,
196
+ })
197
+ return jsonError(409, statusCheck.message, 'expired', {
198
+ pendingAction: serializePendingActionForClient(cancelResult.row),
199
+ })
200
+ }
201
+ return jsonError(statusCheck.status, statusCheck.message, statusCheck.code, statusCheck.extra)
202
+ }
203
+
204
+ const cancelResult = await executePendingActionCancel({
205
+ action: row,
206
+ ctx: {
207
+ tenantId: auth.tenantId,
208
+ organizationId: auth.orgId ?? null,
209
+ userId: auth.sub,
210
+ container,
211
+ },
212
+ reason: parsedBody.reason,
213
+ repo,
214
+ })
215
+
216
+ if (cancelResult.status === 'expired') {
217
+ return jsonError(
218
+ 409,
219
+ 'Pending action has expired. The model must re-propose the mutation.',
220
+ 'expired',
221
+ { pendingAction: serializePendingActionForClient(cancelResult.row) },
222
+ )
223
+ }
224
+
225
+ return NextResponse.json({
226
+ ok: true,
227
+ pendingAction: serializePendingActionForClient(cancelResult.row),
228
+ })
229
+ } catch (error) {
230
+ console.error('[AI Pending Action CANCEL] Failure:', error)
231
+ return jsonError(
232
+ 500,
233
+ error instanceof Error ? error.message : 'Failed to cancel pending action.',
234
+ 'cancel_internal_error',
235
+ )
236
+ }
237
+ }