@open-mercato/ai-assistant 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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.
Files changed (120) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +12 -29
  3. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js +80 -0
  4. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js.map +7 -0
  5. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js +124 -0
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js.map +7 -0
  7. package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js +30 -0
  8. package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js.map +2 -2
  9. package/dist/modules/ai_assistant/api/ai/chat/route.js +7 -0
  10. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
  11. package/dist/modules/ai_assistant/api/moderation-flags/route.js +121 -0
  12. package/dist/modules/ai_assistant/api/moderation-flags/route.js.map +7 -0
  13. package/dist/modules/ai_assistant/api/settings/allowlist/route.js +10 -10
  14. package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
  15. package/dist/modules/ai_assistant/api/settings/route.js +34 -13
  16. package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
  17. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +121 -15
  18. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
  19. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -2
  20. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
  21. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +139 -0
  22. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +7 -0
  23. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js +10 -0
  24. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js.map +7 -0
  25. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js +26 -0
  26. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js.map +7 -0
  27. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +4 -4
  28. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
  29. package/dist/modules/ai_assistant/data/entities.js +42 -0
  30. package/dist/modules/ai_assistant/data/entities.js.map +2 -2
  31. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +3 -1
  32. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
  33. package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js +44 -0
  34. package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js.map +7 -0
  35. package/dist/modules/ai_assistant/di.js +6 -2
  36. package/dist/modules/ai_assistant/di.js.map +2 -2
  37. package/dist/modules/ai_assistant/events.js +6 -0
  38. package/dist/modules/ai_assistant/events.js.map +2 -2
  39. package/dist/modules/ai_assistant/i18n/de.json +42 -0
  40. package/dist/modules/ai_assistant/i18n/en.json +42 -0
  41. package/dist/modules/ai_assistant/i18n/es.json +42 -0
  42. package/dist/modules/ai_assistant/i18n/ko.json +527 -0
  43. package/dist/modules/ai_assistant/i18n/pl.json +42 -0
  44. package/dist/modules/ai_assistant/lib/agent-runtime.js +158 -5
  45. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
  46. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
  47. package/dist/modules/ai_assistant/lib/auth.js +6 -7
  48. package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
  49. package/dist/modules/ai_assistant/lib/entity-graph.js +3 -3
  50. package/dist/modules/ai_assistant/lib/entity-graph.js.map +2 -2
  51. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +3 -0
  52. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
  53. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js +8 -1
  54. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js.map +2 -2
  55. package/dist/modules/ai_assistant/lib/model-factory.js +6 -1
  56. package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
  57. package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js +43 -0
  58. package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js.map +7 -0
  59. package/dist/modules/ai_assistant/lib/moderation-policy.js +24 -0
  60. package/dist/modules/ai_assistant/lib/moderation-policy.js.map +7 -0
  61. package/dist/modules/ai_assistant/lib/moderation.js +109 -0
  62. package/dist/modules/ai_assistant/lib/moderation.js.map +7 -0
  63. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +2 -0
  64. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
  65. package/dist/modules/ai_assistant/lib/tool-search.js +4 -12
  66. package/dist/modules/ai_assistant/lib/tool-search.js.map +2 -2
  67. package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js +19 -0
  68. package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js.map +7 -0
  69. package/generated/entities/ai_agent_runtime_override/index.ts +1 -0
  70. package/generated/entities/ai_moderation_flag/index.ts +9 -0
  71. package/generated/entities.ids.generated.ts +2 -1
  72. package/generated/entity-fields-registry.ts +13 -1
  73. package/package.json +13 -12
  74. package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.ts +126 -0
  75. package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.ts +161 -0
  76. package/src/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.ts +48 -0
  77. package/src/modules/ai_assistant/api/ai/chat/route.ts +10 -0
  78. package/src/modules/ai_assistant/api/moderation-flags/__tests__/route.test.ts +122 -0
  79. package/src/modules/ai_assistant/api/moderation-flags/route.ts +137 -0
  80. package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +58 -8
  81. package/src/modules/ai_assistant/api/settings/allowlist/route.ts +10 -12
  82. package/src/modules/ai_assistant/api/settings/route.ts +33 -10
  83. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +161 -15
  84. package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +2 -2
  85. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +186 -0
  86. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.ts +24 -0
  87. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.tsx +12 -0
  88. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +4 -4
  89. package/src/modules/ai_assistant/data/entities.ts +56 -0
  90. package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +4 -0
  91. package/src/modules/ai_assistant/data/repositories/AiModerationFlagRepository.ts +79 -0
  92. package/src/modules/ai_assistant/di.ts +5 -1
  93. package/src/modules/ai_assistant/events.ts +21 -0
  94. package/src/modules/ai_assistant/i18n/de.json +42 -0
  95. package/src/modules/ai_assistant/i18n/en.json +42 -0
  96. package/src/modules/ai_assistant/i18n/es.json +42 -0
  97. package/src/modules/ai_assistant/i18n/ko.json +527 -0
  98. package/src/modules/ai_assistant/i18n/pl.json +42 -0
  99. package/src/modules/ai_assistant/lib/__tests__/auth.test.ts +2 -5
  100. package/src/modules/ai_assistant/lib/__tests__/input-moderation-gate.test.ts +121 -0
  101. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +13 -0
  102. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +41 -0
  103. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +60 -0
  104. package/src/modules/ai_assistant/lib/__tests__/moderation-flag-recorder.test.ts +76 -0
  105. package/src/modules/ai_assistant/lib/__tests__/moderation-policy.test.ts +86 -0
  106. package/src/modules/ai_assistant/lib/__tests__/moderation.test.ts +114 -0
  107. package/src/modules/ai_assistant/lib/agent-runtime.ts +259 -0
  108. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +10 -0
  109. package/src/modules/ai_assistant/lib/auth.ts +6 -15
  110. package/src/modules/ai_assistant/lib/entity-graph.ts +3 -3
  111. package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +10 -0
  112. package/src/modules/ai_assistant/lib/llm-adapters/openai.ts +32 -1
  113. package/src/modules/ai_assistant/lib/model-factory.ts +33 -0
  114. package/src/modules/ai_assistant/lib/moderation-flag-recorder.ts +75 -0
  115. package/src/modules/ai_assistant/lib/moderation-policy.ts +77 -0
  116. package/src/modules/ai_assistant/lib/moderation.ts +179 -0
  117. package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +2 -0
  118. package/src/modules/ai_assistant/lib/tool-search.ts +4 -17
  119. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +388 -16
  120. 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": "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",
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": "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",
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;AAExB,SAAS,oBAAoB;AAEtB,SAAS,SAAS,WAAkC;AACzD,YAAU,SAAS;AAAA,IACjB,iBAAiB,QAAQ,YAAY;AAAA,EACvC,CAAC;AACH;",
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;AA+EjD,IAAO,iBAAQ;",
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
  }
