/** * 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 (
onChange(withStartingPoint(draft, value as "all" | "none"))} layout="grid" disabled={disabled} > {AGENT_STARTING_POINTS.map((option) => ( ))} {AGENT_CAPABILITY_GROUPS.map((group) => ( {group.capabilities.map((id) => ( onChange(withCapability(draft, id, value))} onAccessChange={(level) => { if (!learning) return; if (id === "skills") { onChange( withCapability( draft, id, level === "off" ? false : level === "read" ? "read" : "manage", ), ); if (level === "write") learning.onChange(enableWrites(learning.modes, ["skills"])); } else { onChange(withCapability(draft, id, level !== "off")); if (level === "write") { learning.onChange(enableWrites(learning.modes, ["knowledge", "instructions"])); } else if (level === "read") { learning.onChange(disableWrites(learning.modes, ["knowledge", "instructions"])); } } }} /> ))} ))}
); } export function CapabilityGroup({ label, children }: { label: string; children: ReactNode }) { const id = useId(); return (

{label}

); } function CapabilityRow({ id, values, availability, disabled, aside, learning, onChange, onAccessChange, }: { id: AgentCapabilityId; values: ResolvedAgentCapabilities; availability: CapabilityAvailability; disabled: boolean; aside?: ReactNode | undefined; learning?: CapabilityLearning | undefined; onChange: (value: boolean | ResolvedAgentCapabilities["skills"]) => void; onAccessChange: (level: AccessLevel) => void; }) { const labelId = useId(); const descriptionId = useId(); const available = availability.isAvailable(id); const locked = disabled || !available; const text = ( {capabilityLabel(id)} {available ? ( capabilityDescription(id) ) : ( <> {aside && available ? ( {aside} ) : null} ); if (learning && (id === "skills" || id === "knowledge")) { const level = !available ? "off" : id === "skills" ? skillsAccess(values.skills, learning.modes) : knowledgeAccess(values.knowledge === true, learning.modes); 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.
    ) : null}
  • ); } if (id === "skills") { return (
  • {text} onChange(skillsFromOption(value))} disabled={locked} options={SKILLS_OPTIONS} className="shrink-0 max-sm:w-full" fullWidth={false} />
  • ); } const checked = available && values[id] === true; return (
  • ); } 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 (
    {AGENT_CAPABILITY_GROUPS.map((group) => { const ids = group.capabilities.filter(on); if (ids.length === 0) return null; return ( {ids.map((id) => (
  • {capabilityLabel(id)} {id === "skills" || (id === "knowledge" && learningModes) ? ( {accessLabel(id, values, learningModes)} ) : null} {accessDescription(id, values, learningModes)} {rowAside?.(id) ? ( {rowAside(id)} ) : null}
  • ))}
    ); })} {off.length > 0 ? (

    Off: {off.map(capabilityLabel).join(", ")}.

    ) : null} {unavailable.length > 0 ? (

    ) : null}
    ); }