From 4e241ffe415d1ac5b7cddd35ce9d1d6f9aa99bde Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Sun, 12 Jul 2026 17:30:14 +0200 Subject: [PATCH 1/2] feat(frontend): auto-expand agent config list sections when the agent populates them --- .../SchemaControls/AgentTemplateControl.tsx | 75 ++++++++++++++----- .../SchemaControls/agentSectionAutoExpand.ts | 40 ++++++++++ .../tests/unit/agentSectionAutoExpand.test.ts | 28 +++++++ 3 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts create mode 100644 web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx index 8328bec77a..e967bf260d 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx @@ -61,6 +61,7 @@ import {useAtom, useAtomValue, useStore} from "jotai" import {useOptionalDrillIn} from "../components/MoleculeDrillInContext" import {AddTextLink} from "./AddTextLink" +import {useAutoExpandOnPopulate} from "./agentSectionAutoExpand" import {AgentIntegrationDrawer} from "./agentTemplate/AgentIntegrationDrawer" import {countSummary} from "./agentTemplate/agentTemplateUtils" import {AgentToolSelectorPopover} from "./agentTemplate/AgentToolSelectorPopover" @@ -124,6 +125,10 @@ const ModelHarnessSectionDrawerBody = ({ return <>{section === "advanced" ? mh.advancedDrawerBody : mh.modelHarnessDrawerBody} } +// The four list sections whose open-state is controlled so the accordion can auto-expand when +// the agent populates them (see `useAutoExpandOnPopulate`). +const CONTROLLED_SECTION_KEYS = new Set(["tools", "mcp", "skills", "triggers"]) + export function AgentTemplateControl({ schema, value, @@ -391,6 +396,30 @@ export function AgentTemplateControl({ [openCreate], ) + // Controlled open-state for the four list sections so the accordion can react to the agent + // populating a section. Seeded once from the initial counts; the edge hook below flips it. + const [sectionOpen, setSectionOpen] = useState>(() => ({ + tools: tools.length > 0, + mcp: mcpServers.length > 0, + skills: skills.length > 0, + triggers: triggerCount > 0, + })) + const setSectionOpenByKey = useCallback( + (key: string, open: boolean) => + setSectionOpen((m) => (m[key] === open ? m : {...m, [key]: open})), + [], + ) + const sectionCounts = useMemo( + () => ({ + tools: tools.length, + mcp: mcpServers.length, + skills: skills.length, + triggers: triggerCount, + }), + [tools.length, mcpServers.length, skills.length, triggerCount], + ) + useAutoExpandOnPopulate(sectionCounts, setSectionOpenByKey) + // ``instructions.agents_md`` is the one instruction document (flat on the template). const instructions = config.instructions && typeof config.instructions === "object" @@ -942,25 +971,33 @@ export function AgentTemplateControl({ ))} ) : ( - sections.map((s, index) => ( - - {s.content} - - )) + sections.map((s, index) => { + // Controlled keys drive `open`/`onOpenChange` so the agent can auto-expand them; + // everything else keeps the mount-collapsed-then-unfold `defaultOpen` behaviour. + const controlled = CONTROLLED_SECTION_KEYS.has(s.key) + return ( + + setSectionOpenByKey(s.key, open), + } + : {defaultOpen: s.defaultOpen, animateInitialOpen: true})} + > + {s.content} + + ) + }) )} {shownEditing diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts new file mode 100644 index 0000000000..1ef471295d --- /dev/null +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts @@ -0,0 +1,40 @@ +import {useEffect, useRef} from "react" + +export type SectionCounts = Record +export interface SectionCrossing { + key: string + open: boolean +} + +/** Sections whose count crossed the 0 boundary since `prev`: 0→>0 opens, >0→0 closes. */ +export function computeSectionCrossings( + prev: SectionCounts, + next: SectionCounts, +): SectionCrossing[] { + const crossings: SectionCrossing[] = [] + for (const key of Object.keys(next)) { + const before = prev[key] ?? 0 + const now = next[key] ?? 0 + if (before === 0 && now > 0) crossings.push({key, open: true}) + else if (before > 0 && now === 0) crossings.push({key, open: false}) + } + return crossings +} + +/** + * Auto-open a list section when it goes from empty to populated (and close it when it + * empties). Edge-triggered against the previous counts, so a manual collapse of a populated + * section is never overridden by an unrelated re-render. + */ +export function useAutoExpandOnPopulate( + counts: SectionCounts, + setOpen: (key: string, open: boolean) => void, +): void { + const prev = useRef(counts) + useEffect(() => { + for (const {key, open} of computeSectionCrossings(prev.current, counts)) { + setOpen(key, open) + } + prev.current = counts + }, [counts, setOpen]) +} diff --git a/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts b/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts new file mode 100644 index 0000000000..aed5e708ac --- /dev/null +++ b/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts @@ -0,0 +1,28 @@ +import {describe, expect, it} from "vitest" + +import {computeSectionCrossings} from "../../src/DrillInView/SchemaControls/agentSectionAutoExpand" + +describe("computeSectionCrossings", () => { + it("opens a section on a 0 → >0 crossing", () => { + expect(computeSectionCrossings({tools: 0}, {tools: 2})).toEqual([ + {key: "tools", open: true}, + ]) + }) + it("closes a section on a >0 → 0 crossing", () => { + expect(computeSectionCrossings({tools: 3}, {tools: 0})).toEqual([ + {key: "tools", open: false}, + ]) + }) + it("does nothing when the count changes but does not cross 0 (manual collapse sticks)", () => { + expect(computeSectionCrossings({tools: 1}, {tools: 2})).toEqual([]) + }) + it("does nothing when unchanged", () => { + expect(computeSectionCrossings({tools: 2, skills: 0}, {tools: 2, skills: 0})).toEqual([]) + }) + it("reports each crossing key independently", () => { + expect(computeSectionCrossings({tools: 0, skills: 1}, {tools: 1, skills: 0})).toEqual([ + {key: "tools", open: true}, + {key: "skills", open: false}, + ]) + }) +}) From fe6bb256b554f985a4ff828a3bca17f543093d36 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Sun, 12 Jul 2026 22:47:04 +0200 Subject: [PATCH 2/2] fix(frontend): compute section crossings over the union of keys --- .../src/DrillInView/SchemaControls/agentSectionAutoExpand.ts | 3 ++- .../agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts index 1ef471295d..99eac8796c 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentSectionAutoExpand.ts @@ -12,7 +12,8 @@ export function computeSectionCrossings( next: SectionCounts, ): SectionCrossing[] { const crossings: SectionCrossing[] = [] - for (const key of Object.keys(next)) { + // Union of keys so a key that vanishes from `next` still yields its >0→0 close. + for (const key of new Set([...Object.keys(prev), ...Object.keys(next)])) { const before = prev[key] ?? 0 const now = next[key] ?? 0 if (before === 0 && now > 0) crossings.push({key, open: true}) diff --git a/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts b/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts index aed5e708ac..4d1ce6a238 100644 --- a/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts +++ b/web/packages/agenta-entity-ui/tests/unit/agentSectionAutoExpand.test.ts @@ -25,4 +25,7 @@ describe("computeSectionCrossings", () => { {key: "skills", open: false}, ]) }) + it("closes a key present in prev but absent from next", () => { + expect(computeSectionCrossings({tools: 2}, {})).toEqual([{key: "tools", open: false}]) + }) })