@@ -36,21 +36,44 @@
36
36
  "ai_assistant.agents.meta.readOnlyYes": "Ja",
37
37
  "ai_assistant.agents.meta.unlimited": "Unbegrenzt",
38
38
  "ai_assistant.agents.model_override.allowlistCustom": "benutzerdefiniert",
39
+ "ai_assistant.agents.model_override.allowlistEmpty": "Keine konfigurierten Anbieter für Chat-Überschreibungen verfügbar.",
40
+ "ai_assistant.agents.model_override.allowlistHelp": "Lege fest, welche Anbieter- und Modell-Überschreibungen Nutzer in der Chat-Fußzeile für diesen Agenten wählen können.",
39
41
  "ai_assistant.agents.model_override.allowlistInherited": "geerbt",
40
42
  "ai_assistant.agents.model_override.allowlistReset": "Erben",
41
43
  "ai_assistant.agents.model_override.allowlistSave": "Auswahl speichern",
44
+ "ai_assistant.agents.model_override.allowlistSaved": "Auswahlmöglichkeiten für Chat-Überschreibungen gespeichert.",
45
+ "ai_assistant.agents.model_override.allowlistTitle": "Auswahlmöglichkeiten für Chat-Überschreibungen",
42
46
  "ai_assistant.agents.model_override.anyProvider": "erste konfigurierte",
