import { useEffect, useMemo, useState } from "react";
import type { AgentPermissions, TrustPreset } from "@paperclipai/shared";
import { Lock, ShieldAlert } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Field, CollapsibleSection } from "./agent-config-primitives";
import {
buildPermissionsForTrustPreset,
clearSingleLowTrustBoundaryTarget,
getLowTrustBoundary,
getSingleLowTrustBoundaryTarget,
getTrustPreset,
isCeLowTrustBoundaryEditable,
lowTrustBoundaryHasScope,
setSingleLowTrustBoundaryTarget,
summarizeLowTrustBoundaryTarget,
TRUST_PRESET_DESCRIPTIONS,
TRUST_PRESET_LABELS,
type LowTrustBoundaryTarget,
} from "../lib/trust-policy-ui";
import { cn } from "../lib/utils";
const inputClass =
"w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40";
function formatCount(value: readonly unknown[] | undefined, singular: string, plural: string) {
const count = value?.length ?? 0;
if (count === 0) return "-";
return `${count} ${count === 1 ? singular : plural}`;
}
function PolicyRow({ label, value }: { label: string; value: string }) {
return (
{label}
{value}
);
}
export interface LowTrustBoundaryCandidate {
id: string;
label: string;
}
type LowTrustBoundaryTargetType = LowTrustBoundaryTarget["type"];
const BOUNDARY_TARGET_LABELS: Record = {
project: "Project",
root_issue: "Root issue",
issue: "Issue",
};
export function TrustPresetSection({
permissions,
onChange,
disabled,
companyId,
projectCandidates = [],
issueCandidates = [],
candidatesLoading,
}: {
permissions: Partial | null | undefined;
onChange: (permissions: Partial) => void;
disabled?: boolean;
companyId?: string | null;
projectCandidates?: LowTrustBoundaryCandidate[];
issueCandidates?: LowTrustBoundaryCandidate[];
candidatesLoading?: boolean;
}) {
const [policyOpen, setPolicyOpen] = useState(false);
const preset = getTrustPreset(permissions);
const boundary = getLowTrustBoundary(permissions);
const boundaryTarget = getSingleLowTrustBoundaryTarget(boundary);
const [targetType, setTargetType] = useState(boundaryTarget?.type ?? "project");
const lowTrust = preset === "low_trust_review";
const hasScope = lowTrustBoundaryHasScope(boundary);
const boundaryEditable = isCeLowTrustBoundaryEditable(boundary);
const policy = permissions?.authorizationPolicy ?? null;
const managedPermissions = useMemo(
() => buildPermissionsForTrustPreset(permissions, preset),
[permissions, preset],
);
useEffect(() => {
if (boundaryTarget) setTargetType(boundaryTarget.type);
}, [boundaryTarget?.type]);
function handlePresetChange(value: string) {
const nextPreset: TrustPreset = value === "low_trust_review" ? "low_trust_review" : "standard";
onChange(buildPermissionsForTrustPreset(permissions, nextPreset));
}
function handleBoundaryTargetChange(targetId: string) {
if (!companyId || !targetId) return;
onChange(setSingleLowTrustBoundaryTarget(permissions, companyId, { type: targetType, id: targetId }));
}
function handleClearBoundary() {
onChange(clearSingleLowTrustBoundaryTarget(permissions));
}
const targetCandidates = targetType === "project" ? projectCandidates : issueCandidates;
const boundaryValue = boundaryTarget?.type === targetType ? boundaryTarget.id : "";
return (
Trust
handlePresetChange(event.target.value)}
disabled={disabled}
>
{TRUST_PRESET_LABELS.standard}
{TRUST_PRESET_LABELS.low_trust_review}
{TRUST_PRESET_DESCRIPTIONS[preset]}
{lowTrust ? (
{hasScope ? (
) : (
)}
{hasScope ? "Containment active" : "Containment not configured"}
{hasScope
? "This agent can only read and mutate work inside its assigned review boundary. Raw output is quarantined from higher-trust agents until a trusted reviewer promotes it."
: "This agent is set to low-trust review, but no project, root issue, or issue scope is set in the core policy. Add a scope before this agent can run without denial."}
{boundaryEditable ? (
setTargetType(event.target.value as LowTrustBoundaryTargetType)}
disabled={disabled}
>
Project
Root issue
Issue
handleBoundaryTargetChange(event.target.value)}
disabled={disabled || !companyId || candidatesLoading || targetCandidates.length === 0}
>
{candidatesLoading
? "Loading…"
: targetCandidates.length === 0
? `No ${targetType === "project" ? "projects" : "issues"} available`
: "Select boundary"}
{targetCandidates.map((candidate) => (
{candidate.label}
))}
CE saves one containment boundary at a time. Saved policies include this company id.
{boundaryTarget ? (
Clear boundary
) : null}
) : (
Managed by EE/API
This policy has {summarizeLowTrustBoundaryTarget(boundary).toLowerCase()} and cannot be edited by the CE single-boundary editor.
)}
Want to set more than one containment boundary?{" "}
Get Paperclip EE.
setPolicyOpen((open) => !open)}
>
!["trustPreset", "reviewPreset", "trustBoundary"].includes(key))
? "Custom advanced policy fields preserved"
: "-"}
/>
) : null}
{managedPermissions.authorizationPolicy?.reviewPreset ? null : (
Advanced permissions remain editable through the EE permissions extension when installed.
)}
);
}