@open-mercato/ai-assistant 0.6.7-develop.6775.1.c2313bb8a3 → 0.6.7-develop.6785.1.1dd7cfac55
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 +12 -29
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js +80 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js +124 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js +30 -0
- package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +7 -0
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/moderation-flags/route.js +121 -0
- package/dist/modules/ai_assistant/api/moderation-flags/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +10 -10
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +34 -13
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +106 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +139 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js +26 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +42 -0
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +3 -1
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js +44 -0
- package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +6 -2
- package/dist/modules/ai_assistant/di.js.map +2 -2
- package/dist/modules/ai_assistant/events.js +6 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +26 -0
- package/dist/modules/ai_assistant/i18n/en.json +26 -0
- package/dist/modules/ai_assistant/i18n/es.json +26 -0
- package/dist/modules/ai_assistant/i18n/pl.json +26 -0
- package/dist/modules/ai_assistant/lib/agent-runtime.js +158 -5
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/auth.js +6 -7
- package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +3 -0
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/openai.js +8 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/openai.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +6 -1
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js +43 -0
- package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/moderation-policy.js +24 -0
- package/dist/modules/ai_assistant/lib/moderation-policy.js.map +7 -0
- package/dist/modules/ai_assistant/lib/moderation.js +109 -0
- package/dist/modules/ai_assistant/lib/moderation.js.map +7 -0
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +2 -0
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-search.js +4 -12
- package/dist/modules/ai_assistant/lib/tool-search.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js +19 -0
- package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +1 -0
- package/generated/entities/ai_moderation_flag/index.ts +9 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +13 -1
- package/package.json +6 -6
- package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.ts +126 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.ts +161 -0
- package/src/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.ts +48 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +10 -0
- package/src/modules/ai_assistant/api/moderation-flags/__tests__/route.test.ts +122 -0
- package/src/modules/ai_assistant/api/moderation-flags/route.ts +137 -0
- package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +58 -8
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +10 -12
- package/src/modules/ai_assistant/api/settings/route.ts +33 -10
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +146 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +186 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.ts +24 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.tsx +12 -0
- package/src/modules/ai_assistant/data/entities.ts +56 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +4 -0
- package/src/modules/ai_assistant/data/repositories/AiModerationFlagRepository.ts +79 -0
- package/src/modules/ai_assistant/di.ts +5 -1
- package/src/modules/ai_assistant/events.ts +21 -0
- package/src/modules/ai_assistant/i18n/de.json +26 -0
- package/src/modules/ai_assistant/i18n/en.json +26 -0
- package/src/modules/ai_assistant/i18n/es.json +26 -0
- package/src/modules/ai_assistant/i18n/pl.json +26 -0
- package/src/modules/ai_assistant/lib/__tests__/auth.test.ts +2 -5
- package/src/modules/ai_assistant/lib/__tests__/input-moderation-gate.test.ts +121 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +13 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +41 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +60 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation-flag-recorder.test.ts +76 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation-policy.test.ts +86 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation.test.ts +114 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +259 -0
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +10 -0
- package/src/modules/ai_assistant/lib/auth.ts +6 -15
- package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +10 -0
- package/src/modules/ai_assistant/lib/llm-adapters/openai.ts +32 -1
- package/src/modules/ai_assistant/lib/model-factory.ts +33 -0
- package/src/modules/ai_assistant/lib/moderation-flag-recorder.ts +75 -0
- package/src/modules/ai_assistant/lib/moderation-policy.ts +77 -0
- package/src/modules/ai_assistant/lib/moderation.ts +179 -0
- package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +2 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +4 -17
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +388 -16
- package/src/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.ts +21 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { canonicalProviderId } from '../../lib/model-allowlist'\nimport { AiAgentRuntimeOverride } from '../entities'\nimport type { AiAgentLoopStopCondition } from '../../lib/ai-agent-definition'\n\nexport interface AiAgentRuntimeOverrideContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiAgentRuntimeOverrideLoopInput {\n /** Kill switch \u2014 when true, runtime forces stepCountIs(1). */\n loopDisabled?: boolean | null\n /** Override loop.maxSteps. */\n loopMaxSteps?: number | null\n /** Override loop.budget.maxToolCalls. */\n loopMaxToolCalls?: number | null\n /** Override loop.budget.maxWallClockMs. */\n loopMaxWallClockMs?: number | null\n /** Override loop.budget.maxTokens. */\n loopMaxTokens?: number | null\n /** Override loop.stopWhen \u2014 JSON-safe variants only (stepCount, hasToolCall). */\n loopStopWhenJson?: AiAgentLoopStopCondition[] | null\n /** Override loop.activeTools \u2014 must be a subset of agent.allowedTools. */\n loopActiveToolsJson?: string[] | null\n}\n\nexport interface AiAgentRuntimeOverrideInput extends AiAgentRuntimeOverrideLoopInput {\n /** null means tenant-wide default (no agent pinning). */\n agentId?: string | null\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n allowedOverrideProviders?: string[] | null\n allowedOverrideModelsByProvider?: Record<string, string[]>\n /**\n * Optional: the agent's declared allowedTools. When provided, loopActiveToolsJson\n * is validated to be a subset. When omitted, allowlist validation is skipped\n * (write-time defense only; the runtime re-validates at read time).\n */\n agentAllowedTools?: string[]\n}\n\n/**\n * Repository for per-tenant AI runtime overrides (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * All reads MUST filter by `tenant_id` first. The three public methods follow\n * the same fail-safe pattern as sibling repositories: `getDefault` returns\n * null on missing tenant context; `upsertDefault` validates provider id at\n * write time; `clearDefault` soft-deletes via `deleted_at`.\n *\n * Resolution precedence returned by `getDefault`:\n * 1. Agent-specific row (non-null `agent_id`) when `agentId` is provided.\n * 2. Tenant-wide row (`agent_id IS NULL`) for the same `(tenant_id, org_id)`.\n * Both rows are always scoped to the caller's `tenant_id` \u2014 cross-tenant\n * reads are impossible because every query filters `WHERE tenant_id = ?`.\n */\nexport class AiAgentRuntimeOverrideRepository {\n constructor(private readonly em: EntityManager) {}\n\n /**\n * Returns the most-specific active runtime override for the given context.\n *\n * When `agentId` is provided, an agent-specific row takes precedence over a\n * tenant-wide null-agent row. Returns null when neither exists.\n *\n * Never returns a row with `deleted_at IS NOT NULL`.\n */\n async getDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n\n const orgFilter = ctx.organizationId ?? null\n\n // Try agent-specific first when caller provided an agentId.\n if (ctx.agentId) {\n const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: ctx.agentId,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (agentRow) return agentRow\n }\n\n // Fall back to tenant-wide row (agentId = null).\n const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return tenantRow ?? null\n }\n\n async getExact(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n const row = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: ctx.agentId ?? null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return row ?? null\n }\n\n /**\n * Validates and normalizes the loop override fields from an input object.\n * Throws `AiAgentRuntimeOverrideValidationError` with code\n * `invalid_loop_override` for any validation failure.\n *\n * Validation rules (Phase 3 \u2014 R5 mitigation):\n * - `loopStopWhenJson`: all items must have kind `stepCount` or `hasToolCall`.\n * Items with kind `custom` are rejected \u2014 they cannot be stored as JSON.\n * - `loopActiveToolsJson`: when `agentAllowedTools` is provided, every entry\n * must be in that allowlist.\n */\n private validateLoopInput(input: AiAgentRuntimeOverrideInput): void {\n if (input.loopStopWhenJson != null) {\n if (!Array.isArray(input.loopStopWhenJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson must be an array of stop condition objects.',\n 'invalid_loop_override',\n )\n }\n for (const item of input.loopStopWhenJson) {\n if (!item || typeof item !== 'object' || !('kind' in item)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson items must have a \"kind\" field.',\n 'invalid_loop_override',\n )\n }\n const kind = (item as AiAgentLoopStopCondition).kind\n if (kind === 'custom') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson does not support kind \"custom\" \u2014 only \"stepCount\" and \"hasToolCall\" are JSON-safe and storable.',\n 'invalid_loop_override',\n )\n }\n if (kind !== 'stepCount' && kind !== 'hasToolCall') {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopStopWhenJson contains unknown kind \"${String(kind)}\". Allowed: \"stepCount\", \"hasToolCall\".`,\n 'invalid_loop_override',\n )\n }\n if (kind === 'stepCount' && typeof (item as { count?: unknown }).count !== 'number') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson stepCount item must have a numeric \"count\" field.',\n 'invalid_loop_override',\n )\n }\n if (kind === 'hasToolCall' && typeof (item as { toolName?: unknown }).toolName !== 'string') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson hasToolCall item must have a string \"toolName\" field.',\n 'invalid_loop_override',\n )\n }\n }\n }\n\n if (input.loopActiveToolsJson != null) {\n if (!Array.isArray(input.loopActiveToolsJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson must be an array of tool name strings.',\n 'invalid_loop_override',\n )\n }\n for (const name of input.loopActiveToolsJson) {\n if (typeof name !== 'string' || name.length === 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson entries must be non-empty strings.',\n 'invalid_loop_override',\n )\n }\n }\n if (input.agentAllowedTools && input.agentAllowedTools.length > 0) {\n const outsideAllowlist = input.loopActiveToolsJson.filter(\n (name) => !input.agentAllowedTools!.includes(name),\n )\n if (outsideAllowlist.length > 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopActiveToolsJson contains tools outside the agent's allowedTools: ${outsideAllowlist.join(', ')}.`,\n 'invalid_loop_override',\n )\n }\n }\n }\n }\n\n /**\n * Inserts or updates the runtime override for the given context.\n *\n * Validates `providerId` against the registry at write time so an admin\n * cannot save a typo (Phase 1.4 contract re-applied per spec \u00A7Data Models).\n * An unknown provider id throws a typed error.\n *\n * Also validates loop override fields (R5 mitigation \u2014 Phase 3):\n * - `loopStopWhenJson` items must use only JSON-safe kinds.\n * - `loopActiveToolsJson` items must be a subset of `agentAllowedTools`\n * when that is provided.\n *\n * The R6 base-URL allowlist check is intentionally NOT performed here \u2014\n * that enforcement lives at the HTTP layer (PUT settings route). The\n * repository trusts that callers have already validated the value.\n */\n async upsertDefault(\n input: AiAgentRuntimeOverrideInput,\n ctx: AiAgentRuntimeOverrideContext,\n ): Promise<AiAgentRuntimeOverride> {\n if (!ctx?.tenantId) {\n throw new Error('AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId')\n }\n\n const normalizedProviderId = input.providerId\n ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id))\n : null\n if (input.providerId) {\n const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null\n if (!knownProvider) {\n throw new AiAgentRuntimeOverrideValidationError(\n `Unknown provider id \"${input.providerId}\". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(', ')}.`,\n )\n }\n }\n\n this.validateLoopInput(input)\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = input.agentId ?? null\n const hasProviderId = Object.prototype.hasOwnProperty.call(input, 'providerId')\n const hasModelId = Object.prototype.hasOwnProperty.call(input, 'modelId')\n const hasBaseURL = Object.prototype.hasOwnProperty.call(input, 'baseURL')\n const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideProviders')\n const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideModelsByProvider')\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n\n if (existing) {\n if (hasProviderId) existing.providerId = normalizedProviderId\n if (hasModelId) existing.modelId = input.modelId ?? null\n if (hasBaseURL) existing.baseUrl = input.baseURL ?? null\n if (hasAllowedOverrideProviders) {\n existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null\n }\n if (hasAllowedOverrideModels) {\n existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {}\n }\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n if ('loopDisabled' in input) existing.loopDisabled = input.loopDisabled ?? null\n if ('loopMaxSteps' in input) existing.loopMaxSteps = input.loopMaxSteps ?? null\n if ('loopMaxToolCalls' in input) existing.loopMaxToolCalls = input.loopMaxToolCalls ?? null\n if ('loopMaxWallClockMs' in input) existing.loopMaxWallClockMs = input.loopMaxWallClockMs ?? null\n if ('loopMaxTokens' in input) existing.loopMaxTokens = input.loopMaxTokens ?? null\n if ('loopStopWhenJson' in input) existing.loopStopWhenJson = input.loopStopWhenJson ?? null\n if ('loopActiveToolsJson' in input) existing.loopActiveToolsJson = input.loopActiveToolsJson ?? null\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n providerId: hasProviderId ? normalizedProviderId : null,\n modelId: hasModelId ? (input.modelId ?? null) : null,\n baseUrl: hasBaseURL ? (input.baseURL ?? null) : null,\n allowedOverrideProviders: hasAllowedOverrideProviders\n ? (input.allowedOverrideProviders ?? null)\n : null,\n allowedOverrideModelsByProvider: hasAllowedOverrideModels\n ? (input.allowedOverrideModelsByProvider ?? {})\n : {},\n updatedByUserId: ctx.userId ?? null,\n loopDisabled: input.loopDisabled ?? null,\n loopMaxSteps: input.loopMaxSteps ?? null,\n loopMaxToolCalls: input.loopMaxToolCalls ?? null,\n loopMaxWallClockMs: input.loopMaxWallClockMs ?? null,\n loopMaxTokens: input.loopMaxTokens ?? null,\n loopStopWhenJson: input.loopStopWhenJson ?? null,\n loopActiveToolsJson: input.loopActiveToolsJson ?? null,\n } as unknown as AiAgentRuntimeOverride)\n await tx.persist(row).flush()\n return row\n })\n }\n\n /**\n * Soft-deletes the active override matching the given context by setting\n * `deleted_at = now()`. Returns true when a row was found and cleared,\n * false when no active row existed.\n */\n async clearDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = ctx.agentId ?? null\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (!existing) return false\n if (\n existing.allowedOverrideProviders != null ||\n Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0\n ) {\n existing.providerId = null\n existing.modelId = null\n existing.baseUrl = null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return true\n }\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\n/**\n * Thrown by `upsertDefault` when validation fails (unknown provider id,\n * invalid loop override JSON).\n */\nexport class AiAgentRuntimeOverrideValidationError extends Error {\n readonly code: string\n\n constructor(message: string, code = 'invalid_override') {\n super(message)\n this.name = 'AiAgentRuntimeOverrideValidationError'\n this.code = code\n }\n}\n\nexport default AiAgentRuntimeOverrideRepository\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { canonicalProviderId } from '../../lib/model-allowlist'\nimport { AiAgentRuntimeOverride } from '../entities'\nimport type { AiAgentLoopStopCondition } from '../../lib/ai-agent-definition'\n\nexport interface AiAgentRuntimeOverrideContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiAgentRuntimeOverrideLoopInput {\n /** Kill switch \u2014 when true, runtime forces stepCountIs(1). */\n loopDisabled?: boolean | null\n /** Override loop.maxSteps. */\n loopMaxSteps?: number | null\n /** Override loop.budget.maxToolCalls. */\n loopMaxToolCalls?: number | null\n /** Override loop.budget.maxWallClockMs. */\n loopMaxWallClockMs?: number | null\n /** Override loop.budget.maxTokens. */\n loopMaxTokens?: number | null\n /** Override loop.stopWhen \u2014 JSON-safe variants only (stepCount, hasToolCall). */\n loopStopWhenJson?: AiAgentLoopStopCondition[] | null\n /** Override loop.activeTools \u2014 must be a subset of agent.allowedTools. */\n loopActiveToolsJson?: string[] | null\n}\n\nexport interface AiAgentRuntimeOverrideInput extends AiAgentRuntimeOverrideLoopInput {\n /** null means tenant-wide default (no agent pinning). */\n agentId?: string | null\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n allowedOverrideProviders?: string[] | null\n allowedOverrideModelsByProvider?: Record<string, string[]>\n /** Input-moderation override: true = on, false = off, null = inherit. */\n inputModeration?: boolean | null\n /**\n * Optional: the agent's declared allowedTools. When provided, loopActiveToolsJson\n * is validated to be a subset. When omitted, allowlist validation is skipped\n * (write-time defense only; the runtime re-validates at read time).\n */\n agentAllowedTools?: string[]\n}\n\n/**\n * Repository for per-tenant AI runtime overrides (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * All reads MUST filter by `tenant_id` first. The three public methods follow\n * the same fail-safe pattern as sibling repositories: `getDefault` returns\n * null on missing tenant context; `upsertDefault` validates provider id at\n * write time; `clearDefault` soft-deletes via `deleted_at`.\n *\n * Resolution precedence returned by `getDefault`:\n * 1. Agent-specific row (non-null `agent_id`) when `agentId` is provided.\n * 2. Tenant-wide row (`agent_id IS NULL`) for the same `(tenant_id, org_id)`.\n * Both rows are always scoped to the caller's `tenant_id` \u2014 cross-tenant\n * reads are impossible because every query filters `WHERE tenant_id = ?`.\n */\nexport class AiAgentRuntimeOverrideRepository {\n constructor(private readonly em: EntityManager) {}\n\n /**\n * Returns the most-specific active runtime override for the given context.\n *\n * When `agentId` is provided, an agent-specific row takes precedence over a\n * tenant-wide null-agent row. Returns null when neither exists.\n *\n * Never returns a row with `deleted_at IS NOT NULL`.\n */\n async getDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n\n const orgFilter = ctx.organizationId ?? null\n\n // Try agent-specific first when caller provided an agentId.\n if (ctx.agentId) {\n const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: ctx.agentId,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (agentRow) return agentRow\n }\n\n // Fall back to tenant-wide row (agentId = null).\n const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return tenantRow ?? null\n }\n\n async getExact(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n const row = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: ctx.agentId ?? null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return row ?? null\n }\n\n /**\n * Validates and normalizes the loop override fields from an input object.\n * Throws `AiAgentRuntimeOverrideValidationError` with code\n * `invalid_loop_override` for any validation failure.\n *\n * Validation rules (Phase 3 \u2014 R5 mitigation):\n * - `loopStopWhenJson`: all items must have kind `stepCount` or `hasToolCall`.\n * Items with kind `custom` are rejected \u2014 they cannot be stored as JSON.\n * - `loopActiveToolsJson`: when `agentAllowedTools` is provided, every entry\n * must be in that allowlist.\n */\n private validateLoopInput(input: AiAgentRuntimeOverrideInput): void {\n if (input.loopStopWhenJson != null) {\n if (!Array.isArray(input.loopStopWhenJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson must be an array of stop condition objects.',\n 'invalid_loop_override',\n )\n }\n for (const item of input.loopStopWhenJson) {\n if (!item || typeof item !== 'object' || !('kind' in item)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson items must have a \"kind\" field.',\n 'invalid_loop_override',\n )\n }\n const kind = (item as AiAgentLoopStopCondition).kind\n if (kind === 'custom') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson does not support kind \"custom\" \u2014 only \"stepCount\" and \"hasToolCall\" are JSON-safe and storable.',\n 'invalid_loop_override',\n )\n }\n if (kind !== 'stepCount' && kind !== 'hasToolCall') {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopStopWhenJson contains unknown kind \"${String(kind)}\". Allowed: \"stepCount\", \"hasToolCall\".`,\n 'invalid_loop_override',\n )\n }\n if (kind === 'stepCount' && typeof (item as { count?: unknown }).count !== 'number') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson stepCount item must have a numeric \"count\" field.',\n 'invalid_loop_override',\n )\n }\n if (kind === 'hasToolCall' && typeof (item as { toolName?: unknown }).toolName !== 'string') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson hasToolCall item must have a string \"toolName\" field.',\n 'invalid_loop_override',\n )\n }\n }\n }\n\n if (input.loopActiveToolsJson != null) {\n if (!Array.isArray(input.loopActiveToolsJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson must be an array of tool name strings.',\n 'invalid_loop_override',\n )\n }\n for (const name of input.loopActiveToolsJson) {\n if (typeof name !== 'string' || name.length === 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson entries must be non-empty strings.',\n 'invalid_loop_override',\n )\n }\n }\n if (input.agentAllowedTools && input.agentAllowedTools.length > 0) {\n const outsideAllowlist = input.loopActiveToolsJson.filter(\n (name) => !input.agentAllowedTools!.includes(name),\n )\n if (outsideAllowlist.length > 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopActiveToolsJson contains tools outside the agent's allowedTools: ${outsideAllowlist.join(', ')}.`,\n 'invalid_loop_override',\n )\n }\n }\n }\n }\n\n /**\n * Inserts or updates the runtime override for the given context.\n *\n * Validates `providerId` against the registry at write time so an admin\n * cannot save a typo (Phase 1.4 contract re-applied per spec \u00A7Data Models).\n * An unknown provider id throws a typed error.\n *\n * Also validates loop override fields (R5 mitigation \u2014 Phase 3):\n * - `loopStopWhenJson` items must use only JSON-safe kinds.\n * - `loopActiveToolsJson` items must be a subset of `agentAllowedTools`\n * when that is provided.\n *\n * The R6 base-URL allowlist check is intentionally NOT performed here \u2014\n * that enforcement lives at the HTTP layer (PUT settings route). The\n * repository trusts that callers have already validated the value.\n */\n async upsertDefault(\n input: AiAgentRuntimeOverrideInput,\n ctx: AiAgentRuntimeOverrideContext,\n ): Promise<AiAgentRuntimeOverride> {\n if (!ctx?.tenantId) {\n throw new Error('AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId')\n }\n\n const normalizedProviderId = input.providerId\n ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id))\n : null\n if (input.providerId) {\n const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null\n if (!knownProvider) {\n throw new AiAgentRuntimeOverrideValidationError(\n `Unknown provider id \"${input.providerId}\". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(', ')}.`,\n )\n }\n }\n\n this.validateLoopInput(input)\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = input.agentId ?? null\n const hasProviderId = Object.prototype.hasOwnProperty.call(input, 'providerId')\n const hasModelId = Object.prototype.hasOwnProperty.call(input, 'modelId')\n const hasBaseURL = Object.prototype.hasOwnProperty.call(input, 'baseURL')\n const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideProviders')\n const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideModelsByProvider')\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n\n if (existing) {\n if (hasProviderId) existing.providerId = normalizedProviderId\n if (hasModelId) existing.modelId = input.modelId ?? null\n if (hasBaseURL) existing.baseUrl = input.baseURL ?? null\n if (hasAllowedOverrideProviders) {\n existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null\n }\n if (hasAllowedOverrideModels) {\n existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {}\n }\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n if ('loopDisabled' in input) existing.loopDisabled = input.loopDisabled ?? null\n if ('loopMaxSteps' in input) existing.loopMaxSteps = input.loopMaxSteps ?? null\n if ('loopMaxToolCalls' in input) existing.loopMaxToolCalls = input.loopMaxToolCalls ?? null\n if ('loopMaxWallClockMs' in input) existing.loopMaxWallClockMs = input.loopMaxWallClockMs ?? null\n if ('loopMaxTokens' in input) existing.loopMaxTokens = input.loopMaxTokens ?? null\n if ('loopStopWhenJson' in input) existing.loopStopWhenJson = input.loopStopWhenJson ?? null\n if ('loopActiveToolsJson' in input) existing.loopActiveToolsJson = input.loopActiveToolsJson ?? null\n if ('inputModeration' in input) existing.inputModeration = input.inputModeration ?? null\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n providerId: hasProviderId ? normalizedProviderId : null,\n modelId: hasModelId ? (input.modelId ?? null) : null,\n baseUrl: hasBaseURL ? (input.baseURL ?? null) : null,\n allowedOverrideProviders: hasAllowedOverrideProviders\n ? (input.allowedOverrideProviders ?? null)\n : null,\n allowedOverrideModelsByProvider: hasAllowedOverrideModels\n ? (input.allowedOverrideModelsByProvider ?? {})\n : {},\n updatedByUserId: ctx.userId ?? null,\n loopDisabled: input.loopDisabled ?? null,\n loopMaxSteps: input.loopMaxSteps ?? null,\n loopMaxToolCalls: input.loopMaxToolCalls ?? null,\n loopMaxWallClockMs: input.loopMaxWallClockMs ?? null,\n loopMaxTokens: input.loopMaxTokens ?? null,\n loopStopWhenJson: input.loopStopWhenJson ?? null,\n loopActiveToolsJson: input.loopActiveToolsJson ?? null,\n inputModeration: input.inputModeration ?? null,\n } as unknown as AiAgentRuntimeOverride)\n await tx.persist(row).flush()\n return row\n })\n }\n\n /**\n * Soft-deletes the active override matching the given context by setting\n * `deleted_at = now()`. Returns true when a row was found and cleared,\n * false when no active row existed.\n */\n async clearDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = ctx.agentId ?? null\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (!existing) return false\n if (\n existing.allowedOverrideProviders != null ||\n Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0\n ) {\n existing.providerId = null\n existing.modelId = null\n existing.baseUrl = null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return true\n }\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\n/**\n * Thrown by `upsertDefault` when validation fails (unknown provider id,\n * invalid loop override JSON).\n */\nexport class AiAgentRuntimeOverrideValidationError extends Error {\n readonly code: string\n\n constructor(message: string, code = 'invalid_override') {\n super(message)\n this.name = 'AiAgentRuntimeOverrideValidationError'\n this.code = code\n }\n}\n\nexport default AiAgentRuntimeOverrideRepository\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AA2DhC,MAAM,iCAAiC;AAAA,EAC5C,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUjD,MAAM,WAAW,KAI0B;AACzC,QAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,UAAM,YAAY,IAAI,kBAAkB;AAGxC,QAAI,IAAI,SAAS;AACf,YAAM,WAAW,MAAM,KAAK,GAAG,QAAQ,wBAAwB;AAAA,QAC7D,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,SAAS,IAAI;AAAA,QACb,WAAW;AAAA,MACb,CAA+C;AAC/C,UAAI,SAAU,QAAO;AAAA,IACvB;AAGA,UAAM,YAAY,MAAM,KAAK,GAAG,QAAQ,wBAAwB;AAAA,MAC9D,UAAU,IAAI;AAAA,MACd,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,WAAW;AAAA,IACb,CAA+C;AAC/C,WAAO,aAAa;AAAA,EACtB;AAAA,EAEA,MAAM,SAAS,KAI4B;AACzC,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,UAAM,MAAM,MAAM,KAAK,GAAG,QAAQ,wBAAwB;AAAA,MACxD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,SAAS,IAAI,WAAW;AAAA,MACxB,WAAW;AAAA,IACb,CAA+C;AAC/C,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,kBAAkB,OAA0C;AAClE,QAAI,MAAM,oBAAoB,MAAM;AAClC,UAAI,CAAC,MAAM,QAAQ,MAAM,gBAAgB,GAAG;AAC1C,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,iBAAW,QAAQ,MAAM,kBAAkB;AACzC,YAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,EAAE,UAAU,OAAO;AAC1D,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM,OAAQ,KAAkC;AAChD,YAAI,SAAS,UAAU;AACrB,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,eAAe,SAAS,eAAe;AAClD,gBAAM,IAAI;AAAA,YACR,2CAA2C,OAAO,IAAI,CAAC;AAAA,YACvD;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,eAAe,OAAQ,KAA6B,UAAU,UAAU;AACnF,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,iBAAiB,OAAQ,KAAgC,aAAa,UAAU;AAC3F,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,uBAAuB,MAAM;AACrC,UAAI,CAAC,MAAM,QAAQ,MAAM,mBAAmB,GAAG;AAC7C,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,iBAAW,QAAQ,MAAM,qBAAqB;AAC5C,YAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG;AACjD,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,UAAI,MAAM,qBAAqB,MAAM,kBAAkB,SAAS,GAAG;AACjE,cAAM,mBAAmB,MAAM,oBAAoB;AAAA,UACjD,CAAC,SAAS,CAAC,MAAM,kBAAmB,SAAS,IAAI;AAAA,QACnD;AACA,YAAI,iBAAiB,SAAS,GAAG;AAC/B,gBAAM,IAAI;AAAA,YACR,wEAAwE,iBAAiB,KAAK,IAAI,CAAC;AAAA,YACnG;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,cACJ,OACA,KACiC;AACjC,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAEA,UAAM,uBAAuB,MAAM,aAC/B,oBAAoB,MAAM,YAAY,oBAAoB,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,IACjF;AACJ,QAAI,MAAM,YAAY;AACpB,YAAM,gBAAgB,uBAAuB,oBAAoB,IAAI,oBAAoB,IAAI;AAC7F,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI;AAAA,UACR,wBAAwB,MAAM,UAAU,+BAA+B,oBAAoB,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,QAC/H;AAAA,MACF;AAAA,IACF;AAEA,SAAK,kBAAkB,KAAK;AAE5B,UAAM,YAAY,IAAI,kBAAkB;AACxC,UAAM,gBAAgB,MAAM,WAAW;AACvC,UAAM,gBAAgB,OAAO,UAAU,eAAe,KAAK,OAAO,YAAY;AAC9E,UAAM,aAAa,OAAO,UAAU,eAAe,KAAK,OAAO,SAAS;AACxE,UAAM,aAAa,OAAO,UAAU,eAAe,KAAK,OAAO,SAAS;AACxE,UAAM,8BAA8B,OAAO,UAAU,eAAe,KAAK,OAAO,0BAA0B;AAC1G,UAAM,2BAA2B,OAAO,UAAU,eAAe,KAAK,OAAO,iCAAiC;AAE9G,WAAO,KAAK,GAAG,cAAc,OAAO,OAAO;AACzC,YAAM,WAAW,MAAM,GAAG,QAAQ,wBAAwB;AAAA,QACxD,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAA+C;AAE/C,UAAI,UAAU;AACZ,YAAI,cAAe,UAAS,aAAa;AACzC,YAAI,WAAY,UAAS,UAAU,MAAM,WAAW;AACpD,YAAI,WAAY,UAAS,UAAU,MAAM,WAAW;AACpD,YAAI,6BAA6B;AAC/B,mBAAS,2BAA2B,MAAM,4BAA4B;AAAA,QACxE;AACA,YAAI,0BAA0B;AAC5B,mBAAS,kCAAkC,MAAM,mCAAmC,CAAC;AAAA,QACvF;AACA,iBAAS,kBAAkB,IAAI,UAAU;AACzC,iBAAS,YAAY,oBAAI,KAAK;AAC9B,YAAI,kBAAkB,MAAO,UAAS,eAAe,MAAM,gBAAgB;AAC3E,YAAI,kBAAkB,MAAO,UAAS,eAAe,MAAM,gBAAgB;AAC3E,YAAI,sBAAsB,MAAO,UAAS,mBAAmB,MAAM,oBAAoB;AACvF,YAAI,wBAAwB,MAAO,UAAS,qBAAqB,MAAM,sBAAsB;AAC7F,YAAI,mBAAmB,MAAO,UAAS,gBAAgB,MAAM,iBAAiB;AAC9E,YAAI,sBAAsB,MAAO,UAAS,mBAAmB,MAAM,oBAAoB;AACvF,YAAI,yBAAyB,MAAO,UAAS,sBAAsB,MAAM,uBAAuB;AAChG,YAAI,qBAAqB,MAAO,UAAS,kBAAkB,MAAM,mBAAmB;AACpF,cAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AACjC,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,GAAG,OAAO,wBAAwB;AAAA,QAC5C,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,SAAS;AAAA,QACT,YAAY,gBAAgB,uBAAuB;AAAA,QACnD,SAAS,aAAc,MAAM,WAAW,OAAQ;AAAA,QAChD,SAAS,aAAc,MAAM,WAAW,OAAQ;AAAA,QAChD,0BAA0B,8BACrB,MAAM,4BAA4B,OACnC;AAAA,QACJ,iCAAiC,2BAC5B,MAAM,mCAAmC,CAAC,IAC3C,CAAC;AAAA,QACL,iBAAiB,IAAI,UAAU;AAAA,QAC/B,cAAc,MAAM,gBAAgB;AAAA,QACpC,cAAc,MAAM,gBAAgB;AAAA,QACpC,kBAAkB,MAAM,oBAAoB;AAAA,QAC5C,oBAAoB,MAAM,sBAAsB;AAAA,QAChD,eAAe,MAAM,iBAAiB;AAAA,QACtC,kBAAkB,MAAM,oBAAoB;AAAA,QAC5C,qBAAqB,MAAM,uBAAuB;AAAA,QAClD,iBAAiB,MAAM,mBAAmB;AAAA,MAC5C,CAAsC;AACtC,YAAM,GAAG,QAAQ,GAAG,EAAE,MAAM;AAC5B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,KAIE;AACnB,QAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,UAAM,YAAY,IAAI,kBAAkB;AACxC,UAAM,gBAAgB,IAAI,WAAW;AAErC,WAAO,KAAK,GAAG,cAAc,OAAO,OAAO;AACzC,YAAM,WAAW,MAAM,GAAG,QAAQ,wBAAwB;AAAA,QACxD,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAA+C;AAC/C,UAAI,CAAC,SAAU,QAAO;AACtB,UACE,SAAS,4BAA4B,QACrC,OAAO,KAAK,SAAS,mCAAmC,CAAC,CAAC,EAAE,SAAS,GACrE;AACA,iBAAS,aAAa;AACtB,iBAAS,UAAU;AACnB,iBAAS,UAAU;AACnB,iBAAS,YAAY,oBAAI,KAAK;AAC9B,cAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AACjC,eAAO;AAAA,MACT;AACA,eAAS,YAAY,oBAAI,KAAK;AAC9B,YAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AACjC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAMO,MAAM,8CAA8C,MAAM;AAAA,EAG/D,YAAY,SAAiB,OAAO,oBAAoB;AACtD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAO,2CAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AiModerationFlag } from "../entities.js";
|
|
2
|
+
class AiModerationFlagRepository {
|
|
3
|
+
constructor(em) {
|
|
4
|
+
this.em = em;
|
|
5
|
+
}
|
|
6
|
+
async create(input) {
|
|
7
|
+
const flag = this.em.create(AiModerationFlag, {
|
|
8
|
+
tenantId: input.tenantId,
|
|
9
|
+
organizationId: input.organizationId ?? null,
|
|
10
|
+
agentId: input.agentId,
|
|
11
|
+
userId: input.userId,
|
|
12
|
+
providerId: input.providerId,
|
|
13
|
+
modelId: input.modelId,
|
|
14
|
+
categories: input.categories
|
|
15
|
+
});
|
|
16
|
+
this.em.persist(flag);
|
|
17
|
+
await this.em.flush();
|
|
18
|
+
return flag;
|
|
19
|
+
}
|
|
20
|
+
async list(filter) {
|
|
21
|
+
const where = { tenantId: filter.tenantId };
|
|
22
|
+
if (filter.organizationId !== void 0 && filter.organizationId !== null) {
|
|
23
|
+
where.organizationId = filter.organizationId;
|
|
24
|
+
}
|
|
25
|
+
if (filter.agentId) where.agentId = filter.agentId;
|
|
26
|
+
if (filter.userId) where.userId = filter.userId;
|
|
27
|
+
if (filter.from || filter.to) {
|
|
28
|
+
const createdAt = {};
|
|
29
|
+
if (filter.from) createdAt.$gte = filter.from;
|
|
30
|
+
if (filter.to) createdAt.$lte = filter.to;
|
|
31
|
+
where.createdAt = createdAt;
|
|
32
|
+
}
|
|
33
|
+
const [items, total] = await this.em.findAndCount(AiModerationFlag, where, {
|
|
34
|
+
orderBy: { createdAt: "desc" },
|
|
35
|
+
limit: filter.pageSize,
|
|
36
|
+
offset: (filter.page - 1) * filter.pageSize
|
|
37
|
+
});
|
|
38
|
+
return { items, total };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
AiModerationFlagRepository
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=AiModerationFlagRepository.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiModerationFlagRepository.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { AiModerationFlag } from '../entities'\nimport type { ModerationCategoryResult } from '../../lib/moderation'\n\nexport interface CreateModerationFlagInput {\n tenantId: string\n organizationId?: string | null\n agentId: string\n userId: string\n providerId: string\n modelId: string\n categories: Record<string, ModerationCategoryResult>\n}\n\nexport interface ListModerationFlagsFilter {\n tenantId: string\n organizationId?: string | null\n agentId?: string\n userId?: string\n /** Inclusive lower bound on `created_at`. */\n from?: Date\n /** Inclusive upper bound on `created_at`. */\n to?: Date\n page: number\n pageSize: number\n}\n\nexport interface ModerationFlagListResult {\n items: AiModerationFlag[]\n total: number\n}\n\n/**\n * Repository for the append-only `ai_moderation_flags` audit table. Every read\n * is tenant-scoped \u2014 cross-tenant access is structurally impossible because\n * `tenantId` is always part of the filter.\n *\n * Spec `2026-06-04-ai-input-moderation-and-safety-identifiers`.\n */\nexport class AiModerationFlagRepository {\n constructor(private readonly em: EntityManager) {}\n\n async create(input: CreateModerationFlagInput): Promise<AiModerationFlag> {\n const flag = this.em.create(AiModerationFlag, {\n tenantId: input.tenantId,\n organizationId: input.organizationId ?? null,\n agentId: input.agentId,\n userId: input.userId,\n providerId: input.providerId,\n modelId: input.modelId,\n categories: input.categories,\n })\n this.em.persist(flag)\n await this.em.flush()\n return flag\n }\n\n async list(filter: ListModerationFlagsFilter): Promise<ModerationFlagListResult> {\n const where: Record<string, unknown> = { tenantId: filter.tenantId }\n if (filter.organizationId !== undefined && filter.organizationId !== null) {\n where.organizationId = filter.organizationId\n }\n if (filter.agentId) where.agentId = filter.agentId\n if (filter.userId) where.userId = filter.userId\n if (filter.from || filter.to) {\n const createdAt: Record<string, Date> = {}\n if (filter.from) createdAt.$gte = filter.from\n if (filter.to) createdAt.$lte = filter.to\n where.createdAt = createdAt\n }\n\n const [items, total] = await this.em.findAndCount(AiModerationFlag, where, {\n orderBy: { createdAt: 'desc' },\n limit: filter.pageSize,\n offset: (filter.page - 1) * filter.pageSize,\n })\n return { items, total }\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,wBAAwB;AAsC1B,MAAM,2BAA2B;AAAA,EACtC,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAEjD,MAAM,OAAO,OAA6D;AACxE,UAAM,OAAO,KAAK,GAAG,OAAO,kBAAkB;AAAA,MAC5C,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM,kBAAkB;AAAA,MACxC,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,IACpB,CAAC;AACD,SAAK,GAAG,QAAQ,IAAI;AACpB,UAAM,KAAK,GAAG,MAAM;AACpB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,QAAsE;AAC/E,UAAM,QAAiC,EAAE,UAAU,OAAO,SAAS;AACnE,QAAI,OAAO,mBAAmB,UAAa,OAAO,mBAAmB,MAAM;AACzE,YAAM,iBAAiB,OAAO;AAAA,IAChC;AACA,QAAI,OAAO,QAAS,OAAM,UAAU,OAAO;AAC3C,QAAI,OAAO,OAAQ,OAAM,SAAS,OAAO;AACzC,QAAI,OAAO,QAAQ,OAAO,IAAI;AAC5B,YAAM,YAAkC,CAAC;AACzC,UAAI,OAAO,KAAM,WAAU,OAAO,OAAO;AACzC,UAAI,OAAO,GAAI,WAAU,OAAO,OAAO;AACvC,YAAM,YAAY;AAAA,IACpB;AAEA,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM,KAAK,GAAG,aAAa,kBAAkB,OAAO;AAAA,MACzE,SAAS,EAAE,WAAW,OAAO;AAAA,MAC7B,OAAO,OAAO;AAAA,MACd,SAAS,OAAO,OAAO,KAAK,OAAO;AAAA,IACrC,CAAC;AACD,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { asValue } from "awilix";
|
|
1
|
+
import { asFunction, asValue } from "awilix";
|
|
2
2
|
import { toolRegistry } from "./lib/tool-registry.js";
|
|
3
|
+
import { createModerationService } from "./lib/moderation.js";
|
|
3
4
|
function register(container) {
|
|
4
5
|
container.register({
|
|
5
|
-
mcpToolRegistry: asValue(toolRegistry)
|
|
6
|
+
mcpToolRegistry: asValue(toolRegistry),
|
|
7
|
+
// Input pre-moderation service (OpenAI /v1/moderations). Singleton +
|
|
8
|
+
// overridable by downstream apps via the module overrides DI seam.
|
|
9
|
+
moderationService: asFunction(() => createModerationService()).singleton()
|
|
6
10
|
});
|
|
7
11
|
}
|
|
8
12
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/ai_assistant/di.ts"],
|
|
4
|
-
"sourcesContent": ["import { asValue } from 'awilix'\nimport type { AwilixContainer } from 'awilix'\nimport { toolRegistry } from './lib/tool-registry'\n\nexport function register(container: AwilixContainer): void {\n container.register({\n mcpToolRegistry: asValue(toolRegistry),\n })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,eAAe;
|
|
4
|
+
"sourcesContent": ["import { asFunction, asValue } from 'awilix'\nimport type { AwilixContainer } from 'awilix'\nimport { toolRegistry } from './lib/tool-registry'\nimport { createModerationService } from './lib/moderation'\n\nexport function register(container: AwilixContainer): void {\n container.register({\n mcpToolRegistry: asValue(toolRegistry),\n // Input pre-moderation service (OpenAI /v1/moderations). Singleton +\n // overridable by downstream apps via the module overrides DI seam.\n moderationService: asFunction(() => createModerationService()).singleton(),\n })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,YAAY,eAAe;AAEpC,SAAS,oBAAoB;AAC7B,SAAS,+BAA+B;AAEjC,SAAS,SAAS,WAAkC;AACzD,YAAU,SAAS;AAAA,IACjB,iBAAiB,QAAQ,YAAY;AAAA;AAAA;AAAA,IAGrC,mBAAmB,WAAW,MAAM,wBAAwB,CAAC,EAAE,UAAU;AAAA,EAC3E,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -39,6 +39,12 @@ const events = [
|
|
|
39
39
|
entity: "ai_chat_conversation",
|
|
40
40
|
category: "lifecycle",
|
|
41
41
|
clientBroadcast: true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "ai_assistant.moderation_flag.created",
|
|
45
|
+
label: "AI Moderation Flag Created",
|
|
46
|
+
entity: "ai_moderation_flag",
|
|
47
|
+
category: "system"
|
|
42
48
|
}
|
|
43
49
|
];
|
|
44
50
|
const eventsConfig = createModuleEvents({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/ai_assistant/events.ts"],
|
|
4
|
-
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * AI Assistant Module Events\n *\n * Typed declarations for the pending-action lifecycle events emitted by\n * the Phase 3 WS-C mutation approval flow. The event IDs are FROZEN per\n * `BACKWARD_COMPATIBILITY.md` \u00A75 (contract surface #5) and MUST NOT be\n * renamed; additive payload changes are allowed.\n *\n * - `ai.action.confirmed` \u2014 emitted by `executePendingActionConfirm`\n * (Step 5.8) after the `pending \u2192 confirmed \u2192 executing \u2192 {confirmed|\n * failed}` transition. The handler's outcome lives in\n * `executionResult`; partial-stale rows carry the surviving stale\n * records via `failedRecords`.\n * - `ai.action.cancelled` \u2014 emitted by `executePendingActionCancel`\n * (Step 5.9) after the atomic `pending \u2192 cancelled` transition.\n * - `ai.action.expired` \u2014 emitted by the Step 5.9 expired short-circuit\n * AND by the Step 5.12 cleanup worker when the TTL elapses. The\n * worker is the actor in that path, so `resolvedByUserId` is NOT part\n * of the payload.\n */\nconst events = [\n {\n id: 'ai.action.confirmed',\n label: 'AI Pending Action Confirmed',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.cancelled',\n label: 'AI Pending Action Cancelled',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.expired',\n label: 'AI Pending Action Expired',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.token_usage.recorded',\n label: 'AI Token Usage Recorded',\n entity: 'token_usage',\n category: 'system' as const,\n clientBroadcast: false,\n portalBroadcast: false,\n },\n {\n id: 'ai_assistant.conversation.shared',\n label: 'AI Conversation Shared',\n entity: 'ai_chat_conversation',\n category: 'lifecycle' as const,\n clientBroadcast: true,\n },\n {\n id: 'ai_assistant.conversation.unshared',\n label: 'AI Conversation Unshared',\n entity: 'ai_chat_conversation',\n category: 'lifecycle' as const,\n clientBroadcast: true,\n },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'ai_assistant',\n events,\n})\n\n/** Type-safe event emitter for the ai_assistant module. */\nexport const emitAiAssistantEvent = eventsConfig.emit\n\n/** Event IDs declared by the ai_assistant module. */\nexport type AiAssistantEventId = (typeof events)[number]['id']\n\n/**\n * Typed payload contracts for each ai_assistant event. Payloads are\n * additive-only \u2014 extend existing fields rather than renaming/removing.\n */\nexport interface AiActionFailedRecordPayload {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport interface AiActionExecutionResultPayload {\n recordId?: string\n commandName?: string\n error?: { code: string; message: string }\n}\n\nexport interface AiActionConfirmedPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n failedRecords?: AiActionFailedRecordPayload[] | null\n}\n\nexport interface AiActionCancelledPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n reason?: string\n}\n\nexport interface AiActionExpiredPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n resolvedByUserId: null\n resolvedAt: string\n expiresAt?: string\n expiredAt?: string\n}\n\nexport interface AiConversationSharedPayload {\n conversationId: string\n tenantId: string\n organizationId: string | null\n ownerUserId: string\n participantUserId: string\n role: string\n}\n\nexport interface AiConversationUnsharedPayload {\n conversationId: string\n tenantId: string\n organizationId: string | null\n ownerUserId: string\n participantUserId: string\n}\n\nexport default eventsConfig\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,0BAA0B;AAsBnC,MAAM,SAAS;AAAA,EACb;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,EACnB;AACF;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAGM,MAAM,uBAAuB,aAAa;
|
|
4
|
+
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * AI Assistant Module Events\n *\n * Typed declarations for the pending-action lifecycle events emitted by\n * the Phase 3 WS-C mutation approval flow. The event IDs are FROZEN per\n * `BACKWARD_COMPATIBILITY.md` \u00A75 (contract surface #5) and MUST NOT be\n * renamed; additive payload changes are allowed.\n *\n * - `ai.action.confirmed` \u2014 emitted by `executePendingActionConfirm`\n * (Step 5.8) after the `pending \u2192 confirmed \u2192 executing \u2192 {confirmed|\n * failed}` transition. The handler's outcome lives in\n * `executionResult`; partial-stale rows carry the surviving stale\n * records via `failedRecords`.\n * - `ai.action.cancelled` \u2014 emitted by `executePendingActionCancel`\n * (Step 5.9) after the atomic `pending \u2192 cancelled` transition.\n * - `ai.action.expired` \u2014 emitted by the Step 5.9 expired short-circuit\n * AND by the Step 5.12 cleanup worker when the TTL elapses. The\n * worker is the actor in that path, so `resolvedByUserId` is NOT part\n * of the payload.\n */\nconst events = [\n {\n id: 'ai.action.confirmed',\n label: 'AI Pending Action Confirmed',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.cancelled',\n label: 'AI Pending Action Cancelled',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.expired',\n label: 'AI Pending Action Expired',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.token_usage.recorded',\n label: 'AI Token Usage Recorded',\n entity: 'token_usage',\n category: 'system' as const,\n clientBroadcast: false,\n portalBroadcast: false,\n },\n {\n id: 'ai_assistant.conversation.shared',\n label: 'AI Conversation Shared',\n entity: 'ai_chat_conversation',\n category: 'lifecycle' as const,\n clientBroadcast: true,\n },\n {\n id: 'ai_assistant.conversation.unshared',\n label: 'AI Conversation Unshared',\n entity: 'ai_chat_conversation',\n category: 'lifecycle' as const,\n clientBroadcast: true,\n },\n {\n id: 'ai_assistant.moderation_flag.created',\n label: 'AI Moderation Flag Created',\n entity: 'ai_moderation_flag',\n category: 'system' as const,\n },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'ai_assistant',\n events,\n})\n\n/** Type-safe event emitter for the ai_assistant module. */\nexport const emitAiAssistantEvent = eventsConfig.emit\n\n/** Event IDs declared by the ai_assistant module. */\nexport type AiAssistantEventId = (typeof events)[number]['id']\n\n/**\n * Typed payload contracts for each ai_assistant event. Payloads are\n * additive-only \u2014 extend existing fields rather than renaming/removing.\n */\nexport interface AiActionFailedRecordPayload {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport interface AiActionExecutionResultPayload {\n recordId?: string\n commandName?: string\n error?: { code: string; message: string }\n}\n\nexport interface AiActionConfirmedPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n failedRecords?: AiActionFailedRecordPayload[] | null\n}\n\nexport interface AiActionCancelledPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n reason?: string\n}\n\nexport interface AiActionExpiredPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n resolvedByUserId: null\n resolvedAt: string\n expiresAt?: string\n expiredAt?: string\n}\n\nexport interface AiConversationSharedPayload {\n conversationId: string\n tenantId: string\n organizationId: string | null\n ownerUserId: string\n participantUserId: string\n role: string\n}\n\nexport interface AiConversationUnsharedPayload {\n conversationId: string\n tenantId: string\n organizationId: string | null\n ownerUserId: string\n participantUserId: string\n}\n\n/**\n * Emitted (best-effort) when the input moderation gate blocks a turn and the\n * audit row is persisted. Carries category flags only \u2014 never prompt content.\n *\n * Spec `2026-06-04-ai-input-moderation-and-safety-identifiers`.\n */\nexport interface AiModerationFlagCreatedPayload {\n id: string\n tenantId: string\n organizationId: string | null\n agentId: string\n userId: string\n categories: string[]\n}\n\nexport default eventsConfig\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,0BAA0B;AAsBnC,MAAM,SAAS;AAAA,EACb;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAGM,MAAM,uBAAuB,aAAa;AA8FjD,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -62,6 +62,18 @@
|
|
|
62
62
|
"ai_assistant.agents.model_override.subtitle": "Überschreibe Standardanbieter und -modell für diesen Mandanten und Agenten.",
|
|
63
63
|
"ai_assistant.agents.model_override.tenantOverride": "Mandanten-Überschreibung",
|
|
64
64
|
"ai_assistant.agents.model_override.title": "Anbieter und Modell",
|
|
65
|
+
"ai_assistant.agents.moderation.controlLabel": "Moderation",
|
|
66
|
+
"ai_assistant.agents.moderation.effectiveEnforced": "Erzwungen",
|
|
67
|
+
"ai_assistant.agents.moderation.effectiveOff": "Aus",
|
|
68
|
+
"ai_assistant.agents.moderation.effectiveOn": "Ein",
|
|
69
|
+
"ai_assistant.agents.moderation.enforcedBadge": "Erzwungen",
|
|
70
|
+
"ai_assistant.agents.moderation.enforcedHint": "Dieser Agent akzeptiert nicht vertrauenswürdige Eingaben, daher ist die Moderation immer aktiv und kann hier nicht deaktiviert werden.",
|
|
71
|
+
"ai_assistant.agents.moderation.inheritLabel": "Erben",
|
|
72
|
+
"ai_assistant.agents.moderation.offLabel": "Aus",
|
|
73
|
+
"ai_assistant.agents.moderation.onLabel": "Ein",
|
|
74
|
+
"ai_assistant.agents.moderation.saved": "Eingabe-Moderationseinstellung gespeichert.",
|
|
75
|
+
"ai_assistant.agents.moderation.subtitle": "Benutzereingaben vor dem Modelllauf auf unsichere Inhalte prüfen. Gilt nur, wenn der aufgelöste Anbieter Moderation unterstützt.",
|
|
76
|
+
"ai_assistant.agents.moderation.title": "Eingabe-Moderation",
|
|
65
77
|
"ai_assistant.agents.mutation_policy.clear": "Überschreibung löschen",
|
|
66
78
|
"ai_assistant.agents.mutation_policy.clearedMessage": "Überschreibung der Mutationsrichtlinie gelöscht; Agent verwendet die im Code deklarierte Richtlinie.",
|
|
67
79
|
"ai_assistant.agents.mutation_policy.codeDeclared": "Im Code deklariert",
|
|
@@ -297,6 +309,8 @@
|
|
|
297
309
|
"ai_assistant.dock.left": "Links andocken",
|
|
298
310
|
"ai_assistant.dock.minimize": "Minimieren",
|
|
299
311
|
"ai_assistant.dock.right": "Rechts andocken",
|
|
312
|
+
"ai_assistant.errors.moderationBlocked": "Ihre Nachricht wurde vom Inhaltssicherheitsfilter blockiert. Bitte formulieren Sie sie um und versuchen Sie es erneut.",
|
|
313
|
+
"ai_assistant.errors.moderationUnavailable": "Die Inhaltssicherheitsprüfung ist vorübergehend nicht verfügbar. Bitte versuchen Sie es in einem Moment erneut.",
|
|
300
314
|
"ai_assistant.launcher.composerPlaceholder": "Fragen Sie mich etwas…",
|
|
301
315
|
"ai_assistant.launcher.dialogDescription": "Wähle einen Assistenten. Mit ↑/↓ navigieren, Enter startet, Esc schließt.",
|
|
302
316
|
"ai_assistant.launcher.dialogTitle": "AI assistants",
|
|
@@ -342,6 +356,18 @@
|
|
|
342
356
|
"ai_assistant.modelPicker.triggerAriaLabel": "KI-Modell auswählen",
|
|
343
357
|
"ai_assistant.modelPicker.useDefault": "Standard des Agenten verwenden",
|
|
344
358
|
"ai_assistant.modelPicker.useDefaultWithModel": "Standard des Agenten verwenden: {{model}}",
|
|
359
|
+
"ai_assistant.moderationFlags.columns.agent": "Agent",
|
|
360
|
+
"ai_assistant.moderationFlags.columns.categories": "Kategorien",
|
|
361
|
+
"ai_assistant.moderationFlags.columns.createdAt": "Markiert am",
|
|
362
|
+
"ai_assistant.moderationFlags.columns.user": "Benutzer",
|
|
363
|
+
"ai_assistant.moderationFlags.empty": "Keine markierten Nachrichten.",
|
|
364
|
+
"ai_assistant.moderationFlags.filters.agentPlaceholder": "modul.agent",
|
|
365
|
+
"ai_assistant.moderationFlags.filters.apply": "Anwenden",
|
|
366
|
+
"ai_assistant.moderationFlags.filters.from": "Von",
|
|
367
|
+
"ai_assistant.moderationFlags.filters.to": "Bis",
|
|
368
|
+
"ai_assistant.moderationFlags.navTitle": "Moderations-Flags",
|
|
369
|
+
"ai_assistant.moderationFlags.subtitle": "Vom Inhaltssicherheitsfilter blockierte Eingaben. Nur Kategorie-Flags und Scores — es wird kein Prompt-Inhalt gespeichert.",
|
|
370
|
+
"ai_assistant.moderationFlags.title": "Moderations-Flags",
|
|
345
371
|
"ai_assistant.notifications.conversation_shared.body": "Eine KI-Konversation wurde mit Ihnen geteilt.",
|
|
346
372
|
"ai_assistant.notifications.conversation_shared.title": "Konversation mit Ihnen geteilt",
|
|
347
373
|
"ai_assistant.notifications.conversation_shared.view_button": "Konversation öffnen",
|
|
@@ -62,6 +62,18 @@
|
|
|
62
62
|
"ai_assistant.agents.model_override.subtitle": "Override the default provider and model for this tenant and agent.",
|
|
63
63
|
"ai_assistant.agents.model_override.tenantOverride": "Tenant override",
|
|
64
64
|
"ai_assistant.agents.model_override.title": "Provider and model",
|
|
65
|
+
"ai_assistant.agents.moderation.controlLabel": "Moderation",
|
|
66
|
+
"ai_assistant.agents.moderation.effectiveEnforced": "Enforced",
|
|
67
|
+
"ai_assistant.agents.moderation.effectiveOff": "Off",
|
|
68
|
+
"ai_assistant.agents.moderation.effectiveOn": "On",
|
|
69
|
+
"ai_assistant.agents.moderation.enforcedBadge": "Enforced",
|
|
70
|
+
"ai_assistant.agents.moderation.enforcedHint": "This agent accepts untrusted input, so moderation is always on and cannot be disabled here.",
|
|
71
|
+
"ai_assistant.agents.moderation.inheritLabel": "Inherit",
|
|
72
|
+
"ai_assistant.agents.moderation.offLabel": "Off",
|
|
73
|
+
"ai_assistant.agents.moderation.onLabel": "On",
|
|
74
|
+
"ai_assistant.agents.moderation.saved": "Input moderation setting saved.",
|
|
75
|
+
"ai_assistant.agents.moderation.subtitle": "Screen user input for unsafe content before the model runs. Applies only when the resolved provider supports moderation.",
|
|
76
|
+
"ai_assistant.agents.moderation.title": "Input moderation",
|
|
65
77
|
"ai_assistant.agents.mutation_policy.clear": "Clear override",
|
|
66
78
|
"ai_assistant.agents.mutation_policy.clearedMessage": "Mutation policy override cleared; agent is using its code-declared policy.",
|
|
67
79
|
"ai_assistant.agents.mutation_policy.codeDeclared": "Code-declared",
|
|
@@ -297,6 +309,8 @@
|
|
|
297
309
|
"ai_assistant.dock.left": "Dock Left",
|
|
298
310
|
"ai_assistant.dock.minimize": "Minimize",
|
|
299
311
|
"ai_assistant.dock.right": "Dock Right",
|
|
312
|
+
"ai_assistant.errors.moderationBlocked": "Your message was blocked by the content safety filter. Please rephrase and try again.",
|
|
313
|
+
"ai_assistant.errors.moderationUnavailable": "The content safety check is temporarily unavailable. Please try again in a moment.",
|
|
300
314
|
"ai_assistant.launcher.composerPlaceholder": "Ask anything…",
|
|
301
315
|
"ai_assistant.launcher.dialogDescription": "Pick an assistant. Use ↑/↓ to navigate, Enter to launch, Esc to close.",
|
|
302
316
|
"ai_assistant.launcher.dialogTitle": "AI assistants",
|
|
@@ -342,6 +356,18 @@
|
|
|
342
356
|
"ai_assistant.modelPicker.triggerAriaLabel": "Select AI model",
|
|
343
357
|
"ai_assistant.modelPicker.useDefault": "Use agent default",
|
|
344
358
|
"ai_assistant.modelPicker.useDefaultWithModel": "Use agent default: {{model}}",
|
|
359
|
+
"ai_assistant.moderationFlags.columns.agent": "Agent",
|
|
360
|
+
"ai_assistant.moderationFlags.columns.categories": "Categories",
|
|
361
|
+
"ai_assistant.moderationFlags.columns.createdAt": "Flagged at",
|
|
362
|
+
"ai_assistant.moderationFlags.columns.user": "User",
|
|
363
|
+
"ai_assistant.moderationFlags.empty": "No flagged messages.",
|
|
364
|
+
"ai_assistant.moderationFlags.filters.agentPlaceholder": "module.agent",
|
|
365
|
+
"ai_assistant.moderationFlags.filters.apply": "Apply",
|
|
366
|
+
"ai_assistant.moderationFlags.filters.from": "From",
|
|
367
|
+
"ai_assistant.moderationFlags.filters.to": "To",
|
|
368
|
+
"ai_assistant.moderationFlags.navTitle": "Moderation flags",
|
|
369
|
+
"ai_assistant.moderationFlags.subtitle": "Inputs blocked by the content safety filter. Category flags and scores only — no prompt content is stored.",
|
|
370
|
+
"ai_assistant.moderationFlags.title": "Moderation flags",
|
|
345
371
|
"ai_assistant.notifications.conversation_shared.body": "An AI conversation has been shared with you.",
|
|
346
372
|
"ai_assistant.notifications.conversation_shared.title": "Conversation shared with you",
|
|
347
373
|
"ai_assistant.notifications.conversation_shared.view_button": "View Conversation",
|
|
@@ -62,6 +62,18 @@
|
|
|
62
62
|
"ai_assistant.agents.model_override.subtitle": "Anula el proveedor y el modelo predeterminados para este inquilino y agente.",
|
|
63
63
|
"ai_assistant.agents.model_override.tenantOverride": "Sobrescritura del tenant",
|
|
64
64
|
"ai_assistant.agents.model_override.title": "Proveedor y modelo",
|
|
65
|
+
"ai_assistant.agents.moderation.controlLabel": "Moderación",
|
|
66
|
+
"ai_assistant.agents.moderation.effectiveEnforced": "Forzada",
|
|
67
|
+
"ai_assistant.agents.moderation.effectiveOff": "Desactivada",
|
|
68
|
+
"ai_assistant.agents.moderation.effectiveOn": "Activada",
|
|
69
|
+
"ai_assistant.agents.moderation.enforcedBadge": "Forzada",
|
|
70
|
+
"ai_assistant.agents.moderation.enforcedHint": "Este agente acepta entradas no confiables, por lo que la moderación siempre está activada y no se puede desactivar aquí.",
|
|
71
|
+
"ai_assistant.agents.moderation.inheritLabel": "Heredar",
|
|
72
|
+
"ai_assistant.agents.moderation.offLabel": "Desactivada",
|
|
73
|
+
"ai_assistant.agents.moderation.onLabel": "Activada",
|
|
74
|
+
"ai_assistant.agents.moderation.saved": "Ajuste de moderación de entrada guardado.",
|
|
75
|
+
"ai_assistant.agents.moderation.subtitle": "Filtra la entrada del usuario en busca de contenido no seguro antes de ejecutar el modelo. Solo aplica cuando el proveedor resuelto admite moderación.",
|
|
76
|
+
"ai_assistant.agents.moderation.title": "Moderación de entrada",
|
|
65
77
|
"ai_assistant.agents.mutation_policy.clear": "Eliminar sobrescritura",
|
|
66
78
|
"ai_assistant.agents.mutation_policy.clearedMessage": "Sobrescritura de política de mutación eliminada; el agente usa la política declarada en el código.",
|
|
67
79
|
"ai_assistant.agents.mutation_policy.codeDeclared": "Declarada en el código",
|
|
@@ -297,6 +309,8 @@
|
|
|
297
309
|
"ai_assistant.dock.left": "Anclar a la izquierda",
|
|
298
310
|
"ai_assistant.dock.minimize": "Minimizar",
|
|
299
311
|
"ai_assistant.dock.right": "Anclar a la derecha",
|
|
312
|
+
"ai_assistant.errors.moderationBlocked": "Tu mensaje fue bloqueado por el filtro de seguridad de contenido. Reformúlalo e inténtalo de nuevo.",
|
|
313
|
+
"ai_assistant.errors.moderationUnavailable": "La verificación de seguridad de contenido no está disponible temporalmente. Inténtalo de nuevo en un momento.",
|
|
300
314
|
"ai_assistant.launcher.composerPlaceholder": "Pregunte lo que necesite…",
|
|
301
315
|
"ai_assistant.launcher.dialogDescription": "Elige un asistente. Usa ↑/↓ para navegar, Enter para iniciar, Esc para cerrar.",
|
|
302
316
|
"ai_assistant.launcher.dialogTitle": "AI assistants",
|
|
@@ -342,6 +356,18 @@
|
|
|
342
356
|
"ai_assistant.modelPicker.triggerAriaLabel": "Selector de modelo de IA",
|
|
343
357
|
"ai_assistant.modelPicker.useDefault": "Usar predeterminado",
|
|
344
358
|
"ai_assistant.modelPicker.useDefaultWithModel": "Usar predeterminado del agente: {{model}}",
|
|
359
|
+
"ai_assistant.moderationFlags.columns.agent": "Agente",
|
|
360
|
+
"ai_assistant.moderationFlags.columns.categories": "Categorías",
|
|
361
|
+
"ai_assistant.moderationFlags.columns.createdAt": "Marcado el",
|
|
362
|
+
"ai_assistant.moderationFlags.columns.user": "Usuario",
|
|
363
|
+
"ai_assistant.moderationFlags.empty": "No hay mensajes marcados.",
|
|
364
|
+
"ai_assistant.moderationFlags.filters.agentPlaceholder": "modulo.agente",
|
|
365
|
+
"ai_assistant.moderationFlags.filters.apply": "Aplicar",
|
|
366
|
+
"ai_assistant.moderationFlags.filters.from": "Desde",
|
|
367
|
+
"ai_assistant.moderationFlags.filters.to": "Hasta",
|
|
368
|
+
"ai_assistant.moderationFlags.navTitle": "Marcas de moderación",
|
|
369
|
+
"ai_assistant.moderationFlags.subtitle": "Entradas bloqueadas por el filtro de seguridad de contenido. Solo indicadores de categoría y puntuaciones — no se almacena el contenido del prompt.",
|
|
370
|
+
"ai_assistant.moderationFlags.title": "Marcas de moderación",
|
|
345
371
|
"ai_assistant.notifications.conversation_shared.body": "Se ha compartido una conversación de IA con usted.",
|
|
346
372
|
"ai_assistant.notifications.conversation_shared.title": "Conversación compartida con usted",
|
|
347
373
|
"ai_assistant.notifications.conversation_shared.view_button": "Ver conversación",
|
|
@@ -62,6 +62,18 @@
|
|
|
62
62
|
"ai_assistant.agents.model_override.subtitle": "Nadpisz domyślnego dostawcę i model dla tego najemcy i agenta.",
|
|
63
63
|
"ai_assistant.agents.model_override.tenantOverride": "Nadpisanie tenant",
|
|
64
64
|
"ai_assistant.agents.model_override.title": "Dostawca i model",
|
|
65
|
+
"ai_assistant.agents.moderation.controlLabel": "Moderacja",
|
|
66
|
+
"ai_assistant.agents.moderation.effectiveEnforced": "Wymuszona",
|
|
67
|
+
"ai_assistant.agents.moderation.effectiveOff": "Wyłączona",
|
|
68
|
+
"ai_assistant.agents.moderation.effectiveOn": "Włączona",
|
|
69
|
+
"ai_assistant.agents.moderation.enforcedBadge": "Wymuszona",
|
|
70
|
+
"ai_assistant.agents.moderation.enforcedHint": "Ten agent przyjmuje niezaufane dane wejściowe, więc moderacja jest zawsze włączona i nie można jej tutaj wyłączyć.",
|
|
71
|
+
"ai_assistant.agents.moderation.inheritLabel": "Dziedzicz",
|
|
72
|
+
"ai_assistant.agents.moderation.offLabel": "Wyłączona",
|
|
73
|
+
"ai_assistant.agents.moderation.onLabel": "Włączona",
|
|
74
|
+
"ai_assistant.agents.moderation.saved": "Zapisano ustawienie moderacji danych wejściowych.",
|
|
75
|
+
"ai_assistant.agents.moderation.subtitle": "Filtruj dane wejściowe użytkownika pod kątem niebezpiecznych treści przed uruchomieniem modelu. Działa tylko, gdy wybrany dostawca obsługuje moderację.",
|
|
76
|
+
"ai_assistant.agents.moderation.title": "Moderacja danych wejściowych",
|
|
65
77
|
"ai_assistant.agents.mutation_policy.clear": "Wyczyść nadpisanie",
|
|
66
78
|
"ai_assistant.agents.mutation_policy.clearedMessage": "Nadpisanie polityki mutacji usunięte; agent korzysta z polityki zadeklarowanej w kodzie.",
|
|
67
79
|
"ai_assistant.agents.mutation_policy.codeDeclared": "Zadeklarowane w kodzie",
|
|
@@ -297,6 +309,8 @@
|
|
|
297
309
|
"ai_assistant.dock.left": "Dokuj po lewej",
|
|
298
310
|
"ai_assistant.dock.minimize": "Minimalizuj",
|
|
299
311
|
"ai_assistant.dock.right": "Dokuj po prawej",
|
|
312
|
+
"ai_assistant.errors.moderationBlocked": "Twoja wiadomość została zablokowana przez filtr bezpieczeństwa treści. Przeformułuj ją i spróbuj ponownie.",
|
|
313
|
+
"ai_assistant.errors.moderationUnavailable": "Sprawdzanie bezpieczeństwa treści jest chwilowo niedostępne. Spróbuj ponownie za chwilę.",
|
|
300
314
|
"ai_assistant.launcher.composerPlaceholder": "Zapytaj o cokolwiek…",
|
|
301
315
|
"ai_assistant.launcher.dialogDescription": "Wybierz asystenta. Nawiguj klawiszami ↑/↓, Enter uruchamia, Esc zamyka.",
|
|
302
316
|
"ai_assistant.launcher.dialogTitle": "Asystenci AI",
|
|
@@ -342,6 +356,18 @@
|
|
|
342
356
|
"ai_assistant.modelPicker.triggerAriaLabel": "Wybierz model AI",
|
|
343
357
|
"ai_assistant.modelPicker.useDefault": "Użyj domyślnego agenta",
|
|
344
358
|
"ai_assistant.modelPicker.useDefaultWithModel": "Użyj domyślnego agenta: {{model}}",
|
|
359
|
+
"ai_assistant.moderationFlags.columns.agent": "Agent",
|
|
360
|
+
"ai_assistant.moderationFlags.columns.categories": "Kategorie",
|
|
361
|
+
"ai_assistant.moderationFlags.columns.createdAt": "Oznaczono",
|
|
362
|
+
"ai_assistant.moderationFlags.columns.user": "Użytkownik",
|
|
363
|
+
"ai_assistant.moderationFlags.empty": "Brak oznaczonych wiadomości.",
|
|
364
|
+
"ai_assistant.moderationFlags.filters.agentPlaceholder": "moduł.agent",
|
|
365
|
+
"ai_assistant.moderationFlags.filters.apply": "Zastosuj",
|
|
366
|
+
"ai_assistant.moderationFlags.filters.from": "Od",
|
|
367
|
+
"ai_assistant.moderationFlags.filters.to": "Do",
|
|
368
|
+
"ai_assistant.moderationFlags.navTitle": "Flagi moderacji",
|
|
369
|
+
"ai_assistant.moderationFlags.subtitle": "Dane wejściowe zablokowane przez filtr bezpieczeństwa treści. Tylko flagi kategorii i wyniki — treść promptu nie jest przechowywana.",
|
|
370
|
+
"ai_assistant.moderationFlags.title": "Flagi moderacji",
|
|
345
371
|
"ai_assistant.notifications.conversation_shared.body": "Udostępniono Ci konwersację z asystentem AI.",
|
|
346
372
|
"ai_assistant.notifications.conversation_shared.title": "Udostępniono Ci konwersację",
|
|
347
373
|
"ai_assistant.notifications.conversation_shared.view_button": "Otwórz konwersację",
|