@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
@@ -1,14 +1,9 @@
1
1
  import type { ModuleCli } from '@open-mercato/shared/modules/registry'
2
2
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
3
- import { fileURLToPath } from 'node:url'
4
- import { dirname, resolve } from 'node:path'
5
- import { pathToFileURL } from 'node:url'
6
-
7
3
  /**
8
4
  * Ensure app bootstrap is called before creating DI container.
9
- * Uses import.meta.url for runtime path resolution since @/ alias
10
- * doesn't work with dynamic imports (TypeScript path aliases are
11
- * compile-time only, not available to Node.js at runtime).
5
+ * Uses the shared generated-bootstrap loader so the command works both from
6
+ * the monorepo app and from standalone apps installed through npm packages.
12
7
  */
13
8
  async function ensureBootstrap(): Promise<void> {
14
9
  // First check if DI is already available
@@ -20,23 +15,9 @@ async function ensureBootstrap(): Promise<void> {
20
15
  // DI not available, need to bootstrap
21
16
  }
22
17
 
23
- // Construct absolute path to bootstrap using import.meta.url
24
18
  try {
25
- const __filename = fileURLToPath(import.meta.url)
26
- const __dirname = dirname(__filename)
27
- // From packages/ai-assistant/src/modules/ai_assistant/cli.ts
28
- // to apps/mercato/src/bootstrap.ts:
29
- // ai_assistant → modules → src → ai-assistant → packages → root (6 levels)
30
- // then into apps/mercato/src/bootstrap.ts
31
- const bootstrapPath = resolve(__dirname, '../../../../../../apps/mercato/src/bootstrap.ts')
32
-
33
- // Dynamic import using file URL
34
- const bootstrapUrl = pathToFileURL(bootstrapPath).href
35
- const { bootstrap, isBootstrapped } = await import(bootstrapUrl)
36
-
37
- if (!isBootstrapped()) {
38
- bootstrap()
39
- }
19
+ const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')
20
+ await bootstrapFromAppRoot()
40
21
  } catch (error) {
41
22
  console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)
42
23
  // Continue - some contexts may not have bootstrap available
@@ -271,4 +252,98 @@ const entityGraph: ModuleCli = {
271
252
  },
272
253
  }
273
254
 
