/**
* The one capability picker for forms with a Save button: workspace defaults,
* a running session's Agent panel and the schedule form. A starting point
* (choice cards, each with its consequence) and then every capability as a
* checkbox row in three groups; Skills is the one three-way setting. A
* capability this server doesn't offer stays visible, disabled, with the
* reason. Tool ids never appear here.
*/
import { LockIcon } from "lucide-react";
import { useId, type ReactNode } from "react";
import {
ACCESS_OPTIONS,
disableWrites,
enableWrites,
knowledgeAccess,
skillsAccess,
type AccessLevel,
type CapabilityLearning,
} from "./capability-learning";
import { ChoiceCard, ChoiceCards } from "../../../ui/choice-cards";
import { Checkbox } from "../../../ui/field";
import { SegmentedControl } from "../../../ui/segmented-control";
import { Select } from "../../../ui/select";
import {
AGENT_CAPABILITY_GROUPS,
AGENT_STARTING_POINTS,
SKILLS_OPTIONS,
UNAVAILABLE_CAPABILITY_REASON,
capabilityDescription,
capabilityLabel,
skillsFromOption,
skillsOptionValue,
withCapability,
withStartingPoint,
type AgentCapabilityDraft,
type AgentCapabilityId,
type CapabilityAvailability,
type ResolvedAgentCapabilities,
} from "../../lib/agent-capabilities";
import { LEARNING_MODE_LABEL } from "../../lib/agent-learning-vocabulary";
import { cn } from "../../lib/utils";
export function AgentCapabilityPicker({
draft,
onChange,
availability,
disabled = false,
disabledReason,
startingPointLabel = "Starting point",
startingPointDescription,
rowAside,
learning,
}: {
draft: AgentCapabilityDraft;
onChange: (draft: AgentCapabilityDraft) => void;
/**
* Agent learning for the same scope. With it, Skills and Knowledge are each
* one Off / Read / Read and write choice, and Read and write shows whether
* the agent's saves apply right away or wait for review.
*/
learning?: CapabilityLearning | undefined;
availability: CapabilityAvailability;
/** Read-only: shows the values, changes nothing. */
disabled?: boolean | undefined;
/** Who can change it, shown once on the starting point. */
disabledReason?: ReactNode | undefined;
startingPointLabel?: ReactNode | undefined;
startingPointDescription?: ReactNode | undefined;
/** Quiet extra line under one capability (for example, which apps it covers). */
rowAside?: ((id: AgentCapabilityId) => ReactNode) | undefined;
}) {
return (
{text}
onAccessChange(value as AccessLevel)}
disabled={locked}
options={ACCESS_OPTIONS}
className="shrink-0 max-sm:w-full"
fullWidth={false}
/>
{level === "write" ? (
{(id === "skills"
? ([["skills", "New and changed skills"]] as const)
: ([
["knowledge", "Saved knowledge"],
["instructions", "Edits to instructions"],
] as const)
).map(([category, label]) => (
learning.onChange({ ...learning.modes, [category]: mode })}
/>
))}
Automatic applies right away. Review first waits for your OK in Knowledge › Review.
);
}
function accessLevelOf(
id: AgentCapabilityId,
values: ResolvedAgentCapabilities,
modes: CapabilityLearning["modes"] | null | undefined,
): AccessLevel | null {
if (id === "skills") {
return modes
? skillsAccess(values.skills, modes)
: values.skills === "manage"
? "write"
: values.skills
? "read"
: "off";
}
if (id === "knowledge" && modes) return knowledgeAccess(values.knowledge === true, modes);
return null;
}
function accessLabel(
id: AgentCapabilityId,
values: ResolvedAgentCapabilities,
modes: CapabilityLearning["modes"] | null | undefined,
): string {
const level = accessLevelOf(id, values, modes);
if (level !== "write") return "Read only";
if (!modes) return "Read and write";
const mode =
id === "skills"
? modes.skills
: modes.knowledge !== "off"
? modes.knowledge
: modes.instructions;
return `Read and write · ${LEARNING_MODE_LABEL[mode]}`;
}
function accessDescription(
id: AgentCapabilityId,
values: ResolvedAgentCapabilities,
modes: CapabilityLearning["modes"] | null | undefined,
): string {
const level = accessLevelOf(id, values, modes);
if (id === "skills") {
return level === "write"
? "Uses installed Skills and can save, install, and publish them."
: "Uses installed Skills.";
}
if (id === "knowledge" && level) {
return level === "write"
? "Searches the Library and instructions, and can save to them."
: "Searches the Library and instructions. Saves nothing.";
}
return capabilityDescription(id);
}
function LearningModeRow({
label,
value,
allowOff,
disabled,
onChange,
}: {
label: string;
value: CapabilityLearning["modes"]["skills"];
allowOff: boolean;
disabled: boolean;
onChange: (mode: CapabilityLearning["modes"]["skills"]) => void;
}) {
const id = useId();
const modes = allowOff
? (["automatic", "review_first", "off"] as const)
: (["automatic", "review_first"] as const);
return (
);
}
/**
* Read-only summary for narrow views (the session dock): what is on, grouped,
* with its one-line description; then one line each for what is off and what
* this server doesn't offer.
*/
export function AgentCapabilitySummary({
values,
availability,
rowAside,
learningModes,
}: {
values: ResolvedAgentCapabilities;
availability: CapabilityAvailability;
/** Agent learning for the same scope: Skills and Knowledge then say Read or Read and write. */
learningModes?: CapabilityLearning["modes"] | null | undefined;
/** Quiet extra line under one capability that is on (for example, a related setting). */
rowAside?: ((id: AgentCapabilityId) => ReactNode) | undefined;
}) {
const on = (id: AgentCapabilityId) =>
availability.isAvailable(id) &&
(id === "skills" ? values.skills !== false : values[id] === true);
const off = AGENT_CAPABILITY_GROUPS.flatMap((group) => group.capabilities).filter(
(id) => availability.isAvailable(id) && !on(id),
);
const unavailable = AGENT_CAPABILITY_GROUPS.flatMap((group) => group.capabilities).filter(
(id) => !availability.isAvailable(id),
);
return (