43
47
  "ai_assistant.agents.model_override.clear": "Überschreibung löschen",
48
+ "ai_assistant.agents.model_override.cleared": "Modell-Überschreibung entfernt; der Agent nutzt wieder die normale Auflösungskette.",
44
49
  "ai_assistant.agents.model_override.codeDefault": "Im Code deklarierter Standard",
45
50
  "ai_assistant.agents.model_override.defaultBadge": "Standard",
51
+ "ai_assistant.agents.model_override.envAlsoNarrows": "schränkt diese Liste zusätzlich über die Umgebungsvariablen ein.",
52
+ "ai_assistant.agents.model_override.loadErrorTitle": "Anbieterkatalog konnte nicht geladen werden",
53
+ "ai_assistant.agents.model_override.loading": "Anbieterkatalog wird geladen...",
46
54
  "ai_assistant.agents.model_override.model": "Modell",
47
55
  "ai_assistant.agents.model_override.noOverride": "Keine agentenspezifische Überschreibung",
48
56
  "ai_assistant.agents.model_override.provider": "Anbieter",
49
57
  "ai_assistant.agents.model_override.providerDefault": "Anbieter-Standard",
50
58
  "ai_assistant.agents.model_override.save": "Überschreibung speichern",
51
59
  "ai_assistant.agents.model_override.saved": "Modell-Überschreibung gespeichert.",
60
+ "ai_assistant.agents.model_override.selectModel": "Modell auswählen",
61
+ "ai_assistant.agents.model_override.selectProvider": "Anbieter auswählen",
62
+ "ai_assistant.agents.model_override.subtitle": "Überschreibe Standardanbieter und -modell für diesen Mandanten und Agenten.",
52
63
  "ai_assistant.agents.model_override.tenantOverride": "Mandanten-Überschreibung",
53
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",
54
77
  "ai_assistant.agents.mutation_policy.clear": "Überschreibung löschen",
55
78
  "ai_assistant.agents.mutation_policy.clearedMessage": "Überschreibung der Mutationsrichtlinie gelöscht; Agent verwendet die im Code deklarierte Richtlinie.",
56
79
  "ai_assistant.agents.mutation_policy.codeDeclared": "Im Code deklariert",
@@ -275,7 +298,9 @@
275
298
  "ai_assistant.chat.toolRunning": "running…",
276
299
  "ai_assistant.chat.transcriptLabel": "Chat-Transkript",
277
300
  "ai_assistant.chat.uiPartPending": "Ausstehendes UI-Element:",
301
+ "ai_assistant.chat.uploadingHint": "Anhänge werden hochgeladen… Senden ist deaktiviert, bis der Vorgang abgeschlossen ist.",
278
302
  "ai_assistant.chat.userRoleLabel": "Sie",
303
+ "ai_assistant.chat.welcomeDescription": "Frag mich alles zu deinen Daten. Das kann ich zum Beispiel tun:",
279
304
  "ai_assistant.chat.welcomeTitle": "How can I help?",
280
305
  "ai_assistant.chat.zoomImage": "Vorschau von {name} öffnen",
281
306
  "ai_assistant.dock.bottom": "Unten andocken",
@@ -284,13 +309,17 @@
284
309
  "ai_assistant.dock.left": "Links andocken",
285
310
  "ai_assistant.dock.minimize": "Minimieren",