274
- export default [mcpServe, mcpServeHttp, mcpDev, listTools, entityGraph]
255
+ const runPendingActionCleanup: ModuleCli = {
256
+ command: 'run-pending-action-cleanup',
257
+ async run() {
258
+ await ensureBootstrap()
259
+ const container = await createRequestContainer()
260
+
261
+ const { runPendingActionCleanup: runCleanup } = await import(
262
+ './workers/ai-pending-action-cleanup'
263
+ )
264
+
265
+ const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')
266
+ const summary = await runCleanup({ em })
267
+
268
+ console.log('[ai-pending-action-cleanup] Sweep complete:', summary)
269
+ },
270
+ }
271
+
272
+ const testTools: ModuleCli = {
273
+ command: 'test-tools',
274
+ async run(rest) {
275
+ const args = parseArgs(rest)
276
+ const json = args.json === true || args.json === 'true'
277
+ const moduleFilter =
278
+ typeof args.module === 'string' && args.module.length > 0 ? args.module : null
279
+ const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'
280
+ const tenantId =
281
+ typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null
282
+ const organizationId =
283
+ typeof args.org === 'string' && args.org.length > 0 ? args.org : null
284
+
285
+ await ensureBootstrap()
286
+ const { runToolTests } = await import('./lib/tool-test-runner')
287
+ const report = await runToolTests({
288
+ tenantId,
289
+ organizationId,
290
+ moduleFilter,
291
+ includeMutations,
292
+ })
293
+
294
+ if (json) {
295
+ // Wrap in markers so a Playwright spec (or any caller) can extract the
296
+ // JSON payload without being thrown off by bootstrap log lines emitted
297
+ // to stdout by other modules during DI container creation.
298
+ console.log('---TOOL_TEST_REPORT_BEGIN---')
299
+ console.log(JSON.stringify(report))
300
+ console.log('---TOOL_TEST_REPORT_END---')
301
+ } else {
302
+ console.log('')
303
+ console.log(
304
+ `AI tool test report — tenant=${report.tenantId ?? '<none>'} org=${
305
+ report.organizationId ?? '<none>'
306
+ }`,
307
+ )
308
+ console.log(
309
+ `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,
310
+ )
311
+ const byModule = new Map<string, typeof report.records>()
312
+ for (const record of report.records) {
313
+ const list = byModule.get(record.module) ?? []
314
+ list.push(record)
315
+ byModule.set(record.module, list)
316
+ }
317
+ const sortedModules = Array.from(byModule.keys()).sort()
318
+ for (const moduleId of sortedModules) {
319
+ const list = byModule.get(moduleId)!
320
+ console.log('')
321
+ console.log(`${moduleId} (${list.length}):`)
322
+ for (const record of list) {
323
+ const marker =
324
+ record.status === 'pass' ? '✓' : record.status === 'fail' ? '✗' : '·'
325
+ const reason = record.reason ? ` — ${record.reason}` : ''
326
+ const mutation = record.isMutation ? ' [mutation]' : ''
327
+ console.log(
328
+ ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,
329
+ )
330
+ }
331
+ }
332
+ console.log('')
333
+ }
334
+
335
+ if (report.failed > 0) {
336
+ process.exitCode = 1
337
+ }
338
+ },
339
+ }
340
+
341
+ export default [
342
+ mcpServe,
343
+ mcpServeHttp,
344
+ mcpDev,
345
+ listTools,
346
+ entityGraph,
347
+ runPendingActionCleanup,
348
+ testTools,
349
+ ]
@@ -0,0 +1,69 @@
1
+ import { readFileSync } from 'node:fs'
2
+
3
+ const migrationCases = [
4
+ {
5
+ file: 'Migration20260419100521.ts',
6
+ orgIndex: 'create unique index "ai_agent_prompt_overrides_tenant_org_agent_version_uq"',
7
+ tenantIndex:
8
+ 'create unique index "ai_agent_prompt_overrides_tenant_agent_version_null_org_uq"',
9
+ },
10
+ {
11
+ file: 'Migration20260419132948.ts',
12
+ orgIndex:
13
+ 'create unique index "ai_agent_mutation_policy_overrides_tenant_org_agent_uq"',
14
+ tenantIndex:
15
+ 'create unique index "ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq"',
16
+ },
17
+ {
18
+ file: 'Migration20260419134235.ts',
19
+ orgIndex: 'create unique index "ai_pending_actions_tenant_org_idempotency_uq"',
20
+ tenantIndex: 'create unique index "ai_pending_actions_tenant_idem_null_org_uq"',
21
+ },
22
+ ]
23
+
24
+ describe('AI assistant nullable organization unique indexes', () => {
25
+ it.each(migrationCases)(
26
+ '$file uses partial unique indexes for org and tenant-wide rows',
27
+ ({ file, orgIndex, tenantIndex }) => {
28
+ const source = readFileSync(
29
+ new URL(`../../migrations/${file}`, import.meta.url),
30
+ 'utf8',
31
+ )
32
+
33
+ expect(source).toContain(orgIndex)
34
+ expect(source).toContain('where "organization_id" is not null')
35
+ expect(source).toContain(tenantIndex)
36
+ expect(source).toContain('where "organization_id" is null')
37
+ expect(source).not.toContain(' add constraint ')
38
+ },
39
+ )
40
+
41
+ it('keeps the module snapshot aligned with partial unique indexes', () => {
42
+ const snapshot = JSON.parse(
43
+ readFileSync(
44
+ new URL('../../migrations/.snapshot-open-mercato.json', import.meta.url),
45
+ 'utf8',
46
+ ),
47
+ ) as {
48
+ tables: Array<{ name: string; indexes: Array<{ keyName: string; expression?: string }> }>
49
+ }
50
+
51
+ const indexes = new Map<string, string>()
52
+ for (const table of snapshot.tables) {
53
+ for (const index of table.indexes) {
54
+ if (index.expression) indexes.set(index.keyName, index.expression)
55
+ }
56
+ }
57
+
58
+ for (const { orgIndex, tenantIndex } of migrationCases) {
59
+ const orgIndexName = orgIndex.match(/"([^"]+)"/)?.[1]
60
+ const tenantIndexName = tenantIndex.match(/"([^"]+)"/)?.[1]
61
+ expect(orgIndexName ? indexes.get(orgIndexName) : undefined).toContain(
62
+ 'where "organization_id" is not null',
63
+ )
64
+ expect(tenantIndexName ? indexes.get(tenantIndexName) : undefined).toContain(
65
+ 'where "organization_id" is null',
66
+ )
67
+ }
68
+ })
69
+ })
@@ -0,0 +1,7 @@
1
+ // Canonical entity class lives in `../entities.ts` (single-file aggregate, same
2
+ // pattern as `packages/core/src/modules/customers/data/entities.ts`). This
3
+ // thin re-export exists so call sites that prefer the by-name import path
4
+ // (`.../entities/AiAgentMutationPolicyOverride`) keep working.
5
+
6
+ export { AiAgentMutationPolicyOverride } from '../entities'
7
+ export type { AiAgentMutationPolicyOverride as default } from '../entities'
@@ -0,0 +1,7 @@
1
+ // Canonical entity class lives in `../entities.ts` (single-file aggregate, same
2
+ // pattern as `packages/core/src/modules/customers/data/entities.ts`). This
3
+ // thin re-export exists so call sites that prefer the by-name import path
4
+ // (`.../entities/AiAgentPromptOverride`) keep working.
5
+
6
+ export { AiAgentPromptOverride } from '../entities'
7
+ export type { AiAgentPromptOverride as default } from '../entities'
@@ -0,0 +1,7 @@
1
+ // Canonical entity class lives in `../entities.ts` (single-file aggregate, same
2
+ // pattern as `packages/core/src/modules/customers/data/entities.ts`). This
3
+ // thin re-export exists so call sites that prefer the by-name import path
4
+ // (`.../entities/AiPendingAction`) keep working.
5
+
6
+ export { AiPendingAction } from '../entities'
7
+ export type { AiPendingAction as default } from '../entities'
@@ -0,0 +1,270 @@
1
+ import { OptionalProps } from '@mikro-orm/core'
2
+ import {
3
+ Entity,
4
+ Index,
5
+ PrimaryKey,
6
+ Property,
7
+ } from '@mikro-orm/decorators/legacy'
8
+ import type {
9
+ AiPendingActionExecutionResult,
10
+ AiPendingActionFailedRecord,
11
+ AiPendingActionFieldDiff,
12
+ AiPendingActionQueueMode,
13
+ AiPendingActionRecordDiff,
14
+ AiPendingActionStatus,
15
+ } from '../lib/pending-action-types'
16
+
17
+ /**
18
+ * Versioned additive prompt-override for a registered AI agent (Step 5.3).
19
+ *
20
+ * Each write creates a new row with `version = latest + 1`. Rows are never
21
+ * updated in place — history is preserved so operators can roll back by
22
+ * reading an earlier `version`. Column set is tenant/org-scoped per the
23
+ * standard Open Mercato RBAC contract.
24
+ *
25
+ * `sections` holds additive text keyed by prompt section id. The runtime
26
+ * composes the final `systemPrompt` via `composeSystemPromptWithOverride`
27
+ * (see `lib/prompt-override-merge.ts`), which NEVER replaces a built-in
28
+ * section — overrides are append-only by contract.
29
+ */
30
+ @Entity({ tableName: 'ai_agent_prompt_overrides' })
31
+ @Index({
32
+ name: 'ai_agent_prompt_overrides_tenant_org_agent_version_uq',
33
+ expression:
34
+ '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',
35
+ })
36
+ @Index({
37
+ name: 'ai_agent_prompt_overrides_tenant_agent_version_null_org_uq',
38
+ expression:
39
+ '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',
40
+ })
41
+ @Index({
42
+ name: 'ai_agent_prompt_overrides_tenant_agent_idx',
43
+ properties: ['tenantId', 'agentId'],
44
+ })
45
+ @Index({
46
+ name: 'ai_agent_prompt_overrides_tenant_org_agent_version_idx',
47
+ expression:
48
+ 'create index "ai_agent_prompt_overrides_tenant_org_agent_version_idx" on "ai_agent_prompt_overrides" ("tenant_id", "organization_id", "agent_id", "version" desc)',
49
+ })
50
+ export class AiAgentPromptOverride {
51
+ [OptionalProps]?: 'createdAt' | 'updatedAt' | 'organizationId' | 'createdByUserId' | 'notes'
52
+
53
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
54
+ id!: string
55
+
56
+ @Property({ name: 'tenant_id', type: 'uuid' })
57
+ tenantId!: string
58
+
59
+ @Property({ name: 'organization_id', type: 'uuid', nullable: true })
60
+ organizationId?: string | null
61
+
62
+ @Property({ name: 'agent_id', type: 'text' })
63
+ agentId!: string
64
+
65
+ @Property({ name: 'version', type: 'int' })
66
+ version!: number
67
+
68
+ @Property({ name: 'sections', type: 'jsonb' })
69
+ sections!: Record<string, string>
70
+
71
+ @Property({ name: 'notes', type: 'text', nullable: true })
72
+ notes?: string | null
73
+
74
+ @Property({ name: 'created_by_user_id', type: 'uuid', nullable: true })
75
+ createdByUserId?: string | null
76
+
77
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
78
+ createdAt: Date = new Date()
79
+
80
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
81
+ updatedAt: Date = new Date()
82
+ }
83
+
84
+ /**
85
+ * Persistent mutation-approval gate row backing the Phase 3 WS-C contract
86
+ * (spec §8 `AiPendingAction` + §9 confirm/cancel flow, Step 5.5).
87
+ *
88
+ * One row is created by `prepareMutation` (Step 5.6) whenever the runtime
89
+ * intercepts an `isMutation: true` tool call from a non-read-only agent.
90
+ * The row stores the normalized tool input, a precomputed `fieldDiff` (or
91
+ * per-record batch diff in `records[]`), the target record version, an
92
+ * `idempotencyKey` that dedupes double-submits within the TTL, and a
93
+ * `status` that walks the state machine defined in
94
+ * {@link AI_PENDING_ACTION_ALLOWED_TRANSITIONS}.
95
+ *
96
+ * The cleanup worker (Step 5.12) sweeps `status='pending' AND expiresAt < now`
97
+ * rows and transitions them to `expired`. The confirm route (Step 5.8)
98
+ * walks `pending → confirmed → executing → (failed | terminal success)`.
99
+ * Reads always flow through `findOneWithDecryption` /
100
+ * `findWithDecryption`, even though no column is GDPR-flagged today, so
101
+ * future encrypted columns (e.g. `normalizedInput`) are handled.
102
+ */
103
+ @Entity({ tableName: 'ai_pending_actions' })
104
+ @Index({
105
+ name: 'ai_pending_actions_tenant_org_idempotency_uq',
106
+ expression:
107
+ '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',
108
+ })
109
+ @Index({
110
+ name: 'ai_pending_actions_tenant_idem_null_org_uq',
111
+ expression:
112
+ 'create unique index "ai_pending_actions_tenant_idem_null_org_uq" on "ai_pending_actions" ("tenant_id", "idempotency_key") where "organization_id" is null',
113
+ })
114
+ @Index({
115
+ name: 'ai_pending_actions_tenant_org_status_expires_idx',
116
+ properties: ['tenantId', 'organizationId', 'status', 'expiresAt'],
117
+ })
118
+ @Index({
119
+ name: 'ai_pending_actions_tenant_org_agent_status_idx',
120
+ properties: ['tenantId', 'organizationId', 'agentId', 'status'],
121
+ })
122
+ export class AiPendingAction {
123
+ [OptionalProps]?:
124
+ | 'createdAt'
125
+ | 'organizationId'
126
+ | 'conversationId'
127
+ | 'targetEntityType'
128
+ | 'targetRecordId'
129
+ | 'fieldDiff'
130
+ | 'records'
131
+ | 'failedRecords'
132
+ | 'sideEffectsSummary'
133
+ | 'recordVersion'
134
+ | 'attachmentIds'
135
+ | 'executionResult'
136
+ | 'resolvedAt'
137
+ | 'resolvedByUserId'
138
+ | 'queueMode'
139
+
140
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
141
+ id!: string
142
+
143
+ @Property({ name: 'tenant_id', type: 'uuid' })
144
+ tenantId!: string
145
+
146
+ @Property({ name: 'organization_id', type: 'uuid', nullable: true })
147
+ organizationId?: string | null
148
+
149
+ @Property({ name: 'agent_id', type: 'text' })
150
+ agentId!: string
151
+
152
+ @Property({ name: 'tool_name', type: 'text' })
153
+ toolName!: string
154
+
155
+ @Property({ name: 'conversation_id', type: 'text', nullable: true })
156
+ conversationId?: string | null
157
+
158
+ @Property({ name: 'target_entity_type', type: 'text', nullable: true })
159
+ targetEntityType?: string | null
160
+
161
+ @Property({ name: 'target_record_id', type: 'text', nullable: true })
162
+ targetRecordId?: string | null
163
+
164
+ @Property({ name: 'normalized_input', type: 'jsonb' })
165
+ normalizedInput!: Record<string, unknown>
166
+
167
+ @Property({ name: 'field_diff', type: 'jsonb', default: [] })
168
+ fieldDiff: AiPendingActionFieldDiff[] = []
169
+
170
+ @Property({ name: 'records', type: 'jsonb', nullable: true })
171
+ records?: AiPendingActionRecordDiff[] | null
172
+
173
+ @Property({ name: 'failed_records', type: 'jsonb', nullable: true })
174
+ failedRecords?: AiPendingActionFailedRecord[] | null
175
+
176
+ @Property({ name: 'side_effects_summary', type: 'text', nullable: true })
177
+ sideEffectsSummary?: string | null
178
+
179
+ @Property({ name: 'record_version', type: 'text', nullable: true })
180
+ recordVersion?: string | null
181
+
182
+ @Property({ name: 'attachment_ids', type: 'jsonb', default: [] })
183
+ attachmentIds: string[] = []
184
+
185
+ @Property({ name: 'idempotency_key', type: 'text' })
186
+ idempotencyKey!: string
187
+
188
+ @Property({ name: 'created_by_user_id', type: 'uuid' })
189
+ createdByUserId!: string
190
+
191
+ @Property({ name: 'status', type: 'text' })
192
+ status!: AiPendingActionStatus
193
+
194
+ @Property({ name: 'queue_mode', type: 'text', default: 'inline' })
195
+ queueMode: AiPendingActionQueueMode = 'inline'
196
+
197
+ @Property({ name: 'execution_result', type: 'jsonb', nullable: true })
198
+ executionResult?: AiPendingActionExecutionResult | null
199
+
200
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
201
+ createdAt: Date = new Date()
202
+
203
+ @Property({ name: 'expires_at', type: Date })
204
+ expiresAt!: Date
205
+
206
+ @Property({ name: 'resolved_at', type: Date, nullable: true })
207
+ resolvedAt?: Date | null
208
+
209
+ @Property({ name: 'resolved_by_user_id', type: 'uuid', nullable: true })
210
+ resolvedByUserId?: string | null
211
+ }
212
+
213
+ /**
214
+ * Tenant-scoped override of an agent's declared `mutationPolicy` (Step 5.4).
215
+ *
216
+ * Unlike {@link AiAgentPromptOverride}, this surface is NOT versioned — it is
217
+ * a single-value policy switch per `(tenantId, organizationId, agentId)`. The
218
+ * runtime enforces the override as a DOWNGRADE only: the effective policy
219
+ * equals the MOST RESTRICTIVE of `{ code-declared, override }`. Escalation is
220
+ * a code-level change and is rejected at the route layer.
221
+ *
222
+ * Hierarchy (most restrictive → least): `read-only` < `destructive-confirm-required`
223
+ * < `confirm-required`. The route never allows an override to widen the
224
+ * code-declared policy.
225
+ */
226
+ @Entity({ tableName: 'ai_agent_mutation_policy_overrides' })
227
+ @Index({
228
+ name: 'ai_agent_mutation_policy_overrides_tenant_org_agent_uq',
229
+ expression:
230
+ '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',
231
+ })
232
+ @Index({
233
+ name: 'ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq',
234
+ expression:
235
+ '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',
236
+ })
237
+ @Index({
238
+ name: 'ai_agent_mutation_policy_overrides_tenant_agent_idx',
239
+ properties: ['tenantId', 'agentId'],
240
+ })
241
+ export class AiAgentMutationPolicyOverride {
242
+ [OptionalProps]?: 'createdAt' | 'updatedAt' | 'organizationId' | 'createdByUserId' | 'notes'
243
+
244
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
245
+ id!: string
246
+
247
+ @Property({ name: 'tenant_id', type: 'uuid' })
248
+ tenantId!: string
249
+
250
+ @Property({ name: 'organization_id', type: 'uuid', nullable: true })
251
+ organizationId?: string | null
252
+
253
+ @Property({ name: 'agent_id', type: 'text' })
254
+ agentId!: string
255
+
256
+ @Property({ name: 'mutation_policy', type: 'text' })
257
+ mutationPolicy!: string
258
+
259
+ @Property({ name: 'notes', type: 'text', nullable: true })
260
+ notes?: string | null
261
+
262
+ @Property({ name: 'created_by_user_id', type: 'uuid', nullable: true })
263
+ createdByUserId?: string | null
264
+
265
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
266
+ createdAt: Date = new Date()
267
+
268
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
269
+ updatedAt: Date = new Date()
270
+ }
@@ -0,0 +1,129 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import {
3
+ findOneWithDecryption,
4
+ } from '@open-mercato/shared/lib/encryption/find'
5
+ import { AiAgentMutationPolicyOverride } from '../entities'
6
+ import type { AiAgentMutationPolicy } from '../../lib/ai-agent-definition'
7
+
8
+ export interface AiAgentMutationPolicyOverrideContext {
9
+ tenantId: string
10
+ organizationId?: string | null
11
+ userId?: string | null
12
+ }
13
+
14
+ export interface AiAgentMutationPolicyOverrideInput {
15
+ agentId: string
16
+ mutationPolicy: AiAgentMutationPolicy
17
+ notes?: string | null
18
+ }
19
+
20
+ /**
21
+ * Single-value mutation-policy override repository (Step 5.4).
22
+ *
23
+ * Unlike the versioned prompt override, there is ONE current mutation-policy
24
+ * override per `(tenantId, organizationId, agentId)`. `set()` replaces the
25
+ * existing row (or inserts a new one) inside a transaction so concurrent
26
+ * writers cannot produce ghost rows. `clear()` deletes the row so the runtime
27
+ * falls back to the code-declared policy.
28
+ *
29
+ * Reads always go through `findOneWithDecryption` to stay consistent with the
30
+ * rest of the module even though `mutation_policy` isn't encrypted today.
31
+ */
32
+ export class AiAgentMutationPolicyOverrideRepository {
33
+ constructor(private readonly em: EntityManager) {}
34
+
35
+ async get(
36
+ agentId: string,
37
+ ctx: AiAgentMutationPolicyOverrideContext,
38
+ ): Promise<AiAgentMutationPolicyOverride | null> {
39
+ if (!agentId || !ctx?.tenantId) return null
40
+ const row = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
41
+ this.em,
42
+ AiAgentMutationPolicyOverride,
43
+ {
44
+ tenantId: ctx.tenantId,
45
+ organizationId: ctx.organizationId ?? null,
46
+ agentId,
47
+ } as any,
48
+ {},
49
+ { tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
50
+ )
51
+ return row ?? null
52
+ }
53
+
54
+ async set(
55
+ input: AiAgentMutationPolicyOverrideInput,
56
+ ctx: AiAgentMutationPolicyOverrideContext,
57
+ ): Promise<AiAgentMutationPolicyOverride> {
58
+ if (!ctx?.tenantId) {
59
+ throw new Error('AiAgentMutationPolicyOverrideRepository.set requires tenantId')
60
+ }
61
+ if (!input?.agentId) {
62
+ throw new Error('AiAgentMutationPolicyOverrideRepository.set requires agentId')
63
+ }
64
+ const normalized = normalizeNotes(input.notes)
65
+ return this.em.transactional(async (tx) => {
66
+ const existing = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
67
+ tx as unknown as EntityManager,
68
+ AiAgentMutationPolicyOverride,
69
+ {
70
+ tenantId: ctx.tenantId,
71
+ organizationId: ctx.organizationId ?? null,
72
+ agentId: input.agentId,
73
+ } as any,
74
+ {},
75
+ { tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
76
+ )
77
+ if (existing) {
78
+ existing.mutationPolicy = input.mutationPolicy
79
+ existing.notes = normalized
80
+ existing.createdByUserId = ctx.userId ?? existing.createdByUserId ?? null
81
+ existing.updatedAt = new Date()
82
+ await tx.persist(existing).flush()
83
+ return existing
84
+ }
85
+ const row = tx.create(AiAgentMutationPolicyOverride, {
86
+ tenantId: ctx.tenantId,
87
+ organizationId: ctx.organizationId ?? null,
88
+ agentId: input.agentId,
89
+ mutationPolicy: input.mutationPolicy,
90
+ notes: normalized,
91
+ createdByUserId: ctx.userId ?? null,
92
+ } as unknown as AiAgentMutationPolicyOverride)
93
+ await tx.persist(row).flush()
94
+ return row
95
+ })
96
+ }
97
+
98
+ async clear(
99
+ agentId: string,
100
+ ctx: AiAgentMutationPolicyOverrideContext,
101
+ ): Promise<boolean> {
102
+ if (!agentId || !ctx?.tenantId) return false
103
+ return this.em.transactional(async (tx) => {
104
+ const existing = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
105
+ tx as unknown as EntityManager,
106
+ AiAgentMutationPolicyOverride,
107
+ {
108
+ tenantId: ctx.tenantId,
109
+ organizationId: ctx.organizationId ?? null,
110
+ agentId,
111
+ } as any,
112
+ {},
113
+ { tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
114
+ )
115
+ if (!existing) return false
116
+ await tx.remove(existing).flush()
117
+ return true
118
+ })
119
+ }
120
+ }
121
+
122
+ function normalizeNotes(notes: string | null | undefined): string | null {
123
+ if (typeof notes !== 'string') return null
124
+ const trimmed = notes.trim()
125
+ if (!trimmed) return null
126
+ return notes
127
+ }
128
+
129
+ export default AiAgentMutationPolicyOverrideRepository