@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,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyPromptOverride,
|
|
3
|
+
composeSystemPromptWithOverride,
|
|
4
|
+
findReservedKeys,
|
|
5
|
+
PromptOverrideReservedKeyError,
|
|
6
|
+
} from '../prompt-override-merge'
|
|
7
|
+
import type { PromptTemplate } from '../prompt-composition-types'
|
|
8
|
+
|
|
9
|
+
function makeTemplate(): PromptTemplate {
|
|
10
|
+
return {
|
|
11
|
+
id: 'test-template',
|
|
12
|
+
sections: [
|
|
13
|
+
{ name: 'role', content: 'You are an assistant.' },
|
|
14
|
+
{ name: 'scope', content: 'Answer questions only.' },
|
|
15
|
+
{ name: 'data', content: 'Scoped to tenant.' },
|
|
16
|
+
{ name: 'tools', content: 'Use read-only tools.' },
|
|
17
|
+
{ name: 'attachments', content: 'Images accepted.' },
|
|
18
|
+
{ name: 'mutationPolicy', content: 'Read-only.' },
|
|
19
|
+
{ name: 'responseStyle', content: 'Terse, factual.' },
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('applyPromptOverride', () => {
|
|
25
|
+
it('is identity when override is empty', () => {
|
|
26
|
+
const template = makeTemplate()
|
|
27
|
+
const result = applyPromptOverride(template, { sections: {} })
|
|
28
|
+
expect(result.sections).toEqual(template.sections)
|
|
29
|
+
expect(result.systemPrompt).toContain('[ROLE]\nYou are an assistant.')
|
|
30
|
+
expect(result.systemPrompt).toContain('[RESPONSE STYLE]\nTerse, factual.')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('is identity when override is null/undefined', () => {
|
|
34
|
+
const template = makeTemplate()
|
|
35
|
+
const nullResult = applyPromptOverride(template, null)
|
|
36
|
+
const undefinedResult = applyPromptOverride(template, undefined)
|
|
37
|
+
expect(nullResult.sections).toEqual(template.sections)
|
|
38
|
+
expect(undefinedResult.sections).toEqual(template.sections)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('appends text to an existing canonical section preserving built-in text', () => {
|
|
42
|
+
const template = makeTemplate()
|
|
43
|
+
const result = applyPromptOverride(template, {
|
|
44
|
+
sections: { role: 'Be friendly.' },
|
|
45
|
+
})
|
|
46
|
+
const role = result.sections.find((s) => s.name === 'role')
|
|
47
|
+
expect(role?.content).toBe('You are an assistant.\n\nBe friendly.')
|
|
48
|
+
expect(result.systemPrompt).toContain('You are an assistant.\n\nBe friendly.')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('appends to MUTATION POLICY via the pretty header key', () => {
|
|
52
|
+
const template = makeTemplate()
|
|
53
|
+
const result = applyPromptOverride(template, {
|
|
54
|
+
sections: { 'MUTATION POLICY': 'Strict.' },
|
|
55
|
+
})
|
|
56
|
+
const section = result.sections.find((s) => s.name === 'mutationPolicy')
|
|
57
|
+
expect(section?.content).toBe('Read-only.\n\nStrict.')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('inserts a brand-new section after RESPONSE STYLE preserving canonical order', () => {
|
|
61
|
+
const template = makeTemplate()
|
|
62
|
+
const result = applyPromptOverride(template, {
|
|
63
|
+
sections: { 'Company Voice': 'Always use the first-person plural.' },
|
|
64
|
+
})
|
|
65
|
+
const names = result.sections.map((s) => s.name)
|
|
66
|
+
const canonicalOrder = [
|
|
67
|
+
'role',
|
|
68
|
+
'scope',
|
|
69
|
+
'data',
|
|
70
|
+
'tools',
|
|
71
|
+
'attachments',
|
|
72
|
+
'mutationPolicy',
|
|
73
|
+
'responseStyle',
|
|
74
|
+
]
|
|
75
|
+
// Canonical sections appear in the expected order.
|
|
76
|
+
expect(names.slice(0, canonicalOrder.length)).toEqual(canonicalOrder)
|
|
77
|
+
// The new "overrides" section carrying the brand-new header lands after responseStyle.
|
|
78
|
+
expect(names[canonicalOrder.length]).toBe('overrides')
|
|
79
|
+
expect(result.systemPrompt).toMatch(/COMPANY VOICE/)
|
|
80
|
+
expect(result.systemPrompt).toContain('Always use the first-person plural.')
|
|
81
|
+
// And RESPONSE STYLE still appears before the new section.
|
|
82
|
+
const responseStyleIndex = result.systemPrompt.indexOf('[RESPONSE STYLE]')
|
|
83
|
+
const overridesIndex = result.systemPrompt.indexOf('[OVERRIDES]')
|
|
84
|
+
expect(responseStyleIndex).toBeGreaterThanOrEqual(0)
|
|
85
|
+
expect(overridesIndex).toBeGreaterThan(responseStyleIndex)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('ignores empty / whitespace-only override values', () => {
|
|
89
|
+
const template = makeTemplate()
|
|
90
|
+
const result = applyPromptOverride(template, {
|
|
91
|
+
sections: { role: ' ', scope: '' },
|
|
92
|
+
})
|
|
93
|
+
expect(result.sections).toEqual(template.sections)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('throws when a reserved policy key is present', () => {
|
|
97
|
+
const template = makeTemplate()
|
|
98
|
+
expect(() =>
|
|
99
|
+
applyPromptOverride(template, {
|
|
100
|
+
sections: { mutationPolicy: 'Allow writes', role: 'Be nice.' },
|
|
101
|
+
}),
|
|
102
|
+
).toThrow(PromptOverrideReservedKeyError)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('findReservedKeys returns matching keys case-insensitively', () => {
|
|
106
|
+
expect(findReservedKeys({ role: 'x' })).toEqual([])
|
|
107
|
+
expect(findReservedKeys({ mutationPolicy: 'x' })).toEqual(['mutationPolicy'])
|
|
108
|
+
expect(findReservedKeys({ READONLY: 'x' })).toEqual(['READONLY'])
|
|
109
|
+
expect(findReservedKeys({ AllowedTools: 'x', extra: 'y' })).toEqual(['AllowedTools'])
|
|
110
|
+
expect(findReservedKeys(null)).toEqual([])
|
|
111
|
+
expect(findReservedKeys(undefined)).toEqual([])
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('composeSystemPromptWithOverride', () => {
|
|
116
|
+
it('returns base prompt when no override is present', () => {
|
|
117
|
+
const result = composeSystemPromptWithOverride('base prompt', null)
|
|
118
|
+
expect(result).toBe('base prompt')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('layers a role-section override onto a plain-string base prompt', () => {
|
|
122
|
+
const result = composeSystemPromptWithOverride('You are a helpful bot.', {
|
|
123
|
+
sections: { role: 'Stay concise.' },
|
|
124
|
+
})
|
|
125
|
+
expect(result).toContain('[ROLE]\nYou are a helpful bot.\n\nStay concise.')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('layers a brand-new section override onto a plain-string base prompt', () => {
|
|
129
|
+
const result = composeSystemPromptWithOverride('Base.', {
|
|
130
|
+
sections: { 'Tenant Voice': 'Use British English.' },
|
|
131
|
+
})
|
|
132
|
+
expect(result).toContain('Base.')
|
|
133
|
+
expect(result).toContain('TENANT VOICE')
|
|
134
|
+
expect(result).toContain('Use British English.')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import {
|
|
3
|
+
registerGeneratedAiToolEntries,
|
|
4
|
+
type AiToolConfigEntry,
|
|
5
|
+
} from '../tool-loader'
|
|
6
|
+
import { toolRegistry } from '../tool-registry'
|
|
7
|
+
import { convertMcpToolsToAiSdk } from '../mcp-tool-adapter'
|
|
8
|
+
import type { InProcessMcpClient, ToolInfoWithSchema } from '../in-process-client'
|
|
9
|
+
|
|
10
|
+
function makeTool(name: string) {
|
|
11
|
+
return {
|
|
12
|
+
name,
|
|
13
|
+
description: `Tool ${name}`,
|
|
14
|
+
inputSchema: z.object({ q: z.string() }),
|
|
15
|
+
requiredFeatures: [`${name}.view`],
|
|
16
|
+
handler: async (input: { q: string }) => ({ echo: input.q, name }),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('registerGeneratedAiToolEntries', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
toolRegistry.clear()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
toolRegistry.clear()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('registers tools from modules that populate ai-tools.ts', () => {
|
|
30
|
+
const entries: AiToolConfigEntry[] = [
|
|
31
|
+
{ moduleId: 'search', tools: [makeTool('search_query'), makeTool('search_status')] },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const registered = registerGeneratedAiToolEntries(entries)
|
|
35
|
+
|
|
36
|
+
expect(registered).toBe(2)
|
|
37
|
+
expect(toolRegistry.listToolsByModule('search')).toEqual([
|
|
38
|
+
'search_query',
|
|
39
|
+
'search_status',
|
|
40
|
+
])
|
|
41
|
+
expect(toolRegistry.getTool('search_query')?.requiredFeatures).toEqual(['search_query.view'])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('stays silent for modules without an ai-tools.ts (empty or missing tools)', () => {
|
|
45
|
+
const entries: AiToolConfigEntry[] = [
|
|
46
|
+
{ moduleId: 'auth', tools: [] },
|
|
47
|
+
{ moduleId: 'catalog', tools: undefined as unknown as unknown[] },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const registered = registerGeneratedAiToolEntries(entries)
|
|
51
|
+
|
|
52
|
+
expect(registered).toBe(0)
|
|
53
|
+
expect(toolRegistry.listToolNames()).toEqual([])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('is idempotent — re-running does not duplicate registrations', () => {
|
|
57
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
58
|
+
const entries: AiToolConfigEntry[] = [
|
|
59
|
+
{ moduleId: 'search', tools: [makeTool('search_query')] },
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
registerGeneratedAiToolEntries(entries)
|
|
63
|
+
registerGeneratedAiToolEntries(entries)
|
|
64
|
+
|
|
65
|
+
expect(toolRegistry.listToolsByModule('search')).toEqual(['search_query'])
|
|
66
|
+
expect(toolRegistry.listToolNames()).toEqual(['search_query'])
|
|
67
|
+
warnSpy.mockRestore()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('skips malformed tool objects instead of throwing', () => {
|
|
71
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
72
|
+
const entries: AiToolConfigEntry[] = [
|
|
73
|
+
{
|
|
74
|
+
moduleId: 'broken',
|
|
75
|
+
tools: [
|
|
76
|
+
{ name: 'missing_handler', description: 'x', inputSchema: z.object({}) } as unknown,
|
|
77
|
+
null as unknown,
|
|
78
|
+
makeTool('ok_tool'),
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
const registered = registerGeneratedAiToolEntries(entries)
|
|
84
|
+
|
|
85
|
+
expect(registered).toBe(1)
|
|
86
|
+
expect(toolRegistry.getTool('ok_tool')).toBeDefined()
|
|
87
|
+
expect(toolRegistry.getTool('missing_handler')).toBeUndefined()
|
|
88
|
+
warnSpy.mockRestore()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('keeps tools resolvable through mcp-tool-adapter with identical shape', async () => {
|
|
92
|
+
const entries: AiToolConfigEntry[] = [
|
|
93
|
+
{ moduleId: 'search', tools: [makeTool('search_query')] },
|
|
94
|
+
]
|
|
95
|
+
registerGeneratedAiToolEntries(entries)
|
|
96
|
+
|
|
97
|
+
const registered = toolRegistry.getTool('search_query')
|
|
98
|
+
expect(registered).toBeDefined()
|
|
99
|
+
if (!registered) return
|
|
100
|
+
|
|
101
|
+
const mcpTools: ToolInfoWithSchema[] = [
|
|
102
|
+
{
|
|
103
|
+
name: registered.name,
|
|
104
|
+
description: registered.description,
|
|
105
|
+
inputSchema: registered.inputSchema,
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
const callToolMock = jest.fn(async () => ({ success: true, result: { ok: true } }))
|
|
110
|
+
const fakeClient = { callTool: callToolMock } as unknown as InProcessMcpClient
|
|
111
|
+
|
|
112
|
+
const aiSdkTools = convertMcpToolsToAiSdk(fakeClient, mcpTools)
|
|
113
|
+
|
|
114
|
+
expect(Object.keys(aiSdkTools)).toEqual(['search_query'])
|
|
115
|
+
const adapted = aiSdkTools.search_query as unknown as {
|
|
116
|
+
description: string
|
|
117
|
+
execute: (args: unknown) => Promise<string>
|
|
118
|
+
}
|
|
119
|
+
expect(adapted.description).toBe(registered.description)
|
|
120
|
+
|
|
121
|
+
const out = await adapted.execute({ q: 'hello' })
|
|
122
|
+
expect(callToolMock).toHaveBeenCalledWith('search_query', { q: 'hello' })
|
|
123
|
+
expect(out).toContain('"ok": true')
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { getAgent } from './agent-registry'
|
|
2
|
+
import { hasRequiredFeatures } from './auth'
|
|
3
|
+
import { toolRegistry } from './tool-registry'
|
|
4
|
+
import type {
|
|
5
|
+
AiAgentAcceptedMediaType,
|
|
6
|
+
AiAgentDefinition,
|
|
7
|
+
AiAgentMutationPolicy,
|
|
8
|
+
} from './ai-agent-definition'
|
|
9
|
+
import type { AiToolDefinition } from './types'
|
|
10
|
+
|
|
11
|
+
export type AgentPolicyDenyCode =
|
|
12
|
+
| 'agent_unknown'
|
|
13
|
+
| 'agent_features_denied'
|
|
14
|
+
| 'tool_not_whitelisted'
|
|
15
|
+
| 'tool_unknown'
|
|
16
|
+
| 'tool_features_denied'
|
|
17
|
+
| 'mutation_blocked_by_readonly'
|
|
18
|
+
| 'mutation_blocked_by_policy'
|
|
19
|
+
| 'execution_mode_not_supported'
|
|
20
|
+
| 'attachment_type_not_accepted'
|
|
21
|
+
|
|
22
|
+
export type AgentPolicyDecision =
|
|
23
|
+
| { ok: true; agent: AiAgentDefinition; tool?: AiToolDefinition }
|
|
24
|
+
| { ok: false; code: AgentPolicyDenyCode; message: string }
|
|
25
|
+
|
|
26
|
+
export interface AgentPolicyAuthContext {
|
|
27
|
+
userFeatures: string[]
|
|
28
|
+
isSuperAdmin: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AgentPolicyCheckInput {
|
|
32
|
+
agentId: string
|
|
33
|
+
authContext: AgentPolicyAuthContext
|
|
34
|
+
toolName?: string
|
|
35
|
+
attachmentMediaTypes?: string[]
|
|
36
|
+
requestedExecutionMode?: 'chat' | 'object'
|
|
37
|
+
/**
|
|
38
|
+
* Optional tenant-scoped downgrade for the agent's code-declared
|
|
39
|
+
* `mutationPolicy`. When supplied, the effective policy is the MOST
|
|
40
|
+
* RESTRICTIVE of `{ code-declared, override }` — escalation is never
|
|
41
|
+
* allowed through this channel (that is enforced at the route layer).
|
|
42
|
+
* Callers that omit this field get the exact pre-Step-5.4 behavior.
|
|
43
|
+
*/
|
|
44
|
+
mutationPolicyOverride?: AiAgentMutationPolicy | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Restrictiveness ranking of `AiAgentMutationPolicy` (most restrictive first).
|
|
49
|
+
* `read-only` blocks all mutation tools. `destructive-confirm-required` forces
|
|
50
|
+
* confirmation for every write (including non-destructive ones). `confirm-required`
|
|
51
|
+
* is the least restrictive policy — writes go through a single confirmation.
|
|
52
|
+
*
|
|
53
|
+
* This ordering is load-bearing for both the runtime's effective-policy
|
|
54
|
+
* computation AND the route-layer escalation guard. Changing the order is a
|
|
55
|
+
* security-sensitive change.
|
|
56
|
+
*/
|
|
57
|
+
const POLICY_RESTRICTIVENESS: Record<AiAgentMutationPolicy, number> = {
|
|
58
|
+
'read-only': 0,
|
|
59
|
+
'destructive-confirm-required': 1,
|
|
60
|
+
'confirm-required': 2,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isKnownMutationPolicy(value: unknown): value is AiAgentMutationPolicy {
|
|
64
|
+
return (
|
|
65
|
+
value === 'read-only' ||
|
|
66
|
+
value === 'confirm-required' ||
|
|
67
|
+
value === 'destructive-confirm-required'
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns the effective mutation policy — the MOST RESTRICTIVE of
|
|
73
|
+
* `{ codeDeclared, override }`. Missing override → `codeDeclared`. A corrupt
|
|
74
|
+
* override value (unknown string from DB) is logged and falls back to
|
|
75
|
+
* `codeDeclared` so the system fails SAFE when a schema drift leaks through.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveEffectiveMutationPolicy(
|
|
78
|
+
codeDeclared: AiAgentMutationPolicy | undefined,
|
|
79
|
+
override: AiAgentMutationPolicy | null | undefined,
|
|
80
|
+
agentId?: string,
|
|
81
|
+
): AiAgentMutationPolicy {
|
|
82
|
+
const base: AiAgentMutationPolicy =
|
|
83
|
+
codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'
|
|
84
|
+
if (override === undefined || override === null) return base
|
|
85
|
+
if (!isKnownMutationPolicy(override)) {
|
|
86
|
+
console.warn(
|
|
87
|
+
`[AI Agents] Ignoring corrupt mutationPolicy override for agent "${agentId ?? '<unknown>'}": ${String(
|
|
88
|
+
override,
|
|
89
|
+
)}. Falling back to code-declared policy "${base}".`,
|
|
90
|
+
)
|
|
91
|
+
return base
|
|
92
|
+
}
|
|
93
|
+
const baseRank = POLICY_RESTRICTIVENESS[base]
|
|
94
|
+
const overrideRank = POLICY_RESTRICTIVENESS[override]
|
|
95
|
+
return overrideRank < baseRank ? override : base
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns `true` when `candidate` would WIDEN `codeDeclared` — i.e. would
|
|
100
|
+
* grant the agent more mutation surface than its code declares. Used by the
|
|
101
|
+
* mutation-policy override route to reject escalation attempts with 400.
|
|
102
|
+
*/
|
|
103
|
+
export function isMutationPolicyEscalation(
|
|
104
|
+
codeDeclared: AiAgentMutationPolicy | undefined,
|
|
105
|
+
candidate: AiAgentMutationPolicy,
|
|
106
|
+
): boolean {
|
|
107
|
+
const base: AiAgentMutationPolicy =
|
|
108
|
+
codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'
|
|
109
|
+
return POLICY_RESTRICTIVENESS[candidate] > POLICY_RESTRICTIVENESS[base]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function classifyMediaType(value: string): AiAgentAcceptedMediaType {
|
|
113
|
+
const normalized = value.trim().toLowerCase()
|
|
114
|
+
if (normalized.startsWith('image/')) return 'image'
|
|
115
|
+
if (normalized === 'application/pdf') return 'pdf'
|
|
116
|
+
return 'file'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isAgentReadOnly(agent: AiAgentDefinition): boolean {
|
|
120
|
+
if (typeof agent.readOnly === 'boolean') return agent.readOnly
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Returns the effective mutation policy for a policy-check invocation — the
|
|
126
|
+
* most restrictive of `{ agent.mutationPolicy, input.mutationPolicyOverride }`.
|
|
127
|
+
* Pure-lookup helper; no I/O. Callers that need to know the same value outside
|
|
128
|
+
* of a policy check should use {@link resolveEffectiveMutationPolicy} directly.
|
|
129
|
+
*/
|
|
130
|
+
function resolvePolicyCheckMutationPolicy(
|
|
131
|
+
agent: AiAgentDefinition,
|
|
132
|
+
override: AiAgentMutationPolicy | null | undefined,
|
|
133
|
+
): AiAgentMutationPolicy {
|
|
134
|
+
return resolveEffectiveMutationPolicy(agent.mutationPolicy, override, agent.id)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hasAgentStructuredOutput(agent: AiAgentDefinition): boolean {
|
|
138
|
+
return Boolean(agent.output)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function agentExecutionMode(agent: AiAgentDefinition): 'chat' | 'object' {
|
|
142
|
+
return agent.executionMode ?? 'chat'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function checkAgentPolicy(input: AgentPolicyCheckInput): AgentPolicyDecision {
|
|
146
|
+
const {
|
|
147
|
+
agentId,
|
|
148
|
+
authContext,
|
|
149
|
+
toolName,
|
|
150
|
+
attachmentMediaTypes,
|
|
151
|
+
requestedExecutionMode,
|
|
152
|
+
mutationPolicyOverride,
|
|
153
|
+
} = input
|
|
154
|
+
|
|
155
|
+
const agent = getAgent(agentId)
|
|
156
|
+
if (!agent) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
code: 'agent_unknown',
|
|
160
|
+
message: `Agent "${agentId}" is not registered.`,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const agentFeatures = agent.requiredFeatures ?? []
|
|
165
|
+
if (
|
|
166
|
+
!hasRequiredFeatures(agentFeatures, authContext.userFeatures, authContext.isSuperAdmin)
|
|
167
|
+
) {
|
|
168
|
+
return {
|
|
169
|
+
ok: false,
|
|
170
|
+
code: 'agent_features_denied',
|
|
171
|
+
message: `Access to agent "${agentId}" requires features: ${agentFeatures.join(', ')}`,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let resolvedTool: AiToolDefinition | undefined
|
|
176
|
+
if (typeof toolName === 'string') {
|
|
177
|
+
if (!agent.allowedTools.includes(toolName)) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
code: 'tool_not_whitelisted',
|
|
181
|
+
message: `Tool "${toolName}" is not whitelisted for agent "${agentId}".`,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const toolRecord = toolRegistry.getTool(toolName) as AiToolDefinition | undefined
|
|
186
|
+
if (!toolRecord) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
code: 'tool_unknown',
|
|
190
|
+
message: `Tool "${toolName}" is not registered in the tool registry.`,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const toolFeatures = toolRecord.requiredFeatures ?? []
|
|
195
|
+
if (
|
|
196
|
+
!hasRequiredFeatures(toolFeatures, authContext.userFeatures, authContext.isSuperAdmin)
|
|
197
|
+
) {
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
code: 'tool_features_denied',
|
|
201
|
+
message: `Access to tool "${toolName}" requires features: ${toolFeatures.join(', ')}`,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (toolRecord.isMutation === true) {
|
|
206
|
+
if (isAgentReadOnly(agent)) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
code: 'mutation_blocked_by_readonly',
|
|
210
|
+
message: `Mutation tool "${toolName}" cannot be executed by read-only agent "${agentId}".`,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const effectivePolicy = resolvePolicyCheckMutationPolicy(agent, mutationPolicyOverride)
|
|
214
|
+
if (effectivePolicy === 'read-only') {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
code: 'mutation_blocked_by_policy',
|
|
218
|
+
message: `Mutation tool "${toolName}" is blocked by agent "${agentId}" mutationPolicy=read-only.`,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
resolvedTool = toolRecord
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (requestedExecutionMode) {
|
|
227
|
+
const declaredMode = agentExecutionMode(agent)
|
|
228
|
+
if (requestedExecutionMode === 'object') {
|
|
229
|
+
if (declaredMode !== 'object' && !hasAgentStructuredOutput(agent)) {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
code: 'execution_mode_not_supported',
|
|
233
|
+
message: `Agent "${agentId}" does not support execution mode "object" (no output schema declared).`,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else if (requestedExecutionMode === 'chat') {
|
|
237
|
+
if (declaredMode === 'object' && hasAgentStructuredOutput(agent)) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
code: 'execution_mode_not_supported',
|
|
241
|
+
message: `Agent "${agentId}" is declared as object-mode and cannot run via chat transport.`,
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (Array.isArray(attachmentMediaTypes) && attachmentMediaTypes.length > 0) {
|
|
248
|
+
const accepted = agent.acceptedMediaTypes
|
|
249
|
+
if (!accepted || accepted.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
code: 'attachment_type_not_accepted',
|
|
253
|
+
message: `Agent "${agentId}" does not accept attachments.`,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const acceptedSet = new Set<AiAgentAcceptedMediaType>(accepted)
|
|
257
|
+
for (const raw of attachmentMediaTypes) {
|
|
258
|
+
const kind = classifyMediaType(raw)
|
|
259
|
+
if (!acceptedSet.has(kind)) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
code: 'attachment_type_not_accepted',
|
|
263
|
+
message: `Agent "${agentId}" does not accept media type "${raw}" (classified as "${kind}").`,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { ok: true, agent, tool: resolvedTool }
|
|
270
|
+
}
|