diff --git a/ui/src/components/agent-configuration-shell.test.ts b/ui/src/components/agent-configuration-shell.test.ts index 5abd7232382..ed7c7950885 100644 --- a/ui/src/components/agent-configuration-shell.test.ts +++ b/ui/src/components/agent-configuration-shell.test.ts @@ -1,7 +1,12 @@ +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import type { Agent } from "@paperclipai/shared"; import { getAgentConfigDirtyDetails } from "./AgentConfigForm"; import { + AgentConfigurationRail, + EffectiveConfigurationStrip, + buildEffectiveConfigurationChips, filterAgentConfigurationSections, resolveEffectiveConfiguration, } from "./agent-configuration-shell"; @@ -43,4 +48,50 @@ describe("agent configuration shell", () => { sections: ["environment", "runtime", "danger", "schedule"], }); }); + + it("builds the effective chips in spec order without a separate Variables chip", () => { + const agent = { + adapterType: "codex_local", + adapterConfig: { model: "gpt-5.6-terra" }, + runtimeConfig: { heartbeat: { enabled: false } }, + permissions: { trustPreset: "standard" }, + defaultEnvironmentId: null, + } as unknown as Agent; + + expect(buildEffectiveConfigurationChips(resolveEffectiveConfiguration(agent, 2)).map((chip) => chip.label)).toEqual([ + "Adapter", + "Model", + "Cost saver", + "Environment", + "Cadence", + "Trust", + "API keys", + ]); + }); + + it("explains the rail status glyphs", () => { + const markup = renderToStaticMarkup(createElement(AgentConfigurationRail, { + query: "", + onQueryChange: () => undefined, + visibleSections: filterAgentConfigurationSections(""), + dirtySections: new Set(["runtime"]), + })); + + expect(markup).toContain(">● = unsaved change in section"); + expect(markup).toContain(">⚡ = changes apply immediately"); + }); + + it("keeps inherited chip metadata compact and the strip reachable", () => { + const markup = renderToStaticMarkup(createElement(EffectiveConfigurationStrip, { + chips: [ + { label: "Adapter", value: "Codex", section: "runtime" }, + { label: "Model", value: "Adapter default", section: "runtime", inherited: true }, + ], + })); + + expect(markup).toContain("overflow-x-auto"); + expect(markup).toContain('tabindex="0"'); + expect(markup).toContain('title="Model is inherited"'); + expect(markup).toContain('class="sr-only">inherited'); + }); }); diff --git a/ui/src/components/agent-configuration-shell.tsx b/ui/src/components/agent-configuration-shell.tsx index f0e9824faa8..da58b23b6aa 100644 --- a/ui/src/components/agent-configuration-shell.tsx +++ b/ui/src/components/agent-configuration-shell.tsx @@ -69,6 +69,25 @@ export function resolveEffectiveConfiguration(agent: Agent, apiKeyCount = 0) { }; } +export type EffectiveConfigurationChip = { + label: string; + value: string; + section: AgentConfigurationSectionId; + inherited?: boolean; +}; + +export function buildEffectiveConfigurationChips(effectiveConfig: ReturnType): EffectiveConfigurationChip[] { + return [ + { label: "Adapter", value: effectiveConfig.adapter, section: "runtime" }, + { label: "Model", value: effectiveConfig.model, section: "runtime", inherited: effectiveConfig.modelInherited }, + { label: "Cost saver", value: effectiveConfig.cheapModel, section: "runtime", inherited: effectiveConfig.cheapInherited }, + { label: "Environment", value: effectiveConfig.environment, section: "environment", inherited: effectiveConfig.environmentInherited }, + { label: "Cadence", value: effectiveConfig.cadence, section: "schedule" }, + { label: "Trust", value: effectiveConfig.trust, section: "access" }, + { label: "API keys", value: String(effectiveConfig.apiKeyCount), section: "keys" }, + ]; +} + export function AgentConfigurationRail({ query, onQueryChange, @@ -120,20 +139,29 @@ export function AgentConfigurationRail({ ))} +
+

= unsaved change in section

+

= changes apply immediately

+
); } -export function EffectiveConfigurationStrip({ chips }: { chips: Array<{ label: string; value: string; section: AgentConfigurationSectionId; inherited?: boolean }> }) { +export function EffectiveConfigurationStrip({ chips }: { chips: EffectiveConfigurationChip[] }) { return ( -
+
{chips.map((chip) => ( - + {chip.label} {chip.value} - {chip.inherited ? inherited : null} + {chip.inherited ? inherited : null} ))} diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index 42a44e51002..861dc690901 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -30,6 +30,7 @@ import { AgentConfigurationRail, ConfigurationSection, EffectiveConfigurationStrip, + buildEffectiveConfigurationChips, filterAgentConfigurationSections, resolveEffectiveConfiguration, type AgentConfigurationSectionId, @@ -1778,16 +1779,7 @@ function AgentConfigurePage({ }, }); - const effectiveChips: Array<{ label: string; value: string; section: AgentConfigurationSectionId; inherited?: boolean }> = [ - { label: "Adapter", value: effectiveConfig.adapter, section: "runtime" }, - { label: "Model", value: effectiveConfig.model, section: "runtime", inherited: effectiveConfig.modelInherited }, - { label: "Cost saver", value: effectiveConfig.cheapModel, section: "runtime", inherited: effectiveConfig.cheapInherited }, - { label: "Environment", value: effectiveConfig.environment, section: "environment", inherited: effectiveConfig.environmentInherited }, - { label: "Variables", value: String(effectiveConfig.environmentVariableCount), section: "environment" }, - { label: "Heartbeat", value: effectiveConfig.cadence, section: "schedule" }, - { label: "Trust", value: effectiveConfig.trust, section: "access" }, - { label: "API keys", value: String(effectiveConfig.apiKeyCount), section: "keys" }, - ]; + const effectiveChips = buildEffectiveConfigurationChips(effectiveConfig); return (