From f8e41ed9a78dc12d31a33b0c083079aa950b66d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yohan=20Gon=C3=A7alves?= Date: Sat, 25 Jul 2026 14:16:27 +0200 Subject: [PATCH 1/9] feat: subagent selection from existing agents + UI cleanup - Subagents can only be added by selecting from existing agents (agent_ref) - Add Description input in General section of agent config form - Remove Tools section from form, subagent editor, and viewer (MCP-only) - Remove Debug toggle from form and viewer - Fix modal overflow: add overflow-x-hidden + min-w-0 on inputs/grid children - Serialize description and agent_ref in YAML, preserve tools/debug silently - Fix AgentsPage is_builtin reference and appConfig mcpApiBaseUrl --- README.md | 9 ++ .../components/agent/AgentCard.tsx | 2 - .../components/agent/AgentConfigForm.tsx | 131 ++++++++++++------ .../components/agent/AgentConfigViewer.tsx | 31 ++--- .../components/agent/CreateAgentDialog.tsx | 2 +- .../components/agent/KeyValueEditor.tsx | 4 +- .../components/agent/McpServerEditor.tsx | 12 +- .../components/agent/StringListEditor.tsx | 2 +- .../components/agent/SubAgentEditor.tsx | 93 +++---------- src/application/components/ui/input.tsx | 2 +- src/application/lib/yaml.ts | 4 + src/application/pages/AgentsPage.tsx | 3 +- src/domain/entities/agent/agentConfig.ts | 2 + .../entities/agent/agentConfigMetadata.ts | 2 +- .../entities/agent/agentConfigSchema.ts | 2 + src/domain/entities/config/appConfig.ts | 1 + tests/fixtures/external.ts | 2 +- tests/unit/application/lib/yaml.test.ts | 70 +++++++++- .../unit/components/agent/AgentCard.test.tsx | 8 +- .../components/agent/AgentConfigForm.test.tsx | 123 +++++++++++++--- .../agent/AgentConfigViewer.test.tsx | 70 ++++++++-- .../agent/CreateAgentDialog.test.tsx | 2 +- .../components/agent/SubAgentEditor.test.tsx | 60 ++++++-- tests/unit/components/ui/input.test.tsx | 13 ++ .../unit/domain/entities/agentConfig.test.ts | 85 +++++++++++- .../entities/agentConfigMetadata.test.ts | 41 ++++++ .../domain/entities/agentConfigSchema.test.ts | 81 ++++++++++- 27 files changed, 652 insertions(+), 205 deletions(-) create mode 100644 tests/unit/components/ui/input.test.tsx create mode 100644 tests/unit/domain/entities/agentConfigMetadata.test.ts diff --git a/README.md b/README.md index 6784471..5e69d0c 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,15 @@ tests/ | `/rag` | RagPage | Browse MinIO folders and files with breadcrumb navigation | | `/settings` | SettingsPage | Theme, chat, LLM provider, and reset preferences (persisted to `localStorage`) | +## Agent Configuration + +The agent creation/edit form (in `CreateAgentDialog`) reflects the current backend schema. The following UI changes have been made: + +- **General section** — A new **Description** text input is available alongside the agent name. Tools are no longer managed here; they are configured exclusively via MCP servers (see the MCP servers section of the form). +- **Debug toggle removed** — The Debug toggle has been removed from the form. +- **Tools section removed** — Tools are managed via MCP servers only. The dedicated "Tools" section no longer appears in the form. +- **Subagents section** — A new **"Add from existing agents"** dropdown lets you select an existing agent as a subagent reference (populating `agent_ref`). When a subagent references an existing agent, a **`ref:`** badge is displayed next to its name and the name field becomes read-only (the name is derived from the referenced agent). + ## RAG File Browser The `/rag` page provides a MinIO-backed file browser with three tabs: Browse, Query, and Classical. The browse tab uses breadcrumb navigation driven by `useFolders` and `useFiles`, and supports the following file/folder management actions alongside read and upload: diff --git a/src/application/components/agent/AgentCard.tsx b/src/application/components/agent/AgentCard.tsx index ce47a6f..33e9c47 100644 --- a/src/application/components/agent/AgentCard.tsx +++ b/src/application/components/agent/AgentCard.tsx @@ -1,6 +1,5 @@ import { Bot, Settings, Trash2 } from "lucide-react"; import type { AgentConfigMetadata } from "@/domain/entities/agent/agentConfigMetadata"; -import StatusBadge from "@/application/components/shared/StatusBadge"; import { Button } from "@/application/components/ui/button"; interface AgentCardProps { @@ -22,7 +21,6 @@ export default function AgentCard({ agent, onConfigure, onDelete }: Readonly -