286
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.",
287
314
  "ai_assistant.launcher.composerPlaceholder": "Fragen Sie mich etwas…",
315
+ "ai_assistant.launcher.dialogDescription": "Wähle einen Assistenten. Mit ↑/↓ navigieren, Enter startet, Esc schließt.",
288
316
  "ai_assistant.launcher.dialogTitle": "AI assistants",
289
317
  "ai_assistant.launcher.dock.subtitle": "AI assistant",
290
318
  "ai_assistant.launcher.empty": "No assistants match your search.",
291
319
  "ai_assistant.launcher.hint.close": "Close",
292
320
  "ai_assistant.launcher.hint.launch": "Launch",
293
321
  "ai_assistant.launcher.hint.navigate": "Navigate",
322
+ "ai_assistant.launcher.none": "Für dein Konto sind keine Assistenten verfügbar.",
294
323
  "ai_assistant.launcher.searchPlaceholder": "Search assistants...",
295
324
  "ai_assistant.launcher.setup.body": "KI-Assistenten sind installiert, aber es ist kein Anbieterschlüssel konfiguriert. Lege OM_AI_PROVIDER und einen passenden API-Schlüssel in deiner .env-Datei fest und starte die App neu.",
296
325
  "ai_assistant.launcher.setup.docs": "Dokumentation zum KI-Assistenten",
@@ -299,6 +328,7 @@
299
328
  "ai_assistant.launcher.sheet.dock": "Dock to side",
300
329
  "ai_assistant.launcher.triggerAriaLabel": "Open AI assistant",
301
330
  "ai_assistant.launcher.triggerLabel": "AI",
331
+ "ai_assistant.launcher.welcome.fallback": "Wie kann ich helfen?",
302
332
  "ai_assistant.launcher.welcome.suggestion1": "What can you help me with?",
303
333
  "ai_assistant.launcher.welcome.suggestion2": "Show what data you can access",
304
334
  "ai_assistant.launcher.welcome.suggestion3": "Suggest things to try",
@@ -326,6 +356,18 @@
326
356
  "ai_assistant.modelPicker.triggerAriaLabel": "KI-Modell auswählen",
327
357
  "ai_assistant.modelPicker.useDefault": "Standard des Agenten verwenden",
328
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",
329
371
  "ai_assistant.notifications.conversation_shared.body": "Eine KI-Konversation wurde mit Ihnen geteilt.",
330
372
  "ai_assistant.notifications.conversation_shared.title": "Konversation mit Ihnen geteilt",
331
373
  "ai_assistant.notifications.conversation_shared.view_button": "Konversation öffnen",
@@ -36,21 +36,44 @@
36
36
  "ai_assistant.agents.meta.readOnlyYes": "Yes",
37
37
  "ai_assistant.agents.meta.unlimited": "Unlimited",
38
38
  "ai_assistant.agents.model_override.allowlistCustom": "custom",
39
+ "ai_assistant.agents.model_override.allowlistEmpty": "No configured providers are available for chat overrides.",
40
+ "ai_assistant.agents.model_override.allowlistHelp": "Limit which provider/model overrides users can pick in the chat footer for this agent.",
39
41
  "ai_assistant.agents.model_override.allowlistInherited": "inherited",
40
42
  "ai_assistant.agents.model_override.allowlistReset": "Inherit",
41
43
  "ai_assistant.agents.model_override.allowlistSave": "Save choices",
44
+ "ai_assistant.agents.model_override.allowlistSaved": "Chat override choices saved.",
45
+ "ai_assistant.agents.model_override.allowlistTitle": "Chat override choices",
42
46
  "ai_assistant.agents.model_override.anyProvider": "first configured",
43
47
  "ai_assistant.agents.model_override.clear": "Clear override",
48
+ "ai_assistant.agents.model_override.cleared": "Model override cleared; the agent is using the normal resolution chain.",
44
49
  "ai_assistant.agents.model_override.codeDefault": "Code-declared default",
