/** * Composer "+" > Capabilities: what the agent can do in this chat. Off * ("Customize for this chat" switch) the list shows the workspace's defaults * read-only and the chat sends nothing; on, every row is a toggle and the chat * sends its own `agent.capabilities`. Workspace connectors opens the * connectors list nested under it. Product words only; no tool ids. */ import { CheckIcon, ChevronRightIcon, LockIcon } from "lucide-react"; import type { ReactNode } from "react"; import { ConnectorAction } from "../../ui/composer-menu-action"; import { ComposerMenuHeader, ComposerMenuSwitchIndicator } from "../../ui/composer-menu"; import { AGENT_CAPABILITY_GROUPS, UNAVAILABLE_CAPABILITY_REASON, capabilityDescription, capabilityLabel, capabilitySummary, effectiveCapabilityOn, withCapability, type AgentCapabilityDraft, type AgentCapabilityId, type CapabilityAvailability, } from "../lib/agent-capabilities"; import { cn } from "../lib/utils"; export type ComposerAgentCapabilities = { /** "Customize for this chat" is on: the chat sends its own capabilities. */ customized: boolean; /** What the chat gets: its own values when customized, else the workspace's. */ draft: AgentCapabilityDraft; availability: CapabilityAvailability; onCustomizedChange: (customized: boolean) => void; onChange: (draft: AgentCapabilityDraft) => void; disabled?: boolean | undefined; }; export function ComposerCapabilitiesMenuBody(props: { capabilities: ComposerAgentCapabilities; presentation?: "menu" | "dialog" | undefined; leading?: ReactNode | undefined; /** Number of connected apps on for this chat, shown under Workspace connectors. */ connectorsSelected: number; connectorsTotal: number; onOpenConnectors: () => void; }) { const { capabilities } = props; const { customized, draft, availability } = capabilities; const set = (id: AgentCapabilityId, value: boolean | "read" | "manage" | false) => capabilities.onChange(withCapability(draft, id, value)); return ( <>
capabilities.onCustomizedChange(!customized)} > Customize for this chat {customized ? "This chat uses the choices below." : `Off uses the workspace's defaults: ${capabilitySummary(draft.values, availability).toLowerCase()}.`}
{AGENT_CAPABILITY_GROUPS.map((group) => (

{group.label}

{group.capabilities.map((id) => { const available = availability.isAvailable(id); const on = effectiveCapabilityOn(draft.values, id, availability); const editable = customized && available && !capabilities.disabled; // The menu stays scannable: labels only, plus the one line that // changes the decision (unavailable, or which apps are on). const note = !available ? ( {UNAVAILABLE_CAPABILITY_REASON} ) : id === "workspaceConnectors" && on ? ( {props.connectorsTotal === 0 ? "No apps connected yet." : `${props.connectorsSelected} of ${props.connectorsTotal} connected apps on`} ) : null; const text = ( {capabilityLabel(id)} {note} ); const row = (
{editable ? ( set(id, id === "skills" ? (on ? false : "read") : !on)} > {text} ) : ( // Read-only until "Customize for this chat" is on: still // reachable with the arrow keys and read as the current value. {}} > {text} {on ? : "Off"} )} {id === "workspaceConnectors" && on ? ( ) : ( // Keeps every toggle in one column with the connectors row. )}
); if (id !== "skills" || !on) return row; // Skills on: one indented choice to also let the agent change them. const manage = draft.values.skills === "manage"; return (
{row} {editable ? ( set("skills", manage ? "read" : "manage")} > Also save and install Skills ) : ( {}} > {manage ? "Can also save and install Skills" : "Reads Skills only"} )}
); })}
))}
); }