diff --git a/src/application/components/agent/AgentConfigForm.tsx b/src/application/components/agent/AgentConfigForm.tsx index ffae6c4..98f4c05 100644 --- a/src/application/components/agent/AgentConfigForm.tsx +++ b/src/application/components/agent/AgentConfigForm.tsx @@ -14,7 +14,6 @@ import { import { Button } from "@/application/components/ui/button"; import { Input } from "@/application/components/ui/input"; import { Label } from "@/application/components/ui/label"; -import { Switch } from "@/application/components/ui/switch"; import { Textarea } from "@/application/components/ui/textarea"; import { Accordion, @@ -29,18 +28,19 @@ import { SelectTrigger, SelectValue, } from "@/application/components/ui/select"; -import StringListEditor from "./StringListEditor"; import { SkillPillMultiSelect, MemoryPillMultiSelect } from "./SkillMemorySelects"; import McpServerEditor from "./McpServerEditor"; import SubAgentEditor from "./SubAgentEditor"; import HITLEditor from "./HITLEditor"; import ResponseFormatEditor from "./ResponseFormatEditor"; +import { useMcpRegistry } from "@/application/hooks/mcpServer/useMcpRegistry"; +import { useAgents } from "@/application/hooks/agent/useAgents"; +import { mcpRegistryApi } from "@/infrastructure/api/mcpServer/mcpRegistryApi"; import { AGENT_CONFIG_FORM_ID as FORM_ID } from "./formConstants"; type SectionValue = | "general" | "system-prompt" - | "tools" | "backend" | "hitl" | "memory-skills" @@ -53,7 +53,6 @@ const DEFAULT_OPEN_SECTIONS: SectionValue[] = ["general", "system-prompt"]; const SECTION_META: Record = { general: { label: "General" }, "system-prompt": { label: "System Prompt" }, - tools: { label: "Tools" }, backend: { label: "Backend" }, hitl: { label: "HITL" }, "memory-skills": { label: "Memory & Skills" }, @@ -78,17 +77,6 @@ const EMPTY_MCP_SERVER: McpServerConfig = { auth_token: undefined, }; -const EMPTY_SUBAGENT: SubAgentConfig = { - name: "", - description: "", - instructions: undefined, - model: undefined, - tools: [], - skills: [], - mcp_servers: [], - response_format: undefined, -}; - const DEFAULT_FORM_VALUES: AgentConfigFormData = { name: "", model: "", @@ -150,6 +138,8 @@ export default function AgentConfigForm({ const subagentsArray = useFieldArray({ control, name: "subagents" }); const checkpointBackend = useWatch({ control, name: "backend.checkpoint_backend" }); + const currentAgentName = useWatch({ control, name: "name" }); + const { data: existingAgents } = useAgents(); function handleFormSubmit(data: AgentConfigFormData) { const cleaned: AgentConfigFormData = { @@ -161,8 +151,20 @@ export default function AgentConfigForm({ onSubmit(cleaned); } - function addSubagent() { - subagentsArray.append({ ...EMPTY_SUBAGENT }); + function addFromExistingAgent(agentName: string) { + const existing = (existingAgents ?? []).find((a) => a.name === agentName); + if (!existing) return; + subagentsArray.append({ + name: agentName, + agent_ref: agentName, + description: existing.description ?? "", + instructions: undefined, + model: undefined, + tools: [], + skills: [], + mcp_servers: [], + response_format: undefined, + }); } return ( @@ -185,6 +187,14 @@ export default function AgentConfigForm({ aria-invalid={!!errors.name} /> + + + -
- - setValue("debug", v)} - /> -
@@ -232,18 +232,6 @@ export default function AgentConfigForm({ - - - - setValue("tools", tools)} - placeholder="Add tool…" - /> - - - @@ -309,6 +297,33 @@ export default function AgentConfigForm({ + {(existingAgents ?? []).length > 0 && ( +
+ + +
+ )} {subagentsArray.fields.map((field, index) => ( subagentsArray.remove(index)} /> ))} -
@@ -409,6 +423,7 @@ const McpServersAccordionItem = memo(function McpServersAccordionItem({ defaultValue: SectionValue[]; }>) { const mcpServersArray = useFieldArray({ control, name: "mcp_servers" }); + const { data: registryServers } = useMcpRegistry(); const isOpen = defaultValue.includes("mcp-servers"); const addMcpServer = useCallback( () => mcpServersArray.append({ ...EMPTY_MCP_SERVER }), @@ -422,10 +437,46 @@ const McpServersAccordionItem = memo(function McpServersAccordionItem({ (index: number) => mcpServersArray.remove(index), [mcpServersArray], ); + const addFromRegistry = useCallback( + async (name: string) => { + const revealed = await mcpRegistryApi.reveal(name); + const serverConfig: McpServerConfig = { + name: revealed.name, + transport: McpTransportType.HTTP, + command: undefined, + args: [], + url: revealed.url, + headers: revealed.headers, + env: revealed.env, + auth_token: revealed.auth_token ?? undefined, + }; + mcpServersArray.append(serverConfig); + }, + [mcpServersArray], + ); return ( + {(registryServers ?? []).length > 0 && ( +
+ + +
+ )} {mcpServersArray.fields.map((field, index) => ( -
+
{mode === "edit" && config ? ( <> Model

{config.model}

+
+ + {config.description && (
- Debug - + Description +

{config.description}

- + )} {config.system_prompt && (
@@ -190,17 +193,6 @@ export default function AgentConfigViewer({
)} - {config.tools.length > 0 && ( -
- Tools ({config.tools.length}) -
- {config.tools.map((tool) => ( - - ))} -
-
- )} -
Backend
@@ -274,7 +266,12 @@ export default function AgentConfigViewer({ key={sub.name} className="border border-border-soft bg-surface-warm p-3" > -

{sub.name}

+
+

{sub.name}

+ {sub.agent_ref && ( + ref: {sub.agent_ref} + )} +

{sub.description}

))} diff --git a/src/application/components/agent/CreateAgentDialog.tsx b/src/application/components/agent/CreateAgentDialog.tsx index b80e612..cea261b 100644 --- a/src/application/components/agent/CreateAgentDialog.tsx +++ b/src/application/components/agent/CreateAgentDialog.tsx @@ -123,7 +123,7 @@ export default function CreateAgentDialog({
-
+
{mode === "form" ? ( setKeyInput(e.target.value)} placeholder={keyPlaceholder} - className="flex-1" + className="flex-1 min-w-0" aria-label={`${label} key`} /> setValueInput(e.target.value)} placeholder={valuePlaceholder} - className="flex-1" + className="flex-1 min-w-0" aria-label={`${label} value`} />
-
+
-
+
-
-
- - update({ name: e.target.value })} - placeholder="researcher" - /> -
-
- - update({ model: e.target.value || undefined })} - placeholder="openai:gpt-4o (optional)" - /> -
+
+ +
-
+
update({ description: e.target.value })} placeholder="Describe what this subagent does" />
- -
- -