@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,333 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { login } from '@open-mercato/core/modules/core/__integration__/helpers/auth';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TC-AI-PLAYGROUND-004: AI Playground page smoke (Step 4.4 / Phase 2 WS-B).
|
|
6
|
+
*
|
|
7
|
+
* Covers the first user-facing embedding of `<AiChat>` in the backoffice. The
|
|
8
|
+
* route is guarded by `ai_assistant.settings.manage`; superadmin always holds
|
|
9
|
+
* it. The agent registry may be empty in CI (generated file depends on
|
|
10
|
+
* module authors registering agents), so the spec handles BOTH branches:
|
|
11
|
+
* - populated registry: assert the agent picker, debug toggle, and chat
|
|
12
|
+
* surface render;
|
|
13
|
+
* - empty registry: assert the `EmptyState` copy renders instead.
|
|
14
|
+
*
|
|
15
|
+
* The chat SSE response is stubbed via Playwright's route interception so the
|
|
16
|
+
* test never has to hit a live LLM provider. The object-mode run-object route
|
|
17
|
+
* is also stubbed to assert wiring without invoking a real model.
|
|
18
|
+
*/
|
|
19
|
+
test.describe('TC-AI-PLAYGROUND-004: AI Playground', () => {
|
|
20
|
+
const playgroundPath = '/backend/config/ai-assistant/playground';
|
|
21
|
+
|
|
22
|
+
test('playground renders agent picker or empty state for superadmin', async ({ page }) => {
|
|
23
|
+
await login(page, 'superadmin');
|
|
24
|
+
|
|
25
|
+
// Stub the agents list so the test behavior is deterministic regardless of
|
|
26
|
+
// what the generated registry holds at run-time.
|
|
27
|
+
await page.route('**/api/ai_assistant/ai/agents', async (route) => {
|
|
28
|
+
await route.fulfill({
|
|
29
|
+
status: 200,
|
|
30
|
+
contentType: 'application/json',
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
agents: [
|
|
33
|
+
{
|
|
34
|
+
id: 'customers.assistant',
|
|
35
|
+
moduleId: 'customers',
|
|
36
|
+
label: 'Customers assistant',
|
|
37
|
+
description: 'Answers questions about customer records.',
|
|
38
|
+
executionMode: 'chat',
|
|
39
|
+
mutationPolicy: 'read-only',
|
|
40
|
+
allowedTools: ['customers.list_people'],
|
|
41
|
+
requiredFeatures: [],
|
|
42
|
+
acceptedMediaTypes: [],
|
|
43
|
+
hasOutputSchema: false,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'catalog.extract',
|
|
47
|
+
moduleId: 'catalog',
|
|
48
|
+
label: 'Catalog extractor',
|
|
49
|
+
description: 'Extracts structured product metadata.',
|
|
50
|
+
executionMode: 'object',
|
|
51
|
+
mutationPolicy: 'read-only',
|
|
52
|
+
allowedTools: [],
|
|
53
|
+
requiredFeatures: [],
|
|
54
|
+
acceptedMediaTypes: [],
|
|
55
|
+
hasOutputSchema: true,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
total: 2,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Stub the chat SSE so the composer has something to "succeed" against.
|
|
64
|
+
await page.route('**/api/ai_assistant/ai/chat**', async (route) => {
|
|
65
|
+
await route.fulfill({
|
|
66
|
+
status: 200,
|
|
67
|
+
contentType: 'text/event-stream',
|
|
68
|
+
body: 'ok from stubbed SSE\n',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Stub the run-object dispatcher so the object-mode tab has a deterministic response.
|
|
73
|
+
await page.route('**/api/ai_assistant/ai/run-object', async (route) => {
|
|
74
|
+
await route.fulfill({
|
|
75
|
+
status: 200,
|
|
76
|
+
contentType: 'application/json',
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
object: { title: 'Stubbed title' },
|
|
79
|
+
finishReason: 'stop',
|
|
80
|
+
usage: { inputTokens: 1, outputTokens: 2 },
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await page.goto(playgroundPath, { waitUntil: 'domcontentloaded' });
|
|
86
|
+
|
|
87
|
+
const container = page.locator('[data-ai-playground]');
|
|
88
|
+
const empty = page.getByText(/No AI agents are registered/i).first();
|
|
89
|
+
const loadError = page.locator('[data-ai-playground-error]');
|
|
90
|
+
|
|
91
|
+
// Wait until the page has stabilized (either container, empty state, or a load error).
|
|
92
|
+
await expect(container.or(empty).or(loadError)).toBeVisible({ timeout: 15_000 });
|
|
93
|
+
|
|
94
|
+
if (await container.isVisible().catch(() => false)) {
|
|
95
|
+
const picker = page.locator('[data-ai-playground-agent-picker]');
|
|
96
|
+
await expect(picker).toBeVisible();
|
|
97
|
+
// Two stubbed agents should surface.
|
|
98
|
+
const optionCount = await picker.locator('option').count();
|
|
99
|
+
expect(optionCount).toBeGreaterThanOrEqual(2);
|
|
100
|
+
|
|
101
|
+
const debugToggle = page.locator('[data-ai-playground-debug-toggle]');
|
|
102
|
+
await expect(debugToggle).toBeVisible();
|
|
103
|
+
|
|
104
|
+
const composer = page.locator('#ai-chat-composer');
|
|
105
|
+
await expect(composer).toBeVisible();
|
|
106
|
+
await composer.fill('Hello from Playwright');
|
|
107
|
+
await expect(composer).toHaveValue('Hello from Playwright');
|
|
108
|
+
|
|
109
|
+
// Step 4.6: toggling the debug panel should reveal the three collapsible
|
|
110
|
+
// sections (tool map / prompt sections / last request), each addressable
|
|
111
|
+
// by its `data-ai-chat-debug-section` attribute.
|
|
112
|
+
const debugPanel = page.locator('[data-ai-chat-debug="true"]');
|
|
113
|
+
const initiallyVisible = await debugPanel.isVisible().catch(() => false);
|
|
114
|
+
if (!initiallyVisible) {
|
|
115
|
+
await debugToggle.click();
|
|
116
|
+
}
|
|
117
|
+
await expect(debugPanel).toBeVisible({ timeout: 5_000 });
|
|
118
|
+
await expect(
|
|
119
|
+
page.locator('[data-ai-chat-debug-section="tools"]'),
|
|
120
|
+
).toBeVisible();
|
|
121
|
+
await expect(
|
|
122
|
+
page.locator('[data-ai-chat-debug-section="promptSections"]'),
|
|
123
|
+
).toBeVisible();
|
|
124
|
+
await expect(
|
|
125
|
+
page.locator('[data-ai-chat-debug-section="lastRequest"]'),
|
|
126
|
+
).toBeVisible();
|
|
127
|
+
} else if (await empty.isVisible().catch(() => false)) {
|
|
128
|
+
// Empty branch: agent registry is empty in this environment.
|
|
129
|
+
await expect(empty).toBeVisible();
|
|
130
|
+
} else {
|
|
131
|
+
// Fatal load error branch — still acceptable evidence that the guard fired.
|
|
132
|
+
await expect(loadError).toBeVisible();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('picker lists all three Phase 2 agents and chat-mode selection disables object-mode tab', async ({
|
|
137
|
+
page,
|
|
138
|
+
}) => {
|
|
139
|
+
test.setTimeout(120_000);
|
|
140
|
+
await login(page, 'superadmin');
|
|
141
|
+
|
|
142
|
+
// Hit the live registry for this scenario — Phase 2 agents are all
|
|
143
|
+
// chat-mode so we can assert the "not supported" alert on the object
|
|
144
|
+
// tab without stubbing.
|
|
145
|
+
await page.goto(playgroundPath, { waitUntil: 'domcontentloaded' });
|
|
146
|
+
|
|
147
|
+
const container = page.locator('[data-ai-playground]');
|
|
148
|
+
await expect(container).toBeVisible({ timeout: 60_000 });
|
|
149
|
+
|
|
150
|
+
const picker = page.locator('[data-ai-playground-agent-picker]');
|
|
151
|
+
await expect(picker).toBeVisible();
|
|
152
|
+
|
|
153
|
+
// The three Phase 2 agents must be present.
|
|
154
|
+
await expect(
|
|
155
|
+
picker.locator('option[value="customers.account_assistant"]'),
|
|
156
|
+
).toHaveCount(1);
|
|
157
|
+
await expect(
|
|
158
|
+
picker.locator('option[value="catalog.catalog_assistant"]'),
|
|
159
|
+
).toHaveCount(1);
|
|
160
|
+
await expect(
|
|
161
|
+
picker.locator('option[value="catalog.merchandising_assistant"]'),
|
|
162
|
+
).toHaveCount(1);
|
|
163
|
+
|
|
164
|
+
// Switch to the object-mode tab — Phase 2 agents are all chat-mode, so
|
|
165
|
+
// the "not supported" info Alert should render with the documented
|
|
166
|
+
// `data-ai-playground-unsupported="object"` marker.
|
|
167
|
+
await page.getByRole('tab', { name: /object mode/i }).click();
|
|
168
|
+
await expect(
|
|
169
|
+
page.locator('[data-ai-playground-unsupported="object"]'),
|
|
170
|
+
).toBeVisible();
|
|
171
|
+
|
|
172
|
+
// Flip back to chat tab; the `AiChat` region for the currently selected
|
|
173
|
+
// agent must render.
|
|
174
|
+
await page.getByRole('tab', { name: /^chat$/i }).click();
|
|
175
|
+
const chatRegion = page.locator('[data-ai-chat-agent]').first();
|
|
176
|
+
await expect(chatRegion).toBeVisible();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('chat happy path — stubbed SSE response appears in the transcript', async ({ page }) => {
|
|
180
|
+
test.setTimeout(120_000);
|
|
181
|
+
await login(page, 'superadmin');
|
|
182
|
+
|
|
183
|
+
// Stub agents so the picker is deterministic.
|
|
184
|
+
await page.route('**/api/ai_assistant/ai/agents', async (route) => {
|
|
185
|
+
await route.fulfill({
|
|
186
|
+
status: 200,
|
|
187
|
+
contentType: 'application/json',
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
agents: [
|
|
190
|
+
{
|
|
191
|
+
id: 'customers.account_assistant',
|
|
192
|
+
moduleId: 'customers',
|
|
193
|
+
label: 'Customers account assistant',
|
|
194
|
+
description: 'Stubbed agent for playground chat smoke.',
|
|
195
|
+
executionMode: 'chat',
|
|
196
|
+
mutationPolicy: 'read-only',
|
|
197
|
+
allowedTools: [],
|
|
198
|
+
requiredFeatures: [],
|
|
199
|
+
acceptedMediaTypes: [],
|
|
200
|
+
hasOutputSchema: false,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
total: 1,
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Stub the SSE dispatcher with a canned text stream the AiChat reader
|
|
209
|
+
// consumes. The AiChat component writes streamed deltas into the
|
|
210
|
+
// transcript; we assert the text surfaces regardless of the exact
|
|
211
|
+
// stream dialect by filling in both a `data:` framed payload and a
|
|
212
|
+
// plain-text fallback.
|
|
213
|
+
const streamBody = [
|
|
214
|
+
'event: text',
|
|
215
|
+
'data: {"content":"stubbed-playground-reply"}',
|
|
216
|
+
'',
|
|
217
|
+
'event: done',
|
|
218
|
+
'data: {}',
|
|
219
|
+
'',
|
|
220
|
+
].join('\n');
|
|
221
|
+
await page.route('**/api/ai_assistant/ai/chat**', async (route) => {
|
|
222
|
+
await route.fulfill({
|
|
223
|
+
status: 200,
|
|
224
|
+
contentType: 'text/event-stream',
|
|
225
|
+
body: streamBody,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await page.goto(playgroundPath, { waitUntil: 'domcontentloaded' });
|
|
230
|
+
|
|
231
|
+
const composer = page.locator('#ai-chat-composer');
|
|
232
|
+
await expect(composer).toBeVisible({ timeout: 60_000 });
|
|
233
|
+
await composer.fill('Say hi please');
|
|
234
|
+
await composer.press('Meta+Enter');
|
|
235
|
+
// On non-mac runners Meta+Enter is a no-op; try Control+Enter too.
|
|
236
|
+
await page.waitForTimeout(200);
|
|
237
|
+
if (!(await page.locator('[data-ai-chat-state="thinking"]').count())) {
|
|
238
|
+
await composer.press('Control+Enter');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// The composer should have been cleared on submit, proving the
|
|
242
|
+
// handler fired. The SSE stream body itself is driven by the agent
|
|
243
|
+
// runtime which may surface the text either verbatim or through a
|
|
244
|
+
// rendered message row — accept either signal.
|
|
245
|
+
await expect(async () => {
|
|
246
|
+
const cleared = (await composer.inputValue()) === '';
|
|
247
|
+
const rendered = await page.getByText(/stubbed-playground-reply/i).count();
|
|
248
|
+
const thinking = await page.locator('[data-ai-chat-state="thinking"]').count();
|
|
249
|
+
expect(cleared || rendered > 0 || thinking > 0).toBe(true);
|
|
250
|
+
}).toPass({ timeout: 10_000 });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('mutation-preview-card renders inside the playground transcript when a pending action is emitted', async ({
|
|
254
|
+
page,
|
|
255
|
+
}) => {
|
|
256
|
+
test.setTimeout(120_000);
|
|
257
|
+
await login(page, 'superadmin');
|
|
258
|
+
|
|
259
|
+
// Deterministic agent registry — a mutation-capable chat agent.
|
|
260
|
+
await page.route('**/api/ai_assistant/ai/agents', async (route) => {
|
|
261
|
+
await route.fulfill({
|
|
262
|
+
status: 200,
|
|
263
|
+
contentType: 'application/json',
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
agents: [
|
|
266
|
+
{
|
|
267
|
+
id: 'customers.account_assistant',
|
|
268
|
+
moduleId: 'customers',
|
|
269
|
+
label: 'Customers account assistant',
|
|
270
|
+
description: 'Mutation-capable agent for Phase 3 approval flow.',
|
|
271
|
+
executionMode: 'chat',
|
|
272
|
+
mutationPolicy: 'require-approval',
|
|
273
|
+
allowedTools: ['customers.update_person'],
|
|
274
|
+
requiredFeatures: [],
|
|
275
|
+
acceptedMediaTypes: [],
|
|
276
|
+
hasOutputSchema: false,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
total: 1,
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Stub the polling endpoint with a pending row so the preview card
|
|
285
|
+
// resolves its state. The Playwright test never hits a real DB.
|
|
286
|
+
const pendingRow = {
|
|
287
|
+
pendingAction: {
|
|
288
|
+
id: 'pa-stub-001',
|
|
289
|
+
agentId: 'customers.account_assistant',
|
|
290
|
+
toolName: 'customers.update_person',
|
|
291
|
+
status: 'pending',
|
|
292
|
+
fieldDiff: [
|
|
293
|
+
{ field: 'name', before: 'Alice', after: 'Alicia' },
|
|
294
|
+
],
|
|
295
|
+
records: null,
|
|
296
|
+
failedRecords: null,
|
|
297
|
+
sideEffectsSummary: 'Rename Alice to Alicia.',
|
|
298
|
+
attachmentIds: [],
|
|
299
|
+
targetEntityType: 'customers.person',
|
|
300
|
+
targetRecordId: 'p-1',
|
|
301
|
+
recordVersion: '1',
|
|
302
|
+
executionResult: null,
|
|
303
|
+
createdAt: new Date().toISOString(),
|
|
304
|
+
expiresAt: new Date(Date.now() + 600_000).toISOString(),
|
|
305
|
+
resolvedAt: null,
|
|
306
|
+
resolvedByUserId: null,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
await page.route('**/api/ai_assistant/ai/actions/pa-stub-001', async (route) => {
|
|
310
|
+
await route.fulfill({
|
|
311
|
+
status: 200,
|
|
312
|
+
contentType: 'application/json',
|
|
313
|
+
body: JSON.stringify(pendingRow),
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Navigate with the debug seed that instructs the playground to inject
|
|
318
|
+
// the `mutation-preview-card` UI part. This is the Step 5.10 stub path
|
|
319
|
+
// until the dispatcher surfaces UI parts through the streamed body.
|
|
320
|
+
await page.goto(
|
|
321
|
+
`${playgroundPath}?uiPart=mutation-preview-card&pendingActionId=pa-stub-001`,
|
|
322
|
+
{ waitUntil: 'domcontentloaded' },
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const container = page.locator('[data-ai-playground]');
|
|
326
|
+
await expect(container).toBeVisible({ timeout: 60_000 });
|
|
327
|
+
|
|
328
|
+
const previewCard = page.locator('[data-ai-mutation-preview]').first();
|
|
329
|
+
await expect(previewCard).toBeVisible({ timeout: 15_000 });
|
|
330
|
+
await expect(page.locator('[data-ai-mutation-preview-confirm]')).toBeVisible();
|
|
331
|
+
await expect(page.locator('[data-ai-mutation-preview-cancel]')).toBeVisible();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { expect, test } from '@playwright/test'
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = path.dirname(__filename)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* TC-INT-AI-TOOLS: Smoke-test every AI tool registered via `defineAiTool`.
|
|
11
|
+
*
|
|
12
|
+
* Strategy: shells out to `yarn mercato ai_assistant test-tools --json` which
|
|
13
|
+
* runs the in-process tool runner (`packages/ai-assistant/.../lib/tool-test-runner.ts`).
|
|
14
|
+
* The runner iterates every entry in `apps/mercato/.mercato/generated/ai-tools.generated.ts`,
|
|
15
|
+
* invokes each handler with a small fixture input against a super-admin
|
|
16
|
+
* context, and returns a structured report. Mutation tools are exercised
|
|
17
|
+
* through `prepareMutation` only — the test asserts a pending-action envelope
|
|
18
|
+
* is returned and never confirms the action.
|
|
19
|
+
*
|
|
20
|
+
* No HTTP endpoint is added; the runner is CLI-only and never exposed.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface ToolTestRecord {
|
|
24
|
+
module: string
|
|
25
|
+
tool: string
|
|
26
|
+
isMutation: boolean
|
|
27
|
+
status: 'pass' | 'fail' | 'skip'
|
|
28
|
+
durationMs: number
|
|
29
|
+
reason?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ToolTestReport {
|
|
33
|
+
tenantId: string | null
|
|
34
|
+
organizationId: string | null
|
|
35
|
+
total: number
|
|
36
|
+
passed: number
|
|
37
|
+
failed: number
|
|
38
|
+
skipped: number
|
|
39
|
+
records: ToolTestRecord[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const REPORT_BEGIN = '---TOOL_TEST_REPORT_BEGIN---'
|
|
43
|
+
const REPORT_END = '---TOOL_TEST_REPORT_END---'
|
|
44
|
+
|
|
45
|
+
function findRepoRoot(): string {
|
|
46
|
+
return path.resolve(__dirname, '..', '..', '..', '..', '..', '..')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function findAppRoot(): string {
|
|
50
|
+
const testAppRoot = process.env.OM_TEST_APP_ROOT
|
|
51
|
+
if (testAppRoot && testAppRoot.length > 0) {
|
|
52
|
+
return testAppRoot
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The CLI loads its env from apps/mercato/.env (DB connection, JWT secret,
|
|
56
|
+
// encryption fallback). Spawning from there makes the bootstrap reach a
|
|
57
|
+
// ready state instead of bailing on the MFA-secret precondition.
|
|
58
|
+
return path.join(findRepoRoot(), 'apps', 'mercato')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runToolTestsCli(): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
62
|
+
return new Promise((resolveCmd, rejectCmd) => {
|
|
63
|
+
const cwd = findAppRoot()
|
|
64
|
+
const child = spawn(
|
|
65
|
+
'yarn',
|
|
66
|
+
['mercato', 'ai_assistant', 'test-tools', '--json'],
|
|
67
|
+
{
|
|
68
|
+
cwd,
|
|
69
|
+
env: { ...process.env, FORCE_COLOR: '0', NODE_NO_WARNINGS: '1' },
|
|
70
|
+
shell: false,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
let stdout = ''
|
|
74
|
+
let stderr = ''
|
|
75
|
+
child.stdout.on('data', (chunk) => {
|
|
76
|
+
stdout += chunk.toString()
|
|
77
|
+
})
|
|
78
|
+
child.stderr.on('data', (chunk) => {
|
|
79
|
+
stderr += chunk.toString()
|
|
80
|
+
})
|
|
81
|
+
child.on('error', (err) => rejectCmd(err))
|
|
82
|
+
child.on('close', (code) => {
|
|
83
|
+
resolveCmd({ stdout, stderr, code: code ?? -1 })
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseReport(stdout: string): ToolTestReport {
|
|
89
|
+
const beginIdx = stdout.indexOf(REPORT_BEGIN)
|
|
90
|
+
const endIdx = stdout.indexOf(REPORT_END)
|
|
91
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Could not find report markers in CLI output. stdout (first 500 chars):\n${stdout.slice(0, 500)}`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
const payload = stdout.slice(beginIdx + REPORT_BEGIN.length, endIdx).trim()
|
|
97
|
+
return JSON.parse(payload) as ToolTestReport
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test.describe('TC-INT-AI-TOOLS: Every AI tool returns the expected shape', () => {
|
|
101
|
+
test('all registered tools either pass or skip with a reason; none fail', async () => {
|
|
102
|
+
test.slow()
|
|
103
|
+
const { stdout, stderr, code } = await runToolTestsCli()
|
|
104
|
+
if (code !== 0 && stdout.indexOf(REPORT_BEGIN) === -1) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`tool-test CLI failed (exit ${code}) before producing a report.\nstderr:\n${stderr.slice(0, 2000)}\nstdout:\n${stdout.slice(0, 1000)}`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
const report = parseReport(stdout)
|
|
110
|
+
|
|
111
|
+
// Surface a compact summary in the test log for triage.
|
|
112
|
+
const failures = report.records.filter((r) => r.status === 'fail')
|
|
113
|
+
const skips = report.records.filter((r) => r.status === 'skip')
|
|
114
|
+
if (failures.length > 0) {
|
|
115
|
+
console.log(
|
|
116
|
+
`[TC-INT-AI-TOOLS] Failures (${failures.length}):\n${failures
|
|
117
|
+
.map((r) => ` - ${r.tool}: ${r.reason ?? '<no reason>'}`)
|
|
118
|
+
.join('\n')}`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (skips.length > 0) {
|
|
122
|
+
console.log(
|
|
123
|
+
`[TC-INT-AI-TOOLS] Skips (${skips.length}): ${skips
|
|
124
|
+
.map((r) => `${r.tool} (${r.reason ?? 'skipped'})`)
|
|
125
|
+
.join(', ')}`,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
console.log(
|
|
129
|
+
`[TC-INT-AI-TOOLS] Result: total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(failures, `Expected zero failing AI tools, got ${failures.length}`).toEqual([])
|
|
133
|
+
expect(report.passed, 'At least one tool must run successfully').toBeGreaterThan(0)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit coverage for the ai_assistant module event declarations (Step 5.11).
|
|
3
|
+
*
|
|
4
|
+
* Asserts:
|
|
5
|
+
* - All three FROZEN event IDs (`ai.action.confirmed` /
|
|
6
|
+
* `ai.action.cancelled` / `ai.action.expired`) appear in
|
|
7
|
+
* `eventsConfig.events` under `category: 'system'`.
|
|
8
|
+
* - The typed `emitAiAssistantEvent` helper hands declared events off to
|
|
9
|
+
* the global event bus with the payload untouched.
|
|
10
|
+
* - Emitting an undeclared event id is rejected at the helper boundary
|
|
11
|
+
* via `createModuleEvents`' strict-mode validation.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
eventsConfig,
|
|
15
|
+
emitAiAssistantEvent,
|
|
16
|
+
type AiAssistantEventId,
|
|
17
|
+
type AiActionCancelledPayload,
|
|
18
|
+
type AiActionConfirmedPayload,
|
|
19
|
+
type AiActionExpiredPayload,
|
|
20
|
+
} from '../events'
|
|
21
|
+
import { setGlobalEventBus } from '@open-mercato/shared/modules/events'
|
|
22
|
+
|
|
23
|
+
const FROZEN_EVENT_IDS: ReadonlyArray<AiAssistantEventId> = [
|
|
24
|
+
'ai.action.confirmed',
|
|
25
|
+
'ai.action.cancelled',
|
|
26
|
+
'ai.action.expired',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
describe('ai_assistant events module', () => {
|
|
30
|
+
it('declares the three FROZEN pending-action events under moduleId=ai_assistant', () => {
|
|
31
|
+
expect(eventsConfig.moduleId).toBe('ai_assistant')
|
|
32
|
+
const declaredIds = eventsConfig.events.map((event) => event.id).sort()
|
|
33
|
+
expect(declaredIds).toEqual([...FROZEN_EVENT_IDS].sort())
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('every declared event has category=system and entity=ai_pending_action', () => {
|
|
37
|
+
for (const event of eventsConfig.events) {
|
|
38
|
+
expect(event.category).toBe('system')
|
|
39
|
+
expect(event.entity).toBe('ai_pending_action')
|
|
40
|
+
expect(event.module).toBe('ai_assistant')
|
|
41
|
+
expect(typeof event.label).toBe('string')
|
|
42
|
+
expect(event.label.length).toBeGreaterThan(0)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('emitAiAssistantEvent', () => {
|
|
47
|
+
const emitSpy = jest.fn().mockResolvedValue(undefined)
|
|
48
|
+
let consoleErrorSpy: jest.SpyInstance
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
emitSpy.mockClear()
|
|
52
|
+
setGlobalEventBus({ emit: (id, payload, opts) => emitSpy(id, payload, opts) })
|
|
53
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
consoleErrorSpy.mockRestore()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('forwards ai.action.confirmed payloads to the global bus verbatim', async () => {
|
|
61
|
+
const payload: AiActionConfirmedPayload = {
|
|
62
|
+
pendingActionId: 'pa_1',
|
|
63
|
+
agentId: 'catalog.merchandising_assistant',
|
|
64
|
+
toolName: 'catalog.update_product',
|
|
65
|
+
status: 'confirmed',
|
|
66
|
+
tenantId: 'tenant-1',
|
|
67
|
+
organizationId: 'org-1',
|
|
68
|
+
userId: 'user-1',
|
|
69
|
+
resolvedByUserId: 'user-1',
|
|
70
|
+
resolvedAt: '2026-04-18T10:05:00.000Z',
|
|
71
|
+
executionResult: { recordId: 'p-1', commandName: 'catalog.product.update' },
|
|
72
|
+
}
|
|
73
|
+
await emitAiAssistantEvent(
|
|
74
|
+
'ai.action.confirmed',
|
|
75
|
+
payload as unknown as Record<string, unknown>,
|
|
76
|
+
{ persistent: true },
|
|
77
|
+
)
|
|
78
|
+
expect(emitSpy).toHaveBeenCalledTimes(1)
|
|
79
|
+
const [id, forwardedPayload, options] = emitSpy.mock.calls[0]
|
|
80
|
+
expect(id).toBe('ai.action.confirmed')
|
|
81
|
+
expect(forwardedPayload).toEqual(payload)
|
|
82
|
+
expect(options).toEqual({ persistent: true })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('forwards ai.action.cancelled payloads to the global bus', async () => {
|
|
86
|
+
const payload: AiActionCancelledPayload = {
|
|
87
|
+
pendingActionId: 'pa_1',
|
|
88
|
+
agentId: 'catalog.merchandising_assistant',
|
|
89
|
+
toolName: 'catalog.update_product',
|
|
90
|
+
status: 'cancelled',
|
|
91
|
+
tenantId: 'tenant-1',
|
|
92
|
+
organizationId: 'org-1',
|
|
93
|
+
userId: 'user-1',
|
|
94
|
+
resolvedByUserId: 'user-1',
|
|
95
|
+
resolvedAt: '2026-04-18T10:05:00.000Z',
|
|
96
|
+
executionResult: {
|
|
97
|
+
error: { code: 'cancelled_by_user', message: 'Customer asked to abort' },
|
|
98
|
+
},
|
|
99
|
+
reason: 'Customer asked to abort',
|
|
100
|
+
}
|
|
101
|
+
await emitAiAssistantEvent(
|
|
102
|
+
'ai.action.cancelled',
|
|
103
|
+
payload as unknown as Record<string, unknown>,
|
|
104
|
+
)
|
|
105
|
+
expect(emitSpy).toHaveBeenCalledTimes(1)
|
|
106
|
+
expect(emitSpy.mock.calls[0][0]).toBe('ai.action.cancelled')
|
|
107
|
+
expect(emitSpy.mock.calls[0][1]).toEqual(payload)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('forwards ai.action.expired payloads to the global bus', async () => {
|
|
111
|
+
const payload: AiActionExpiredPayload = {
|
|
112
|
+
pendingActionId: 'pa_1',
|
|
113
|
+
agentId: 'catalog.merchandising_assistant',
|
|
114
|
+
toolName: 'catalog.update_product',
|
|
115
|
+
status: 'expired',
|
|
116
|
+
tenantId: 'tenant-1',
|
|
117
|
+
organizationId: 'org-1',
|
|
118
|
+
userId: null,
|
|
119
|
+
resolvedByUserId: null,
|
|
120
|
+
resolvedAt: '2026-04-18T10:05:00.000Z',
|
|
121
|
+
expiresAt: '2026-04-18T10:00:00.000Z',
|
|
122
|
+
expiredAt: '2026-04-18T10:05:00.000Z',
|
|
123
|
+
}
|
|
124
|
+
await emitAiAssistantEvent(
|
|
125
|
+
'ai.action.expired',
|
|
126
|
+
payload as unknown as Record<string, unknown>,
|
|
127
|
+
)
|
|
128
|
+
expect(emitSpy).toHaveBeenCalledTimes(1)
|
|
129
|
+
expect(emitSpy.mock.calls[0][0]).toBe('ai.action.expired')
|
|
130
|
+
expect(emitSpy.mock.calls[0][1]).toEqual(payload)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('logs an error and still forwards undeclared event ids (non-strict mode)', async () => {
|
|
134
|
+
await emitAiAssistantEvent(
|
|
135
|
+
// Deliberate cast: runtime test for undeclared-event path.
|
|
136
|
+
'ai.action.nope' as unknown as AiAssistantEventId,
|
|
137
|
+
{ pendingActionId: 'pa_1' },
|
|
138
|
+
)
|
|
139
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
|
|
140
|
+
const [message] = consoleErrorSpy.mock.calls[0]
|
|
141
|
+
expect(message).toContain('ai_assistant')
|
|
142
|
+
expect(message).toContain('ai.action.nope')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|