"use client" /** * The tenant's standing decision about unattended action. * * `resolveTenantAutoApprovalPolicy` has always read this record, and until now * nothing wrote it: every tenant ran the conservative default (`enabled`, * ceiling `medium`) with no way to see that, let alone change it. An operator * whose high-risk proposal was held had a queue that had simply stopped. * * The screen deliberately shows the OTHER gates too. Auto-approval is a policy, * not a threshold — a proposal also needs its guardrails passed, a readable * trace and a risk within the ceiling — and an admin who reads this page as * "confidence is the rule" is the person who later files the bug that it did not * fire. * * optimistic-lock-exempt — the target is a single ModuleConfigService value, not * a versioned entity: there is no `updatedAt` to send, and the surface is a * single-admin settings screen rather than a collaborative-edit record. */ import * as React from 'react' import { Button } from '@open-mercato/ui/primitives/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card' import { Label } from '@open-mercato/ui/primitives/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select' import { Spinner } from '@open-mercato/ui/primitives/spinner' import { StatusBadge } from '@open-mercato/ui/primitives/status-badge' import { Switch } from '@open-mercato/ui/primitives/switch' import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page' import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider' import { hasFeature } from '@open-mercato/shared/security/features' import { ErrorMessage } from '@open-mercato/ui/backend/detail' import { apiCall } from '@open-mercato/ui/backend/utils/apiCall' import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation' import { flash } from '@open-mercato/ui/backend/FlashMessages' import { useT } from '@open-mercato/shared/lib/i18n/context' const SETTINGS_URL = '/api/agent_orchestrator/auto-approval/settings' type ActionRisk = 'low' | 'medium' | 'high' type Policy = { enabled: boolean maxAutoApproveRisk: ActionRisk } type PolicyResponse = { policy: Policy source: 'tenant' | 'default' } const RISK_TIERS: readonly ActionRisk[] = ['low', 'medium', 'high'] function isPolicyResponse(value: unknown): value is PolicyResponse { if (typeof value !== 'object' || value === null || !('policy' in value)) return false const policy = (value as { policy: unknown }).policy return typeof policy === 'object' && policy !== null && 'enabled' in policy } function samePolicy(left: Policy, right: Policy): boolean { return left.enabled === right.enabled && left.maxAutoApproveRisk === right.maxAutoApproveRisk } /** * The gates a proposal clears BEFORE its confidence is ever compared, in the * order `evaluateAutoApproval` answers them. * * Rendered as plain copy rather than controls: none of them is tunable here, and * the reason an admin lands on this page is usually a proposal that was held by * one of them. */ function GateList() { const t = useT() const gates = [ t( 'agent_orchestrator.settings.autoApproval.gate.node', 'The workflow step does not say "always ask".', ), t( 'agent_orchestrator.settings.autoApproval.gate.guardrail', 'Every guardrail check on the run passed.', ), t( 'agent_orchestrator.settings.autoApproval.gate.trace', 'The run left a trace an auditor can read.', ), t( 'agent_orchestrator.settings.autoApproval.gate.risk', 'The proposed action is within the risk ceiling set above.', ), t( 'agent_orchestrator.settings.autoApproval.gate.confidence', 'Only then: the confidence clears the step’s threshold and its margin over the runner-up.', ), ] return (
{t( 'agent_orchestrator.settings.autoApproval.enabledHint', 'Off sends every proposal to the caseload, however confident the agent is.', )}
{t( 'agent_orchestrator.settings.autoApproval.maxRiskHint', 'An action whose risk is not declared counts as medium. Anything above this goes to a person.', )}
{t( 'agent_orchestrator.settings.autoApproval.readOnly', 'You can see this policy but not change it.', )}
)}