@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +361 -0
- package/README.md +5 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js +73 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js +484 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js +251 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js +91 -0
- package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js.map +7 -0
- package/dist/modules/ai_assistant/ai-tools/attachments-pack.js +202 -0
- package/dist/modules/ai_assistant/ai-tools/attachments-pack.js.map +7 -0
- package/dist/modules/ai_assistant/ai-tools/meta-pack.js +121 -0
- package/dist/modules/ai_assistant/ai-tools/meta-pack.js.map +7 -0
- package/dist/modules/ai_assistant/ai-tools/search-pack.js +94 -0
- package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +7 -0
- package/dist/modules/ai_assistant/ai-tools.js +14 -0
- package/dist/modules/ai_assistant/ai-tools.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js +175 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js +174 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js +101 -0
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js +311 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js +246 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js +94 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/chat/route.js +173 -0
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/run-object/route.js +167 -0
- package/dist/modules/ai_assistant/api/ai/run-object/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +1111 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js +30 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +4 -6
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +1 -21
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +462 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +78 -12
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities/AiPendingAction.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiPendingAction.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +228 -0
- package/dist/modules/ai_assistant/data/entities.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js +95 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js +95 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js +223 -0
- package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js.map +7 -0
- package/dist/modules/ai_assistant/events.js +33 -0
- package/dist/modules/ai_assistant/events.js.map +7 -0
- package/dist/modules/ai_assistant/i18n/de.json +252 -0
- package/dist/modules/ai_assistant/i18n/en.json +252 -0
- package/dist/modules/ai_assistant/i18n/es.json +252 -0
- package/dist/modules/ai_assistant/i18n/pl.json +252 -0
- package/dist/modules/ai_assistant/lib/agent-policy.js +168 -0
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +7 -0
- package/dist/modules/ai_assistant/lib/agent-registry.js +195 -0
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +7 -0
- package/dist/modules/ai_assistant/lib/agent-runtime.js +451 -0
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +7 -0
- package/dist/modules/ai_assistant/lib/agent-tools.js +223 -0
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +7 -0
- package/dist/modules/ai_assistant/lib/agent-transport.js +25 -0
- package/dist/modules/ai_assistant/lib/agent-transport.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js +11 -0
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js +1 -0
- package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js +239 -0
- package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-overrides.js +189 -0
- package/dist/modules/ai_assistant/lib/ai-overrides.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-tool-definition.js +7 -0
- package/dist/modules/ai_assistant/lib/ai-tool-definition.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js +1 -0
- package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-backed-tool.js +48 -0
- package/dist/modules/ai_assistant/lib/api-backed-tool.js.map +7 -0
- package/dist/modules/ai_assistant/lib/attachment-bridge-types.js +1 -0
- package/dist/modules/ai_assistant/lib/attachment-bridge-types.js.map +7 -0
- package/dist/modules/ai_assistant/lib/attachment-parts.js +276 -0
- package/dist/modules/ai_assistant/lib/attachment-parts.js.map +7 -0
- package/dist/modules/ai_assistant/lib/model-factory.js +68 -0
- package/dist/modules/ai_assistant/lib/model-factory.js.map +7 -0
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js +86 -0
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +7 -0
- package/dist/modules/ai_assistant/lib/pending-action-client.js +35 -0
- package/dist/modules/ai_assistant/lib/pending-action-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/pending-action-executor.js +243 -0
- package/dist/modules/ai_assistant/lib/pending-action-executor.js.map +7 -0
- package/dist/modules/ai_assistant/lib/pending-action-recheck.js +246 -0
- package/dist/modules/ai_assistant/lib/pending-action-recheck.js.map +7 -0
- package/dist/modules/ai_assistant/lib/pending-action-types.js +70 -0
- package/dist/modules/ai_assistant/lib/pending-action-types.js.map +7 -0
- package/dist/modules/ai_assistant/lib/prepare-mutation.js +315 -0
- package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +7 -0
- package/dist/modules/ai_assistant/lib/prompt-composition-types.js +7 -0
- package/dist/modules/ai_assistant/lib/prompt-composition-types.js.map +7 -0
- package/dist/modules/ai_assistant/lib/prompt-override-merge.js +175 -0
- package/dist/modules/ai_assistant/lib/prompt-override-merge.js.map +7 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js +5 -1
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-executor.js +13 -2
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-loader.js +86 -11
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-test-fixtures.js +120 -0
- package/dist/modules/ai_assistant/lib/tool-test-fixtures.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-test-runner.js +418 -0
- package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419100521.js +17 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419100521.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419132948.js +16 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419132948.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419134235.js +17 -0
- package/dist/modules/ai_assistant/migrations/Migration20260419134235.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +36 -0
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js +161 -0
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js.map +7 -0
- package/generated/entities/ai_agent_mutation_policy_override/index.ts +9 -0
- package/generated/entities/ai_agent_prompt_override/index.ts +10 -0
- package/generated/entities/ai_pending_action/index.ts +24 -0
- package/generated/entities.ids.generated.ts +13 -0
- package/generated/entity-fields-registry.ts +57 -0
- package/jest.config.cjs +7 -0
- package/package.json +4 -4
- package/src/index.ts +215 -0
- package/src/modules/ai_assistant/__integration__/README.md +5 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.ts +115 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.ts +574 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.ts +333 -0
- package/src/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.ts +135 -0
- package/src/modules/ai_assistant/__tests__/events.test.ts +145 -0
- package/src/modules/ai_assistant/__tests__/integration/pending-action-contract.test.ts +1015 -0
- package/src/modules/ai_assistant/__tests__/integration/ws-c-attachment-bridge.test.ts +235 -0
- package/src/modules/ai_assistant/__tests__/integration/ws-c-policy-and-tools.test.ts +330 -0
- package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +285 -0
- package/src/modules/ai_assistant/ai-tools/__tests__/attachments-pack.test.ts +322 -0
- package/src/modules/ai_assistant/ai-tools/__tests__/meta-pack.test.ts +218 -0
- package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +192 -0
- package/src/modules/ai_assistant/ai-tools/attachments-pack.ts +269 -0
- package/src/modules/ai_assistant/ai-tools/meta-pack.ts +140 -0
- package/src/modules/ai_assistant/ai-tools/search-pack.ts +122 -0
- package/src/modules/ai_assistant/ai-tools.ts +21 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/__tests__/route.test.ts +222 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/__tests__/route.test.ts +286 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/route.ts +237 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/__tests__/route.test.ts +339 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/route.ts +229 -0
- package/src/modules/ai_assistant/api/ai/actions/[id]/route.ts +142 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/__tests__/route.test.ts +367 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.ts +380 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/__tests__/route.test.ts +333 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.ts +307 -0
- package/src/modules/ai_assistant/api/ai/agents/route.ts +107 -0
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +282 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +207 -0
- package/src/modules/ai_assistant/api/ai/run-object/__tests__/route.test.ts +282 -0
- package/src/modules/ai_assistant/api/ai/run-object/route.ts +204 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +1419 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.ts +28 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +8 -23
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +15 -10
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +604 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +99 -24
- package/src/modules/ai_assistant/data/__tests__/schema-unique-indexes.test.ts +69 -0
- package/src/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.ts +7 -0
- package/src/modules/ai_assistant/data/entities/AiAgentPromptOverride.ts +7 -0
- package/src/modules/ai_assistant/data/entities/AiPendingAction.ts +7 -0
- package/src/modules/ai_assistant/data/entities.ts +270 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.ts +129 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.ts +132 -0
- package/src/modules/ai_assistant/data/repositories/AiPendingActionRepository.ts +334 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentMutationPolicyOverrideRepository.test.ts +195 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentPromptOverrideRepository.test.ts +197 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiPendingActionRepository.test.ts +357 -0
- package/src/modules/ai_assistant/events.ts +112 -0
- package/src/modules/ai_assistant/i18n/de.json +252 -0
- package/src/modules/ai_assistant/i18n/en.json +252 -0
- package/src/modules/ai_assistant/i18n/es.json +252 -0
- package/src/modules/ai_assistant/i18n/pl.json +252 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-policy.mutation-override.test.ts +203 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-policy.test.ts +385 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +217 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-object.test.ts +329 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-parity.test.ts +573 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +291 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-tools.test.ts +172 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-transport.test.ts +41 -0
- package/src/modules/ai_assistant/lib/__tests__/ai-agent-definition.test.ts +183 -0
- package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +432 -0
- package/src/modules/ai_assistant/lib/__tests__/ai-overrides.test.ts +308 -0
- package/src/modules/ai_assistant/lib/__tests__/api-backed-tool.test.ts +302 -0
- package/src/modules/ai_assistant/lib/__tests__/attachment-bridge-and-prompt-types.test.ts +188 -0
- package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +531 -0
- package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +263 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.integration.test.ts +183 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +168 -0
- package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +235 -0
- package/src/modules/ai_assistant/lib/__tests__/pending-action-client.test.ts +148 -0
- package/src/modules/ai_assistant/lib/__tests__/pending-action-executor.test.ts +348 -0
- package/src/modules/ai_assistant/lib/__tests__/pending-action-recheck.test.ts +378 -0
- package/src/modules/ai_assistant/lib/__tests__/phase-0-additive-contract.test.ts +299 -0
- package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +610 -0
- package/src/modules/ai_assistant/lib/__tests__/prompt-override-merge.test.ts +136 -0
- package/src/modules/ai_assistant/lib/__tests__/tool-loader.test.ts +125 -0
- package/src/modules/ai_assistant/lib/agent-policy.ts +270 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +277 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +751 -0
- package/src/modules/ai_assistant/lib/agent-tools.ts +396 -0
- package/src/modules/ai_assistant/lib/agent-transport.ts +51 -0
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +86 -0
- package/src/modules/ai_assistant/lib/ai-agents-generated.d.ts +18 -0
- package/src/modules/ai_assistant/lib/ai-api-operation-runner.ts +333 -0
- package/src/modules/ai_assistant/lib/ai-overrides.ts +389 -0
- package/src/modules/ai_assistant/lib/ai-tool-definition.ts +7 -0
- package/src/modules/ai_assistant/lib/ai-tools-generated.d.ts +7 -0
- package/src/modules/ai_assistant/lib/api-backed-tool.ts +85 -0
- package/src/modules/ai_assistant/lib/attachment-bridge-types.ts +24 -0
- package/src/modules/ai_assistant/lib/attachment-parts.ts +433 -0
- package/src/modules/ai_assistant/lib/model-factory.ts +212 -0
- package/src/modules/ai_assistant/lib/pending-action-cancel.ts +179 -0
- package/src/modules/ai_assistant/lib/pending-action-client.ts +126 -0
- package/src/modules/ai_assistant/lib/pending-action-executor.ts +424 -0
- package/src/modules/ai_assistant/lib/pending-action-recheck.ts +410 -0
- package/src/modules/ai_assistant/lib/pending-action-types.ts +194 -0
- package/src/modules/ai_assistant/lib/prepare-mutation.ts +448 -0
- package/src/modules/ai_assistant/lib/prompt-composition-types.ts +24 -0
- package/src/modules/ai_assistant/lib/prompt-override-merge.ts +253 -0
- package/src/modules/ai_assistant/lib/schema-utils.ts +14 -2
- package/src/modules/ai_assistant/lib/tool-executor.ts +25 -3
- package/src/modules/ai_assistant/lib/tool-loader.ts +159 -13
- package/src/modules/ai_assistant/lib/tool-test-fixtures.ts +160 -0
- package/src/modules/ai_assistant/lib/tool-test-runner.ts +596 -0
- package/src/modules/ai_assistant/lib/types.ts +105 -2
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +871 -0
- package/src/modules/ai_assistant/migrations/Migration20260419100521.ts +17 -0
- package/src/modules/ai_assistant/migrations/Migration20260419132948.ts +16 -0
- package/src/modules/ai_assistant/migrations/Migration20260419134235.ts +17 -0
- package/src/modules/ai_assistant/setup.ts +53 -0
- package/src/modules/ai_assistant/workers/__tests__/ai-pending-action-cleanup.test.ts +333 -0
- package/src/modules/ai_assistant/workers/ai-pending-action-cleanup.ts +269 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process AI tool test runner.
|
|
3
|
+
*
|
|
4
|
+
* Iterates every tool registered in `ai-tools.generated.ts`, invokes the
|
|
5
|
+
* handler against a super-admin tenant context, and returns a structured
|
|
6
|
+
* report. Used by the `mercato ai_assistant test-tools` CLI subcommand and
|
|
7
|
+
* by `.ai/qa/tests/integration/TC-INT-AI-TOOLS.spec.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Safety posture:
|
|
10
|
+
* - No HTTP exposure — runs only inside the Node process driving the CLI.
|
|
11
|
+
* - Mutation tools are exercised through `prepareMutation` and we assert a
|
|
12
|
+
* pending-action envelope is returned. The pending-action row is created
|
|
13
|
+
* in `ai_pending_actions` but never confirmed, so no real write happens.
|
|
14
|
+
* - Tools without an explicit fixture entry are skipped with reason
|
|
15
|
+
* `'no fixture'` rather than failing.
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
import fs from 'node:fs'
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
20
|
+
import type { AwilixContainer } from 'awilix'
|
|
21
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
22
|
+
import type { McpToolContext, AiToolDefinition } from './types'
|
|
23
|
+
import type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'
|
|
24
|
+
import { getFixture, type ToolFixture } from './tool-test-fixtures'
|
|
25
|
+
import { prepareMutation } from './prepare-mutation'
|
|
26
|
+
import { executePendingActionConfirm } from './pending-action-executor'
|
|
27
|
+
|
|
28
|
+
export type ToolTestStatus = 'pass' | 'fail' | 'skip'
|
|
29
|
+
|
|
30
|
+
export interface ToolTestRecord {
|
|
31
|
+
module: string
|
|
32
|
+
tool: string
|
|
33
|
+
isMutation: boolean
|
|
34
|
+
status: ToolTestStatus
|
|
35
|
+
durationMs: number
|
|
36
|
+
reason?: string
|
|
37
|
+
resultPreview?: unknown
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ToolTestReport {
|
|
41
|
+
tenantId: string | null
|
|
42
|
+
organizationId: string | null
|
|
43
|
+
total: number
|
|
44
|
+
passed: number
|
|
45
|
+
failed: number
|
|
46
|
+
skipped: number
|
|
47
|
+
records: ToolTestRecord[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface RawAiToolsModule {
|
|
51
|
+
aiToolConfigEntriesRaw?: { moduleId: string; tools: unknown[] }[]
|
|
52
|
+
aiToolConfigEntries?: { moduleId: string; tools: unknown[] }[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isToolDefinition(value: unknown): value is AiToolDefinition {
|
|
56
|
+
if (!value || typeof value !== 'object') return false
|
|
57
|
+
const candidate = value as Record<string, unknown>
|
|
58
|
+
return (
|
|
59
|
+
typeof candidate.name === 'string' &&
|
|
60
|
+
typeof candidate.description === 'string' &&
|
|
61
|
+
candidate.inputSchema !== undefined &&
|
|
62
|
+
typeof candidate.handler === 'function'
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Locate `apps/mercato/.mercato/generated/ai-tools.generated.ts` without
|
|
68
|
+
* hardcoding the workspace layout. Searches upward from this file's
|
|
69
|
+
* compiled location; in the monorepo dist this is
|
|
70
|
+
* `packages/ai-assistant/dist/...` so we walk up until we hit a directory
|
|
71
|
+
* containing `apps/mercato/.mercato/generated`.
|
|
72
|
+
*/
|
|
73
|
+
function findGeneratedAiToolsPath(): string | null {
|
|
74
|
+
const here = (() => {
|
|
75
|
+
try {
|
|
76
|
+
return fileURLToPath(import.meta.url)
|
|
77
|
+
} catch {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
})()
|
|
81
|
+
if (!here) return null
|
|
82
|
+
let cursor = path.dirname(here)
|
|
83
|
+
for (let i = 0; i < 12; i++) {
|
|
84
|
+
const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
85
|
+
if (fs.existsSync(candidate)) return candidate
|
|
86
|
+
const next = path.dirname(cursor)
|
|
87
|
+
if (next === cursor) break
|
|
88
|
+
cursor = next
|
|
89
|
+
}
|
|
90
|
+
// Fallback: cwd-based lookup (when CLI is invoked from apps/mercato).
|
|
91
|
+
const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
92
|
+
if (fs.existsSync(fromCwd)) return fromCwd
|
|
93
|
+
const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
94
|
+
if (fs.existsSync(fromCwdDirect)) return fromCwdDirect
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compile-and-import `ai-tools.generated.ts` on the fly. Mirrors the approach
|
|
100
|
+
* used by `loadBootstrapData` in `@open-mercato/shared/lib/bootstrap/dynamicLoader`:
|
|
101
|
+
* resolves the `@/` alias to the app root, marks every other package import as
|
|
102
|
+
* external, and emits a sibling `.mjs` we can `import()` from Node. Cached on
|
|
103
|
+
* mtime so repeat runs in the same process don't recompile.
|
|
104
|
+
*/
|
|
105
|
+
async function compileAndImportGenerated(tsPath: string): Promise<RawAiToolsModule> {
|
|
106
|
+
const jsPath = tsPath.replace(/\.ts$/, '.mjs')
|
|
107
|
+
// appRoot is two directories up from `.mercato/generated/<file>.ts`.
|
|
108
|
+
const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
|
|
109
|
+
const tsExists = fs.existsSync(tsPath)
|
|
110
|
+
if (!tsExists) {
|
|
111
|
+
throw new Error(`Generated file not found: ${tsPath}`)
|
|
112
|
+
}
|
|
113
|
+
const jsExists = fs.existsSync(jsPath)
|
|
114
|
+
const needsCompile =
|
|
115
|
+
!jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs
|
|
116
|
+
if (needsCompile) {
|
|
117
|
+
const esbuild = await import('esbuild')
|
|
118
|
+
// Transpile-only: don't bundle. Generated registry files only declare an
|
|
119
|
+
// array literal whose entries are static `import("…")` arrow functions —
|
|
120
|
+
// we want those `import()` strings to stay as runtime imports so Node
|
|
121
|
+
// resolves them lazily through the workspace's normal module resolution.
|
|
122
|
+
// Eagerly bundling them pulls Next.js / route handler internals into the
|
|
123
|
+
// .mjs and breaks at runtime (e.g. `next/server` package-exports map).
|
|
124
|
+
const tsSource = fs.readFileSync(tsPath, 'utf-8')
|
|
125
|
+
// Rewrite `@/...` aliases to absolute paths so Node can resolve them.
|
|
126
|
+
const aliasRewritten = tsSource.replace(
|
|
127
|
+
/from\s+["']@\/([^"']+)["']/g,
|
|
128
|
+
(_match, p1: string) => {
|
|
129
|
+
const target = path.join(appRoot, p1)
|
|
130
|
+
const candidate = fs.existsSync(target)
|
|
131
|
+
? target
|
|
132
|
+
: fs.existsSync(target + '.ts')
|
|
133
|
+
? target + '.ts'
|
|
134
|
+
: target
|
|
135
|
+
return `from ${JSON.stringify(pathToFileURL(candidate).href)}`
|
|
136
|
+
},
|
|
137
|
+
).replace(
|
|
138
|
+
/import\s*\(\s*["']@\/([^"']+)["']\s*\)/g,
|
|
139
|
+
(_match, p1: string) => {
|
|
140
|
+
const target = path.join(appRoot, p1)
|
|
141
|
+
const candidate = fs.existsSync(target)
|
|
142
|
+
? target
|
|
143
|
+
: fs.existsSync(target + '.ts')
|
|
144
|
+
? target + '.ts'
|
|
145
|
+
: target
|
|
146
|
+
return `import(${JSON.stringify(pathToFileURL(candidate).href)})`
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
const result = await esbuild.transform(aliasRewritten, {
|
|
150
|
+
loader: 'ts',
|
|
151
|
+
format: 'esm',
|
|
152
|
+
target: 'node18',
|
|
153
|
+
sourcemap: false,
|
|
154
|
+
sourcefile: tsPath,
|
|
155
|
+
})
|
|
156
|
+
fs.writeFileSync(jsPath, result.code)
|
|
157
|
+
}
|
|
158
|
+
return (await import(pathToFileURL(jsPath).href)) as RawAiToolsModule
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Compile-and-import `api-routes.generated.ts` and register its manifest with
|
|
163
|
+
* the shared registry. Many tool handlers delegate to `aiApiOperationRunner`
|
|
164
|
+
* which fails closed when no manifest is registered. Idempotent: registering
|
|
165
|
+
* the same array twice is safe.
|
|
166
|
+
*/
|
|
167
|
+
async function ensureApiRouteManifestsRegistered(generatedDir: string): Promise<void> {
|
|
168
|
+
const tsPath = path.join(generatedDir, 'api-routes.generated.ts')
|
|
169
|
+
if (!fs.existsSync(tsPath)) return
|
|
170
|
+
try {
|
|
171
|
+
const mod = (await compileAndImportGenerated(tsPath)) as Record<string, unknown>
|
|
172
|
+
const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes
|
|
173
|
+
if (!Array.isArray(apiRoutes)) {
|
|
174
|
+
console.warn('[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array')
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
const registry = await import('@open-mercato/shared/modules/registry')
|
|
178
|
+
registry.registerApiRouteManifests(
|
|
179
|
+
apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],
|
|
180
|
+
)
|
|
181
|
+
if (process.env.OM_TOOL_TEST_DEBUG === '1') {
|
|
182
|
+
console.log(
|
|
183
|
+
`[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.warn(
|
|
188
|
+
'[tool-test-runner] Could not register api-routes manifest:',
|
|
189
|
+
error instanceof Error ? error.message : error,
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function loadGeneratedTools(): Promise<{ moduleId: string; tools: AiToolDefinition[] }[]> {
|
|
195
|
+
const tsPath = findGeneratedAiToolsPath()
|
|
196
|
+
if (!tsPath) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
'Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first.',
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
await ensureApiRouteManifestsRegistered(path.dirname(tsPath))
|
|
202
|
+
const mod = await compileAndImportGenerated(tsPath)
|
|
203
|
+
const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? []
|
|
204
|
+
const result: { moduleId: string; tools: AiToolDefinition[] }[] = []
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (!entry || typeof entry.moduleId !== 'string') continue
|
|
207
|
+
const tools = Array.isArray(entry.tools)
|
|
208
|
+
? entry.tools.filter(isToolDefinition)
|
|
209
|
+
: []
|
|
210
|
+
result.push({ moduleId: entry.moduleId, tools })
|
|
211
|
+
}
|
|
212
|
+
return result
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function pickDefaultTenant(
|
|
216
|
+
container: AwilixContainer,
|
|
217
|
+
): Promise<{ tenantId: string; organizationId: string | null; userId: string | null } | null> {
|
|
218
|
+
try {
|
|
219
|
+
const em = container.resolve<{
|
|
220
|
+
getConnection: () => { execute: (sql: string) => Promise<unknown[]> }
|
|
221
|
+
}>('em') as unknown as {
|
|
222
|
+
getConnection: () => { execute: (sql: string) => Promise<Record<string, unknown>[]> }
|
|
223
|
+
}
|
|
224
|
+
const conn = em.getConnection()
|
|
225
|
+
const tenantRows = await conn.execute(
|
|
226
|
+
`SELECT id FROM tenants WHERE deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,
|
|
227
|
+
)
|
|
228
|
+
const tenantId =
|
|
229
|
+
Array.isArray(tenantRows) && tenantRows[0]
|
|
230
|
+
? String((tenantRows[0] as Record<string, unknown>).id)
|
|
231
|
+
: null
|
|
232
|
+
if (!tenantId) return null
|
|
233
|
+
const escaped = tenantId.replace(/'/g, "''")
|
|
234
|
+
const orgRows = await conn.execute(
|
|
235
|
+
`SELECT id FROM organizations WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,
|
|
236
|
+
)
|
|
237
|
+
const organizationId =
|
|
238
|
+
Array.isArray(orgRows) && orgRows[0]
|
|
239
|
+
? String((orgRows[0] as Record<string, unknown>).id)
|
|
240
|
+
: null
|
|
241
|
+
const userRows = await conn.execute(
|
|
242
|
+
`SELECT id FROM users WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`,
|
|
243
|
+
)
|
|
244
|
+
const userId =
|
|
245
|
+
Array.isArray(userRows) && userRows[0]
|
|
246
|
+
? String((userRows[0] as Record<string, unknown>).id)
|
|
247
|
+
: null
|
|
248
|
+
return { tenantId, organizationId, userId }
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (process.env.OM_TOOL_TEST_DEBUG === '1') {
|
|
251
|
+
console.warn('[tool-test-runner] pickDefaultTenant failed:', error)
|
|
252
|
+
}
|
|
253
|
+
return null
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function buildSuperAdminContext(
|
|
258
|
+
container: AwilixContainer,
|
|
259
|
+
tenantId: string | null,
|
|
260
|
+
organizationId: string | null,
|
|
261
|
+
userId: string | null,
|
|
262
|
+
): McpToolContext {
|
|
263
|
+
return {
|
|
264
|
+
tenantId,
|
|
265
|
+
organizationId,
|
|
266
|
+
userId,
|
|
267
|
+
container,
|
|
268
|
+
userFeatures: ['*'],
|
|
269
|
+
isSuperAdmin: true,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function clipPreview(value: unknown): unknown {
|
|
274
|
+
try {
|
|
275
|
+
const json = JSON.stringify(value)
|
|
276
|
+
if (json.length <= 400) return value
|
|
277
|
+
return `${json.slice(0, 400)}…(truncated, ${json.length} bytes)`
|
|
278
|
+
} catch {
|
|
279
|
+
return '[unserializable]'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function dummyAgentForTool(tool: AiToolDefinition): AiAgentDefinition {
|
|
284
|
+
// The runner never registers this agent; it's only used as input to
|
|
285
|
+
// `prepareMutation` for shape compatibility. The id namespace is namespaced
|
|
286
|
+
// under `__test__` so it cannot collide with real agents.
|
|
287
|
+
const policy: AiAgentMutationPolicy = 'destructive-confirm-required'
|
|
288
|
+
return {
|
|
289
|
+
id: `__test__.${tool.name}`,
|
|
290
|
+
moduleId: tool.name.split('.')[0] ?? '__test__',
|
|
291
|
+
label: 'Tool Test Runner',
|
|
292
|
+
description: 'Synthetic agent used by the tool test runner',
|
|
293
|
+
systemPrompt: '',
|
|
294
|
+
allowedTools: [tool.name],
|
|
295
|
+
readOnly: false,
|
|
296
|
+
mutationPolicy: policy,
|
|
297
|
+
} as AiAgentDefinition
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function executeReadTool(
|
|
301
|
+
tool: AiToolDefinition,
|
|
302
|
+
args: Record<string, unknown>,
|
|
303
|
+
container: AwilixContainer,
|
|
304
|
+
ctx: McpToolContext,
|
|
305
|
+
): Promise<unknown> {
|
|
306
|
+
// Mirror the dispatcher: resolve a fresh container per call so EM identity
|
|
307
|
+
// map state is clean between tools.
|
|
308
|
+
const fresh = await createRequestContainer()
|
|
309
|
+
const freshCtx: McpToolContext = { ...ctx, container: fresh, tool }
|
|
310
|
+
void container // keep arg for future use
|
|
311
|
+
return tool.handler(args as never, freshCtx)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function executeMutationTool(
|
|
315
|
+
tool: AiToolDefinition,
|
|
316
|
+
args: Record<string, unknown>,
|
|
317
|
+
container: AwilixContainer,
|
|
318
|
+
ctx: McpToolContext,
|
|
319
|
+
): Promise<unknown> {
|
|
320
|
+
// Route through prepareMutation so we assert the approval contract still
|
|
321
|
+
// holds — the handler must NEVER write directly. We pass a destructive
|
|
322
|
+
// policy so the runtime treats the call as a confirmation candidate and
|
|
323
|
+
// creates a pending-action row.
|
|
324
|
+
const fresh = await createRequestContainer()
|
|
325
|
+
const agent = dummyAgentForTool(tool)
|
|
326
|
+
const userId = ctx.userId ?? '00000000-0000-0000-0000-000000000000'
|
|
327
|
+
const { uiPart, pendingAction } = await prepareMutation(
|
|
328
|
+
{
|
|
329
|
+
agent,
|
|
330
|
+
tool,
|
|
331
|
+
toolCallArgs: args,
|
|
332
|
+
conversationId: null,
|
|
333
|
+
mutationPolicyOverride: null,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
tenantId: ctx.tenantId,
|
|
337
|
+
organizationId: ctx.organizationId,
|
|
338
|
+
userId,
|
|
339
|
+
features: ctx.userFeatures,
|
|
340
|
+
isSuperAdmin: ctx.isSuperAdmin,
|
|
341
|
+
container: fresh,
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
// Exercise the actual handler via the same path the production confirm
|
|
345
|
+
// route uses. Without this, mutation tools whose handler crashes (e.g.
|
|
346
|
+
// missing `ctx.tool` for the API operation runner) would be reported as
|
|
347
|
+
// passing because `prepareMutation` never invokes the handler.
|
|
348
|
+
// `prepareMutation` already enforced `tenantId` is non-null above; cast
|
|
349
|
+
// here for the stricter `PendingActionExecuteContext` shape.
|
|
350
|
+
const confirmTenantId = ctx.tenantId as string
|
|
351
|
+
const confirmContainer = await createRequestContainer()
|
|
352
|
+
const confirmation = await executePendingActionConfirm({
|
|
353
|
+
action: pendingAction,
|
|
354
|
+
agent,
|
|
355
|
+
tool,
|
|
356
|
+
ctx: {
|
|
357
|
+
tenantId: confirmTenantId,
|
|
358
|
+
organizationId: ctx.organizationId,
|
|
359
|
+
userId,
|
|
360
|
+
container: confirmContainer,
|
|
361
|
+
userFeatures: ctx.userFeatures,
|
|
362
|
+
isSuperAdmin: ctx.isSuperAdmin,
|
|
363
|
+
},
|
|
364
|
+
emitEvent: async () => {},
|
|
365
|
+
})
|
|
366
|
+
if (!confirmation.ok) {
|
|
367
|
+
const error = (confirmation.executionResult as { error?: { message?: string } } | undefined)?.error
|
|
368
|
+
const cause = confirmation.cause
|
|
369
|
+
const causeMessage =
|
|
370
|
+
cause instanceof Error
|
|
371
|
+
? cause.message
|
|
372
|
+
: typeof cause === 'string'
|
|
373
|
+
? cause
|
|
374
|
+
: null
|
|
375
|
+
const message = error?.message ?? causeMessage ?? 'handler invocation failed'
|
|
376
|
+
throw new Error(message)
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
status: 'pending-confirmation',
|
|
380
|
+
pendingActionId: pendingAction.id,
|
|
381
|
+
expiresAt: pendingAction.expiresAt.toISOString(),
|
|
382
|
+
uiPartType: (uiPart as { type?: string } | undefined)?.type ?? null,
|
|
383
|
+
handlerExecuted: true,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function resolveFixtureInput(
|
|
388
|
+
fixture: ToolFixture,
|
|
389
|
+
resolved: Map<string, AiToolDefinition>,
|
|
390
|
+
container: AwilixContainer,
|
|
391
|
+
ctx: McpToolContext,
|
|
392
|
+
): Promise<{ ok: true; input: Record<string, unknown> } | { ok: false; reason: string }> {
|
|
393
|
+
if (fixture.input) return { ok: true, input: { ...fixture.input } }
|
|
394
|
+
if (!fixture.idFrom) {
|
|
395
|
+
return { ok: false, reason: 'fixture has neither input nor idFrom' }
|
|
396
|
+
}
|
|
397
|
+
const sourceTool = resolved.get(fixture.idFrom)
|
|
398
|
+
if (!sourceTool) {
|
|
399
|
+
return { ok: false, reason: `idFrom tool not found: ${fixture.idFrom}` }
|
|
400
|
+
}
|
|
401
|
+
const sourceFixture = getFixture(fixture.idFrom)
|
|
402
|
+
if (!sourceFixture?.input) {
|
|
403
|
+
return {
|
|
404
|
+
ok: false,
|
|
405
|
+
reason: `idFrom source ${fixture.idFrom} has no static input fixture`,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let listResult: unknown
|
|
409
|
+
try {
|
|
410
|
+
listResult = await executeReadTool(
|
|
411
|
+
sourceTool,
|
|
412
|
+
{ ...sourceFixture.input },
|
|
413
|
+
container,
|
|
414
|
+
ctx,
|
|
415
|
+
)
|
|
416
|
+
} catch (error) {
|
|
417
|
+
return {
|
|
418
|
+
ok: false,
|
|
419
|
+
reason: `idFrom source ${fixture.idFrom} threw: ${
|
|
420
|
+
error instanceof Error ? error.message : String(error)
|
|
421
|
+
}`,
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const records = extractRecords(listResult)
|
|
425
|
+
if (!records.length) {
|
|
426
|
+
return {
|
|
427
|
+
ok: false,
|
|
428
|
+
reason: `idFrom source ${fixture.idFrom} returned no records`,
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const idField = 'id'
|
|
432
|
+
const id = (records[0] as Record<string, unknown>)[idField]
|
|
433
|
+
if (typeof id !== 'string' || !id) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
reason: `idFrom source ${fixture.idFrom} first record has no string id`,
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const bindAs = fixture.bindAs ?? 'id'
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
input: { [bindAs]: id, ...(fixture.extra ?? {}) },
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function extractRecords(value: unknown): unknown[] {
|
|
447
|
+
if (!value || typeof value !== 'object') return []
|
|
448
|
+
const candidate = value as Record<string, unknown>
|
|
449
|
+
for (const key of ['records', 'items', 'people', 'companies', 'deals', 'results', 'data']) {
|
|
450
|
+
const arr = candidate[key]
|
|
451
|
+
if (Array.isArray(arr)) return arr
|
|
452
|
+
}
|
|
453
|
+
return []
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface RunToolTestsOptions {
|
|
457
|
+
tenantId?: string | null
|
|
458
|
+
organizationId?: string | null
|
|
459
|
+
userId?: string | null
|
|
460
|
+
moduleFilter?: string | null
|
|
461
|
+
includeMutations?: boolean
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function runToolTests(
|
|
465
|
+
options: RunToolTestsOptions = {},
|
|
466
|
+
): Promise<ToolTestReport> {
|
|
467
|
+
const includeMutations = options.includeMutations ?? true
|
|
468
|
+
const container = await createRequestContainer()
|
|
469
|
+
let tenantId: string | null = options.tenantId ?? null
|
|
470
|
+
let organizationId: string | null = options.organizationId ?? null
|
|
471
|
+
let userId: string | null = options.userId ?? null
|
|
472
|
+
if (!tenantId) {
|
|
473
|
+
const picked = await pickDefaultTenant(container)
|
|
474
|
+
if (picked) {
|
|
475
|
+
tenantId = picked.tenantId
|
|
476
|
+
organizationId = picked.organizationId
|
|
477
|
+
userId = userId ?? picked.userId
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const ctx = buildSuperAdminContext(container, tenantId, organizationId, userId)
|
|
481
|
+
const grouped = await loadGeneratedTools()
|
|
482
|
+
|
|
483
|
+
const flat: { moduleId: string; tool: AiToolDefinition }[] = []
|
|
484
|
+
for (const group of grouped) {
|
|
485
|
+
if (options.moduleFilter && group.moduleId !== options.moduleFilter) continue
|
|
486
|
+
for (const tool of group.tools) flat.push({ moduleId: group.moduleId, tool })
|
|
487
|
+
}
|
|
488
|
+
const resolved = new Map<string, AiToolDefinition>()
|
|
489
|
+
for (const { tool } of flat) resolved.set(tool.name, tool)
|
|
490
|
+
|
|
491
|
+
const records: ToolTestRecord[] = []
|
|
492
|
+
for (const { moduleId, tool } of flat) {
|
|
493
|
+
const start = Date.now()
|
|
494
|
+
const isMutation = tool.isMutation === true
|
|
495
|
+
const fixture = getFixture(tool.name)
|
|
496
|
+
if (!fixture) {
|
|
497
|
+
records.push({
|
|
498
|
+
module: moduleId,
|
|
499
|
+
tool: tool.name,
|
|
500
|
+
isMutation,
|
|
501
|
+
status: 'skip',
|
|
502
|
+
durationMs: Date.now() - start,
|
|
503
|
+
reason: 'no fixture',
|
|
504
|
+
})
|
|
505
|
+
continue
|
|
506
|
+
}
|
|
507
|
+
if (fixture.skip) {
|
|
508
|
+
records.push({
|
|
509
|
+
module: moduleId,
|
|
510
|
+
tool: tool.name,
|
|
511
|
+
isMutation,
|
|
512
|
+
status: 'skip',
|
|
513
|
+
durationMs: Date.now() - start,
|
|
514
|
+
reason: fixture.note ?? 'skip-by-fixture',
|
|
515
|
+
})
|
|
516
|
+
continue
|
|
517
|
+
}
|
|
518
|
+
if (isMutation && !includeMutations) {
|
|
519
|
+
records.push({
|
|
520
|
+
module: moduleId,
|
|
521
|
+
tool: tool.name,
|
|
522
|
+
isMutation,
|
|
523
|
+
status: 'skip',
|
|
524
|
+
durationMs: Date.now() - start,
|
|
525
|
+
reason: 'mutations excluded',
|
|
526
|
+
})
|
|
527
|
+
continue
|
|
528
|
+
}
|
|
529
|
+
const inputResult = await resolveFixtureInput(fixture, resolved, container, ctx)
|
|
530
|
+
if (!inputResult.ok) {
|
|
531
|
+
records.push({
|
|
532
|
+
module: moduleId,
|
|
533
|
+
tool: tool.name,
|
|
534
|
+
isMutation,
|
|
535
|
+
status: 'skip',
|
|
536
|
+
durationMs: Date.now() - start,
|
|
537
|
+
reason: inputResult.reason,
|
|
538
|
+
})
|
|
539
|
+
continue
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const result = isMutation
|
|
543
|
+
? await executeMutationTool(tool, inputResult.input, container, ctx)
|
|
544
|
+
: await executeReadTool(tool, inputResult.input, container, ctx)
|
|
545
|
+
// Result must be JSON-serializable.
|
|
546
|
+
JSON.stringify(result)
|
|
547
|
+
// Mutation tools must return a pending-confirmation envelope.
|
|
548
|
+
if (
|
|
549
|
+
isMutation &&
|
|
550
|
+
(typeof result !== 'object' ||
|
|
551
|
+
result === null ||
|
|
552
|
+
(result as Record<string, unknown>).status !== 'pending-confirmation')
|
|
553
|
+
) {
|
|
554
|
+
records.push({
|
|
555
|
+
module: moduleId,
|
|
556
|
+
tool: tool.name,
|
|
557
|
+
isMutation,
|
|
558
|
+
status: 'fail',
|
|
559
|
+
durationMs: Date.now() - start,
|
|
560
|
+
reason: 'mutation tool did not route through prepareMutation (no pending-confirmation envelope)',
|
|
561
|
+
resultPreview: clipPreview(result),
|
|
562
|
+
})
|
|
563
|
+
continue
|
|
564
|
+
}
|
|
565
|
+
records.push({
|
|
566
|
+
module: moduleId,
|
|
567
|
+
tool: tool.name,
|
|
568
|
+
isMutation,
|
|
569
|
+
status: 'pass',
|
|
570
|
+
durationMs: Date.now() - start,
|
|
571
|
+
resultPreview: clipPreview(result),
|
|
572
|
+
})
|
|
573
|
+
} catch (error) {
|
|
574
|
+
records.push({
|
|
575
|
+
module: moduleId,
|
|
576
|
+
tool: tool.name,
|
|
577
|
+
isMutation,
|
|
578
|
+
status: 'fail',
|
|
579
|
+
durationMs: Date.now() - start,
|
|
580
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
581
|
+
})
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const passed = records.filter((r) => r.status === 'pass').length
|
|
585
|
+
const failed = records.filter((r) => r.status === 'fail').length
|
|
586
|
+
const skipped = records.filter((r) => r.status === 'skip').length
|
|
587
|
+
return {
|
|
588
|
+
tenantId,
|
|
589
|
+
organizationId,
|
|
590
|
+
total: records.length,
|
|
591
|
+
passed,
|
|
592
|
+
failed,
|
|
593
|
+
skipped,
|
|
594
|
+
records,
|
|
595
|
+
}
|
|
596
|
+
}
|