@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
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { ModuleCli } from '@open-mercato/shared/modules/registry'
|
|
2
2
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { dirname, resolve } from 'node:path'
|
|
5
|
-
import { pathToFileURL } from 'node:url'
|
|
6
|
-
|
|
7
3
|
/**
|
|
8
4
|
* Ensure app bootstrap is called before creating DI container.
|
|
9
|
-
* Uses
|
|
10
|
-
*
|
|
11
|
-
* compile-time only, not available to Node.js at runtime).
|
|
5
|
+
* Uses the shared generated-bootstrap loader so the command works both from
|
|
6
|
+
* the monorepo app and from standalone apps installed through npm packages.
|
|
12
7
|
*/
|
|
13
8
|
async function ensureBootstrap(): Promise<void> {
|
|
14
9
|
// First check if DI is already available
|
|
@@ -20,23 +15,9 @@ async function ensureBootstrap(): Promise<void> {
|
|
|
20
15
|
// DI not available, need to bootstrap
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
// Construct absolute path to bootstrap using import.meta.url
|
|
24
18
|
try {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
// From packages/ai-assistant/src/modules/ai_assistant/cli.ts
|
|
28
|
-
// to apps/mercato/src/bootstrap.ts:
|
|
29
|
-
// ai_assistant → modules → src → ai-assistant → packages → root (6 levels)
|
|
30
|
-
// then into apps/mercato/src/bootstrap.ts
|
|
31
|
-
const bootstrapPath = resolve(__dirname, '../../../../../../apps/mercato/src/bootstrap.ts')
|
|
32
|
-
|
|
33
|
-
// Dynamic import using file URL
|
|
34
|
-
const bootstrapUrl = pathToFileURL(bootstrapPath).href
|
|
35
|
-
const { bootstrap, isBootstrapped } = await import(bootstrapUrl)
|
|
36
|
-
|
|
37
|
-
if (!isBootstrapped()) {
|
|
38
|
-
bootstrap()
|
|
39
|
-
}
|
|
19
|
+
const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')
|
|
20
|
+
await bootstrapFromAppRoot()
|
|
40
21
|
} catch (error) {
|
|
41
22
|
console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)
|
|
42
23
|
// Continue - some contexts may not have bootstrap available
|
|
@@ -271,4 +252,98 @@ const entityGraph: ModuleCli = {
|
|
|
271
252
|
},
|
|
272
253
|
}
|
|
273
254
|
|
|
274
|
-
|
|
255
|
+
const runPendingActionCleanup: ModuleCli = {
|
|
256
|
+
command: 'run-pending-action-cleanup',
|
|
257
|
+
async run() {
|
|
258
|
+
await ensureBootstrap()
|
|
259
|
+
const container = await createRequestContainer()
|
|
260
|
+
|
|
261
|
+
const { runPendingActionCleanup: runCleanup } = await import(
|
|
262
|
+
'./workers/ai-pending-action-cleanup'
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')
|
|
266
|
+
const summary = await runCleanup({ em })
|
|
267
|
+
|
|
268
|
+
console.log('[ai-pending-action-cleanup] Sweep complete:', summary)
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const testTools: ModuleCli = {
|
|
273
|
+
command: 'test-tools',
|
|
274
|
+
async run(rest) {
|
|
275
|
+
const args = parseArgs(rest)
|
|
276
|
+
const json = args.json === true || args.json === 'true'
|
|
277
|
+
const moduleFilter =
|
|
278
|
+
typeof args.module === 'string' && args.module.length > 0 ? args.module : null
|
|
279
|
+
const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'
|
|
280
|
+
const tenantId =
|
|
281
|
+
typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null
|
|
282
|
+
const organizationId =
|
|
283
|
+
typeof args.org === 'string' && args.org.length > 0 ? args.org : null
|
|
284
|
+
|
|
285
|
+
await ensureBootstrap()
|
|
286
|
+
const { runToolTests } = await import('./lib/tool-test-runner')
|
|
287
|
+
const report = await runToolTests({
|
|
288
|
+
tenantId,
|
|
289
|
+
organizationId,
|
|
290
|
+
moduleFilter,
|
|
291
|
+
includeMutations,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (json) {
|
|
295
|
+
// Wrap in markers so a Playwright spec (or any caller) can extract the
|
|
296
|
+
// JSON payload without being thrown off by bootstrap log lines emitted
|
|
297
|
+
// to stdout by other modules during DI container creation.
|
|
298
|
+
console.log('---TOOL_TEST_REPORT_BEGIN---')
|
|
299
|
+
console.log(JSON.stringify(report))
|
|
300
|
+
console.log('---TOOL_TEST_REPORT_END---')
|
|
301
|
+
} else {
|
|
302
|
+
console.log('')
|
|
303
|
+
console.log(
|
|
304
|
+
`AI tool test report — tenant=${report.tenantId ?? '<none>'} org=${
|
|
305
|
+
report.organizationId ?? '<none>'
|
|
306
|
+
}`,
|
|
307
|
+
)
|
|
308
|
+
console.log(
|
|
309
|
+
`total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,
|
|
310
|
+
)
|
|
311
|
+
const byModule = new Map<string, typeof report.records>()
|
|
312
|
+
for (const record of report.records) {
|
|
313
|
+
const list = byModule.get(record.module) ?? []
|
|
314
|
+
list.push(record)
|
|
315
|
+
byModule.set(record.module, list)
|
|
316
|
+
}
|
|
317
|
+
const sortedModules = Array.from(byModule.keys()).sort()
|
|
318
|
+
for (const moduleId of sortedModules) {
|
|
319
|
+
const list = byModule.get(moduleId)!
|
|
320
|
+
console.log('')
|
|
321
|
+
console.log(`${moduleId} (${list.length}):`)
|
|
322
|
+
for (const record of list) {
|
|
323
|
+
const marker =
|
|
324
|
+
record.status === 'pass' ? '✓' : record.status === 'fail' ? '✗' : '·'
|
|
325
|
+
const reason = record.reason ? ` — ${record.reason}` : ''
|
|
326
|
+
const mutation = record.isMutation ? ' [mutation]' : ''
|
|
327
|
+
console.log(
|
|
328
|
+
` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
console.log('')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (report.failed > 0) {
|
|
336
|
+
process.exitCode = 1
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default [
|
|
342
|
+
mcpServe,
|
|
343
|
+
mcpServeHttp,
|
|
344
|
+
mcpDev,
|
|
345
|
+
listTools,
|
|
346
|
+
entityGraph,
|
|
347
|
+
runPendingActionCleanup,
|
|
348
|
+
testTools,
|
|
349
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
const migrationCases = [
|
|
4
|
+
{
|
|
5
|
+
file: 'Migration20260419100521.ts',
|
|
6
|
+
orgIndex: 'create unique index "ai_agent_prompt_overrides_tenant_org_agent_version_uq"',
|
|
7
|
+
tenantIndex:
|
|
8
|
+
'create unique index "ai_agent_prompt_overrides_tenant_agent_version_null_org_uq"',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
file: 'Migration20260419132948.ts',
|
|
12
|
+
orgIndex:
|
|
13
|
+
'create unique index "ai_agent_mutation_policy_overrides_tenant_org_agent_uq"',
|
|
14
|
+
tenantIndex:
|
|
15
|
+
'create unique index "ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq"',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
file: 'Migration20260419134235.ts',
|
|
19
|
+
orgIndex: 'create unique index "ai_pending_actions_tenant_org_idempotency_uq"',
|
|
20
|
+
tenantIndex: 'create unique index "ai_pending_actions_tenant_idem_null_org_uq"',
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
describe('AI assistant nullable organization unique indexes', () => {
|
|
25
|
+
it.each(migrationCases)(
|
|
26
|
+
'$file uses partial unique indexes for org and tenant-wide rows',
|
|
27
|
+
({ file, orgIndex, tenantIndex }) => {
|
|
28
|
+
const source = readFileSync(
|
|
29
|
+
new URL(`../../migrations/${file}`, import.meta.url),
|
|
30
|
+
'utf8',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expect(source).toContain(orgIndex)
|
|
34
|
+
expect(source).toContain('where "organization_id" is not null')
|
|
35
|
+
expect(source).toContain(tenantIndex)
|
|
36
|
+
expect(source).toContain('where "organization_id" is null')
|
|
37
|
+
expect(source).not.toContain(' add constraint ')
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
it('keeps the module snapshot aligned with partial unique indexes', () => {
|
|
42
|
+
const snapshot = JSON.parse(
|
|
43
|
+
readFileSync(
|
|
44
|
+
new URL('../../migrations/.snapshot-open-mercato.json', import.meta.url),
|
|
45
|
+
'utf8',
|
|
46
|
+
),
|
|
47
|
+
) as {
|
|
48
|
+
tables: Array<{ name: string; indexes: Array<{ keyName: string; expression?: string }> }>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const indexes = new Map<string, string>()
|
|
52
|
+
for (const table of snapshot.tables) {
|
|
53
|
+
for (const index of table.indexes) {
|
|
54
|
+
if (index.expression) indexes.set(index.keyName, index.expression)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const { orgIndex, tenantIndex } of migrationCases) {
|
|
59
|
+
const orgIndexName = orgIndex.match(/"([^"]+)"/)?.[1]
|
|
60
|
+
const tenantIndexName = tenantIndex.match(/"([^"]+)"/)?.[1]
|
|
61
|
+
expect(orgIndexName ? indexes.get(orgIndexName) : undefined).toContain(
|
|
62
|
+
'where "organization_id" is not null',
|
|
63
|
+
)
|
|
64
|
+
expect(tenantIndexName ? indexes.get(tenantIndexName) : undefined).toContain(
|
|
65
|
+
'where "organization_id" is null',
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Canonical entity class lives in `../entities.ts` (single-file aggregate, same
|
|
2
|
+
// pattern as `packages/core/src/modules/customers/data/entities.ts`). This
|
|
3
|
+
// thin re-export exists so call sites that prefer the by-name import path
|
|
4
|
+
// (`.../entities/AiAgentMutationPolicyOverride`) keep working.
|
|
5
|
+
|
|
6
|
+
export { AiAgentMutationPolicyOverride } from '../entities'
|
|
7
|
+
export type { AiAgentMutationPolicyOverride as default } from '../entities'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Canonical entity class lives in `../entities.ts` (single-file aggregate, same
|
|
2
|
+
// pattern as `packages/core/src/modules/customers/data/entities.ts`). This
|
|
3
|
+
// thin re-export exists so call sites that prefer the by-name import path
|
|
4
|
+
// (`.../entities/AiAgentPromptOverride`) keep working.
|
|
5
|
+
|
|
6
|
+
export { AiAgentPromptOverride } from '../entities'
|
|
7
|
+
export type { AiAgentPromptOverride as default } from '../entities'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Canonical entity class lives in `../entities.ts` (single-file aggregate, same
|
|
2
|
+
// pattern as `packages/core/src/modules/customers/data/entities.ts`). This
|
|
3
|
+
// thin re-export exists so call sites that prefer the by-name import path
|
|
4
|
+
// (`.../entities/AiPendingAction`) keep working.
|
|
5
|
+
|
|
6
|
+
export { AiPendingAction } from '../entities'
|
|
7
|
+
export type { AiPendingAction as default } from '../entities'
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core'
|
|
2
|
+
import {
|
|
3
|
+
Entity,
|
|
4
|
+
Index,
|
|
5
|
+
PrimaryKey,
|
|
6
|
+
Property,
|
|
7
|
+
} from '@mikro-orm/decorators/legacy'
|
|
8
|
+
import type {
|
|
9
|
+
AiPendingActionExecutionResult,
|
|
10
|
+
AiPendingActionFailedRecord,
|
|
11
|
+
AiPendingActionFieldDiff,
|
|
12
|
+
AiPendingActionQueueMode,
|
|
13
|
+
AiPendingActionRecordDiff,
|
|
14
|
+
AiPendingActionStatus,
|
|
15
|
+
} from '../lib/pending-action-types'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Versioned additive prompt-override for a registered AI agent (Step 5.3).
|
|
19
|
+
*
|
|
20
|
+
* Each write creates a new row with `version = latest + 1`. Rows are never
|
|
21
|
+
* updated in place — history is preserved so operators can roll back by
|
|
22
|
+
* reading an earlier `version`. Column set is tenant/org-scoped per the
|
|
23
|
+
* standard Open Mercato RBAC contract.
|
|
24
|
+
*
|
|
25
|
+
* `sections` holds additive text keyed by prompt section id. The runtime
|
|
26
|
+
* composes the final `systemPrompt` via `composeSystemPromptWithOverride`
|
|
27
|
+
* (see `lib/prompt-override-merge.ts`), which NEVER replaces a built-in
|
|
28
|
+
* section — overrides are append-only by contract.
|
|
29
|
+
*/
|
|
30
|
+
@Entity({ tableName: 'ai_agent_prompt_overrides' })
|
|
31
|
+
@Index({
|
|
32
|
+
name: 'ai_agent_prompt_overrides_tenant_org_agent_version_uq',
|
|
33
|
+
expression:
|
|
34
|
+
'create unique index "ai_agent_prompt_overrides_tenant_org_agent_version_uq" on "ai_agent_prompt_overrides" ("tenant_id", "organization_id", "agent_id", "version") where "organization_id" is not null',
|
|
35
|
+
})
|
|
36
|
+
@Index({
|
|
37
|
+
name: 'ai_agent_prompt_overrides_tenant_agent_version_null_org_uq',
|
|
38
|
+
expression:
|
|
39
|
+
'create unique index "ai_agent_prompt_overrides_tenant_agent_version_null_org_uq" on "ai_agent_prompt_overrides" ("tenant_id", "agent_id", "version") where "organization_id" is null',
|
|
40
|
+
})
|
|
41
|
+
@Index({
|
|
42
|
+
name: 'ai_agent_prompt_overrides_tenant_agent_idx',
|
|
43
|
+
properties: ['tenantId', 'agentId'],
|
|
44
|
+
})
|
|
45
|
+
@Index({
|
|
46
|
+
name: 'ai_agent_prompt_overrides_tenant_org_agent_version_idx',
|
|
47
|
+
expression:
|
|
48
|
+
'create index "ai_agent_prompt_overrides_tenant_org_agent_version_idx" on "ai_agent_prompt_overrides" ("tenant_id", "organization_id", "agent_id", "version" desc)',
|
|
49
|
+
})
|
|
50
|
+
export class AiAgentPromptOverride {
|
|
51
|
+
[OptionalProps]?: 'createdAt' | 'updatedAt' | 'organizationId' | 'createdByUserId' | 'notes'
|
|
52
|
+
|
|
53
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
54
|
+
id!: string
|
|
55
|
+
|
|
56
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
57
|
+
tenantId!: string
|
|
58
|
+
|
|
59
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
60
|
+
organizationId?: string | null
|
|
61
|
+
|
|
62
|
+
@Property({ name: 'agent_id', type: 'text' })
|
|
63
|
+
agentId!: string
|
|
64
|
+
|
|
65
|
+
@Property({ name: 'version', type: 'int' })
|
|
66
|
+
version!: number
|
|
67
|
+
|
|
68
|
+
@Property({ name: 'sections', type: 'jsonb' })
|
|
69
|
+
sections!: Record<string, string>
|
|
70
|
+
|
|
71
|
+
@Property({ name: 'notes', type: 'text', nullable: true })
|
|
72
|
+
notes?: string | null
|
|
73
|
+
|
|
74
|
+
@Property({ name: 'created_by_user_id', type: 'uuid', nullable: true })
|
|
75
|
+
createdByUserId?: string | null
|
|
76
|
+
|
|
77
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
78
|
+
createdAt: Date = new Date()
|
|
79
|
+
|
|
80
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
81
|
+
updatedAt: Date = new Date()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Persistent mutation-approval gate row backing the Phase 3 WS-C contract
|
|
86
|
+
* (spec §8 `AiPendingAction` + §9 confirm/cancel flow, Step 5.5).
|
|
87
|
+
*
|
|
88
|
+
* One row is created by `prepareMutation` (Step 5.6) whenever the runtime
|
|
89
|
+
* intercepts an `isMutation: true` tool call from a non-read-only agent.
|
|
90
|
+
* The row stores the normalized tool input, a precomputed `fieldDiff` (or
|
|
91
|
+
* per-record batch diff in `records[]`), the target record version, an
|
|
92
|
+
* `idempotencyKey` that dedupes double-submits within the TTL, and a
|
|
93
|
+
* `status` that walks the state machine defined in
|
|
94
|
+
* {@link AI_PENDING_ACTION_ALLOWED_TRANSITIONS}.
|
|
95
|
+
*
|
|
96
|
+
* The cleanup worker (Step 5.12) sweeps `status='pending' AND expiresAt < now`
|
|
97
|
+
* rows and transitions them to `expired`. The confirm route (Step 5.8)
|
|
98
|
+
* walks `pending → confirmed → executing → (failed | terminal success)`.
|
|
99
|
+
* Reads always flow through `findOneWithDecryption` /
|
|
100
|
+
* `findWithDecryption`, even though no column is GDPR-flagged today, so
|
|
101
|
+
* future encrypted columns (e.g. `normalizedInput`) are handled.
|
|
102
|
+
*/
|
|
103
|
+
@Entity({ tableName: 'ai_pending_actions' })
|
|
104
|
+
@Index({
|
|
105
|
+
name: 'ai_pending_actions_tenant_org_idempotency_uq',
|
|
106
|
+
expression:
|
|
107
|
+
'create unique index "ai_pending_actions_tenant_org_idempotency_uq" on "ai_pending_actions" ("tenant_id", "organization_id", "idempotency_key") where "organization_id" is not null',
|
|
108
|
+
})
|
|
109
|
+
@Index({
|
|
110
|
+
name: 'ai_pending_actions_tenant_idem_null_org_uq',
|
|
111
|
+
expression:
|
|
112
|
+
'create unique index "ai_pending_actions_tenant_idem_null_org_uq" on "ai_pending_actions" ("tenant_id", "idempotency_key") where "organization_id" is null',
|
|
113
|
+
})
|
|
114
|
+
@Index({
|
|
115
|
+
name: 'ai_pending_actions_tenant_org_status_expires_idx',
|
|
116
|
+
properties: ['tenantId', 'organizationId', 'status', 'expiresAt'],
|
|
117
|
+
})
|
|
118
|
+
@Index({
|
|
119
|
+
name: 'ai_pending_actions_tenant_org_agent_status_idx',
|
|
120
|
+
properties: ['tenantId', 'organizationId', 'agentId', 'status'],
|
|
121
|
+
})
|
|
122
|
+
export class AiPendingAction {
|
|
123
|
+
[OptionalProps]?:
|
|
124
|
+
| 'createdAt'
|
|
125
|
+
| 'organizationId'
|
|
126
|
+
| 'conversationId'
|
|
127
|
+
| 'targetEntityType'
|
|
128
|
+
| 'targetRecordId'
|
|
129
|
+
| 'fieldDiff'
|
|
130
|
+
| 'records'
|
|
131
|
+
| 'failedRecords'
|
|
132
|
+
| 'sideEffectsSummary'
|
|
133
|
+
| 'recordVersion'
|
|
134
|
+
| 'attachmentIds'
|
|
135
|
+
| 'executionResult'
|
|
136
|
+
| 'resolvedAt'
|
|
137
|
+
| 'resolvedByUserId'
|
|
138
|
+
| 'queueMode'
|
|
139
|
+
|
|
140
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
141
|
+
id!: string
|
|
142
|
+
|
|
143
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
144
|
+
tenantId!: string
|
|
145
|
+
|
|
146
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
147
|
+
organizationId?: string | null
|
|
148
|
+
|
|
149
|
+
@Property({ name: 'agent_id', type: 'text' })
|
|
150
|
+
agentId!: string
|
|
151
|
+
|
|
152
|
+
@Property({ name: 'tool_name', type: 'text' })
|
|
153
|
+
toolName!: string
|
|
154
|
+
|
|
155
|
+
@Property({ name: 'conversation_id', type: 'text', nullable: true })
|
|
156
|
+
conversationId?: string | null
|
|
157
|
+
|
|
158
|
+
@Property({ name: 'target_entity_type', type: 'text', nullable: true })
|
|
159
|
+
targetEntityType?: string | null
|
|
160
|
+
|
|
161
|
+
@Property({ name: 'target_record_id', type: 'text', nullable: true })
|
|
162
|
+
targetRecordId?: string | null
|
|
163
|
+
|
|
164
|
+
@Property({ name: 'normalized_input', type: 'jsonb' })
|
|
165
|
+
normalizedInput!: Record<string, unknown>
|
|
166
|
+
|
|
167
|
+
@Property({ name: 'field_diff', type: 'jsonb', default: [] })
|
|
168
|
+
fieldDiff: AiPendingActionFieldDiff[] = []
|
|
169
|
+
|
|
170
|
+
@Property({ name: 'records', type: 'jsonb', nullable: true })
|
|
171
|
+
records?: AiPendingActionRecordDiff[] | null
|
|
172
|
+
|
|
173
|
+
@Property({ name: 'failed_records', type: 'jsonb', nullable: true })
|
|
174
|
+
failedRecords?: AiPendingActionFailedRecord[] | null
|
|
175
|
+
|
|
176
|
+
@Property({ name: 'side_effects_summary', type: 'text', nullable: true })
|
|
177
|
+
sideEffectsSummary?: string | null
|
|
178
|
+
|
|
179
|
+
@Property({ name: 'record_version', type: 'text', nullable: true })
|
|
180
|
+
recordVersion?: string | null
|
|
181
|
+
|
|
182
|
+
@Property({ name: 'attachment_ids', type: 'jsonb', default: [] })
|
|
183
|
+
attachmentIds: string[] = []
|
|
184
|
+
|
|
185
|
+
@Property({ name: 'idempotency_key', type: 'text' })
|
|
186
|
+
idempotencyKey!: string
|
|
187
|
+
|
|
188
|
+
@Property({ name: 'created_by_user_id', type: 'uuid' })
|
|
189
|
+
createdByUserId!: string
|
|
190
|
+
|
|
191
|
+
@Property({ name: 'status', type: 'text' })
|
|
192
|
+
status!: AiPendingActionStatus
|
|
193
|
+
|
|
194
|
+
@Property({ name: 'queue_mode', type: 'text', default: 'inline' })
|
|
195
|
+
queueMode: AiPendingActionQueueMode = 'inline'
|
|
196
|
+
|
|
197
|
+
@Property({ name: 'execution_result', type: 'jsonb', nullable: true })
|
|
198
|
+
executionResult?: AiPendingActionExecutionResult | null
|
|
199
|
+
|
|
200
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
201
|
+
createdAt: Date = new Date()
|
|
202
|
+
|
|
203
|
+
@Property({ name: 'expires_at', type: Date })
|
|
204
|
+
expiresAt!: Date
|
|
205
|
+
|
|
206
|
+
@Property({ name: 'resolved_at', type: Date, nullable: true })
|
|
207
|
+
resolvedAt?: Date | null
|
|
208
|
+
|
|
209
|
+
@Property({ name: 'resolved_by_user_id', type: 'uuid', nullable: true })
|
|
210
|
+
resolvedByUserId?: string | null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Tenant-scoped override of an agent's declared `mutationPolicy` (Step 5.4).
|
|
215
|
+
*
|
|
216
|
+
* Unlike {@link AiAgentPromptOverride}, this surface is NOT versioned — it is
|
|
217
|
+
* a single-value policy switch per `(tenantId, organizationId, agentId)`. The
|
|
218
|
+
* runtime enforces the override as a DOWNGRADE only: the effective policy
|
|
219
|
+
* equals the MOST RESTRICTIVE of `{ code-declared, override }`. Escalation is
|
|
220
|
+
* a code-level change and is rejected at the route layer.
|
|
221
|
+
*
|
|
222
|
+
* Hierarchy (most restrictive → least): `read-only` < `destructive-confirm-required`
|
|
223
|
+
* < `confirm-required`. The route never allows an override to widen the
|
|
224
|
+
* code-declared policy.
|
|
225
|
+
*/
|
|
226
|
+
@Entity({ tableName: 'ai_agent_mutation_policy_overrides' })
|
|
227
|
+
@Index({
|
|
228
|
+
name: 'ai_agent_mutation_policy_overrides_tenant_org_agent_uq',
|
|
229
|
+
expression:
|
|
230
|
+
'create unique index "ai_agent_mutation_policy_overrides_tenant_org_agent_uq" on "ai_agent_mutation_policy_overrides" ("tenant_id", "organization_id", "agent_id") where "organization_id" is not null',
|
|
231
|
+
})
|
|
232
|
+
@Index({
|
|
233
|
+
name: 'ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq',
|
|
234
|
+
expression:
|
|
235
|
+
'create unique index "ai_agent_mutation_policy_overrides_tenant_agent_null_org_uq" on "ai_agent_mutation_policy_overrides" ("tenant_id", "agent_id") where "organization_id" is null',
|
|
236
|
+
})
|
|
237
|
+
@Index({
|
|
238
|
+
name: 'ai_agent_mutation_policy_overrides_tenant_agent_idx',
|
|
239
|
+
properties: ['tenantId', 'agentId'],
|
|
240
|
+
})
|
|
241
|
+
export class AiAgentMutationPolicyOverride {
|
|
242
|
+
[OptionalProps]?: 'createdAt' | 'updatedAt' | 'organizationId' | 'createdByUserId' | 'notes'
|
|
243
|
+
|
|
244
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
245
|
+
id!: string
|
|
246
|
+
|
|
247
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
248
|
+
tenantId!: string
|
|
249
|
+
|
|
250
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
251
|
+
organizationId?: string | null
|
|
252
|
+
|
|
253
|
+
@Property({ name: 'agent_id', type: 'text' })
|
|
254
|
+
agentId!: string
|
|
255
|
+
|
|
256
|
+
@Property({ name: 'mutation_policy', type: 'text' })
|
|
257
|
+
mutationPolicy!: string
|
|
258
|
+
|
|
259
|
+
@Property({ name: 'notes', type: 'text', nullable: true })
|
|
260
|
+
notes?: string | null
|
|
261
|
+
|
|
262
|
+
@Property({ name: 'created_by_user_id', type: 'uuid', nullable: true })
|
|
263
|
+
createdByUserId?: string | null
|
|
264
|
+
|
|
265
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
266
|
+
createdAt: Date = new Date()
|
|
267
|
+
|
|
268
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
269
|
+
updatedAt: Date = new Date()
|
|
270
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import {
|
|
3
|
+
findOneWithDecryption,
|
|
4
|
+
} from '@open-mercato/shared/lib/encryption/find'
|
|
5
|
+
import { AiAgentMutationPolicyOverride } from '../entities'
|
|
6
|
+
import type { AiAgentMutationPolicy } from '../../lib/ai-agent-definition'
|
|
7
|
+
|
|
8
|
+
export interface AiAgentMutationPolicyOverrideContext {
|
|
9
|
+
tenantId: string
|
|
10
|
+
organizationId?: string | null
|
|
11
|
+
userId?: string | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AiAgentMutationPolicyOverrideInput {
|
|
15
|
+
agentId: string
|
|
16
|
+
mutationPolicy: AiAgentMutationPolicy
|
|
17
|
+
notes?: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Single-value mutation-policy override repository (Step 5.4).
|
|
22
|
+
*
|
|
23
|
+
* Unlike the versioned prompt override, there is ONE current mutation-policy
|
|
24
|
+
* override per `(tenantId, organizationId, agentId)`. `set()` replaces the
|
|
25
|
+
* existing row (or inserts a new one) inside a transaction so concurrent
|
|
26
|
+
* writers cannot produce ghost rows. `clear()` deletes the row so the runtime
|
|
27
|
+
* falls back to the code-declared policy.
|
|
28
|
+
*
|
|
29
|
+
* Reads always go through `findOneWithDecryption` to stay consistent with the
|
|
30
|
+
* rest of the module even though `mutation_policy` isn't encrypted today.
|
|
31
|
+
*/
|
|
32
|
+
export class AiAgentMutationPolicyOverrideRepository {
|
|
33
|
+
constructor(private readonly em: EntityManager) {}
|
|
34
|
+
|
|
35
|
+
async get(
|
|
36
|
+
agentId: string,
|
|
37
|
+
ctx: AiAgentMutationPolicyOverrideContext,
|
|
38
|
+
): Promise<AiAgentMutationPolicyOverride | null> {
|
|
39
|
+
if (!agentId || !ctx?.tenantId) return null
|
|
40
|
+
const row = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
|
|
41
|
+
this.em,
|
|
42
|
+
AiAgentMutationPolicyOverride,
|
|
43
|
+
{
|
|
44
|
+
tenantId: ctx.tenantId,
|
|
45
|
+
organizationId: ctx.organizationId ?? null,
|
|
46
|
+
agentId,
|
|
47
|
+
} as any,
|
|
48
|
+
{},
|
|
49
|
+
{ tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
|
|
50
|
+
)
|
|
51
|
+
return row ?? null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async set(
|
|
55
|
+
input: AiAgentMutationPolicyOverrideInput,
|
|
56
|
+
ctx: AiAgentMutationPolicyOverrideContext,
|
|
57
|
+
): Promise<AiAgentMutationPolicyOverride> {
|
|
58
|
+
if (!ctx?.tenantId) {
|
|
59
|
+
throw new Error('AiAgentMutationPolicyOverrideRepository.set requires tenantId')
|
|
60
|
+
}
|
|
61
|
+
if (!input?.agentId) {
|
|
62
|
+
throw new Error('AiAgentMutationPolicyOverrideRepository.set requires agentId')
|
|
63
|
+
}
|
|
64
|
+
const normalized = normalizeNotes(input.notes)
|
|
65
|
+
return this.em.transactional(async (tx) => {
|
|
66
|
+
const existing = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
|
|
67
|
+
tx as unknown as EntityManager,
|
|
68
|
+
AiAgentMutationPolicyOverride,
|
|
69
|
+
{
|
|
70
|
+
tenantId: ctx.tenantId,
|
|
71
|
+
organizationId: ctx.organizationId ?? null,
|
|
72
|
+
agentId: input.agentId,
|
|
73
|
+
} as any,
|
|
74
|
+
{},
|
|
75
|
+
{ tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
|
|
76
|
+
)
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.mutationPolicy = input.mutationPolicy
|
|
79
|
+
existing.notes = normalized
|
|
80
|
+
existing.createdByUserId = ctx.userId ?? existing.createdByUserId ?? null
|
|
81
|
+
existing.updatedAt = new Date()
|
|
82
|
+
await tx.persist(existing).flush()
|
|
83
|
+
return existing
|
|
84
|
+
}
|
|
85
|
+
const row = tx.create(AiAgentMutationPolicyOverride, {
|
|
86
|
+
tenantId: ctx.tenantId,
|
|
87
|
+
organizationId: ctx.organizationId ?? null,
|
|
88
|
+
agentId: input.agentId,
|
|
89
|
+
mutationPolicy: input.mutationPolicy,
|
|
90
|
+
notes: normalized,
|
|
91
|
+
createdByUserId: ctx.userId ?? null,
|
|
92
|
+
} as unknown as AiAgentMutationPolicyOverride)
|
|
93
|
+
await tx.persist(row).flush()
|
|
94
|
+
return row
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async clear(
|
|
99
|
+
agentId: string,
|
|
100
|
+
ctx: AiAgentMutationPolicyOverrideContext,
|
|
101
|
+
): Promise<boolean> {
|
|
102
|
+
if (!agentId || !ctx?.tenantId) return false
|
|
103
|
+
return this.em.transactional(async (tx) => {
|
|
104
|
+
const existing = await findOneWithDecryption<AiAgentMutationPolicyOverride>(
|
|
105
|
+
tx as unknown as EntityManager,
|
|
106
|
+
AiAgentMutationPolicyOverride,
|
|
107
|
+
{
|
|
108
|
+
tenantId: ctx.tenantId,
|
|
109
|
+
organizationId: ctx.organizationId ?? null,
|
|
110
|
+
agentId,
|
|
111
|
+
} as any,
|
|
112
|
+
{},
|
|
113
|
+
{ tenantId: ctx.tenantId ?? null, organizationId: ctx.organizationId ?? null },
|
|
114
|
+
)
|
|
115
|
+
if (!existing) return false
|
|
116
|
+
await tx.remove(existing).flush()
|
|
117
|
+
return true
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeNotes(notes: string | null | undefined): string | null {
|
|
123
|
+
if (typeof notes !== 'string') return null
|
|
124
|
+
const trimmed = notes.trim()
|
|
125
|
+
if (!trimmed) return null
|
|
126
|
+
return notes
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default AiAgentMutationPolicyOverrideRepository
|