@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,432 @@
1
+ import { z } from 'zod'
2
+ import { createAiApiOperationRunner, normalizePath, type AiToolExecutionContext } from '../ai-api-operation-runner'
3
+ import type { ApiRouteManifestEntry } from '@open-mercato/shared/modules/registry'
4
+ import {
5
+ TRUSTED_AUTH_CONTEXT_SYMBOL,
6
+ resolveAuthFromRequestDetailed,
7
+ type AuthContext,
8
+ } from '@open-mercato/shared/lib/auth/server'
9
+ import type { AiToolDefinition, McpToolContext } from '../types'
10
+
11
+ type CapturedHandlerCall = {
12
+ request: Request
13
+ params: Record<string, string | string[]>
14
+ }
15
+
16
+ function makeTool(
17
+ overrides: Partial<AiToolDefinition> & Pick<AiToolDefinition, 'name'>,
18
+ ): AiToolDefinition {
19
+ return {
20
+ description: `${overrides.name} description`,
21
+ inputSchema: z.object({}).passthrough(),
22
+ handler: async () => ({ ok: true }),
23
+ ...overrides,
24
+ }
25
+ }
26
+
27
+ function makeCtx(tool: AiToolDefinition): AiToolExecutionContext {
28
+ const baseTool: McpToolContext = {
29
+ tenantId: 'tenant-1',
30
+ organizationId: 'org-1',
31
+ userId: 'user-1',
32
+ container: {} as McpToolContext['container'],
33
+ userFeatures: ['*'],
34
+ isSuperAdmin: true,
35
+ }
36
+ return { ...baseTool, tool }
37
+ }
38
+
39
+ function makeManifestEntry(overrides: Partial<ApiRouteManifestEntry> & {
40
+ path: string
41
+ load: ApiRouteManifestEntry['load']
42
+ methods: ApiRouteManifestEntry['methods']
43
+ }): ApiRouteManifestEntry {
44
+ return {
45
+ moduleId: 'test',
46
+ kind: 'route-file',
47
+ ...overrides,
48
+ }
49
+ }
50
+
51
+ describe('createAiApiOperationRunner', () => {
52
+ let fetchSpy: jest.SpyInstance | null = null
53
+
54
+ beforeEach(() => {
55
+ if (typeof globalThis.fetch === 'function') {
56
+ fetchSpy = jest.spyOn(globalThis, 'fetch').mockImplementation(() => {
57
+ throw new Error('fetch must not be called by the in-process runner')
58
+ })
59
+ } else {
60
+ const mock = jest.fn(() => {
61
+ throw new Error('fetch must not be called by the in-process runner')
62
+ })
63
+ ;(globalThis as unknown as { fetch: typeof fetch }).fetch = mock as unknown as typeof fetch
64
+ fetchSpy = mock as unknown as jest.SpyInstance
65
+ }
66
+ })
67
+
68
+ afterEach(() => {
69
+ fetchSpy?.mockRestore?.()
70
+ fetchSpy = null
71
+ })
72
+
73
+ it('resolves a documented GET route and invokes it in-process with parsed query', async () => {
74
+ const captured: CapturedHandlerCall[] = []
75
+ const handler = jest.fn(async (req: Request, ctx?: { params: Record<string, string | string[]> }) => {
76
+ captured.push({ request: req, params: ctx?.params ?? {} })
77
+ return new Response(JSON.stringify({ items: [{ id: 'p1' }], total: 1 }), {
78
+ status: 200,
79
+ headers: { 'content-type': 'application/json' },
80
+ })
81
+ })
82
+ const apiRoutes: ApiRouteManifestEntry[] = [
83
+ makeManifestEntry({
84
+ path: '/customers/people',
85
+ methods: ['GET'],
86
+ load: async () => ({
87
+ GET: handler,
88
+ openApi: { tag: 'Customers', methods: { GET: {} } },
89
+ metadata: { GET: { requireAuth: true, requireFeatures: ['customers.people.view'] } },
90
+ }),
91
+ }),
92
+ ]
93
+
94
+ const tool = makeTool({ name: 'customers.list_people', requiredFeatures: ['customers.people.view'] })
95
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
96
+
97
+ const result = await runner.run({
98
+ method: 'GET',
99
+ path: '/customers/people',
100
+ query: { search: 'taylor', page: 1 },
101
+ })
102
+
103
+ expect(result).toEqual({
104
+ success: true,
105
+ statusCode: 200,
106
+ data: { items: [{ id: 'p1' }], total: 1 },
107
+ })
108
+ expect(handler).toHaveBeenCalledTimes(1)
109
+ expect(captured).toHaveLength(1)
110
+ expect(captured[0].request.method).toBe('GET')
111
+ const url = new URL(captured[0].request.url)
112
+ expect(url.pathname).toBe('/api/customers/people')
113
+ expect(url.searchParams.get('search')).toBe('taylor')
114
+ expect(url.searchParams.get('page')).toBe('1')
115
+ expect(fetchSpy).not.toHaveBeenCalled()
116
+ })
117
+
118
+ it('rejects undocumented endpoints (route module without openApi export)', async () => {
119
+ const handler = jest.fn(async () => new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } }))
120
+ const apiRoutes: ApiRouteManifestEntry[] = [
121
+ makeManifestEntry({
122
+ path: '/internal/secret',
123
+ methods: ['GET'],
124
+ load: async () => ({
125
+ GET: handler,
126
+ metadata: { GET: { requireAuth: true, requireFeatures: ['internal.view'] } },
127
+ }),
128
+ }),
129
+ ]
130
+
131
+ const tool = makeTool({ name: 'internal.list', requiredFeatures: ['internal.view'] })
132
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
133
+
134
+ const result = await runner.run({ method: 'GET', path: '/internal/secret' })
135
+
136
+ expect(result.success).toBe(false)
137
+ expect(result.statusCode).toBe(501)
138
+ expect(result.error).toMatch(/undocumented/i)
139
+ expect(handler).not.toHaveBeenCalled()
140
+ })
141
+
142
+ it('rejects when the manifest entry does not declare the requested method', async () => {
143
+ const handler = jest.fn(async () => new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } }))
144
+ const apiRoutes: ApiRouteManifestEntry[] = [
145
+ makeManifestEntry({
146
+ path: '/customers/people',
147
+ methods: ['GET'],
148
+ load: async () => ({
149
+ GET: handler,
150
+ openApi: { tag: 'Customers', methods: { GET: {} } },
151
+ }),
152
+ }),
153
+ ]
154
+
155
+ const tool = makeTool({ name: 'customers.create_person', requiredFeatures: ['customers.people.manage'] })
156
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
157
+
158
+ const result = await runner.run({ method: 'POST', path: '/customers/people', body: { name: 'Taylor' } })
159
+
160
+ expect(result.success).toBe(false)
161
+ expect(result.statusCode).toBe(404)
162
+ })
163
+
164
+ it('rejects mutation routes that declare no requiredFeatures unless allowFeaturelessMutation is set', async () => {
165
+ const handler = jest.fn(async () => new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } }))
166
+ const apiRoutes: ApiRouteManifestEntry[] = [
167
+ makeManifestEntry({
168
+ path: '/customers/loose',
169
+ methods: ['POST'],
170
+ load: async () => ({
171
+ POST: handler,
172
+ openApi: { tag: 'Customers', methods: { POST: {} } },
173
+ metadata: { POST: { requireAuth: true } },
174
+ }),
175
+ }),
176
+ ]
177
+
178
+ const tool = makeTool({ name: 'customers.loose_write', requiredFeatures: ['customers.people.manage'], isMutation: true })
179
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
180
+
181
+ const denied = await runner.run({ method: 'POST', path: '/customers/loose', body: {} })
182
+ expect(denied.success).toBe(false)
183
+ expect(denied.statusCode).toBe(403)
184
+ expect(denied.error).toMatch(/requiredFeatures/i)
185
+ expect(handler).not.toHaveBeenCalled()
186
+
187
+ const allowed = await runner.run({
188
+ method: 'POST',
189
+ path: '/customers/loose',
190
+ body: {},
191
+ allowFeaturelessMutation: true,
192
+ })
193
+ expect(allowed.success).toBe(true)
194
+ expect(allowed.statusCode).toBe(200)
195
+ expect(handler).toHaveBeenCalledTimes(1)
196
+ })
197
+
198
+ it('rejects when the tool requiredFeatures do not cover the route requiredFeatures', async () => {
199
+ const handler = jest.fn(async () => new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } }))
200
+ const apiRoutes: ApiRouteManifestEntry[] = [
201
+ makeManifestEntry({
202
+ path: '/customers/people',
203
+ methods: ['GET'],
204
+ load: async () => ({
205
+ GET: handler,
206
+ openApi: { tag: 'Customers', methods: { GET: {} } },
207
+ metadata: { GET: { requireAuth: true, requireFeatures: ['customers.people.view'] } },
208
+ }),
209
+ }),
210
+ ]
211
+
212
+ const tool = makeTool({ name: 'customers.weak_tool', requiredFeatures: ['catalog.products.view'] })
213
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
214
+
215
+ const result = await runner.run({ method: 'GET', path: '/customers/people' })
216
+
217
+ expect(result.success).toBe(false)
218
+ expect(result.statusCode).toBe(403)
219
+ expect(result.error).toMatch(/do not cover/i)
220
+ expect(handler).not.toHaveBeenCalled()
221
+ })
222
+
223
+ it('propagates the AI tool auth context onto the synthetic Request and the shared resolver short-circuits to it', async () => {
224
+ let resolved: AuthContext = null
225
+ const handler = jest.fn(async (req: Request) => {
226
+ resolved = (await resolveAuthFromRequestDetailed(req)).auth
227
+ const carrier = req as unknown as Record<symbol, unknown>
228
+ const envelope = carrier[TRUSTED_AUTH_CONTEXT_SYMBOL]
229
+ expect(envelope).toBeTruthy()
230
+ return new Response(JSON.stringify({ ok: true }), {
231
+ status: 200,
232
+ headers: { 'content-type': 'application/json' },
233
+ })
234
+ })
235
+ const apiRoutes: ApiRouteManifestEntry[] = [
236
+ makeManifestEntry({
237
+ path: '/customers/people',
238
+ methods: ['GET'],
239
+ load: async () => ({
240
+ GET: handler,
241
+ openApi: { tag: 'Customers', methods: { GET: {} } },
242
+ metadata: { GET: { requireAuth: true, requireFeatures: ['customers.people.view'] } },
243
+ }),
244
+ }),
245
+ ]
246
+
247
+ const tool = makeTool({ name: 'customers.list_people', requiredFeatures: ['customers.people.view'] })
248
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
249
+
250
+ const result = await runner.run({ method: 'GET', path: '/customers/people' })
251
+
252
+ expect(result.success).toBe(true)
253
+ expect(resolved).not.toBeNull()
254
+ expect(resolved?.tenantId).toBe('tenant-1')
255
+ expect(resolved?.orgId).toBe('org-1')
256
+ expect(resolved?.userId).toBe('user-1')
257
+ expect(resolved?.sub).toBe('user-1')
258
+ expect(resolved?.isSuperAdmin).toBe(true)
259
+ })
260
+
261
+ it('normalizes a 4xx JSON error response into { success: false, statusCode, error, details }', async () => {
262
+ const apiRoutes: ApiRouteManifestEntry[] = [
263
+ makeManifestEntry({
264
+ path: '/customers/people',
265
+ methods: ['POST'],
266
+ load: async () => ({
267
+ POST: async () => new Response(
268
+ JSON.stringify({ error: 'Validation failed', fieldErrors: { name: ['required'] } }),
269
+ { status: 422, headers: { 'content-type': 'application/json' } },
270
+ ),
271
+ openApi: { tag: 'Customers', methods: { POST: {} } },
272
+ metadata: { POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] } },
273
+ }),
274
+ }),
275
+ ]
276
+
277
+ const tool = makeTool({ name: 'customers.create_person', requiredFeatures: ['customers.people.manage'], isMutation: true })
278
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
279
+
280
+ const result = await runner.run({ method: 'POST', path: '/customers/people', body: { name: '' } })
281
+
282
+ expect(result).toEqual({
283
+ success: false,
284
+ statusCode: 422,
285
+ error: 'Validation failed',
286
+ details: { fieldErrors: { name: ['required'] } },
287
+ })
288
+ })
289
+
290
+ it('normalizes a 2xx JSON response into { success: true, statusCode, data }', async () => {
291
+ const apiRoutes: ApiRouteManifestEntry[] = [
292
+ makeManifestEntry({
293
+ path: '/customers/people',
294
+ methods: ['POST'],
295
+ load: async () => ({
296
+ POST: async () => new Response(
297
+ JSON.stringify({ id: 'p1', personId: 'p1' }),
298
+ { status: 201, headers: { 'content-type': 'application/json' } },
299
+ ),
300
+ openApi: { tag: 'Customers', methods: { POST: {} } },
301
+ metadata: { POST: { requireAuth: true, requireFeatures: ['customers.people.manage'] } },
302
+ }),
303
+ }),
304
+ ]
305
+
306
+ const tool = makeTool({ name: 'customers.create_person', requiredFeatures: ['customers.people.manage'], isMutation: true })
307
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
308
+
309
+ const result = await runner.run({ method: 'POST', path: '/customers/people', body: { name: 'Taylor' } })
310
+
311
+ expect(result).toEqual({
312
+ success: true,
313
+ statusCode: 201,
314
+ data: { id: 'p1', personId: 'p1' },
315
+ })
316
+ })
317
+
318
+ it('routes dynamic path segments into the handler params', async () => {
319
+ let captured: { request: Request; params: Record<string, string | string[]> } | null = null
320
+ const handler = jest.fn(async (req: Request, ctx?: { params: Record<string, string | string[]> }) => {
321
+ captured = { request: req, params: ctx?.params ?? {} }
322
+ return new Response(
323
+ JSON.stringify({ id: ctx?.params?.itemId ?? null }),
324
+ { status: 200, headers: { 'content-type': 'application/json' } },
325
+ )
326
+ })
327
+ const apiRoutes: ApiRouteManifestEntry[] = [
328
+ makeManifestEntry({
329
+ path: '/dashboards/layout/[itemId]',
330
+ methods: ['PATCH'],
331
+ load: async () => ({
332
+ PATCH: handler,
333
+ openApi: { tag: 'Dashboards', methods: { PATCH: {} } },
334
+ metadata: { PATCH: { requireAuth: true, requireFeatures: ['dashboards.manage'] } },
335
+ }),
336
+ }),
337
+ ]
338
+
339
+ const tool = makeTool({ name: 'dashboards.update_layout_item', requiredFeatures: ['dashboards.manage'], isMutation: true })
340
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
341
+
342
+ const result = await runner.run({
343
+ method: 'PATCH',
344
+ path: '/dashboards/layout/abc-123',
345
+ body: { x: 0, y: 0 },
346
+ })
347
+
348
+ expect(result).toEqual({ success: true, statusCode: 200, data: { id: 'abc-123' } })
349
+ expect(captured).not.toBeNull()
350
+ expect(captured!.params.itemId).toBe('abc-123')
351
+ })
352
+
353
+ it('falls back to default export for legacy route entries', async () => {
354
+ const legacyHandler = jest.fn(async () => new Response(
355
+ JSON.stringify({ ok: true }),
356
+ { status: 200, headers: { 'content-type': 'application/json' } },
357
+ ))
358
+ const apiRoutes: ApiRouteManifestEntry[] = [
359
+ makeManifestEntry({
360
+ moduleId: 'directory',
361
+ kind: 'legacy',
362
+ method: 'GET',
363
+ path: '/directory/organizations/lookup',
364
+ methods: ['GET'],
365
+ load: async () => ({
366
+ default: legacyHandler,
367
+ openApi: { tag: 'Directory', methods: { GET: {} } },
368
+ metadata: { GET: { requireAuth: false } },
369
+ }),
370
+ }),
371
+ ]
372
+
373
+ const tool = makeTool({ name: 'directory.lookup_org' })
374
+ const runner = createAiApiOperationRunner(makeCtx(tool), { apiRoutes })
375
+
376
+ const result = await runner.run({ method: 'GET', path: '/directory/organizations/lookup', query: { slug: 'acme' } })
377
+
378
+ expect(result.success).toBe(true)
379
+ expect(legacyHandler).toHaveBeenCalledTimes(1)
380
+ })
381
+ })
382
+
383
+ describe('normalizePath (CodeQL js/polynomial-redos regression)', () => {
384
+ it('returns "/" for empty / non-string input', () => {
385
+ expect(normalizePath('')).toBe('/')
386
+ // Defensive: the type system says string-only, but agent inputs are JSON.
387
+ expect(normalizePath(undefined as unknown as string)).toBe('/')
388
+ expect(normalizePath(null as unknown as string)).toBe('/')
389
+ })
390
+
391
+ it('preserves a path that already starts with "/" and has no trailing slash', () => {
392
+ expect(normalizePath('/api/customers/people')).toBe('/api/customers/people')
393
+ })
394
+
395
+ it('prepends "/" when the input does not start with one', () => {
396
+ expect(normalizePath('api/foo')).toBe('/api/foo')
397
+ })
398
+
399
+ it('strips a single trailing slash', () => {
400
+ expect(normalizePath('/api/foo/')).toBe('/api/foo')
401
+ })
402
+
403
+ it('strips multiple trailing slashes', () => {
404
+ expect(normalizePath('/api/foo///')).toBe('/api/foo')
405
+ expect(normalizePath('/a/b/c////')).toBe('/a/b/c')
406
+ })
407
+
408
+ it('returns "/" for the all-slashes edge case (matches the previous regex behavior)', () => {
409
+ expect(normalizePath('/')).toBe('/')
410
+ expect(normalizePath('//')).toBe('/')
411
+ expect(normalizePath('////')).toBe('/')
412
+ })
413
+
414
+ it('runs in linear time on long runs of trailing slashes (no polynomial backtracking)', () => {
415
+ // The previous implementation `trimmed.replace(/\/+$/, '')` was flagged by
416
+ // CodeQL js/polynomial-redos. A 1M-character all-slash input must complete
417
+ // in a small bounded budget — anything in the multi-second range would
418
+ // indicate the regex regression has crept back in.
419
+ const huge = '/'.repeat(1_000_000)
420
+ const start = Date.now()
421
+ const out = normalizePath(huge)
422
+ const elapsed = Date.now() - start
423
+ expect(out).toBe('/')
424
+ // 200ms is generous; the linear scan typically finishes in <20ms.
425
+ expect(elapsed).toBeLessThan(200)
426
+ })
427
+
428
+ it('only strips trailing slashes — never internal ones', () => {
429
+ expect(normalizePath('/api//customers//people')).toBe('/api//customers//people')
430
+ expect(normalizePath('/api//customers//people///')).toBe('/api//customers//people')
431
+ })
432
+ })