45
50
  "ai_assistant.agents.model_override.defaultBadge": "default",
51
+ "ai_assistant.agents.model_override.envAlsoNarrows": "also narrows this list from env.",
52
+ "ai_assistant.agents.model_override.loadErrorTitle": "Failed to load provider catalog",
53
+ "ai_assistant.agents.model_override.loading": "Loading provider catalog...",
46
54
  "ai_assistant.agents.model_override.model": "Model",
47
55
  "ai_assistant.agents.model_override.noOverride": "No per-agent override",
48
56
  "ai_assistant.agents.model_override.provider": "Provider",
49
57
  "ai_assistant.agents.model_override.providerDefault": "provider default",
50
58
  "ai_assistant.agents.model_override.save": "Save override",
51
59
  "ai_assistant.agents.model_override.saved": "Model override saved.",
60
+ "ai_assistant.agents.model_override.selectModel": "Select model",
61
+ "ai_assistant.agents.model_override.selectProvider": "Select provider",
62
+ "ai_assistant.agents.model_override.subtitle": "Override the default provider and model for this tenant and agent.",
52
63
  "ai_assistant.agents.model_override.tenantOverride": "Tenant override",
53
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",
54
77
  "ai_assistant.agents.mutation_policy.clear": "Clear override",
55
78
  "ai_assistant.agents.mutation_policy.clearedMessage": "Mutation policy override cleared; agent is using its code-declared policy.",
56
79
  "ai_assistant.agents.mutation_policy.codeDeclared": "Code-declared",
@@ -275,7 +298,9 @@
275
298
  "ai_assistant.chat.toolRunning": "running…",
276
299
  "ai_assistant.chat.transcriptLabel": "Chat transcript",
277
300
  "ai_assistant.chat.uiPartPending": "Pending UI part:",
301
+ "ai_assistant.chat.uploadingHint": "Uploading attachments… Send is disabled until they finish.",
278
302
  "ai_assistant.chat.userRoleLabel": "You",
303
+ "ai_assistant.chat.welcomeDescription": "Ask me anything about your data. Here are some things I can do:",
279
304
  "ai_assistant.chat.welcomeTitle": "How can I help?",
280
305
  "ai_assistant.chat.zoomImage": "Open {name} preview",
281
306
  "ai_assistant.dock.bottom": "Dock Bottom",
@@ -284,13 +309,17 @@
284
309
  "ai_assistant.dock.left": "Dock Left",
285
310
  "ai_assistant.dock.minimize": "Minimize",
286
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.",
287
314
  "ai_assistant.launcher.composerPlaceholder": "Ask anything…",
315
+ "ai_assistant.launcher.dialogDescription": "Pick an assistant. Use ↑/↓ to navigate, Enter to launch, Esc to close.",
288
316
  "ai_assistant.launcher.dialogTitle": "AI assistants",
289
317
  "ai_assistant.launcher.dock.subtitle": "AI assistant",
290
318
  "ai_assistant.launcher.empty": "No assistants match your search.",
291
319
  "ai_assistant.launcher.hint.close": "Close",
292
320
  "ai_assistant.launcher.hint.launch": "Launch",
293
321
  "ai_assistant.launcher.hint.navigate": "Navigate",
322
+ "ai_assistant.launcher.none": "No assistants are available for your account.",
294
323
  "ai_assistant.launcher.searchPlaceholder": "Search assistants...",
295
324
  "ai_assistant.launcher.setup.body": "AI assistants are installed, but no provider key is configured. Set OM_AI_PROVIDER and one matching API key in your .env file, then restart the app.",
296
325
  "ai_assistant.launcher.setup.docs": "AI assistant docs",
@@ -299,6 +328,7 @@
299
328
  "ai_assistant.launcher.sheet.dock": "Dock to side",
