@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,531 @@
1
+ /**
2
+ * Step 3.7 — Attachment-to-model conversion bridge.
3
+ *
4
+ * Covers the four source kinds (`bytes`, `signed-url`, `text`,
5
+ * `metadata-only`), the agent `acceptedMediaTypes` whitelist, the cross-
6
+ * tenant drop, and the unavailable-service graceful skip required by the
7
+ * Step brief.
8
+ */
9
+
10
+ import type { AiAgentAcceptedMediaType } from '../ai-agent-definition'
11
+ import type { AiChatRequestContext } from '../attachment-bridge-types'
12
+
13
+ const findOneWithDecryptionMock = jest.fn()
14
+
15
+ jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
16
+ findOneWithDecryption: (...args: unknown[]) => findOneWithDecryptionMock(...args),
17
+ }))
18
+
19
+ jest.mock('@open-mercato/core/modules/attachments/data/entities', () => ({
20
+ Attachment: class AttachmentStub {},
21
+ }))
22
+
23
+ const resolveAttachmentAbsolutePathMock = jest.fn(
24
+ (_partition: string, storagePath: string) => `/fake/root/${storagePath}`,
25
+ )
26
+
27
+ jest.mock('@open-mercato/core/modules/attachments/lib/storage', () => ({
28
+ resolveAttachmentAbsolutePath: (...args: unknown[]) =>
29
+ resolveAttachmentAbsolutePathMock(...(args as [string, string, string?])),
30
+ }))
31
+
32
+ const fsReadFileMock = jest.fn()
33
+ jest.mock('fs', () => {
34
+ const actual = jest.requireActual('fs')
35
+ return {
36
+ ...actual,
37
+ promises: {
38
+ ...actual.promises,
39
+ readFile: (...args: unknown[]) => fsReadFileMock(...args),
40
+ },
41
+ }
42
+ })
43
+
44
+ import {
45
+ resolveAttachmentParts,
46
+ resolveAttachmentPartsForAgent,
47
+ attachmentPartsToUiFileParts,
48
+ summarizeAttachmentPartsForPrompt,
49
+ type AttachmentSigner,
50
+ } from '../attachment-parts'
51
+
52
+ function makeAuth(overrides: Partial<AiChatRequestContext> = {}): AiChatRequestContext {
53
+ return {
54
+ tenantId: 'tenant-1',
55
+ organizationId: 'org-1',
56
+ userId: 'user-1',
57
+ features: [],
58
+ isSuperAdmin: false,
59
+ ...overrides,
60
+ }
61
+ }
62
+
63
+ type RowOverrides = {
64
+ id?: string
65
+ fileName?: string
66
+ mimeType?: string
67
+ fileSize?: number
68
+ storagePath?: string
69
+ storageDriver?: string
70
+ partitionCode?: string
71
+ tenantId?: string | null
72
+ organizationId?: string | null
73
+ content?: string | null
74
+ entityId?: string
75
+ }
76
+
77
+ function makeRow(overrides: RowOverrides = {}): Record<string, unknown> {
78
+ return {
79
+ id: 'att-1',
80
+ entityId: 'attachments:attachment',
81
+ fileName: 'file.bin',
82
+ mimeType: 'application/octet-stream',
83
+ fileSize: 1024,
84
+ storagePath: 'path/to/file',
85
+ storageDriver: 'local',
86
+ partitionCode: 'default',
87
+ tenantId: 'tenant-1',
88
+ organizationId: 'org-1',
89
+ content: null,
90
+ ...overrides,
91
+ }
92
+ }
93
+
94
+ function makeContainer(options?: { signer?: AttachmentSigner | null; omitEm?: boolean }) {
95
+ const registry: Record<string, unknown> = {}
96
+ if (!options?.omitEm) {
97
+ registry.em = { __label: 'em-stub' }
98
+ }
99
+ if (options?.signer) {
100
+ registry.attachmentSigner = options.signer
101
+ }
102
+ return {
103
+ resolve: (key: string) => {
104
+ if (!(key in registry)) {
105
+ throw new Error(`resolve("${key}") not registered`)
106
+ }
107
+ return registry[key]
108
+ },
109
+ } as unknown as Parameters<typeof resolveAttachmentParts>[0]['container']
110
+ }
111
+
112
+ describe('resolveAttachmentParts — source classification', () => {
113
+ beforeEach(() => {
114
+ jest.clearAllMocks()
115
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
116
+ })
117
+
118
+ afterEach(() => {
119
+ ;(console.warn as jest.Mock).mockRestore?.()
120
+ })
121
+
122
+ it('emits `bytes` for small images (inline fits under the threshold)', async () => {
123
+ findOneWithDecryptionMock.mockResolvedValueOnce(
124
+ makeRow({ id: 'img-1', fileName: 'photo.jpg', mimeType: 'image/jpeg', fileSize: 512 }),
125
+ )
126
+ fsReadFileMock.mockResolvedValueOnce(Buffer.from([1, 2, 3, 4]))
127
+
128
+ const parts = await resolveAttachmentParts({
129
+ attachmentIds: ['img-1'],
130
+ authContext: makeAuth(),
131
+ container: makeContainer(),
132
+ })
133
+
134
+ expect(parts).toHaveLength(1)
135
+ expect(parts[0].source).toBe('bytes')
136
+ expect(parts[0].attachmentId).toBe('img-1')
137
+ expect(parts[0].mediaType).toBe('image/jpeg')
138
+ expect(parts[0].data).toBeInstanceOf(Uint8Array)
139
+ expect(Array.from(parts[0].data as Uint8Array)).toEqual([1, 2, 3, 4])
140
+ })
141
+
142
+ it('emits `signed-url` for oversized images when the container provides an attachmentSigner', async () => {
143
+ findOneWithDecryptionMock.mockResolvedValueOnce(
144
+ makeRow({ id: 'img-2', fileName: 'huge.png', mimeType: 'image/png', fileSize: 50 * 1024 * 1024 }),
145
+ )
146
+ const signer: AttachmentSigner = {
147
+ sign: jest.fn(async () => 'https://signed.example/huge.png?sig=abc'),
148
+ }
149
+
150
+ const parts = await resolveAttachmentParts({
151
+ attachmentIds: ['img-2'],
152
+ authContext: makeAuth(),
153
+ container: makeContainer({ signer }),
154
+ })
155
+
156
+ expect(signer.sign).toHaveBeenCalledWith(
157
+ expect.objectContaining({ attachmentId: 'img-2', mediaType: 'image/png' }),
158
+ )
159
+ expect(parts).toHaveLength(1)
160
+ expect(parts[0].source).toBe('signed-url')
161
+ expect(parts[0].url).toBe('https://signed.example/huge.png?sig=abc')
162
+ expect(fsReadFileMock).not.toHaveBeenCalled()
163
+ })
164
+
165
+ it('emits `text` for text-like files with extracted content', async () => {
166
+ findOneWithDecryptionMock.mockResolvedValueOnce(
167
+ makeRow({
168
+ id: 'txt-1',
169
+ fileName: 'notes.md',
170
+ mimeType: 'text/markdown',
171
+ fileSize: 42,
172
+ content: '# Notes\n- bullet a\n- bullet b',
173
+ }),
174
+ )
175
+
176
+ const parts = await resolveAttachmentParts({
177
+ attachmentIds: ['txt-1'],
178
+ authContext: makeAuth(),
179
+ container: makeContainer(),
180
+ })
181
+
182
+ expect(parts).toHaveLength(1)
183
+ expect(parts[0].source).toBe('text')
184
+ expect(parts[0].textContent).toContain('bullet a')
185
+ // text branch MUST NOT read bytes from disk
186
+ expect(fsReadFileMock).not.toHaveBeenCalled()
187
+ })
188
+
189
+ it('emits `metadata-only` for generic binary files without extracted text', async () => {
190
+ findOneWithDecryptionMock.mockResolvedValueOnce(
191
+ makeRow({
192
+ id: 'bin-1',
193
+ fileName: 'archive.zip',
194
+ mimeType: 'application/zip',
195
+ fileSize: 9999,
196
+ content: null,
197
+ }),
198
+ )
199
+
200
+ const parts = await resolveAttachmentParts({
201
+ attachmentIds: ['bin-1'],
202
+ authContext: makeAuth(),
203
+ container: makeContainer(),
204
+ })
205
+
206
+ expect(parts).toHaveLength(1)
207
+ expect(parts[0].source).toBe('metadata-only')
208
+ expect(parts[0].data).toBeUndefined()
209
+ expect(parts[0].textContent).toBeUndefined()
210
+ expect(parts[0].url).toBeUndefined()
211
+ })
212
+
213
+ it('downgrades images to `metadata-only` when disk read fails and no signer is configured', async () => {
214
+ findOneWithDecryptionMock.mockResolvedValueOnce(
215
+ makeRow({ id: 'img-3', fileName: 'bad.jpg', mimeType: 'image/jpeg', fileSize: 256 }),
216
+ )
217
+ fsReadFileMock.mockRejectedValueOnce(new Error('ENOENT'))
218
+
219
+ const parts = await resolveAttachmentParts({
220
+ attachmentIds: ['img-3'],
221
+ authContext: makeAuth(),
222
+ container: makeContainer(),
223
+ })
224
+
225
+ expect(parts).toHaveLength(1)
226
+ expect(parts[0].source).toBe('metadata-only')
227
+ })
228
+ })
229
+
230
+ describe('resolveAttachmentParts — acceptedMediaTypes whitelist', () => {
231
+ beforeEach(() => {
232
+ jest.clearAllMocks()
233
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
234
+ })
235
+
236
+ afterEach(() => {
237
+ ;(console.warn as jest.Mock).mockRestore?.()
238
+ })
239
+
240
+ it('drops parts whose classified type is not in the agent whitelist', async () => {
241
+ findOneWithDecryptionMock
242
+ .mockResolvedValueOnce(
243
+ makeRow({ id: 'img-1', fileName: 'photo.jpg', mimeType: 'image/jpeg', fileSize: 128 }),
244
+ )
245
+ .mockResolvedValueOnce(
246
+ makeRow({ id: 'pdf-1', fileName: 'invoice.pdf', mimeType: 'application/pdf', fileSize: 256 }),
247
+ )
248
+ .mockResolvedValueOnce(
249
+ makeRow({ id: 'bin-1', fileName: 'archive.zip', mimeType: 'application/zip', fileSize: 999 }),
250
+ )
251
+ fsReadFileMock
252
+ .mockResolvedValueOnce(Buffer.from([1, 2]))
253
+ .mockResolvedValueOnce(Buffer.from([3, 4]))
254
+
255
+ const acceptedMediaTypes: AiAgentAcceptedMediaType[] = ['image', 'pdf']
256
+ const parts = await resolveAttachmentParts({
257
+ attachmentIds: ['img-1', 'pdf-1', 'bin-1'],
258
+ authContext: makeAuth(),
259
+ acceptedMediaTypes,
260
+ container: makeContainer(),
261
+ })
262
+
263
+ expect(parts.map((part) => part.attachmentId)).toEqual(['img-1', 'pdf-1'])
264
+ expect(console.warn).toHaveBeenCalledWith(
265
+ expect.stringContaining('bin-1'),
266
+ )
267
+ })
268
+
269
+ it('omits the whitelist filter entirely when acceptedMediaTypes is undefined', async () => {
270
+ findOneWithDecryptionMock
271
+ .mockResolvedValueOnce(
272
+ makeRow({ id: 'bin-1', fileName: 'archive.zip', mimeType: 'application/zip', fileSize: 999 }),
273
+ )
274
+
275
+ const parts = await resolveAttachmentParts({
276
+ attachmentIds: ['bin-1'],
277
+ authContext: makeAuth(),
278
+ container: makeContainer(),
279
+ })
280
+
281
+ expect(parts).toHaveLength(1)
282
+ expect(parts[0].attachmentId).toBe('bin-1')
283
+ })
284
+ })
285
+
286
+ describe('resolveAttachmentParts — tenant / org scope enforcement', () => {
287
+ beforeEach(() => {
288
+ jest.clearAllMocks()
289
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
290
+ })
291
+
292
+ afterEach(() => {
293
+ ;(console.warn as jest.Mock).mockRestore?.()
294
+ })
295
+
296
+ it('drops records that belong to a different tenant (non super-admin)', async () => {
297
+ findOneWithDecryptionMock.mockResolvedValueOnce(
298
+ makeRow({
299
+ id: 'cross-1',
300
+ tenantId: 'tenant-OTHER',
301
+ organizationId: 'org-OTHER',
302
+ mimeType: 'image/png',
303
+ fileSize: 64,
304
+ }),
305
+ )
306
+
307
+ const parts = await resolveAttachmentParts({
308
+ attachmentIds: ['cross-1'],
309
+ authContext: makeAuth({ tenantId: 'tenant-1', organizationId: 'org-1' }),
310
+ container: makeContainer(),
311
+ })
312
+
313
+ expect(parts).toEqual([])
314
+ expect(fsReadFileMock).not.toHaveBeenCalled()
315
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('out of scope'))
316
+ })
317
+
318
+ it('lets super-admin callers through regardless of tenant scope', async () => {
319
+ findOneWithDecryptionMock.mockResolvedValueOnce(
320
+ makeRow({
321
+ id: 'cross-2',
322
+ tenantId: 'tenant-OTHER',
323
+ organizationId: 'org-OTHER',
324
+ mimeType: 'application/zip',
325
+ fileSize: 64,
326
+ content: null,
327
+ }),
328
+ )
329
+
330
+ const parts = await resolveAttachmentParts({
331
+ attachmentIds: ['cross-2'],
332
+ authContext: makeAuth({ isSuperAdmin: true }),
333
+ container: makeContainer(),
334
+ })
335
+
336
+ expect(parts).toHaveLength(1)
337
+ expect(parts[0].attachmentId).toBe('cross-2')
338
+ })
339
+
340
+ it('drops ids that do not resolve to a record', async () => {
341
+ findOneWithDecryptionMock.mockResolvedValueOnce(null)
342
+
343
+ const parts = await resolveAttachmentParts({
344
+ attachmentIds: ['missing-1'],
345
+ authContext: makeAuth(),
346
+ container: makeContainer(),
347
+ })
348
+
349
+ expect(parts).toEqual([])
350
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('not found'))
351
+ })
352
+ })
353
+
354
+ describe('resolveAttachmentParts — unavailable service graceful skip', () => {
355
+ beforeEach(() => {
356
+ jest.clearAllMocks()
357
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
358
+ })
359
+
360
+ afterEach(() => {
361
+ ;(console.warn as jest.Mock).mockRestore?.()
362
+ })
363
+
364
+ it('returns [] without throwing when no container is provided', async () => {
365
+ const parts = await resolveAttachmentParts({
366
+ attachmentIds: ['att-1'],
367
+ authContext: makeAuth(),
368
+ })
369
+
370
+ expect(parts).toEqual([])
371
+ expect(findOneWithDecryptionMock).not.toHaveBeenCalled()
372
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('without a DI container'))
373
+ })
374
+
375
+ it('returns [] without throwing when the container cannot resolve `em`', async () => {
376
+ const parts = await resolveAttachmentParts({
377
+ attachmentIds: ['att-1'],
378
+ authContext: makeAuth(),
379
+ container: makeContainer({ omitEm: true }),
380
+ })
381
+
382
+ expect(parts).toEqual([])
383
+ expect(findOneWithDecryptionMock).not.toHaveBeenCalled()
384
+ })
385
+
386
+ it('returns [] without throwing when attachmentIds is empty', async () => {
387
+ const parts = await resolveAttachmentParts({
388
+ attachmentIds: [],
389
+ authContext: makeAuth(),
390
+ container: makeContainer(),
391
+ })
392
+ expect(parts).toEqual([])
393
+ expect(findOneWithDecryptionMock).not.toHaveBeenCalled()
394
+ })
395
+ })
396
+
397
+ describe('resolveAttachmentPartsForAgent', () => {
398
+ beforeEach(() => {
399
+ jest.clearAllMocks()
400
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
401
+ })
402
+
403
+ afterEach(() => {
404
+ ;(console.warn as jest.Mock).mockRestore?.()
405
+ })
406
+
407
+ it('threads the agent acceptedMediaTypes into the resolver', async () => {
408
+ findOneWithDecryptionMock.mockResolvedValueOnce(
409
+ makeRow({ id: 'bin-2', fileName: 'x.zip', mimeType: 'application/zip', fileSize: 1 }),
410
+ )
411
+
412
+ const parts = await resolveAttachmentPartsForAgent({
413
+ agent: {
414
+ id: 'customers.assistant',
415
+ moduleId: 'customers',
416
+ label: 'x',
417
+ description: 'x',
418
+ systemPrompt: 'x',
419
+ allowedTools: [],
420
+ acceptedMediaTypes: ['image', 'pdf'],
421
+ },
422
+ attachmentIds: ['bin-2'],
423
+ authContext: makeAuth(),
424
+ container: makeContainer(),
425
+ })
426
+
427
+ expect(parts).toEqual([])
428
+ })
429
+
430
+ it('short-circuits when attachmentIds is undefined or empty', async () => {
431
+ const noIds = await resolveAttachmentPartsForAgent({
432
+ agent: {
433
+ id: 'a.b',
434
+ moduleId: 'a',
435
+ label: 'x',
436
+ description: 'x',
437
+ systemPrompt: 'x',
438
+ allowedTools: [],
439
+ },
440
+ attachmentIds: undefined,
441
+ authContext: makeAuth(),
442
+ container: makeContainer(),
443
+ })
444
+ expect(noIds).toEqual([])
445
+
446
+ const emptyIds = await resolveAttachmentPartsForAgent({
447
+ agent: {
448
+ id: 'a.b',
449
+ moduleId: 'a',
450
+ label: 'x',
451
+ description: 'x',
452
+ systemPrompt: 'x',
453
+ allowedTools: [],
454
+ },
455
+ attachmentIds: [],
456
+ authContext: makeAuth(),
457
+ container: makeContainer(),
458
+ })
459
+ expect(emptyIds).toEqual([])
460
+ expect(findOneWithDecryptionMock).not.toHaveBeenCalled()
461
+ })
462
+ })
463
+
464
+ describe('attachmentPartsToUiFileParts', () => {
465
+ it('emits a `type: file` part with a data URL for bytes sources', () => {
466
+ const parts = attachmentPartsToUiFileParts([
467
+ {
468
+ attachmentId: 'a',
469
+ fileName: 'photo.png',
470
+ mediaType: 'image/png',
471
+ source: 'bytes',
472
+ data: new Uint8Array([1, 2, 3]),
473
+ },
474
+ ])
475
+ expect(parts).toHaveLength(1)
476
+ expect(parts[0].type).toBe('file')
477
+ expect(parts[0].mediaType).toBe('image/png')
478
+ expect(parts[0].filename).toBe('photo.png')
479
+ expect(parts[0].url.startsWith('data:image/png;base64,')).toBe(true)
480
+ })
481
+
482
+ it('emits a `type: file` part with the raw URL for signed-url sources', () => {
483
+ const parts = attachmentPartsToUiFileParts([
484
+ {
485
+ attachmentId: 'b',
486
+ fileName: 'invoice.pdf',
487
+ mediaType: 'application/pdf',
488
+ source: 'signed-url',
489
+ url: 'https://signed.example/invoice.pdf',
490
+ },
491
+ ])
492
+ expect(parts).toHaveLength(1)
493
+ expect(parts[0].url).toBe('https://signed.example/invoice.pdf')
494
+ })
495
+
496
+ it('drops text and metadata-only sources (surfaced via the system prompt instead)', () => {
497
+ const parts = attachmentPartsToUiFileParts([
498
+ { attachmentId: 'c', fileName: 'n.md', mediaType: 'text/markdown', source: 'text', textContent: 'hi' },
499
+ { attachmentId: 'd', fileName: 'm.bin', mediaType: 'application/octet-stream', source: 'metadata-only' },
500
+ ])
501
+ expect(parts).toEqual([])
502
+ })
503
+ })
504
+
505
+ describe('summarizeAttachmentPartsForPrompt', () => {
506
+ it('returns null for empty inputs', () => {
507
+ expect(summarizeAttachmentPartsForPrompt([])).toBeNull()
508
+ })
509
+
510
+ it('includes the extracted text for text sources', () => {
511
+ const summary = summarizeAttachmentPartsForPrompt([
512
+ {
513
+ attachmentId: 'a',
514
+ fileName: 'notes.md',
515
+ mediaType: 'text/markdown',
516
+ source: 'text',
517
+ textContent: '# Notes',
518
+ },
519
+ {
520
+ attachmentId: 'b',
521
+ fileName: 'photo.png',
522
+ mediaType: 'image/png',
523
+ source: 'bytes',
524
+ },
525
+ ])
526
+ expect(summary).toContain('[ATTACHMENTS]')
527
+ expect(summary).toContain('notes.md')
528
+ expect(summary).toContain('# Notes')
529
+ expect(summary).toContain('photo.png')
530
+ })
531
+ })