diff --git a/.changeset/agent-description-ui.md b/.changeset/agent-description-ui.md new file mode 100644 index 000000000..41547498f --- /dev/null +++ b/.changeset/agent-description-ui.md @@ -0,0 +1,5 @@ +--- +"@truefoundry/trueforge-ui": patch +--- + +Round-trip agent description through save/load and show it in the library and agent details. diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7df484c6f..693fa3a78 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -26,7 +26,7 @@ "@assistant-ui/core": "0.2.22", "@assistant-ui/react": "0.14.27", "@assistant-ui/store": "0.2.21", - "@truefoundry/assistant-ui-runtime": "0.1.36", + "@truefoundry/assistant-ui-runtime": "0.1.37", "@truefoundry/trueforge-sdk": "workspace:*", "@truefoundry/trueforge-ui": "workspace:*", "monaco-editor": "^0.52.0", diff --git a/packages/trueforge-ui/package.json b/packages/trueforge-ui/package.json index c2000135a..1607b781f 100644 --- a/packages/trueforge-ui/package.json +++ b/packages/trueforge-ui/package.json @@ -95,7 +95,7 @@ "@openuidev/react-headless": "^0.9.4", "@openuidev/react-lang": "^0.2.9", "@openuidev/react-ui": "^0.13.2", - "@truefoundry/assistant-ui-runtime": "0.1.36", + "@truefoundry/assistant-ui-runtime": "0.1.37", "@truefoundry/trueforge-sdk": "workspace:*", "chart.js": "^4.5.1", "clsx": "^2.1.1", diff --git a/packages/trueforge-ui/src/atoms/AgentOverflowMenu.tsx b/packages/trueforge-ui/src/atoms/AgentOverflowMenu.tsx index c58d8352e..66ab7be54 100644 --- a/packages/trueforge-ui/src/atoms/AgentOverflowMenu.tsx +++ b/packages/trueforge-ui/src/atoms/AgentOverflowMenu.tsx @@ -20,6 +20,7 @@ export function cloneAgentName(agentName: string): string { export type AgentOverflowMenuProps = { agentName: string; + description?: string; agentSpec?: AgentSpec; /** Edit / Clone / Delete when composer is enabled. */ canMutate: boolean; @@ -37,6 +38,7 @@ type PendingAction = 'clone' | 'delete' | null; export function AgentOverflowMenu({ agentName, + description, agentSpec, canMutate, canUse = true, @@ -72,6 +74,7 @@ export function AgentOverflowMenu({ try { await builder.saveAgent({ agentName: clonedName, + ...(description === undefined ? {} : { description }), agentSpec, intent: 'create', }); diff --git a/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx b/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx index df8b4ce1c..436e07639 100644 --- a/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx +++ b/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx @@ -189,10 +189,14 @@ export function AgentLibraryRow({ const skillsTitle = skillNames.length ? skillNames.join(', ') : `${skillsCount} skills`; const hasConfiguration = modelLabel != null || skillsCount > 0 || mcpCount > 0; const hasNoSchedules = scheduleSummary != null && scheduleSummary.count === 0; + const storedDescription = agent.description?.trim() || null; + // Create falls back to name when description is missing; don't echo it under the title. + const description = storedDescription != null && storedDescription !== agent.name ? storedDescription : null; return ( - + {/* Fixed width so truncate works; 24rem = 1.5× the prior min-w-64 name column. */} + {onOpen == null ? ( {agent.name} ) : ( @@ -205,6 +209,15 @@ export function AgentLibraryRow({ {agent.name} )} + {description ? ( + + {description} + + ) : null} {hasConfiguration ? ( @@ -276,6 +289,7 @@ export function AgentLibraryRow({ - Agent name + Agent name Configuration {showCreatedByColumn ? Created by : null} {showSchedulesColumn ? Schedules : null} diff --git a/packages/trueforge-ui/src/atoms/SaveAgentButton.tsx b/packages/trueforge-ui/src/atoms/SaveAgentButton.tsx index e9c5c4fe8..c764dfb3e 100644 --- a/packages/trueforge-ui/src/atoms/SaveAgentButton.tsx +++ b/packages/trueforge-ui/src/atoms/SaveAgentButton.tsx @@ -86,6 +86,7 @@ function SaveAgentButtonContent({ const [open, setOpen] = useState(false); const [intent, setIntent] = useState('create'); const [name, setName] = useState(''); + const [description, setDescription] = useState(''); const [draftSpec, setDraftSpec] = useState(null); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); @@ -93,6 +94,7 @@ function SaveAgentButtonContent({ const close = () => { if (saving) return; setOpen(false); + setDescription(''); setDraftSpec(null); setError(null); }; @@ -111,6 +113,7 @@ function SaveAgentButtonContent({ const currentName = shell?.mode.status === 'active' ? (shell.mode.agentName ?? shell.mode.agentId ?? '') : ''; setIntent(currentName ? 'update' : 'create'); setName(currentName); + setDescription(currentName && shell?.mode.status === 'active' ? (shell.mode.description ?? '') : ''); setDraftSpec(cloneAgentSpec(latestAgentSpec)); setOpen(true); }; @@ -120,11 +123,14 @@ function SaveAgentButtonContent({ if (intent === 'update' && !canManageAgent) return; const normalizedName = name.trim(); if (!normalizedName || !draftSpec.model.name.trim()) return; + const normalizedDescription = description.trim(); + if (!normalizedDescription) return; setSaving(true); setError(null); try { const result = await builder.saveAgent({ agentName: normalizedName, + ...(normalizedDescription ? { description: normalizedDescription } : {}), agentSpec: draftSpec, intent, sessionId: draftSessionId, @@ -133,10 +139,12 @@ function SaveAgentButtonContent({ shell?.bindMutableAgent({ agentId: result.agentId ?? normalizedName, agentName: normalizedName, + ...(normalizedDescription ? { description: normalizedDescription } : {}), agentSpec: draftSpec, }); shell?.invalidateAgentsList(); setOpen(false); + setDescription(''); setDraftSpec(null); } catch (caught) { setError(getErrorMessage(caught, 'Could not save agent')); @@ -180,11 +188,12 @@ function SaveAgentButtonContent({ void save()} /> diff --git a/packages/trueforge-ui/src/atoms/SaveAgentForm.tsx b/packages/trueforge-ui/src/atoms/SaveAgentForm.tsx index 8c6d0f236..780aecebc 100644 --- a/packages/trueforge-ui/src/atoms/SaveAgentForm.tsx +++ b/packages/trueforge-ui/src/atoms/SaveAgentForm.tsx @@ -9,11 +9,12 @@ import { Button } from './primitives/Button.js'; export type SaveAgentFormProps = { intent: 'create' | 'update'; name: string; + description: string; spec: AgentSpec; saving: boolean; error: string | null; onNameChange: (name: string) => void; - onChange: (spec: AgentSpec) => void; + onDescriptionChange: (description: string) => void; onCancel: () => void; onSave: () => void; }; @@ -21,10 +22,12 @@ export type SaveAgentFormProps = { export function SaveAgentForm({ intent, name, + description, spec, saving, error, onNameChange, + onDescriptionChange, onCancel, onSave, }: SaveAgentFormProps) { @@ -36,6 +39,8 @@ export function SaveAgentForm({ errorRef.current?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' }); }, [error]); + const canSave = name.trim() !== '' && spec.model.name.trim() !== '' && description.trim() !== ''; + return (
@@ -50,18 +55,18 @@ export function SaveAgentForm({ /> - {/* TODO: Uncomment the description field when the backend supports description */} - {/*