300
329
  "ai_assistant.launcher.triggerAriaLabel": "Open AI assistant",
301
330
  "ai_assistant.launcher.triggerLabel": "AI",
331
+ "ai_assistant.launcher.welcome.fallback": "How can I help?",
302
332
  "ai_assistant.launcher.welcome.suggestion1": "What can you help me with?",
303
333
  "ai_assistant.launcher.welcome.suggestion2": "Show what data you can access",
304
334
  "ai_assistant.launcher.welcome.suggestion3": "Suggest things to try",
@@ -326,6 +356,18 @@
326
356
  "ai_assistant.modelPicker.triggerAriaLabel": "Select AI model",
327
357
  "ai_assistant.modelPicker.useDefault": "Use agent default",
328
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",
329
371
  "ai_assistant.notifications.conversation_shared.body": "An AI conversation has been shared with you.",
330
372
  "ai_assistant.notifications.conversation_shared.title": "Conversation shared with you",
331
373
  "ai_assistant.notifications.conversation_shared.view_button": "View Conversation",
@@ -36,21 +36,44 @@
36
36
  "ai_assistant.agents.meta.readOnlyYes": "Sí",
37
37
  "ai_assistant.agents.meta.unlimited": "Sin límite",
38
38
  "ai_assistant.agents.model_override.allowlistCustom": "personalizado",
39
+ "ai_assistant.agents.model_override.allowlistEmpty": "No hay proveedores configurados disponibles para anulaciones del chat.",
40
+ "ai_assistant.agents.model_override.allowlistHelp": "Limita qué anulaciones de proveedor y modelo pueden elegir los usuarios en el pie del chat para este agente.",
39
41
  "ai_assistant.agents.model_override.allowlistInherited": "heredado",
40
42
  "ai_assistant.agents.model_override.allowlistReset": "Heredar",
41
43
  "ai_assistant.agents.model_override.allowlistSave": "Guardar opciones",
44
+ "ai_assistant.agents.model_override.allowlistSaved": "Opciones de anulación del chat guardadas.",
45
+ "ai_assistant.agents.model_override.allowlistTitle": "Opciones de anulación del chat",
42
46
  "ai_assistant.agents.model_override.anyProvider": "primer configurado",
43
47
  "ai_assistant.agents.model_override.clear": "Eliminar sobrescritura",
48
+ "ai_assistant.agents.model_override.cleared": "Se ha borrado la anulación del modelo; el agente usa la cadena de resolución normal.",
44
49
  "ai_assistant.agents.model_override.codeDefault": "Predeterminado declarado en código",
45
50
  "ai_assistant.agents.model_override.defaultBadge": "predeterminado",
51
+ "ai_assistant.agents.model_override.envAlsoNarrows": "también restringe esta lista desde el entorno.",
52
+ "ai_assistant.agents.model_override.loadErrorTitle": "No se pudo cargar el catálogo de proveedores",
53
+ "ai_assistant.agents.model_override.loading": "Cargando el catálogo de proveedores...",
46
54
  "ai_assistant.agents.model_override.model": "Modelo",
47
55
  "ai_assistant.agents.model_override.noOverride": "Sin sobrescritura por agente",
48
56
  "ai_assistant.agents.model_override.provider": "Proveedor",
49
57
  "ai_assistant.agents.model_override.providerDefault": "predeterminado del proveedor",
50
58
  "ai_assistant.agents.model_override.save": "Guardar sobrescritura",
51
59
  "ai_assistant.agents.model_override.saved": "Sobrescritura de modelo guardada.",
60
+ "ai_assistant.agents.model_override.selectModel": "Selecciona un modelo",
61
+ "ai_assistant.agents.model_override.selectProvider": "Selecciona un proveedor",
62
+ "ai_assistant.agents.model_override.subtitle": "Anula el proveedor y el modelo predeterminados para este inquilino y agente.",
52
63
  "ai_assistant.agents.model_override.tenantOverride": "Sobrescritura del tenant",
