@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,380 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
8
|
+
import { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'
|
|
9
|
+
import { hasRequiredFeatures } from '../../../../../lib/auth'
|
|
10
|
+
import {
|
|
11
|
+
isKnownMutationPolicy,
|
|
12
|
+
isMutationPolicyEscalation,
|
|
13
|
+
} from '../../../../../lib/agent-policy'
|
|
14
|
+
import { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'
|
|
15
|
+
import type { AiAgentMutationPolicyOverride } from '../../../../../data/entities'
|
|
16
|
+
import type {
|
|
17
|
+
AiAgentDefinition,
|
|
18
|
+
AiAgentMutationPolicy,
|
|
19
|
+
} from '../../../../../lib/ai-agent-definition'
|
|
20
|
+
|
|
21
|
+
const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/
|
|
22
|
+
|
|
23
|
+
const agentIdParamSchema = z.object({
|
|
24
|
+
agentId: z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(agentIdPattern, 'agentId must match "<module>.<agent>" (lowercase, digits, underscores only)'),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const mutationPolicySchema = z.enum([
|
|
30
|
+
'read-only',
|
|
31
|
+
'confirm-required',
|
|
32
|
+
'destructive-confirm-required',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
const mutationPolicyRequestSchema = z.object({
|
|
36
|
+
mutationPolicy: mutationPolicySchema,
|
|
37
|
+
notes: z.string().max(2000).optional(),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const VIEW_FEATURE = 'ai_assistant.view'
|
|
41
|
+
const MANAGE_FEATURE = 'ai_assistant.settings.manage'
|
|
42
|
+
|
|
43
|
+
export const openApi: OpenApiRouteDoc = {
|
|
44
|
+
tag: 'AI Assistant',
|
|
45
|
+
summary: 'Tenant-scoped mutationPolicy override for an AI agent',
|
|
46
|
+
methods: {
|
|
47
|
+
GET: {
|
|
48
|
+
operationId: 'aiAssistantGetMutationPolicyOverride',
|
|
49
|
+
summary:
|
|
50
|
+
'Read the effective mutationPolicy for an agent — code-declared value plus any tenant override.',
|
|
51
|
+
description:
|
|
52
|
+
'Returns `{ agentId, codeDeclared, override }` where `codeDeclared` is the agent\'s ' +
|
|
53
|
+
'compiled-in `mutationPolicy` and `override` is the persisted tenant-scoped override ' +
|
|
54
|
+
'(or `null`). Requires `ai_assistant.view`.',
|
|
55
|
+
responses: [
|
|
56
|
+
{
|
|
57
|
+
status: 200,
|
|
58
|
+
description: 'Effective mutationPolicy payload.',
|
|
59
|
+
mediaType: 'application/json',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
errors: [
|
|
63
|
+
{ status: 400, description: 'Invalid agent id.' },
|
|
64
|
+
{ status: 401, description: 'Unauthenticated caller.' },
|
|
65
|
+
{ status: 403, description: 'Caller lacks `ai_assistant.view`.' },
|
|
66
|
+
{ status: 404, description: 'Unknown agent id.' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
POST: {
|
|
70
|
+
operationId: 'aiAssistantSaveMutationPolicyOverride',
|
|
71
|
+
summary:
|
|
72
|
+
'Set (or replace) the tenant-scoped mutationPolicy override for this agent.',
|
|
73
|
+
description:
|
|
74
|
+
'Body: `{ mutationPolicy: "read-only" | "confirm-required" | "destructive-confirm-required", notes? }`. ' +
|
|
75
|
+
'The override MUST NOT escalate beyond the agent\'s code-declared policy. Escalation attempts ' +
|
|
76
|
+
'are rejected with 400 + `code: "escalation_not_allowed"`. Requires `ai_assistant.settings.manage`.',
|
|
77
|
+
requestBody: {
|
|
78
|
+
contentType: 'application/json',
|
|
79
|
+
description: 'Body: `{ mutationPolicy, notes? }`.',
|
|
80
|
+
schema: mutationPolicyRequestSchema,
|
|
81
|
+
},
|
|
82
|
+
responses: [
|
|
83
|
+
{
|
|
84
|
+
status: 200,
|
|
85
|
+
description: 'Override persisted.',
|
|
86
|
+
mediaType: 'application/json',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
errors: [
|
|
90
|
+
{ status: 400, description: 'Invalid agent id, malformed body, or escalation attempt.' },
|
|
91
|
+
{ status: 401, description: 'Unauthenticated caller.' },
|
|
92
|
+
{ status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },
|
|
93
|
+
{ status: 404, description: 'Unknown agent id.' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
DELETE: {
|
|
97
|
+
operationId: 'aiAssistantClearMutationPolicyOverride',
|
|
98
|
+
summary: 'Remove the tenant-scoped mutationPolicy override for this agent.',
|
|
99
|
+
description:
|
|
100
|
+
'Deletes the override row if it exists; subsequent calls fall back to the agent\'s ' +
|
|
101
|
+
'code-declared policy. Idempotent — returns 200 even when no override exists. Requires ' +
|
|
102
|
+
'`ai_assistant.settings.manage`.',
|
|
103
|
+
responses: [
|
|
104
|
+
{
|
|
105
|
+
status: 200,
|
|
106
|
+
description: 'Override cleared (or already absent).',
|
|
107
|
+
mediaType: 'application/json',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
errors: [
|
|
111
|
+
{ status: 400, description: 'Invalid agent id.' },
|
|
112
|
+
{ status: 401, description: 'Unauthenticated caller.' },
|
|
113
|
+
{ status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },
|
|
114
|
+
{ status: 404, description: 'Unknown agent id.' },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const metadata = {
|
|
121
|
+
GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },
|
|
122
|
+
POST: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] },
|
|
123
|
+
DELETE: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] },
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface RouteContext {
|
|
127
|
+
params: Promise<{ agentId: string }>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function jsonError(
|
|
131
|
+
status: number,
|
|
132
|
+
message: string,
|
|
133
|
+
code: string,
|
|
134
|
+
extra?: Record<string, unknown>,
|
|
135
|
+
): NextResponse {
|
|
136
|
+
return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface ResolvedAuth {
|
|
140
|
+
tenantId: string | null
|
|
141
|
+
organizationId: string | null
|
|
142
|
+
userId: string
|
|
143
|
+
isSuperAdmin: boolean
|
|
144
|
+
features: string[]
|
|
145
|
+
container: Awaited<ReturnType<typeof createRequestContainer>>
|
|
146
|
+
rbacService: RbacService
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function resolveAuthOrRespond(
|
|
150
|
+
req: NextRequest,
|
|
151
|
+
requiredFeature: string,
|
|
152
|
+
): Promise<ResolvedAuth | NextResponse> {
|
|
153
|
+
const auth = await getAuthFromRequest(req)
|
|
154
|
+
if (!auth) {
|
|
155
|
+
return jsonError(401, 'Unauthorized', 'unauthenticated')
|
|
156
|
+
}
|
|
157
|
+
const container = await createRequestContainer()
|
|
158
|
+
const rbacService = container.resolve<RbacService>('rbacService')
|
|
159
|
+
const acl = await rbacService.loadAcl(auth.sub, {
|
|
160
|
+
tenantId: auth.tenantId,
|
|
161
|
+
organizationId: auth.orgId,
|
|
162
|
+
})
|
|
163
|
+
if (!hasRequiredFeatures([requiredFeature], acl.features, acl.isSuperAdmin, rbacService)) {
|
|
164
|
+
return jsonError(403, `Caller lacks required feature "${requiredFeature}".`, 'forbidden')
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
tenantId: auth.tenantId ?? null,
|
|
168
|
+
organizationId: auth.orgId ?? null,
|
|
169
|
+
userId: auth.sub,
|
|
170
|
+
isSuperAdmin: acl.isSuperAdmin,
|
|
171
|
+
features: acl.features,
|
|
172
|
+
container,
|
|
173
|
+
rbacService,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function serializeOverride(row: AiAgentMutationPolicyOverride) {
|
|
178
|
+
return {
|
|
179
|
+
id: row.id,
|
|
180
|
+
agentId: row.agentId,
|
|
181
|
+
mutationPolicy: row.mutationPolicy,
|
|
182
|
+
notes: row.notes ?? null,
|
|
183
|
+
createdByUserId: row.createdByUserId ?? null,
|
|
184
|
+
createdAt: row.createdAt?.toISOString?.() ?? new Date().toISOString(),
|
|
185
|
+
updatedAt: row.updatedAt?.toISOString?.() ?? new Date().toISOString(),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function codeDeclaredPolicy(agent: AiAgentDefinition): AiAgentMutationPolicy {
|
|
190
|
+
const declared = agent.mutationPolicy
|
|
191
|
+
return declared && isKnownMutationPolicy(declared) ? declared : 'read-only'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function GET(req: NextRequest, context: RouteContext): Promise<Response> {
|
|
195
|
+
const authResult = await resolveAuthOrRespond(req, VIEW_FEATURE)
|
|
196
|
+
if (authResult instanceof NextResponse) return authResult
|
|
197
|
+
|
|
198
|
+
const rawParams = await context.params
|
|
199
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams)
|
|
200
|
+
if (!paramResult.success) {
|
|
201
|
+
return jsonError(400, 'Invalid agent id.', 'validation_error', {
|
|
202
|
+
issues: paramResult.error.issues,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
await loadAgentRegistry()
|
|
208
|
+
const agent = getAgent(paramResult.data.agentId)
|
|
209
|
+
if (!agent) {
|
|
210
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, 'agent_unknown')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const declared = codeDeclaredPolicy(agent)
|
|
214
|
+
if (!authResult.tenantId) {
|
|
215
|
+
return NextResponse.json({
|
|
216
|
+
agentId: agent.id,
|
|
217
|
+
codeDeclared: declared,
|
|
218
|
+
override: null,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const em = authResult.container.resolve<EntityManager>('em')
|
|
223
|
+
const repo = new AiAgentMutationPolicyOverrideRepository(em)
|
|
224
|
+
const current = await repo.get(agent.id, {
|
|
225
|
+
tenantId: authResult.tenantId,
|
|
226
|
+
organizationId: authResult.organizationId,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return NextResponse.json({
|
|
230
|
+
agentId: agent.id,
|
|
231
|
+
codeDeclared: declared,
|
|
232
|
+
override: current ? serializeOverride(current) : null,
|
|
233
|
+
})
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('[AI Mutation Policy GET] Failure:', error)
|
|
236
|
+
return jsonError(
|
|
237
|
+
500,
|
|
238
|
+
error instanceof Error ? error.message : 'Failed to load mutationPolicy override.',
|
|
239
|
+
'internal_error',
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function POST(req: NextRequest, context: RouteContext): Promise<Response> {
|
|
245
|
+
const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE)
|
|
246
|
+
if (authResult instanceof NextResponse) return authResult
|
|
247
|
+
|
|
248
|
+
const rawParams = await context.params
|
|
249
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams)
|
|
250
|
+
if (!paramResult.success) {
|
|
251
|
+
return jsonError(400, 'Invalid agent id.', 'validation_error', {
|
|
252
|
+
issues: paramResult.error.issues,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let parsedBody: unknown
|
|
257
|
+
try {
|
|
258
|
+
parsedBody = await req.json()
|
|
259
|
+
} catch {
|
|
260
|
+
return jsonError(400, 'Request body must be valid JSON.', 'validation_error')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const bodyResult = mutationPolicyRequestSchema.safeParse(parsedBody)
|
|
264
|
+
if (!bodyResult.success) {
|
|
265
|
+
return jsonError(400, 'Invalid request body.', 'validation_error', {
|
|
266
|
+
issues: bodyResult.error.issues,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await loadAgentRegistry()
|
|
272
|
+
const agent = getAgent(paramResult.data.agentId)
|
|
273
|
+
if (!agent) {
|
|
274
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, 'agent_unknown')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const declared = codeDeclaredPolicy(agent)
|
|
278
|
+
if (isMutationPolicyEscalation(declared, bodyResult.data.mutationPolicy)) {
|
|
279
|
+
return jsonError(
|
|
280
|
+
400,
|
|
281
|
+
`Cannot set mutationPolicy="${bodyResult.data.mutationPolicy}" for agent "${agent.id}": ` +
|
|
282
|
+
`the agent\'s code-declared policy is "${declared}". Upgrading beyond the declared ` +
|
|
283
|
+
`policy is a code-level change, not a configuration change.`,
|
|
284
|
+
'escalation_not_allowed',
|
|
285
|
+
{ codeDeclared: declared, requested: bodyResult.data.mutationPolicy },
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!authResult.tenantId) {
|
|
290
|
+
return jsonError(
|
|
291
|
+
400,
|
|
292
|
+
'Caller has no tenant context; cannot persist tenant-scoped mutationPolicy override.',
|
|
293
|
+
'tenant_required',
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const em = authResult.container.resolve<EntityManager>('em')
|
|
298
|
+
const repo = new AiAgentMutationPolicyOverrideRepository(em)
|
|
299
|
+
const saved = await repo.set(
|
|
300
|
+
{
|
|
301
|
+
agentId: agent.id,
|
|
302
|
+
mutationPolicy: bodyResult.data.mutationPolicy,
|
|
303
|
+
notes: bodyResult.data.notes ?? null,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
tenantId: authResult.tenantId,
|
|
307
|
+
organizationId: authResult.organizationId,
|
|
308
|
+
userId: authResult.userId,
|
|
309
|
+
},
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return NextResponse.json({
|
|
313
|
+
ok: true,
|
|
314
|
+
agentId: agent.id,
|
|
315
|
+
codeDeclared: declared,
|
|
316
|
+
override: serializeOverride(saved),
|
|
317
|
+
})
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('[AI Mutation Policy POST] Failure:', error)
|
|
320
|
+
return jsonError(
|
|
321
|
+
500,
|
|
322
|
+
error instanceof Error ? error.message : 'Failed to save mutationPolicy override.',
|
|
323
|
+
'internal_error',
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export async function DELETE(req: NextRequest, context: RouteContext): Promise<Response> {
|
|
329
|
+
const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE)
|
|
330
|
+
if (authResult instanceof NextResponse) return authResult
|
|
331
|
+
|
|
332
|
+
const rawParams = await context.params
|
|
333
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams)
|
|
334
|
+
if (!paramResult.success) {
|
|
335
|
+
return jsonError(400, 'Invalid agent id.', 'validation_error', {
|
|
336
|
+
issues: paramResult.error.issues,
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
await loadAgentRegistry()
|
|
342
|
+
const agent = getAgent(paramResult.data.agentId)
|
|
343
|
+
if (!agent) {
|
|
344
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, 'agent_unknown')
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const declared = codeDeclaredPolicy(agent)
|
|
348
|
+
if (!authResult.tenantId) {
|
|
349
|
+
return NextResponse.json({
|
|
350
|
+
ok: true,
|
|
351
|
+
agentId: agent.id,
|
|
352
|
+
codeDeclared: declared,
|
|
353
|
+
override: null,
|
|
354
|
+
cleared: false,
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const em = authResult.container.resolve<EntityManager>('em')
|
|
359
|
+
const repo = new AiAgentMutationPolicyOverrideRepository(em)
|
|
360
|
+
const cleared = await repo.clear(agent.id, {
|
|
361
|
+
tenantId: authResult.tenantId,
|
|
362
|
+
organizationId: authResult.organizationId,
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
return NextResponse.json({
|
|
366
|
+
ok: true,
|
|
367
|
+
agentId: agent.id,
|
|
368
|
+
codeDeclared: declared,
|
|
369
|
+
override: null,
|
|
370
|
+
cleared,
|
|
371
|
+
})
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error('[AI Mutation Policy DELETE] Failure:', error)
|
|
374
|
+
return jsonError(
|
|
375
|
+
500,
|
|
376
|
+
error instanceof Error ? error.message : 'Failed to clear mutationPolicy override.',
|
|
377
|
+
'internal_error',
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/__tests__/route.test.ts
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import type { AiAgentDefinition } from '../../../../../../lib/ai-agent-definition'
|
|
2
|
+
import {
|
|
3
|
+
resetAgentRegistryForTests,
|
|
4
|
+
seedAgentRegistryForTests,
|
|
5
|
+
} from '../../../../../../lib/agent-registry'
|
|
6
|
+
|
|
7
|
+
const authMock = jest.fn()
|
|
8
|
+
const loadAclMock = jest.fn()
|
|
9
|
+
const createRequestContainerMock = jest.fn()
|
|
10
|
+
const repoGetLatestMock = jest.fn()
|
|
11
|
+
const repoSaveMock = jest.fn()
|
|
12
|
+
const repoListVersionsMock = jest.fn()
|
|
13
|
+
|
|
14
|
+
jest.mock('@open-mercato/shared/lib/auth/server', () => ({
|
|
15
|
+
getAuthFromRequest: (...args: unknown[]) => authMock(...args),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
jest.mock('@open-mercato/shared/lib/di/container', () => ({
|
|
19
|
+
createRequestContainer: (...args: unknown[]) => createRequestContainerMock(...args),
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
jest.mock('../../../../../../data/repositories/AiAgentPromptOverrideRepository', () => ({
|
|
23
|
+
AiAgentPromptOverrideRepository: jest.fn().mockImplementation(() => ({
|
|
24
|
+
getLatest: repoGetLatestMock,
|
|
25
|
+
save: repoSaveMock,
|
|
26
|
+
listVersions: repoListVersionsMock,
|
|
27
|
+
})),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
import { GET, POST } from '../route'
|
|
31
|
+
|
|
32
|
+
function makeAgent(
|
|
33
|
+
overrides: Partial<AiAgentDefinition> & Pick<AiAgentDefinition, 'id' | 'moduleId'>,
|
|
34
|
+
): AiAgentDefinition {
|
|
35
|
+
return {
|
|
36
|
+
label: `${overrides.id} label`,
|
|
37
|
+
description: `${overrides.id} description`,
|
|
38
|
+
systemPrompt: 'You are a test agent.',
|
|
39
|
+
allowedTools: [],
|
|
40
|
+
...overrides,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildRequest(body: unknown, method: 'GET' | 'POST' = 'POST'): Request {
|
|
45
|
+
return new Request('http://localhost/api/ai_assistant/ai/agents/catalog.assistant/prompt-override', {
|
|
46
|
+
method,
|
|
47
|
+
headers: { 'content-type': 'application/json' },
|
|
48
|
+
body: method === 'GET' ? undefined : JSON.stringify(body),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildParams(agentId: string) {
|
|
53
|
+
return { params: Promise.resolve({ agentId }) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('prompt-override route (Step 5.3)', () => {
|
|
57
|
+
let consoleErrorSpy: jest.SpyInstance
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
jest.clearAllMocks()
|
|
61
|
+
resetAgentRegistryForTests()
|
|
62
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
63
|
+
authMock.mockResolvedValue({
|
|
64
|
+
sub: 'user-1',
|
|
65
|
+
tenantId: 'tenant-1',
|
|
66
|
+
orgId: 'org-1',
|
|
67
|
+
})
|
|
68
|
+
loadAclMock.mockResolvedValue({
|
|
69
|
+
features: ['ai_assistant.settings.manage'],
|
|
70
|
+
isSuperAdmin: false,
|
|
71
|
+
})
|
|
72
|
+
createRequestContainerMock.mockResolvedValue({
|
|
73
|
+
resolve: (name: string) => {
|
|
74
|
+
if (name === 'rbacService') {
|
|
75
|
+
return {
|
|
76
|
+
loadAcl: loadAclMock,
|
|
77
|
+
hasAllFeatures: (required: string[], granted: string[]) =>
|
|
78
|
+
required.every((feature) => granted.includes(feature)),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (name === 'em') {
|
|
82
|
+
return {}
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
repoGetLatestMock.mockResolvedValue(null)
|
|
88
|
+
repoListVersionsMock.mockResolvedValue([])
|
|
89
|
+
repoSaveMock.mockImplementation(async (input: any) => ({
|
|
90
|
+
id: 'row-1',
|
|
91
|
+
agentId: input.agentId,
|
|
92
|
+
version: 1,
|
|
93
|
+
sections: input.sections,
|
|
94
|
+
notes: input.notes ?? null,
|
|
95
|
+
createdByUserId: null,
|
|
96
|
+
createdAt: new Date('2026-04-18T00:00:00Z'),
|
|
97
|
+
updatedAt: new Date('2026-04-18T00:00:01Z'),
|
|
98
|
+
}))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
consoleErrorSpy.mockRestore()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
afterAll(() => {
|
|
106
|
+
resetAgentRegistryForTests()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('POST', () => {
|
|
110
|
+
it('returns 401 when unauthenticated', async () => {
|
|
111
|
+
authMock.mockResolvedValueOnce(null)
|
|
112
|
+
|
|
113
|
+
const response = await POST(
|
|
114
|
+
buildRequest({ sections: {} }) as any,
|
|
115
|
+
buildParams('catalog.assistant'),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
expect(response.status).toBe(401)
|
|
119
|
+
const json = await response.json()
|
|
120
|
+
expect(json.code).toBe('unauthenticated')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('returns 403 when the caller lacks ai_assistant.settings.manage', async () => {
|
|
124
|
+
loadAclMock.mockResolvedValueOnce({
|
|
125
|
+
features: ['ai_assistant.view'],
|
|
126
|
+
isSuperAdmin: false,
|
|
127
|
+
})
|
|
128
|
+
seedAgentRegistryForTests([
|
|
129
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
130
|
+
])
|
|
131
|
+
|
|
132
|
+
const response = await POST(
|
|
133
|
+
buildRequest({ sections: { role: 'You are friendly.' } }) as any,
|
|
134
|
+
buildParams('catalog.assistant'),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
expect(response.status).toBe(403)
|
|
138
|
+
const json = await response.json()
|
|
139
|
+
expect(json.code).toBe('forbidden')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('returns 404 for an unknown agent id', async () => {
|
|
143
|
+
seedAgentRegistryForTests([
|
|
144
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
145
|
+
])
|
|
146
|
+
|
|
147
|
+
const response = await POST(
|
|
148
|
+
buildRequest({ sections: {} }) as any,
|
|
149
|
+
buildParams('catalog.missing'),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
expect(response.status).toBe(404)
|
|
153
|
+
const json = await response.json()
|
|
154
|
+
expect(json.code).toBe('agent_unknown')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('returns 400 when body is not JSON-shaped as expected', async () => {
|
|
158
|
+
seedAgentRegistryForTests([
|
|
159
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
const response = await POST(
|
|
163
|
+
buildRequest({ sections: 'not-an-object' }) as any,
|
|
164
|
+
buildParams('catalog.assistant'),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
expect(response.status).toBe(400)
|
|
168
|
+
const json = await response.json()
|
|
169
|
+
expect(json.code).toBe('validation_error')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('returns 400 when the agentId param is malformed', async () => {
|
|
173
|
+
const response = await POST(
|
|
174
|
+
buildRequest({ sections: {} }) as any,
|
|
175
|
+
buildParams('NotAValidId'),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
expect(response.status).toBe(400)
|
|
179
|
+
const json = await response.json()
|
|
180
|
+
expect(json.code).toBe('validation_error')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('returns 400 with reserved_key when the body contains a policy key', async () => {
|
|
184
|
+
seedAgentRegistryForTests([
|
|
185
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
186
|
+
])
|
|
187
|
+
|
|
188
|
+
const response = await POST(
|
|
189
|
+
buildRequest({
|
|
190
|
+
sections: { mutationPolicy: 'allow writes', role: 'Be nice.' },
|
|
191
|
+
}) as any,
|
|
192
|
+
buildParams('catalog.assistant'),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
expect(response.status).toBe(400)
|
|
196
|
+
const json = await response.json()
|
|
197
|
+
expect(json.code).toBe('reserved_key')
|
|
198
|
+
expect(json.reservedKeys).toContain('mutationPolicy')
|
|
199
|
+
expect(repoSaveMock).not.toHaveBeenCalled()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('happy path: returns 200 with { ok: true, version } on successful save', async () => {
|
|
203
|
+
seedAgentRegistryForTests([
|
|
204
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
const response = await POST(
|
|
208
|
+
buildRequest({
|
|
209
|
+
sections: { role: 'You are a friendly product expert.' },
|
|
210
|
+
notes: 'Friendly tone rollout.',
|
|
211
|
+
}) as any,
|
|
212
|
+
buildParams('catalog.assistant'),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(response.status).toBe(200)
|
|
216
|
+
const json = await response.json()
|
|
217
|
+
expect(json.ok).toBe(true)
|
|
218
|
+
expect(json.agentId).toBe('catalog.assistant')
|
|
219
|
+
expect(json.version).toBe(1)
|
|
220
|
+
expect(typeof json.updatedAt).toBe('string')
|
|
221
|
+
expect(repoSaveMock).toHaveBeenCalledTimes(1)
|
|
222
|
+
expect(repoSaveMock.mock.calls[0][0]).toMatchObject({
|
|
223
|
+
agentId: 'catalog.assistant',
|
|
224
|
+
sections: { role: 'You are a friendly product expert.' },
|
|
225
|
+
notes: 'Friendly tone rollout.',
|
|
226
|
+
})
|
|
227
|
+
expect(repoSaveMock.mock.calls[0][1]).toMatchObject({
|
|
228
|
+
tenantId: 'tenant-1',
|
|
229
|
+
organizationId: 'org-1',
|
|
230
|
+
userId: 'user-1',
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('accepts legacy overrides key as an alias for sections', async () => {
|
|
235
|
+
seedAgentRegistryForTests([
|
|
236
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
237
|
+
])
|
|
238
|
+
|
|
239
|
+
const response = await POST(
|
|
240
|
+
buildRequest({ overrides: { role: 'legacy shape' } }) as any,
|
|
241
|
+
buildParams('catalog.assistant'),
|
|
242
|
+
)
|
|
243
|
+
expect(response.status).toBe(200)
|
|
244
|
+
const json = await response.json()
|
|
245
|
+
expect(json.ok).toBe(true)
|
|
246
|
+
expect(repoSaveMock.mock.calls[0][0].sections).toEqual({ role: 'legacy shape' })
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('GET', () => {
|
|
251
|
+
it('returns 401 when unauthenticated', async () => {
|
|
252
|
+
authMock.mockResolvedValueOnce(null)
|
|
253
|
+
const response = await GET(
|
|
254
|
+
buildRequest(null, 'GET') as any,
|
|
255
|
+
buildParams('catalog.assistant'),
|
|
256
|
+
)
|
|
257
|
+
expect(response.status).toBe(401)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('returns 404 for an unknown agent id', async () => {
|
|
261
|
+
seedAgentRegistryForTests([
|
|
262
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
263
|
+
])
|
|
264
|
+
const response = await GET(
|
|
265
|
+
buildRequest(null, 'GET') as any,
|
|
266
|
+
buildParams('catalog.missing'),
|
|
267
|
+
)
|
|
268
|
+
expect(response.status).toBe(404)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('returns { agentId, override: null, versions: [] } when no overrides exist', async () => {
|
|
272
|
+
seedAgentRegistryForTests([
|
|
273
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
274
|
+
])
|
|
275
|
+
repoGetLatestMock.mockResolvedValueOnce(null)
|
|
276
|
+
repoListVersionsMock.mockResolvedValueOnce([])
|
|
277
|
+
|
|
278
|
+
const response = await GET(
|
|
279
|
+
buildRequest(null, 'GET') as any,
|
|
280
|
+
buildParams('catalog.assistant'),
|
|
281
|
+
)
|
|
282
|
+
expect(response.status).toBe(200)
|
|
283
|
+
const json = await response.json()
|
|
284
|
+
expect(json).toEqual({
|
|
285
|
+
agentId: 'catalog.assistant',
|
|
286
|
+
override: null,
|
|
287
|
+
versions: [],
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('returns serialized latest + history rows', async () => {
|
|
292
|
+
seedAgentRegistryForTests([
|
|
293
|
+
makeAgent({ id: 'catalog.assistant', moduleId: 'catalog' }),
|
|
294
|
+
])
|
|
295
|
+
const latest = {
|
|
296
|
+
id: 'row-2',
|
|
297
|
+
agentId: 'catalog.assistant',
|
|
298
|
+
version: 2,
|
|
299
|
+
sections: { role: 'v2' },
|
|
300
|
+
notes: null,
|
|
301
|
+
createdByUserId: 'user-1',
|
|
302
|
+
createdAt: new Date('2026-04-17T00:00:00Z'),
|
|
303
|
+
updatedAt: new Date('2026-04-18T00:00:00Z'),
|
|
304
|
+
}
|
|
305
|
+
repoGetLatestMock.mockResolvedValueOnce(latest)
|
|
306
|
+
repoListVersionsMock.mockResolvedValueOnce([
|
|
307
|
+
latest,
|
|
308
|
+
{
|
|
309
|
+
id: 'row-1',
|
|
310
|
+
agentId: 'catalog.assistant',
|
|
311
|
+
version: 1,
|
|
312
|
+
sections: { role: 'v1' },
|
|
313
|
+
notes: null,
|
|
314
|
+
createdByUserId: 'user-1',
|
|
315
|
+
createdAt: new Date('2026-04-16T00:00:00Z'),
|
|
316
|
+
updatedAt: new Date('2026-04-16T00:00:01Z'),
|
|
317
|
+
},
|
|
318
|
+
])
|
|
319
|
+
|
|
320
|
+
const response = await GET(
|
|
321
|
+
buildRequest(null, 'GET') as any,
|
|
322
|
+
buildParams('catalog.assistant'),
|
|
323
|
+
)
|
|
324
|
+
expect(response.status).toBe(200)
|
|
325
|
+
const json = await response.json()
|
|
326
|
+
expect(json.agentId).toBe('catalog.assistant')
|
|
327
|
+
expect(json.override.version).toBe(2)
|
|
328
|
+
expect(json.versions).toHaveLength(2)
|
|
329
|
+
expect(json.versions[0].version).toBe(2)
|
|
330
|
+
expect(json.versions[1].version).toBe(1)
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
})
|