@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,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-action re-check contract (spec §9.4 / Step 5.8).
|
|
3
|
+
*
|
|
4
|
+
* The operator presses "Confirm" on a `mutation-preview-card`. The server
|
|
5
|
+
* MUST re-verify every invariant before executing the wrapped tool — the
|
|
6
|
+
* pending row was created at propose-time and agents / policies / record
|
|
7
|
+
* versions can all drift between then and confirm. This module is the
|
|
8
|
+
* single source of truth for that contract.
|
|
9
|
+
*
|
|
10
|
+
* The individual `check*` helpers are exported independently so the Step
|
|
11
|
+
* 5.8 unit suite can exercise each guard in isolation. The orchestrator
|
|
12
|
+
* {@link runPendingActionRechecks} stops at the first failure and returns
|
|
13
|
+
* a structured denial the route turns into a JSON error envelope.
|
|
14
|
+
*
|
|
15
|
+
* This module is pure aside from DB reads; the caller owns the transaction
|
|
16
|
+
* boundary. The Step 5.9 cancel route reuses {@link checkStatusAndExpiry}.
|
|
17
|
+
*/
|
|
18
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
19
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
20
|
+
import { Attachment } from '@open-mercato/core/modules/attachments/data/entities'
|
|
21
|
+
import type { AiAgentDefinition } from './ai-agent-definition'
|
|
22
|
+
import type { AiToolDefinition, McpToolContext } from './types'
|
|
23
|
+
import type { AiPendingAction } from '../data/entities'
|
|
24
|
+
import type {
|
|
25
|
+
AiPendingActionFailedRecord,
|
|
26
|
+
AiPendingActionRecordDiff,
|
|
27
|
+
AiPendingActionStatus,
|
|
28
|
+
} from './pending-action-types'
|
|
29
|
+
import { resolveEffectiveMutationPolicy } from './agent-policy'
|
|
30
|
+
import { hasRequiredFeatures } from './auth'
|
|
31
|
+
import type { AiAgentMutationPolicy } from './ai-agent-definition'
|
|
32
|
+
|
|
33
|
+
export type PendingActionRecheckCode =
|
|
34
|
+
| 'invalid_status'
|
|
35
|
+
| 'expired'
|
|
36
|
+
| 'agent_unknown'
|
|
37
|
+
| 'agent_features_denied'
|
|
38
|
+
| 'tool_not_whitelisted'
|
|
39
|
+
| 'read_only_agent'
|
|
40
|
+
| 'attachment_cross_tenant'
|
|
41
|
+
| 'stale_version'
|
|
42
|
+
| 'schema_drift'
|
|
43
|
+
|
|
44
|
+
export interface PendingActionRecheckOkResult {
|
|
45
|
+
ok: true
|
|
46
|
+
/**
|
|
47
|
+
* Per-record stale list for batch mutations when only some records were
|
|
48
|
+
* stale. Present only when the batch path produced a partial-stale set;
|
|
49
|
+
* the caller should persist these via `repo.setStatus(..., { failedRecords })`
|
|
50
|
+
* and proceed with the remaining records.
|
|
51
|
+
*/
|
|
52
|
+
failedRecords?: AiPendingActionFailedRecord[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PendingActionRecheckFailResult {
|
|
56
|
+
ok: false
|
|
57
|
+
status: number
|
|
58
|
+
code: PendingActionRecheckCode
|
|
59
|
+
message: string
|
|
60
|
+
extra?: Record<string, unknown>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type PendingActionRecheckResult =
|
|
64
|
+
| PendingActionRecheckOkResult
|
|
65
|
+
| PendingActionRecheckFailResult
|
|
66
|
+
|
|
67
|
+
export interface PendingActionAuthContext {
|
|
68
|
+
tenantId: string
|
|
69
|
+
organizationId: string | null
|
|
70
|
+
userId: string
|
|
71
|
+
userFeatures: string[]
|
|
72
|
+
isSuperAdmin: boolean
|
|
73
|
+
/**
|
|
74
|
+
* Optional DI container used by `checkRecordVersion` to hand the tool's
|
|
75
|
+
* `loadBeforeRecord` resolver an `McpToolContext`.
|
|
76
|
+
*/
|
|
77
|
+
container?: import('awilix').AwilixContainer
|
|
78
|
+
em?: EntityManager
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PendingActionRecheckInput {
|
|
82
|
+
action: AiPendingAction
|
|
83
|
+
agent: AiAgentDefinition | null | undefined
|
|
84
|
+
tool: AiToolDefinition | null | undefined
|
|
85
|
+
ctx: PendingActionAuthContext
|
|
86
|
+
/** Explicit clock for deterministic tests. */
|
|
87
|
+
now?: Date
|
|
88
|
+
/**
|
|
89
|
+
* Optional tenant-scoped mutation-policy downgrade resolved by the caller
|
|
90
|
+
* (Step 5.4). Missing / null → agent's code-declared policy stands alone.
|
|
91
|
+
*/
|
|
92
|
+
mutationPolicyOverride?: AiAgentMutationPolicy | null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Guards 3 + 2: pending action is still in `pending` and has not expired.
|
|
97
|
+
* Returns a structured 409 on mismatch. Reused by the cancel route (which
|
|
98
|
+
* only runs 1-3 of the re-check list).
|
|
99
|
+
*/
|
|
100
|
+
export function checkStatusAndExpiry(
|
|
101
|
+
action: AiPendingAction,
|
|
102
|
+
options: { now?: Date } = {},
|
|
103
|
+
): PendingActionRecheckResult {
|
|
104
|
+
const now = options.now ?? new Date()
|
|
105
|
+
if (action.status !== 'pending') {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
status: 409,
|
|
109
|
+
code: 'invalid_status',
|
|
110
|
+
message: `Pending action is in status "${action.status}"; expected "pending".`,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const expiresAt =
|
|
114
|
+
action.expiresAt instanceof Date ? action.expiresAt : new Date(action.expiresAt)
|
|
115
|
+
if (expiresAt.getTime() <= now.getTime()) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
status: 409,
|
|
119
|
+
code: 'expired',
|
|
120
|
+
message: 'Pending action has expired. The model must re-propose the mutation.',
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { ok: true }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Guard 4: the agent is still registered AND the caller still carries the
|
|
128
|
+
* agent's `requiredFeatures`. Missing agent → 404. Missing features → 403.
|
|
129
|
+
*/
|
|
130
|
+
export function checkAgentAndFeatures(
|
|
131
|
+
agent: AiAgentDefinition | null | undefined,
|
|
132
|
+
ctx: PendingActionAuthContext,
|
|
133
|
+
): PendingActionRecheckResult {
|
|
134
|
+
if (!agent) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
status: 404,
|
|
138
|
+
code: 'agent_unknown',
|
|
139
|
+
message: 'Agent is no longer registered.',
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const required = agent.requiredFeatures ?? []
|
|
143
|
+
if (!hasRequiredFeatures(required, ctx.userFeatures, ctx.isSuperAdmin)) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
status: 403,
|
|
147
|
+
code: 'agent_features_denied',
|
|
148
|
+
message: `Caller lacks one of the agent's required features: ${required.join(', ')}`,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { ok: true }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Guards 5 + 6: effective mutation policy still allows mutation, AND the
|
|
156
|
+
* tool is still whitelisted + still declared `isMutation: true`. Read-only
|
|
157
|
+
* policy or whitelist drop → 403.
|
|
158
|
+
*/
|
|
159
|
+
export function checkToolWhitelist(
|
|
160
|
+
agent: AiAgentDefinition,
|
|
161
|
+
tool: AiToolDefinition | null | undefined,
|
|
162
|
+
action: AiPendingAction,
|
|
163
|
+
options: { mutationPolicyOverride?: AiAgentMutationPolicy | null } = {},
|
|
164
|
+
): PendingActionRecheckResult {
|
|
165
|
+
if (!tool) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
status: 403,
|
|
169
|
+
code: 'tool_not_whitelisted',
|
|
170
|
+
message: `Tool "${action.toolName}" is not registered.`,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!agent.allowedTools.includes(tool.name) || tool.isMutation !== true) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
status: 403,
|
|
177
|
+
code: 'tool_not_whitelisted',
|
|
178
|
+
message: `Tool "${tool.name}" is no longer whitelisted as a mutation tool for agent "${agent.id}".`,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const effective = resolveEffectiveMutationPolicy(
|
|
182
|
+
agent.mutationPolicy,
|
|
183
|
+
options.mutationPolicyOverride ?? null,
|
|
184
|
+
agent.id,
|
|
185
|
+
)
|
|
186
|
+
if (effective === 'read-only') {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
status: 403,
|
|
190
|
+
code: 'read_only_agent',
|
|
191
|
+
message: `Agent "${agent.id}" effective mutationPolicy=read-only; mutation tool "${tool.name}" cannot be executed.`,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { ok: true }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Guard 7: every attachment id referenced by the pending row belongs to the
|
|
199
|
+
* caller's tenant/org. Any cross-tenant id short-circuits with 403 — we do
|
|
200
|
+
* not leak which specific id was rejected.
|
|
201
|
+
*/
|
|
202
|
+
export async function checkAttachmentScope(
|
|
203
|
+
action: AiPendingAction,
|
|
204
|
+
ctx: PendingActionAuthContext,
|
|
205
|
+
): Promise<PendingActionRecheckResult> {
|
|
206
|
+
const ids = Array.isArray(action.attachmentIds) ? action.attachmentIds : []
|
|
207
|
+
if (ids.length === 0) return { ok: true }
|
|
208
|
+
const em = ctx.em
|
|
209
|
+
if (!em) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
status: 500,
|
|
213
|
+
code: 'attachment_cross_tenant',
|
|
214
|
+
message: 'Attachment scope check requires an EntityManager.',
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
const rows = (await findWithDecryption<any>(
|
|
219
|
+
em,
|
|
220
|
+
Attachment as any,
|
|
221
|
+
{ id: { $in: ids } } as any,
|
|
222
|
+
{},
|
|
223
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
224
|
+
)) as Array<{ id: string; tenantId?: string | null; organizationId?: string | null }>
|
|
225
|
+
if (rows.length !== ids.length) {
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
status: 403,
|
|
229
|
+
code: 'attachment_cross_tenant',
|
|
230
|
+
message: 'One or more attachments are not accessible to the caller.',
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const row of rows) {
|
|
234
|
+
if ((row.tenantId ?? null) !== ctx.tenantId) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
status: 403,
|
|
238
|
+
code: 'attachment_cross_tenant',
|
|
239
|
+
message: 'One or more attachments belong to a different tenant.',
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (ctx.organizationId !== null && row.organizationId && row.organizationId !== ctx.organizationId) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
status: 403,
|
|
246
|
+
code: 'attachment_cross_tenant',
|
|
247
|
+
message: 'One or more attachments belong to a different organization.',
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { ok: true }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Guard 8: record version matches the row's captured `recordVersion`. For
|
|
256
|
+
* single-record actions a mismatch is a hard 412. For batch actions we
|
|
257
|
+
* compute per-record and return a `failedRecords[]` entry for each stale
|
|
258
|
+
* record; the caller proceeds with the remaining records. If the batch
|
|
259
|
+
* has NO remaining records (every one is stale) the function returns 412.
|
|
260
|
+
*
|
|
261
|
+
* Also acts as the schema-drift guard: re-parses `action.normalizedInput`
|
|
262
|
+
* through the tool's current zod schema. A shape change between propose
|
|
263
|
+
* and confirm surfaces as 412 `schema_drift` so the model re-proposes.
|
|
264
|
+
*/
|
|
265
|
+
export async function checkRecordVersion(
|
|
266
|
+
action: AiPendingAction,
|
|
267
|
+
tool: AiToolDefinition,
|
|
268
|
+
ctx: PendingActionAuthContext,
|
|
269
|
+
): Promise<PendingActionRecheckResult> {
|
|
270
|
+
const parseResult = tool.inputSchema.safeParse(action.normalizedInput ?? {})
|
|
271
|
+
if (!parseResult.success) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
status: 412,
|
|
275
|
+
code: 'schema_drift',
|
|
276
|
+
message: 'Pending input no longer satisfies the tool schema.',
|
|
277
|
+
extra: { issues: parseResult.error.issues },
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const handlerContext = toHandlerContext(ctx)
|
|
282
|
+
|
|
283
|
+
const records = Array.isArray(action.records) ? action.records : null
|
|
284
|
+
if (records && records.length > 0) {
|
|
285
|
+
if (!tool.loadBeforeRecords) {
|
|
286
|
+
return { ok: true }
|
|
287
|
+
}
|
|
288
|
+
const currentRows = await tool.loadBeforeRecords(parseResult.data as never, handlerContext)
|
|
289
|
+
const currentVersionById = new Map<string, string | null>()
|
|
290
|
+
for (const row of currentRows ?? []) {
|
|
291
|
+
currentVersionById.set(row.recordId, row.recordVersion ?? null)
|
|
292
|
+
}
|
|
293
|
+
const stale: AiPendingActionFailedRecord[] = []
|
|
294
|
+
for (const record of records as AiPendingActionRecordDiff[]) {
|
|
295
|
+
const current = currentVersionById.get(record.recordId)
|
|
296
|
+
const captured = record.recordVersion ?? null
|
|
297
|
+
if (current === undefined) {
|
|
298
|
+
stale.push({
|
|
299
|
+
recordId: record.recordId,
|
|
300
|
+
error: { code: 'stale_version', message: 'Record no longer exists.' },
|
|
301
|
+
})
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
if (captured !== null && current !== captured) {
|
|
305
|
+
stale.push({
|
|
306
|
+
recordId: record.recordId,
|
|
307
|
+
error: { code: 'stale_version', message: 'Record version changed since preview.' },
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (stale.length === records.length) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
status: 412,
|
|
315
|
+
code: 'stale_version',
|
|
316
|
+
message: 'All pending records are stale; the model must re-propose the batch.',
|
|
317
|
+
extra: { staleRecords: stale.map((entry) => entry.recordId) },
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (stale.length > 0) {
|
|
321
|
+
return { ok: true, failedRecords: stale }
|
|
322
|
+
}
|
|
323
|
+
return { ok: true }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!tool.loadBeforeRecord) {
|
|
327
|
+
return { ok: true }
|
|
328
|
+
}
|
|
329
|
+
const before = await tool.loadBeforeRecord(parseResult.data as never, handlerContext)
|
|
330
|
+
if (!before) return { ok: true }
|
|
331
|
+
const captured = action.recordVersion ?? null
|
|
332
|
+
const current = before.recordVersion ?? null
|
|
333
|
+
if (captured !== null && current !== captured) {
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
status: 412,
|
|
337
|
+
code: 'stale_version',
|
|
338
|
+
message: 'Record version changed since preview; re-propose the mutation.',
|
|
339
|
+
extra: { recordId: before.recordId },
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return { ok: true }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function toHandlerContext(ctx: PendingActionAuthContext): McpToolContext {
|
|
346
|
+
return {
|
|
347
|
+
tenantId: ctx.tenantId,
|
|
348
|
+
organizationId: ctx.organizationId,
|
|
349
|
+
userId: ctx.userId,
|
|
350
|
+
container: ctx.container as never,
|
|
351
|
+
userFeatures: ctx.userFeatures,
|
|
352
|
+
isSuperAdmin: ctx.isSuperAdmin,
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Orchestrator: runs every guard in spec §9.4 order and returns the first
|
|
358
|
+
* failure. Callers receive either `{ ok: true, failedRecords? }` — where a
|
|
359
|
+
* non-empty `failedRecords` indicates a partial-stale batch that should
|
|
360
|
+
* proceed with the non-stale subset — or `{ ok: false, status, code, ... }`
|
|
361
|
+
* ready to serialize as an HTTP error envelope.
|
|
362
|
+
*/
|
|
363
|
+
export async function runPendingActionRechecks(
|
|
364
|
+
input: PendingActionRecheckInput,
|
|
365
|
+
): Promise<PendingActionRecheckResult> {
|
|
366
|
+
const { action, agent, tool, ctx, now, mutationPolicyOverride } = input
|
|
367
|
+
|
|
368
|
+
const statusCheck = checkStatusAndExpiry(action, { now })
|
|
369
|
+
if (!statusCheck.ok) return statusCheck
|
|
370
|
+
|
|
371
|
+
const agentCheck = checkAgentAndFeatures(agent, ctx)
|
|
372
|
+
if (!agentCheck.ok) return agentCheck
|
|
373
|
+
|
|
374
|
+
const whitelistCheck = checkToolWhitelist(agent!, tool, action, {
|
|
375
|
+
mutationPolicyOverride: mutationPolicyOverride ?? null,
|
|
376
|
+
})
|
|
377
|
+
if (!whitelistCheck.ok) return whitelistCheck
|
|
378
|
+
|
|
379
|
+
const attachmentCheck = await checkAttachmentScope(action, ctx)
|
|
380
|
+
if (!attachmentCheck.ok) return attachmentCheck
|
|
381
|
+
|
|
382
|
+
const versionCheck = await checkRecordVersion(action, tool!, ctx)
|
|
383
|
+
return versionCheck
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Test-only helper shadowed here to let the route unit tests assert the
|
|
388
|
+
* exhaustive set of codes without importing each guard individually.
|
|
389
|
+
*/
|
|
390
|
+
export const PENDING_ACTION_RECHECK_CODES: ReadonlyArray<PendingActionRecheckCode> = [
|
|
391
|
+
'invalid_status',
|
|
392
|
+
'expired',
|
|
393
|
+
'agent_unknown',
|
|
394
|
+
'agent_features_denied',
|
|
395
|
+
'tool_not_whitelisted',
|
|
396
|
+
'read_only_agent',
|
|
397
|
+
'attachment_cross_tenant',
|
|
398
|
+
'stale_version',
|
|
399
|
+
'schema_drift',
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
export function isPendingActionRecheckCode(
|
|
403
|
+
value: unknown,
|
|
404
|
+
): value is PendingActionRecheckCode {
|
|
405
|
+
return typeof value === 'string' && (PENDING_ACTION_RECHECK_CODES as readonly string[]).includes(value)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export type { AiPendingActionFailedRecord } from './pending-action-types'
|
|
409
|
+
|
|
410
|
+
export type PendingActionRecheckStatus = AiPendingActionStatus
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared enums + error type for the Phase 3 WS-C mutation approval gate
|
|
3
|
+
* (spec §8 `AiPendingAction` + §9 server contract).
|
|
4
|
+
*
|
|
5
|
+
* These values are referenced by the entity, the repository, the
|
|
6
|
+
* `/api/ai/actions/*` routes (Steps 5.7 / 5.8 / 5.9), and the cleanup
|
|
7
|
+
* worker (Step 5.12). Colocated here so every consumer shares the same
|
|
8
|
+
* source of truth.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const AI_PENDING_ACTION_STATUSES = [
|
|
12
|
+
'pending',
|
|
13
|
+
'confirmed',
|
|
14
|
+
'cancelled',
|
|
15
|
+
'expired',
|
|
16
|
+
'executing',
|
|
17
|
+
'failed',
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
export type AiPendingActionStatus = (typeof AI_PENDING_ACTION_STATUSES)[number]
|
|
21
|
+
|
|
22
|
+
export const AI_PENDING_ACTION_QUEUE_MODES = ['inline', 'stack'] as const
|
|
23
|
+
|
|
24
|
+
export type AiPendingActionQueueMode = (typeof AI_PENDING_ACTION_QUEUE_MODES)[number]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Allowed state-machine edges for `AiPendingAction.status`:
|
|
28
|
+
*
|
|
29
|
+
* ```
|
|
30
|
+
* pending ──┬─▶ confirmed ──▶ executing ──▶ failed
|
|
31
|
+
* │ └──▶ (terminal success keeps status = 'confirmed'
|
|
32
|
+
* │ and stores executionResult.recordId)
|
|
33
|
+
* ├─▶ cancelled
|
|
34
|
+
* └─▶ expired
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Every other transition is rejected with `AiPendingActionStateError`.
|
|
38
|
+
*/
|
|
39
|
+
export const AI_PENDING_ACTION_ALLOWED_TRANSITIONS: Record<
|
|
40
|
+
AiPendingActionStatus,
|
|
41
|
+
ReadonlyArray<AiPendingActionStatus>
|
|
42
|
+
> = {
|
|
43
|
+
pending: ['confirmed', 'cancelled', 'expired'],
|
|
44
|
+
confirmed: ['executing'],
|
|
45
|
+
executing: ['confirmed', 'failed'],
|
|
46
|
+
cancelled: [],
|
|
47
|
+
expired: [],
|
|
48
|
+
failed: [],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const AI_PENDING_ACTION_TERMINAL_STATUSES: ReadonlyArray<AiPendingActionStatus> = [
|
|
52
|
+
'confirmed',
|
|
53
|
+
'cancelled',
|
|
54
|
+
'expired',
|
|
55
|
+
'failed',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Per-record batch diff entry, mirrored in `AiPendingAction.records`.
|
|
60
|
+
*
|
|
61
|
+
* When present, the batch diff is authoritative and `fieldDiff` at the
|
|
62
|
+
* top level is ignored by every consumer (spec §8 rule 2).
|
|
63
|
+
*/
|
|
64
|
+
export type AiPendingActionRecordDiff = {
|
|
65
|
+
recordId: string
|
|
66
|
+
entityType: string
|
|
67
|
+
label: string
|
|
68
|
+
fieldDiff: Array<{ field: string; before: unknown; after: unknown }>
|
|
69
|
+
recordVersion: string | null
|
|
70
|
+
attachmentIds?: string[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Per-record failure shape populated by the confirm handler (Step 5.8)
|
|
75
|
+
* when partial success occurs inside a batch.
|
|
76
|
+
*/
|
|
77
|
+
export type AiPendingActionFailedRecord = {
|
|
78
|
+
recordId: string
|
|
79
|
+
error: { code: string; message: string }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type AiPendingActionFieldDiff = {
|
|
83
|
+
field: string
|
|
84
|
+
before: unknown
|
|
85
|
+
after: unknown
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Structured error context the confirm-executor stamps on
|
|
90
|
+
* `executionResult.error` when a tool handler throws. Additive — every
|
|
91
|
+
* field is optional so older serialized snapshots remain valid.
|
|
92
|
+
*
|
|
93
|
+
* - `details`: free-form structured payload extracted from the thrown
|
|
94
|
+
* error (e.g. ZodError `issues`, `cause`, custom error properties).
|
|
95
|
+
* Forwarded verbatim to the operator's "Fix with AI" prompt so the
|
|
96
|
+
* model can correct the call instead of staring at "Invalid input".
|
|
97
|
+
* - `input`: a JSON-serializable echo of the arguments the handler was
|
|
98
|
+
* invoked with. Lets the model compare what it sent vs. what the
|
|
99
|
+
* schema expected. PII-redaction is the caller's responsibility (the
|
|
100
|
+
* confirm-executor passes through `normalizedInput` which has already
|
|
101
|
+
* been Zod-parsed and stripped of unknown keys).
|
|
102
|
+
* - `name`: the constructor name of the thrown error (e.g. `ZodError`,
|
|
103
|
+
* `TypeError`) — useful when the message is generic.
|
|
104
|
+
* - `stack`: short stack snippet for handler-side diagnostics. Kept off
|
|
105
|
+
* by default (only populated when `OM_AI_INCLUDE_HANDLER_STACK=1`).
|
|
106
|
+
*/
|
|
107
|
+
export type AiPendingActionExecutionErrorDetails = {
|
|
108
|
+
issues?: Array<{ path?: (string | number)[]; message?: string; code?: string }>
|
|
109
|
+
fieldErrors?: Record<string, string[]>
|
|
110
|
+
cause?: unknown
|
|
111
|
+
[key: string]: unknown
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type AiPendingActionExecutionResult = {
|
|
115
|
+
recordId?: string
|
|
116
|
+
commandName?: string
|
|
117
|
+
error?: {
|
|
118
|
+
code: string
|
|
119
|
+
message: string
|
|
120
|
+
name?: string
|
|
121
|
+
details?: AiPendingActionExecutionErrorDetails
|
|
122
|
+
input?: unknown
|
|
123
|
+
stack?: string
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Thrown by the repository when a caller attempts an illegal status
|
|
129
|
+
* transition (e.g. `confirmed → pending`). Callers at the route layer
|
|
130
|
+
* turn this into a `409 Conflict` response.
|
|
131
|
+
*/
|
|
132
|
+
export class AiPendingActionStateError extends Error {
|
|
133
|
+
public readonly code = 'ai_pending_action_invalid_transition'
|
|
134
|
+
|
|
135
|
+
constructor(
|
|
136
|
+
public readonly from: AiPendingActionStatus,
|
|
137
|
+
public readonly to: AiPendingActionStatus,
|
|
138
|
+
) {
|
|
139
|
+
super(`Illegal AiPendingAction status transition: ${from} → ${to}`)
|
|
140
|
+
this.name = 'AiPendingActionStateError'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isAiPendingActionStatus(
|
|
145
|
+
value: unknown,
|
|
146
|
+
): value is AiPendingActionStatus {
|
|
147
|
+
return (
|
|
148
|
+
typeof value === 'string' &&
|
|
149
|
+
(AI_PENDING_ACTION_STATUSES as readonly string[]).includes(value)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isAiPendingActionQueueMode(
|
|
154
|
+
value: unknown,
|
|
155
|
+
): value is AiPendingActionQueueMode {
|
|
156
|
+
return (
|
|
157
|
+
typeof value === 'string' &&
|
|
158
|
+
(AI_PENDING_ACTION_QUEUE_MODES as readonly string[]).includes(value)
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function isTerminalAiPendingActionStatus(
|
|
163
|
+
status: AiPendingActionStatus,
|
|
164
|
+
): boolean {
|
|
165
|
+
return AI_PENDING_ACTION_TERMINAL_STATUSES.includes(status)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function isAllowedAiPendingActionTransition(
|
|
169
|
+
from: AiPendingActionStatus,
|
|
170
|
+
to: AiPendingActionStatus,
|
|
171
|
+
): boolean {
|
|
172
|
+
return (AI_PENDING_ACTION_ALLOWED_TRANSITIONS[from] ?? []).includes(to)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Default TTL for a pending action (spec §8 rule `expiresAt defaults to 10 min;
|
|
177
|
+
* overridable per agent`). The runtime default is 15 min here because the
|
|
178
|
+
* Step 5.5 brief pins it there; the repo reads `AI_PENDING_ACTION_TTL_SECONDS`
|
|
179
|
+
* from the environment to allow override without a code change.
|
|
180
|
+
*/
|
|
181
|
+
export const AI_PENDING_ACTION_DEFAULT_TTL_SECONDS = 900
|
|
182
|
+
export const AI_PENDING_ACTION_TTL_ENV_VAR = 'AI_PENDING_ACTION_TTL_SECONDS'
|
|
183
|
+
|
|
184
|
+
export function resolveAiPendingActionTtlSeconds(
|
|
185
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
186
|
+
): number {
|
|
187
|
+
const raw = env[AI_PENDING_ACTION_TTL_ENV_VAR]
|
|
188
|
+
if (raw == null) return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS
|
|
189
|
+
const parsed = Number.parseInt(String(raw), 10)
|
|
190
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
191
|
+
return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS
|
|
192
|
+
}
|
|
193
|
+
return parsed
|
|
194
|
+
}
|