@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,253 @@
1
+ import type {
2
+ PromptSection,
3
+ PromptSectionName,
4
+ PromptTemplate,
5
+ } from './prompt-composition-types'
6
+
7
+ /**
8
+ * Canonical prompt section names the built-in agent prompt templates ship.
9
+ * These align with the 7 section headers the spec (§8) mandates, plus the
10
+ * free-form `overrides` bucket the template already reserves.
11
+ */
12
+ export const CANONICAL_PROMPT_SECTIONS: readonly PromptSectionName[] = [
13
+ 'role',
14
+ 'scope',
15
+ 'data',
16
+ 'tools',
17
+ 'attachments',
18
+ 'mutationPolicy',
19
+ 'responseStyle',
20
+ 'overrides',
21
+ ] as const
22
+
23
+ /**
24
+ * Reserved keys that MUST NOT appear in a prompt override. These name policy
25
+ * fields that live on the agent definition itself — allowing them to be
26
+ * modified via the prompt-override layer would let a tenant silently escalate
27
+ * an agent beyond the mutation / tool / attachment contract enforced by
28
+ * `checkAgentPolicy` at dispatch time.
29
+ */
30
+ export const RESERVED_OVERRIDE_KEYS: readonly string[] = [
31
+ 'mutationPolicy',
32
+ 'readOnly',
33
+ 'allowedTools',
34
+ 'acceptedMediaTypes',
35
+ ] as const
36
+
37
+ const HEADER_MAP: Readonly<Record<string, PromptSectionName>> = {
38
+ role: 'role',
39
+ scope: 'scope',
40
+ data: 'data',
41
+ tools: 'tools',
42
+ attachments: 'attachments',
43
+ 'mutation policy': 'mutationPolicy',
44
+ mutation_policy: 'mutationPolicy',
45
+ mutationpolicy: 'mutationPolicy',
46
+ 'response style': 'responseStyle',
47
+ response_style: 'responseStyle',
48
+ responsestyle: 'responseStyle',
49
+ overrides: 'overrides',
50
+ }
51
+
52
+ function canonicalize(key: string): PromptSectionName | null {
53
+ const normalized = key.trim().toLowerCase()
54
+ if (!normalized) return null
55
+ return HEADER_MAP[normalized] ?? null
56
+ }
57
+
58
+ function prettyHeader(name: PromptSectionName): string {
59
+ switch (name) {
60
+ case 'mutationPolicy':
61
+ return 'MUTATION POLICY'
62
+ case 'responseStyle':
63
+ return 'RESPONSE STYLE'
64
+ default:
65
+ return name.toUpperCase()
66
+ }
67
+ }
68
+
69
+ export interface AppliedPromptOverride {
70
+ sections: PromptSection[]
71
+ systemPrompt: string
72
+ }
73
+
74
+ export interface PromptOverrideInput {
75
+ /** Additive text keyed by canonical section id or free-form new header. */
76
+ sections: Record<string, string> | null | undefined
77
+ }
78
+
79
+ /**
80
+ * Applies an additive prompt override to a built-in template. Rules:
81
+ *
82
+ * - A canonical section key (`role`, `scope`, `data`, `tools`, `attachments`,
83
+ * `MUTATION POLICY`, `RESPONSE STYLE`, `overrides`) APPENDS the override
84
+ * text below the built-in section content with a blank line separator.
85
+ * The built-in content is never removed or rewritten.
86
+ * - A non-canonical key is treated as a brand-new section and inserted
87
+ * after the canonical `RESPONSE STYLE` position (before `overrides`,
88
+ * if any). Canonical section order is always preserved.
89
+ * - Empty or whitespace-only override values are ignored.
90
+ * - If any reserved policy key is present (`mutationPolicy`, `readOnly`,
91
+ * `allowedTools`, `acceptedMediaTypes`), this function throws. Call sites
92
+ * SHOULD validate via {@link validatePromptOverrideInput} first so the
93
+ * error surfaces as a 400 to the API caller.
94
+ */
95
+ export function applyPromptOverride(
96
+ template: PromptTemplate,
97
+ override: PromptOverrideInput | null | undefined,
98
+ ): AppliedPromptOverride {
99
+ const baseSections = [...template.sections]
100
+ const sections = override?.sections ?? null
101
+
102
+ if (!sections || typeof sections !== 'object') {
103
+ return {
104
+ sections: baseSections,
105
+ systemPrompt: renderPrompt(baseSections),
106
+ }
107
+ }
108
+
109
+ assertNoReservedKeys(sections)
110
+
111
+ // First pass: append to canonical sections.
112
+ const byCanonical = new Map<PromptSectionName, string>()
113
+ const unknownKeys: Array<{ rawKey: string; value: string }> = []
114
+ for (const [rawKey, value] of Object.entries(sections)) {
115
+ if (typeof value !== 'string') continue
116
+ const trimmed = value.trim()
117
+ if (!trimmed) continue
118
+ const canonical = canonicalize(rawKey)
119
+ if (canonical) {
120
+ const existing = byCanonical.get(canonical)
121
+ byCanonical.set(canonical, existing ? `${existing}\n\n${trimmed}` : trimmed)
122
+ } else {
123
+ unknownKeys.push({ rawKey, value: trimmed })
124
+ }
125
+ }
126
+
127
+ const appended: PromptSection[] = baseSections.map((section) => {
128
+ const addendum = byCanonical.get(section.name)
129
+ if (!addendum) return section
130
+ const nextContent = section.content.trim().length === 0
131
+ ? addendum
132
+ : `${section.content}\n\n${addendum}`
133
+ return { ...section, content: nextContent }
134
+ })
135
+
136
+ // If a canonical section wasn't already present but an override targets it,
137
+ // append it using its canonical header. Catalog / customer templates ship
138
+ // all 7 canonical headers today, but we defend against future templates
139
+ // that may omit some.
140
+ const declaredNames = new Set(appended.map((s) => s.name))
141
+ for (const [canonical, addendum] of byCanonical.entries()) {
142
+ if (!declaredNames.has(canonical)) {
143
+ appended.push({ name: canonical, content: addendum })
144
+ declaredNames.add(canonical)
145
+ }
146
+ }
147
+
148
+ // Brand-new sections inject after RESPONSE STYLE. We insert by splitting the
149
+ // array at the index directly after `responseStyle`. If `overrides` is
150
+ // already present, new sections land before it so `overrides` stays last.
151
+ if (unknownKeys.length > 0) {
152
+ const newSections: PromptSection[] = unknownKeys.map(({ rawKey, value }) => ({
153
+ name: 'overrides',
154
+ content: `[${rawKey.trim().toUpperCase()}]\n${value}`,
155
+ }))
156
+ const responseStyleIndex = appended.findIndex((s) => s.name === 'responseStyle')
157
+ const overridesIndex = appended.findIndex((s) => s.name === 'overrides')
158
+ let insertAt: number
159
+ if (overridesIndex >= 0) {
160
+ insertAt = overridesIndex
161
+ } else if (responseStyleIndex >= 0) {
162
+ insertAt = responseStyleIndex + 1
163
+ } else {
164
+ insertAt = appended.length
165
+ }
166
+ appended.splice(insertAt, 0, ...newSections)
167
+ }
168
+
169
+ return {
170
+ sections: appended,
171
+ systemPrompt: renderPrompt(appended),
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Produces a flat string representation of a prompt template. The runtime uses
177
+ * this when the agent definition declares sections but the underlying call
178
+ * site expects a single-string `systemPrompt` (today: both `runAiAgentText`
179
+ * and `runAiAgentObject`).
180
+ */
181
+ export function renderPrompt(sections: readonly PromptSection[]): string {
182
+ const lines: string[] = []
183
+ for (const section of sections) {
184
+ const content = section.content.trim()
185
+ if (!content) continue
186
+ lines.push(`[${prettyHeader(section.name)}]\n${content}`)
187
+ }
188
+ return lines.join('\n\n')
189
+ }
190
+
191
+ /**
192
+ * Returns the list of reserved keys present in the supplied override payload.
193
+ * Empty array means the body is safe for persistence. Call this before the
194
+ * repository `save` so the API layer can reject with a 400 / `reserved_key`.
195
+ */
196
+ export function findReservedKeys(sections: Record<string, unknown> | null | undefined): string[] {
197
+ if (!sections || typeof sections !== 'object') return []
198
+ const reservedSet = new Set(RESERVED_OVERRIDE_KEYS.map((key) => key.toLowerCase()))
199
+ const hits: string[] = []
200
+ for (const key of Object.keys(sections)) {
201
+ if (reservedSet.has(key.trim().toLowerCase())) {
202
+ hits.push(key)
203
+ }
204
+ }
205
+ return hits
206
+ }
207
+
208
+ function assertNoReservedKeys(sections: Record<string, unknown>): void {
209
+ const hits = findReservedKeys(sections)
210
+ if (hits.length > 0) {
211
+ throw new PromptOverrideReservedKeyError(hits)
212
+ }
213
+ }
214
+
215
+ export class PromptOverrideReservedKeyError extends Error {
216
+ readonly code = 'reserved_key'
217
+ constructor(public readonly keys: readonly string[]) {
218
+ super(
219
+ `Prompt override includes reserved policy keys: ${keys.join(', ')}. ` +
220
+ `Policy fields (${RESERVED_OVERRIDE_KEYS.join(', ')}) are never editable via prompt overrides.`,
221
+ )
222
+ this.name = 'PromptOverrideReservedKeyError'
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Convenience wrapper: composes the full system prompt for a legacy agent that
228
+ * ships a single-string `systemPrompt`. Returns the base unchanged when no
229
+ * override is present. When an override is present, the base is treated as the
230
+ * `role` section and overrides are layered via {@link applyPromptOverride}.
231
+ */
232
+ export function composeSystemPromptWithOverride(
233
+ baseSystemPrompt: string,
234
+ override: PromptOverrideInput | null | undefined,
235
+ ): string {
236
+ if (!override?.sections || Object.keys(override.sections).length === 0) {
237
+ return baseSystemPrompt
238
+ }
239
+ const template: PromptTemplate = {
240
+ id: 'legacy-system-prompt',
241
+ sections: [
242
+ { name: 'role', content: baseSystemPrompt },
243
+ { name: 'scope', content: '' },
244
+ { name: 'data', content: '' },
245
+ { name: 'tools', content: '' },
246
+ { name: 'attachments', content: '' },
247
+ { name: 'mutationPolicy', content: '' },
248
+ { name: 'responseStyle', content: '' },
249
+ ],
250
+ }
251
+ const applied = applyPromptOverride(template, override)
252
+ return applied.systemPrompt
253
+ }
@@ -44,8 +44,13 @@ export function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {
44
44
  const required = (jsonSchema.required as string[]) || []
45
45
  const additionalProperties = jsonSchema.additionalProperties
46
46
 
47
- // Handle z.record() - objects with additionalProperties but no fixed properties
48
- if (additionalProperties && (!properties || Object.keys(properties).length === 0)) {
47
+ // Handle z.record() - objects with additionalProperties but no fixed properties.
48
+ // Skip the record path when properties is present but empty — that is a
49
+ // no-arg object schema, not a dictionary. OpenAI requires `properties: {}`
50
+ // in the JSON Schema, and `z.object({})` produces that, whereas
51
+ // `z.record()` does not.
52
+ const hasFixedProperties = properties && Object.keys(properties).length > 0
53
+ if (additionalProperties && !hasFixedProperties && properties === undefined) {
49
54
  // This is a record/dictionary type - allow any properties
50
55
  if (typeof additionalProperties === 'object') {
51
56
  return z.record(z.string(), jsonSchemaToZod(additionalProperties as Record<string, unknown>))
@@ -127,6 +132,13 @@ export function toSafeZodSchema(schema: ZodType): ZodType {
127
132
  // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types
128
133
  const jsonSchema = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>
129
134
 
135
+ // OpenAI requires `properties` on object schemas. Empty passthrough objects
136
+ // (tools that take no args) roundtrip as `{ type: "object", additionalProperties: true }`
137
+ // without `properties`, which OpenAI rejects. Ensure `properties` is present.
138
+ if (jsonSchema.type === 'object' && !jsonSchema.properties) {
139
+ jsonSchema.properties = {}
140
+ }
141
+
130
142
  // Convert back to a simple Zod schema without Date types
131
143
  const safeSchema = jsonSchemaToZod(jsonSchema)
132
144
 
@@ -3,6 +3,20 @@ import { getToolRegistry } from './tool-registry'
3
3
  import { hasRequiredFeatures } from './auth'
4
4
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
5
5
 
6
+ /**
7
+ * Strip empty strings from object values so LLM-generated `""` for optional
8
+ * fields becomes `undefined` (passes `.optional()` Zod validators).
9
+ */
10
+ function sanitizeEmptyStrings(input: unknown): unknown {
11
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return input
12
+ const result: Record<string, unknown> = {}
13
+ for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
14
+ if (typeof value === 'string' && value.trim() === '') continue
15
+ result[key] = value
16
+ }
17
+ return result
18
+ }
19
+
6
20
  /**
7
21
  * Execute a tool with full context and ACL checks.
8
22
  */
@@ -41,8 +55,13 @@ export async function executeTool(
41
55
  }
42
56
  }
43
57
 
58
+ // LLMs often send empty strings for optional fields (e.g., `personId: ""`).
59
+ // Strip empty strings to `undefined` before Zod parsing so `.uuid().optional()`
60
+ // fields pass validation when the model meant "omit this field".
61
+ const sanitizedInput = sanitizeEmptyStrings(input)
62
+
44
63
  // Input validation
45
- const parseResult = tool.inputSchema.safeParse(input)
64
+ const parseResult = tool.inputSchema.safeParse(sanitizedInput)
46
65
  if (!parseResult.success) {
47
66
  // Use any cast for Zod v4 compatibility
48
67
  const issues = (parseResult.error as any).issues ?? []
@@ -58,9 +77,12 @@ export async function executeTool(
58
77
  }
59
78
  }
60
79
 
61
- // Execute tool
80
+ // Execute tool. Attach `tool` to the context so handlers that build an
81
+ // `AiToolExecutionContext` (e.g. via `createAiApiOperationRunner`) keep their
82
+ // route-gate coverage check working.
83
+ const handlerContext: McpToolContext = { ...context, tool }
62
84
  try {
63
- const result = await tool.handler(parseResult.data, context)
85
+ const result = await tool.handler(parseResult.data, handlerContext)
64
86
  return { success: true, result }
65
87
  } catch (error) {
66
88
  const message = error instanceof Error ? error.message : String(error)
@@ -1,6 +1,11 @@
1
1
  import { z } from 'zod'
2
2
  import type { SearchService } from '@open-mercato/search/service'
3
- import { registerMcpTool, getToolRegistry } from './tool-registry'
3
+ import { registerMcpTool, getToolRegistry, toolRegistry, unregisterMcpTool } from './tool-registry'
4
+ import {
5
+ applyToolOverrideMap,
6
+ composeToolOverrideMap,
7
+ type AiToolOverrideConfigEntry,
8
+ } from './ai-overrides'
4
9
  import type { McpToolDefinition, McpToolContext } from './types'
5
10
  import { ToolSearchService } from './tool-search'
6
11
 
@@ -15,6 +20,27 @@ type ModuleAiTool = {
15
20
  handler: (input: any, ctx: any) => Promise<unknown>
16
21
  }
17
22
 
23
+ /**
24
+ * Shape of a single entry inside `ai-tools.generated.ts`.
25
+ * Matches the structural contract emitted by
26
+ * `packages/cli/src/lib/generators/extensions/ai-tools.ts`.
27
+ */
28
+ export type AiToolConfigEntry = {
29
+ moduleId: string
30
+ tools: unknown[]
31
+ }
32
+
33
+ function isModuleAiTool(value: unknown): value is ModuleAiTool {
34
+ if (!value || typeof value !== 'object') return false
35
+ const candidate = value as Record<string, unknown>
36
+ return (
37
+ typeof candidate.name === 'string' &&
38
+ typeof candidate.description === 'string' &&
39
+ candidate.inputSchema !== undefined &&
40
+ typeof candidate.handler === 'function'
41
+ )
42
+ }
43
+
18
44
  /**
19
45
  * Built-in context.whoami tool that returns the current authentication context.
20
46
  * This is useful for AI to understand its current tenant/org scope.
@@ -42,19 +68,131 @@ const contextWhoamiTool: McpToolDefinition = {
42
68
  *
43
69
  * @param moduleId - The module identifier (e.g., 'search', 'customers')
44
70
  * @param tools - Array of tool definitions from the module
71
+ *
72
+ * IMPORTANT: We register the full typed tool (spread) — never reconstruct a
73
+ * minimal `{ name, description, inputSchema, requiredFeatures, handler }`
74
+ * payload. Stripping fields like `isMutation`, `displayName`, `isBulk`,
75
+ * `loadBeforeRecord(s)` makes the agent settings UI report mutation tools
76
+ * as read-only and prevents the chat dispatcher's mutation interceptor
77
+ * from firing.
45
78
  */
46
79
  export function loadModuleTools(moduleId: string, tools: ModuleAiTool[]): void {
47
80
  for (const tool of tools) {
48
- registerMcpTool(
49
- {
50
- name: tool.name,
51
- description: tool.description,
52
- inputSchema: tool.inputSchema,
53
- requiredFeatures: tool.requiredFeatures,
54
- handler: tool.handler,
55
- } as McpToolDefinition,
56
- { moduleId }
81
+ registerMcpTool(tool as McpToolDefinition, { moduleId })
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Register the generated `aiToolConfigEntries` shape emitted by the
87
+ * `ai-tools.generated.ts` generator extension. Entries with an empty
88
+ * `tools` array stay silent (the generator already filters those out).
89
+ * Invalid tool objects are skipped with a warning instead of throwing.
90
+ *
91
+ * Returns the number of tools actually registered so callers can log it.
92
+ */
93
+ export function registerGeneratedAiToolEntries(entries: AiToolConfigEntry[]): number {
94
+ let registered = 0
95
+ for (const entry of entries) {
96
+ if (!entry || typeof entry.moduleId !== 'string') continue
97
+ if (!Array.isArray(entry.tools) || entry.tools.length === 0) continue
98
+ for (const candidate of entry.tools) {
99
+ if (!isModuleAiTool(candidate)) {
100
+ console.warn(
101
+ `[MCP Tools] Skipping malformed AI tool in module "${entry.moduleId}"`
102
+ )
103
+ continue
104
+ }
105
+ // Register the full typed tool — see comment on `loadModuleTools`.
106
+ registerMcpTool(candidate as McpToolDefinition, { moduleId: entry.moduleId })
107
+ registered += 1
108
+ }
109
+ }
110
+ return registered
111
+ }
112
+
113
+ /**
114
+ * Apply the list of `aiToolOverrides` exports collected by the generator
115
+ * (one entry per module) to the live tool registry. Tools mapped to
116
+ * `null` are unregistered; tools mapped to a full definition replace the
117
+ * existing registration. Module load order controls precedence — last
118
+ * entry wins. The `modules.ts`-tier and programmatic overrides (set via
119
+ * {@link applyAiOverridesFromEnabledModules} and
120
+ * {@link applyAiToolOverrides}) supersede file-based entries.
121
+ *
122
+ * Safe to call when no override file is present (the entries array is
123
+ * empty); it is a no-op then.
124
+ */
125
+ export function applyAiToolOverrideEntries(
126
+ entries: readonly AiToolOverrideConfigEntry[],
127
+ ): void {
128
+ const overrideMap = composeToolOverrideMap(entries)
129
+ if (Object.keys(overrideMap).length === 0) return
130
+ const baseTools = toolRegistry.getTools() as Map<string, McpToolDefinition>
131
+ const overridden = applyToolOverrideMap<McpToolDefinition>(baseTools, overrideMap)
132
+ for (const [name, value] of Object.entries(overrideMap)) {
133
+ if (value === null) {
134
+ unregisterMcpTool(name)
135
+ console.info(`[AI Overrides] Tool "${name}" disabled by override.`)
136
+ continue
137
+ }
138
+ const next = overridden.get(name)
139
+ if (!next) continue
140
+ // Re-register through the public path so moduleMap stays consistent.
141
+ registerMcpTool(next as McpToolDefinition, { moduleId: 'ai_overrides' })
142
+ console.info(`[AI Overrides] Tool "${name}" replaced by override.`)
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Read `aiToolOverrideEntries` from `ai-tools.generated.ts` and apply
148
+ * them on top of the live tool registry. Safe to call when the file is
149
+ * missing (pre-generate builds, tests) — applies only the
150
+ * `modules.ts`-tier and programmatic overrides in that case.
151
+ */
152
+ export async function loadGeneratedAiToolOverrides(): Promise<void> {
153
+ let entries: AiToolOverrideConfigEntry[] = []
154
+ try {
155
+ const mod = (await import(
156
+ '@/.mercato/generated/ai-tools.generated'
157
+ )) as { aiToolOverrideEntries?: unknown[] }
158
+ entries = Array.isArray(mod.aiToolOverrideEntries)
159
+ ? (mod.aiToolOverrideEntries as AiToolOverrideConfigEntry[])
160
+ : []
161
+ } catch {
162
+ // No override file generated.
163
+ }
164
+ applyAiToolOverrideEntries(entries)
165
+ }
166
+
167
+ /**
168
+ * Load the generated `ai-tools.generated.ts` file emitted by
169
+ * `yarn generate` and register every declared module tool through the
170
+ * existing `registerMcpTool` path. Safe to call when the generated file
171
+ * is missing (e.g., tests or pre-generate builds) — returns 0.
172
+ */
173
+ export async function loadGeneratedModuleAiTools(): Promise<number> {
174
+ try {
175
+ const mod = (await import(
176
+ '@/.mercato/generated/ai-tools.generated'
177
+ )) as { aiToolConfigEntries?: AiToolConfigEntry[] }
178
+ const entries = Array.isArray(mod.aiToolConfigEntries)
179
+ ? mod.aiToolConfigEntries
180
+ : []
181
+ const count = registerGeneratedAiToolEntries(entries)
182
+ // Apply module-to-module + programmatic tool overrides AFTER the base
183
+ // registrations so disable / replace semantics work end-to-end.
184
+ try {
185
+ await loadGeneratedAiToolOverrides()
186
+ } catch (error) {
187
+ console.error('[AI Overrides] Failed to apply tool overrides:', error)
188
+ }
189
+ return count
190
+ } catch (error) {
191
+ console.error(
192
+ '[MCP Tools] Could not load ai-tools.generated.ts (module tools unavailable):',
193
+ error
57
194
  )
195
+ return 0
58
196
  }
59
197
  }
60
198
 
@@ -79,9 +217,17 @@ export async function loadAllModuleTools(): Promise<void> {
79
217
  console.error('[MCP Tools] Could not load Code Mode tools:', error)
80
218
  }
81
219
 
82
- // Note: Auto-discovered module AI tools (from ai-tools.generated.ts) and
83
- // legacy API discovery tools (find_api, call_api, discover_schema) are no
84
- // longer loaded. Code Mode's search + execute tools cover all use cases.
220
+ // 3. Register module-contributed tools from ai-tools.generated.ts.
221
+ // Code Mode stays untouched; module tools are additive. Missing
222
+ // generated file is not fatal (pre-generate builds, tests).
223
+ try {
224
+ const moduleToolCount = await loadGeneratedModuleAiTools()
225
+ console.error(
226
+ `[MCP Tools] Registered ${moduleToolCount} module-contributed AI tools from ai-tools.generated.ts`
227
+ )
228
+ } catch (error) {
229
+ console.error('[MCP Tools] Could not load module AI tools:', error)
230
+ }
85
231
  }
86
232
 
87
233
  /**