@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,136 @@
1
+ import {
2
+ applyPromptOverride,
3
+ composeSystemPromptWithOverride,
4
+ findReservedKeys,
5
+ PromptOverrideReservedKeyError,
6
+ } from '../prompt-override-merge'
7
+ import type { PromptTemplate } from '../prompt-composition-types'
8
+
9
+ function makeTemplate(): PromptTemplate {
10
+ return {
11
+ id: 'test-template',
12
+ sections: [
13
+ { name: 'role', content: 'You are an assistant.' },
14
+ { name: 'scope', content: 'Answer questions only.' },
15
+ { name: 'data', content: 'Scoped to tenant.' },
16
+ { name: 'tools', content: 'Use read-only tools.' },
17
+ { name: 'attachments', content: 'Images accepted.' },
18
+ { name: 'mutationPolicy', content: 'Read-only.' },
19
+ { name: 'responseStyle', content: 'Terse, factual.' },
20
+ ],
21
+ }
22
+ }
23
+
24
+ describe('applyPromptOverride', () => {
25
+ it('is identity when override is empty', () => {
26
+ const template = makeTemplate()
27
+ const result = applyPromptOverride(template, { sections: {} })
28
+ expect(result.sections).toEqual(template.sections)
29
+ expect(result.systemPrompt).toContain('[ROLE]\nYou are an assistant.')
30
+ expect(result.systemPrompt).toContain('[RESPONSE STYLE]\nTerse, factual.')
31
+ })
32
+
33
+ it('is identity when override is null/undefined', () => {
34
+ const template = makeTemplate()
35
+ const nullResult = applyPromptOverride(template, null)
36
+ const undefinedResult = applyPromptOverride(template, undefined)
37
+ expect(nullResult.sections).toEqual(template.sections)
38
+ expect(undefinedResult.sections).toEqual(template.sections)
39
+ })
40
+
41
+ it('appends text to an existing canonical section preserving built-in text', () => {
42
+ const template = makeTemplate()
43
+ const result = applyPromptOverride(template, {
44
+ sections: { role: 'Be friendly.' },
45
+ })
46
+ const role = result.sections.find((s) => s.name === 'role')
47
+ expect(role?.content).toBe('You are an assistant.\n\nBe friendly.')
48
+ expect(result.systemPrompt).toContain('You are an assistant.\n\nBe friendly.')
49
+ })
50
+
51
+ it('appends to MUTATION POLICY via the pretty header key', () => {
52
+ const template = makeTemplate()
53
+ const result = applyPromptOverride(template, {
54
+ sections: { 'MUTATION POLICY': 'Strict.' },
55
+ })
56
+ const section = result.sections.find((s) => s.name === 'mutationPolicy')
57
+ expect(section?.content).toBe('Read-only.\n\nStrict.')
58
+ })
59
+
60
+ it('inserts a brand-new section after RESPONSE STYLE preserving canonical order', () => {
61
+ const template = makeTemplate()
62
+ const result = applyPromptOverride(template, {
63
+ sections: { 'Company Voice': 'Always use the first-person plural.' },
64
+ })
65
+ const names = result.sections.map((s) => s.name)
66
+ const canonicalOrder = [
67
+ 'role',
68
+ 'scope',
69
+ 'data',
70
+ 'tools',
71
+ 'attachments',
72
+ 'mutationPolicy',
73
+ 'responseStyle',
74
+ ]
75
+ // Canonical sections appear in the expected order.
76
+ expect(names.slice(0, canonicalOrder.length)).toEqual(canonicalOrder)
77
+ // The new "overrides" section carrying the brand-new header lands after responseStyle.
78
+ expect(names[canonicalOrder.length]).toBe('overrides')
79
+ expect(result.systemPrompt).toMatch(/COMPANY VOICE/)
80
+ expect(result.systemPrompt).toContain('Always use the first-person plural.')
81
+ // And RESPONSE STYLE still appears before the new section.
82
+ const responseStyleIndex = result.systemPrompt.indexOf('[RESPONSE STYLE]')
83
+ const overridesIndex = result.systemPrompt.indexOf('[OVERRIDES]')
84
+ expect(responseStyleIndex).toBeGreaterThanOrEqual(0)
85
+ expect(overridesIndex).toBeGreaterThan(responseStyleIndex)
86
+ })
87
+
88
+ it('ignores empty / whitespace-only override values', () => {
89
+ const template = makeTemplate()
90
+ const result = applyPromptOverride(template, {
91
+ sections: { role: ' ', scope: '' },
92
+ })
93
+ expect(result.sections).toEqual(template.sections)
94
+ })
95
+
96
+ it('throws when a reserved policy key is present', () => {
97
+ const template = makeTemplate()
98
+ expect(() =>
99
+ applyPromptOverride(template, {
100
+ sections: { mutationPolicy: 'Allow writes', role: 'Be nice.' },
101
+ }),
102
+ ).toThrow(PromptOverrideReservedKeyError)
103
+ })
104
+
105
+ it('findReservedKeys returns matching keys case-insensitively', () => {
106
+ expect(findReservedKeys({ role: 'x' })).toEqual([])
107
+ expect(findReservedKeys({ mutationPolicy: 'x' })).toEqual(['mutationPolicy'])
108
+ expect(findReservedKeys({ READONLY: 'x' })).toEqual(['READONLY'])
109
+ expect(findReservedKeys({ AllowedTools: 'x', extra: 'y' })).toEqual(['AllowedTools'])
110
+ expect(findReservedKeys(null)).toEqual([])
111
+ expect(findReservedKeys(undefined)).toEqual([])
112
+ })
113
+ })
114
+
115
+ describe('composeSystemPromptWithOverride', () => {
116
+ it('returns base prompt when no override is present', () => {
117
+ const result = composeSystemPromptWithOverride('base prompt', null)
118
+ expect(result).toBe('base prompt')
119
+ })
120
+
121
+ it('layers a role-section override onto a plain-string base prompt', () => {
122
+ const result = composeSystemPromptWithOverride('You are a helpful bot.', {
123
+ sections: { role: 'Stay concise.' },
124
+ })
125
+ expect(result).toContain('[ROLE]\nYou are a helpful bot.\n\nStay concise.')
126
+ })
127
+
128
+ it('layers a brand-new section override onto a plain-string base prompt', () => {
129
+ const result = composeSystemPromptWithOverride('Base.', {
130
+ sections: { 'Tenant Voice': 'Use British English.' },
131
+ })
132
+ expect(result).toContain('Base.')
133
+ expect(result).toContain('TENANT VOICE')
134
+ expect(result).toContain('Use British English.')
135
+ })
136
+ })
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ registerGeneratedAiToolEntries,
4
+ type AiToolConfigEntry,
5
+ } from '../tool-loader'
6
+ import { toolRegistry } from '../tool-registry'
7
+ import { convertMcpToolsToAiSdk } from '../mcp-tool-adapter'
8
+ import type { InProcessMcpClient, ToolInfoWithSchema } from '../in-process-client'
9
+
10
+ function makeTool(name: string) {
11
+ return {
12
+ name,
13
+ description: `Tool ${name}`,
14
+ inputSchema: z.object({ q: z.string() }),
15
+ requiredFeatures: [`${name}.view`],
16
+ handler: async (input: { q: string }) => ({ echo: input.q, name }),
17
+ }
18
+ }
19
+
20
+ describe('registerGeneratedAiToolEntries', () => {
21
+ beforeEach(() => {
22
+ toolRegistry.clear()
23
+ })
24
+
25
+ afterAll(() => {
26
+ toolRegistry.clear()
27
+ })
28
+
29
+ it('registers tools from modules that populate ai-tools.ts', () => {
30
+ const entries: AiToolConfigEntry[] = [
31
+ { moduleId: 'search', tools: [makeTool('search_query'), makeTool('search_status')] },
32
+ ]
33
+
34
+ const registered = registerGeneratedAiToolEntries(entries)
35
+
36
+ expect(registered).toBe(2)
37
+ expect(toolRegistry.listToolsByModule('search')).toEqual([
38
+ 'search_query',
39
+ 'search_status',
40
+ ])
41
+ expect(toolRegistry.getTool('search_query')?.requiredFeatures).toEqual(['search_query.view'])
42
+ })
43
+
44
+ it('stays silent for modules without an ai-tools.ts (empty or missing tools)', () => {
45
+ const entries: AiToolConfigEntry[] = [
46
+ { moduleId: 'auth', tools: [] },
47
+ { moduleId: 'catalog', tools: undefined as unknown as unknown[] },
48
+ ]
49
+
50
+ const registered = registerGeneratedAiToolEntries(entries)
51
+
52
+ expect(registered).toBe(0)
53
+ expect(toolRegistry.listToolNames()).toEqual([])
54
+ })
55
+
56
+ it('is idempotent — re-running does not duplicate registrations', () => {
57
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
58
+ const entries: AiToolConfigEntry[] = [
59
+ { moduleId: 'search', tools: [makeTool('search_query')] },
60
+ ]
61
+
62
+ registerGeneratedAiToolEntries(entries)
63
+ registerGeneratedAiToolEntries(entries)
64
+
65
+ expect(toolRegistry.listToolsByModule('search')).toEqual(['search_query'])
66
+ expect(toolRegistry.listToolNames()).toEqual(['search_query'])
67
+ warnSpy.mockRestore()
68
+ })
69
+
70
+ it('skips malformed tool objects instead of throwing', () => {
71
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
72
+ const entries: AiToolConfigEntry[] = [
73
+ {
74
+ moduleId: 'broken',
75
+ tools: [
76
+ { name: 'missing_handler', description: 'x', inputSchema: z.object({}) } as unknown,
77
+ null as unknown,
78
+ makeTool('ok_tool'),
79
+ ],
80
+ },
81
+ ]
82
+
83
+ const registered = registerGeneratedAiToolEntries(entries)
84
+
85
+ expect(registered).toBe(1)
86
+ expect(toolRegistry.getTool('ok_tool')).toBeDefined()
87
+ expect(toolRegistry.getTool('missing_handler')).toBeUndefined()
88
+ warnSpy.mockRestore()
89
+ })
90
+
91
+ it('keeps tools resolvable through mcp-tool-adapter with identical shape', async () => {
92
+ const entries: AiToolConfigEntry[] = [
93
+ { moduleId: 'search', tools: [makeTool('search_query')] },
94
+ ]
95
+ registerGeneratedAiToolEntries(entries)
96
+
97
+ const registered = toolRegistry.getTool('search_query')
98
+ expect(registered).toBeDefined()
99
+ if (!registered) return
100
+
101
+ const mcpTools: ToolInfoWithSchema[] = [
102
+ {
103
+ name: registered.name,
104
+ description: registered.description,
105
+ inputSchema: registered.inputSchema,
106
+ },
107
+ ]
108
+
109
+ const callToolMock = jest.fn(async () => ({ success: true, result: { ok: true } }))
110
+ const fakeClient = { callTool: callToolMock } as unknown as InProcessMcpClient
111
+
112
+ const aiSdkTools = convertMcpToolsToAiSdk(fakeClient, mcpTools)
113
+
114
+ expect(Object.keys(aiSdkTools)).toEqual(['search_query'])
115
+ const adapted = aiSdkTools.search_query as unknown as {
116
+ description: string
117
+ execute: (args: unknown) => Promise<string>
118
+ }
119
+ expect(adapted.description).toBe(registered.description)
120
+
121
+ const out = await adapted.execute({ q: 'hello' })
122
+ expect(callToolMock).toHaveBeenCalledWith('search_query', { q: 'hello' })
123
+ expect(out).toContain('"ok": true')
124
+ })
125
+ })
@@ -0,0 +1,270 @@
1
+ import { getAgent } from './agent-registry'
2
+ import { hasRequiredFeatures } from './auth'
3
+ import { toolRegistry } from './tool-registry'
4
+ import type {
5
+ AiAgentAcceptedMediaType,
6
+ AiAgentDefinition,
7
+ AiAgentMutationPolicy,
8
+ } from './ai-agent-definition'
9
+ import type { AiToolDefinition } from './types'
10
+
11
+ export type AgentPolicyDenyCode =
12
+ | 'agent_unknown'
13
+ | 'agent_features_denied'
14
+ | 'tool_not_whitelisted'
15
+ | 'tool_unknown'
16
+ | 'tool_features_denied'
17
+ | 'mutation_blocked_by_readonly'
18
+ | 'mutation_blocked_by_policy'
19
+ | 'execution_mode_not_supported'
20
+ | 'attachment_type_not_accepted'
21
+
22
+ export type AgentPolicyDecision =
23
+ | { ok: true; agent: AiAgentDefinition; tool?: AiToolDefinition }
24
+ | { ok: false; code: AgentPolicyDenyCode; message: string }
25
+
26
+ export interface AgentPolicyAuthContext {
27
+ userFeatures: string[]
28
+ isSuperAdmin: boolean
29
+ }
30
+
31
+ export interface AgentPolicyCheckInput {
32
+ agentId: string
33
+ authContext: AgentPolicyAuthContext
34
+ toolName?: string
35
+ attachmentMediaTypes?: string[]
36
+ requestedExecutionMode?: 'chat' | 'object'
37
+ /**
38
+ * Optional tenant-scoped downgrade for the agent's code-declared
39
+ * `mutationPolicy`. When supplied, the effective policy is the MOST
40
+ * RESTRICTIVE of `{ code-declared, override }` — escalation is never
41
+ * allowed through this channel (that is enforced at the route layer).
42
+ * Callers that omit this field get the exact pre-Step-5.4 behavior.
43
+ */
44
+ mutationPolicyOverride?: AiAgentMutationPolicy | null
45
+ }
46
+
47
+ /**
48
+ * Restrictiveness ranking of `AiAgentMutationPolicy` (most restrictive first).
49
+ * `read-only` blocks all mutation tools. `destructive-confirm-required` forces
50
+ * confirmation for every write (including non-destructive ones). `confirm-required`
51
+ * is the least restrictive policy — writes go through a single confirmation.
52
+ *
53
+ * This ordering is load-bearing for both the runtime's effective-policy
54
+ * computation AND the route-layer escalation guard. Changing the order is a
55
+ * security-sensitive change.
56
+ */
57
+ const POLICY_RESTRICTIVENESS: Record<AiAgentMutationPolicy, number> = {
58
+ 'read-only': 0,
59
+ 'destructive-confirm-required': 1,
60
+ 'confirm-required': 2,
61
+ }
62
+
63
+ export function isKnownMutationPolicy(value: unknown): value is AiAgentMutationPolicy {
64
+ return (
65
+ value === 'read-only' ||
66
+ value === 'confirm-required' ||
67
+ value === 'destructive-confirm-required'
68
+ )
69
+ }
70
+
71
+ /**
72
+ * Returns the effective mutation policy — the MOST RESTRICTIVE of
73
+ * `{ codeDeclared, override }`. Missing override → `codeDeclared`. A corrupt
74
+ * override value (unknown string from DB) is logged and falls back to
75
+ * `codeDeclared` so the system fails SAFE when a schema drift leaks through.
76
+ */
77
+ export function resolveEffectiveMutationPolicy(
78
+ codeDeclared: AiAgentMutationPolicy | undefined,
79
+ override: AiAgentMutationPolicy | null | undefined,
80
+ agentId?: string,
81
+ ): AiAgentMutationPolicy {
82
+ const base: AiAgentMutationPolicy =
83
+ codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'
84
+ if (override === undefined || override === null) return base
85
+ if (!isKnownMutationPolicy(override)) {
86
+ console.warn(
87
+ `[AI Agents] Ignoring corrupt mutationPolicy override for agent "${agentId ?? '<unknown>'}": ${String(
88
+ override,
89
+ )}. Falling back to code-declared policy "${base}".`,
90
+ )
91
+ return base
92
+ }
93
+ const baseRank = POLICY_RESTRICTIVENESS[base]
94
+ const overrideRank = POLICY_RESTRICTIVENESS[override]
95
+ return overrideRank < baseRank ? override : base
96
+ }
97
+
98
+ /**
99
+ * Returns `true` when `candidate` would WIDEN `codeDeclared` — i.e. would
100
+ * grant the agent more mutation surface than its code declares. Used by the
101
+ * mutation-policy override route to reject escalation attempts with 400.
102
+ */
103
+ export function isMutationPolicyEscalation(
104
+ codeDeclared: AiAgentMutationPolicy | undefined,
105
+ candidate: AiAgentMutationPolicy,
106
+ ): boolean {
107
+ const base: AiAgentMutationPolicy =
108
+ codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'
109
+ return POLICY_RESTRICTIVENESS[candidate] > POLICY_RESTRICTIVENESS[base]
110
+ }
111
+
112
+ function classifyMediaType(value: string): AiAgentAcceptedMediaType {
113
+ const normalized = value.trim().toLowerCase()
114
+ if (normalized.startsWith('image/')) return 'image'
115
+ if (normalized === 'application/pdf') return 'pdf'
116
+ return 'file'
117
+ }
118
+
119
+ function isAgentReadOnly(agent: AiAgentDefinition): boolean {
120
+ if (typeof agent.readOnly === 'boolean') return agent.readOnly
121
+ return true
122
+ }
123
+
124
+ /**
125
+ * Returns the effective mutation policy for a policy-check invocation — the
126
+ * most restrictive of `{ agent.mutationPolicy, input.mutationPolicyOverride }`.
127
+ * Pure-lookup helper; no I/O. Callers that need to know the same value outside
128
+ * of a policy check should use {@link resolveEffectiveMutationPolicy} directly.
129
+ */
130
+ function resolvePolicyCheckMutationPolicy(
131
+ agent: AiAgentDefinition,
132
+ override: AiAgentMutationPolicy | null | undefined,
133
+ ): AiAgentMutationPolicy {
134
+ return resolveEffectiveMutationPolicy(agent.mutationPolicy, override, agent.id)
135
+ }
136
+
137
+ function hasAgentStructuredOutput(agent: AiAgentDefinition): boolean {
138
+ return Boolean(agent.output)
139
+ }
140
+
141
+ function agentExecutionMode(agent: AiAgentDefinition): 'chat' | 'object' {
142
+ return agent.executionMode ?? 'chat'
143
+ }
144
+
145
+ export function checkAgentPolicy(input: AgentPolicyCheckInput): AgentPolicyDecision {
146
+ const {
147
+ agentId,
148
+ authContext,
149
+ toolName,
150
+ attachmentMediaTypes,
151
+ requestedExecutionMode,
152
+ mutationPolicyOverride,
153
+ } = input
154
+
155
+ const agent = getAgent(agentId)
156
+ if (!agent) {
157
+ return {
158
+ ok: false,
159
+ code: 'agent_unknown',
160
+ message: `Agent "${agentId}" is not registered.`,
161
+ }
162
+ }
163
+
164
+ const agentFeatures = agent.requiredFeatures ?? []
165
+ if (
166
+ !hasRequiredFeatures(agentFeatures, authContext.userFeatures, authContext.isSuperAdmin)
167
+ ) {
168
+ return {
169
+ ok: false,
170
+ code: 'agent_features_denied',
171
+ message: `Access to agent "${agentId}" requires features: ${agentFeatures.join(', ')}`,
172
+ }
173
+ }
174
+
175
+ let resolvedTool: AiToolDefinition | undefined
176
+ if (typeof toolName === 'string') {
177
+ if (!agent.allowedTools.includes(toolName)) {
178
+ return {
179
+ ok: false,
180
+ code: 'tool_not_whitelisted',
181
+ message: `Tool "${toolName}" is not whitelisted for agent "${agentId}".`,
182
+ }
183
+ }
184
+
185
+ const toolRecord = toolRegistry.getTool(toolName) as AiToolDefinition | undefined
186
+ if (!toolRecord) {
187
+ return {
188
+ ok: false,
189
+ code: 'tool_unknown',
190
+ message: `Tool "${toolName}" is not registered in the tool registry.`,
191
+ }
192
+ }
193
+
194
+ const toolFeatures = toolRecord.requiredFeatures ?? []
195
+ if (
196
+ !hasRequiredFeatures(toolFeatures, authContext.userFeatures, authContext.isSuperAdmin)
197
+ ) {
198
+ return {
199
+ ok: false,
200
+ code: 'tool_features_denied',
201
+ message: `Access to tool "${toolName}" requires features: ${toolFeatures.join(', ')}`,
202
+ }
203
+ }
204
+
205
+ if (toolRecord.isMutation === true) {
206
+ if (isAgentReadOnly(agent)) {
207
+ return {
208
+ ok: false,
209
+ code: 'mutation_blocked_by_readonly',
210
+ message: `Mutation tool "${toolName}" cannot be executed by read-only agent "${agentId}".`,
211
+ }
212
+ }
213
+ const effectivePolicy = resolvePolicyCheckMutationPolicy(agent, mutationPolicyOverride)
214
+ if (effectivePolicy === 'read-only') {
215
+ return {
216
+ ok: false,
217
+ code: 'mutation_blocked_by_policy',
218
+ message: `Mutation tool "${toolName}" is blocked by agent "${agentId}" mutationPolicy=read-only.`,
219
+ }
220
+ }
221
+ }
222
+
223
+ resolvedTool = toolRecord
224
+ }
225
+
226
+ if (requestedExecutionMode) {
227
+ const declaredMode = agentExecutionMode(agent)
228
+ if (requestedExecutionMode === 'object') {
229
+ if (declaredMode !== 'object' && !hasAgentStructuredOutput(agent)) {
230
+ return {
231
+ ok: false,
232
+ code: 'execution_mode_not_supported',
233
+ message: `Agent "${agentId}" does not support execution mode "object" (no output schema declared).`,
234
+ }
235
+ }
236
+ } else if (requestedExecutionMode === 'chat') {
237
+ if (declaredMode === 'object' && hasAgentStructuredOutput(agent)) {
238
+ return {
239
+ ok: false,
240
+ code: 'execution_mode_not_supported',
241
+ message: `Agent "${agentId}" is declared as object-mode and cannot run via chat transport.`,
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ if (Array.isArray(attachmentMediaTypes) && attachmentMediaTypes.length > 0) {
248
+ const accepted = agent.acceptedMediaTypes
249
+ if (!accepted || accepted.length === 0) {
250
+ return {
251
+ ok: false,
252
+ code: 'attachment_type_not_accepted',
253
+ message: `Agent "${agentId}" does not accept attachments.`,
254
+ }
255
+ }
256
+ const acceptedSet = new Set<AiAgentAcceptedMediaType>(accepted)
257
+ for (const raw of attachmentMediaTypes) {
258
+ const kind = classifyMediaType(raw)
259
+ if (!acceptedSet.has(kind)) {
260
+ return {
261
+ ok: false,
262
+ code: 'attachment_type_not_accepted',
263
+ message: `Agent "${agentId}" does not accept media type "${raw}" (classified as "${kind}").`,
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ return { ok: true, agent, tool: resolvedTool }
270
+ }