53
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",
54
77
  "ai_assistant.agents.mutation_policy.clear": "Eliminar sobrescritura",
55
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.",
56
79
  "ai_assistant.agents.mutation_policy.codeDeclared": "Declarada en el código",
@@ -275,7 +298,9 @@
275
298
  "ai_assistant.chat.toolRunning": "running…",
276
299
  "ai_assistant.chat.transcriptLabel": "Transcripción del chat",
277
300
  "ai_assistant.chat.uiPartPending": "Parte de UI pendiente:",
301
+ "ai_assistant.chat.uploadingHint": "Subiendo archivos adjuntos… El envío está desactivado hasta que terminen.",
278
302
  "ai_assistant.chat.userRoleLabel": "Usted",
303
+ "ai_assistant.chat.welcomeDescription": "Pregúntame lo que quieras sobre tus datos. Esto es algo de lo que puedo hacer:",
279
304
  "ai_assistant.chat.welcomeTitle": "How can I help?",
280
305
  "ai_assistant.chat.zoomImage": "Abrir vista previa de {name}",
281
306
  "ai_assistant.dock.bottom": "Anclar abajo",
@@ -284,13 +309,17 @@
284
309
  "ai_assistant.dock.left": "Anclar a la izquierda",
285
310
  "ai_assistant.dock.minimize": "Minimizar",
286
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.",
287
314
  "ai_assistant.launcher.composerPlaceholder": "Pregunte lo que necesite…",
315
+ "ai_assistant.launcher.dialogDescription": "Elige un asistente. Usa ↑/↓ para navegar, Enter para iniciar, Esc para cerrar.",
288
316
  "ai_assistant.launcher.dialogTitle": "AI assistants",
289
317
  "ai_assistant.launcher.dock.subtitle": "AI assistant",
290
318
  "ai_assistant.launcher.empty": "No assistants match your search.",
291
319
  "ai_assistant.launcher.hint.close": "Close",
292
320
  "ai_assistant.launcher.hint.launch": "Launch",
293
321
  "ai_assistant.launcher.hint.navigate": "Navigate",
322
+ "ai_assistant.launcher.none": "No hay asistentes disponibles para tu cuenta.",
294
323
  "ai_assistant.launcher.searchPlaceholder": "Search assistants...",
295
324
  "ai_assistant.launcher.setup.body": "Los asistentes de IA están instalados, pero no hay una clave de proveedor configurada. Define OM_AI_PROVIDER y una clave de API compatible en tu archivo .env y reinicia la app.",
296
325
  "ai_assistant.launcher.setup.docs": "Documentación del asistente de IA",
@@ -299,6 +328,7 @@
299
328
  "ai_assistant.launcher.sheet.dock": "Dock to side",
300
329
  "ai_assistant.launcher.triggerAriaLabel": "Open AI assistant",
301
330
  "ai_assistant.launcher.triggerLabel": "AI",
331
+ "ai_assistant.launcher.welcome.fallback": "¿En qué puedo ayudarte?",
302
332
  "ai_assistant.launcher.welcome.suggestion1": "What can you help me with?",
303
333
  "ai_assistant.launcher.welcome.suggestion2": "Show what data you can access",
304
334
  "ai_assistant.launcher.welcome.suggestion3": "Suggest things to try",
@@ -326,6 +356,18 @@
326
356
  "ai_assistant.modelPicker.triggerAriaLabel": "Selector de modelo de IA",
327
357
  "ai_assistant.modelPicker.useDefault": "Usar predeterminado",
328
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",
329
371
  "ai_assistant.notifications.conversation_shared.body": "Se ha compartido una conversación de IA con usted.",
330
372
  "ai_assistant.notifications.conversation_shared.title": "Conversación compartida con usted",
331
373
  "ai_assistant.notifications.conversation_shared.view_button": "Ver conversación",