}
// The body itself doesn't scroll — the content (a full-height flex row) gives each
// panel its own overflow, so the left and right panels scroll independently.
- styles={{body: {padding: 16, overflow: "hidden"}}}
+ // Tighter at the sides; the vertical 16 is what the rail's `bleed` negates.
+ styles={{body: {padding: "16px 12px", overflow: "hidden"}}}
>
{children}
diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/BuildKitSection.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/BuildKitSection.tsx
index a0b73cb8284..07a39d99d06 100644
--- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/BuildKitSection.tsx
+++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/BuildKitSection.tsx
@@ -7,11 +7,9 @@
*/
import type {ReactNode} from "react"
-import {ConfigAccordionSection, Tag} from "@agenta/ui/components/presentational"
+import {Tag} from "@agenta/ui/components/presentational"
import {Switch, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@agenta/ui/ui"
-import {Warning, Wrench} from "@phosphor-icons/react"
-
-import {RailField} from "../../../drawers/shared/RailField"
+import {Warning} from "@phosphor-icons/react"
import type {ItemDescriptor} from "./itemDescriptors"
import {ItemRow} from "./ItemRow"
@@ -47,8 +45,6 @@ export interface BuildKitSectionProps {
onSetAllTools: (next: boolean) => void
/** Sandbox permission overlay, rendered read-only as `key → value` rows. */
permissions?: Record | null
- /** Collapsed in the app (it is background information); stories open it. @default false */
- defaultOpen?: boolean
}
/** The build-kit block. Switchable tools get a switch each; the rest, and permissions, are read-only. */
@@ -60,7 +56,6 @@ export function BuildKitSection({
onToggleTool,
onSetAllTools,
permissions,
- defaultOpen = false,
}: BuildKitSectionProps) {
// Per-tool switches only mean anything while the kit as a whole is on.
const toolsDisabled = Boolean(disabled) || !enabled
@@ -70,57 +65,46 @@ export function BuildKitSection({
// Nothing to switch means no bulk action — the button would be a dead control.
const hasSwitchableTools = tools.some((tool) => tool.toggle)
return (
- }
- title="Playground build kit"
- summary={
-
-
- Removed on commit
+
+
+
+ Playground build kit
+
+
+
+ Tools and permissions the assistant builds with here. None of it reaches the
+ published agent.
- }
- extra={
-
- }
- >
-
- These playground-only tools and permissions help the assistant build and revise this
- agent. None of this is part of the published agent.
-
+
{!enabled ? (
The assistant can no longer create files, run code, or edit the agent here.
)
}
diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ItemRow.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ItemRow.tsx
index 4970c63182d..e834d2cb4a8 100644
--- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ItemRow.tsx
+++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ItemRow.tsx
@@ -156,7 +156,7 @@ export function ItemRow({
diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useBuildKit.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useBuildKit.tsx
index d5a87f81da2..0c8ef874a87 100644
--- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useBuildKit.tsx
+++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useBuildKit.tsx
@@ -7,6 +7,7 @@
* revision) plus the user's build-kit state — the master on/off and the platform ops switched off
* individually — and returns:
* - `hasBuildKitOverlay`: whether to render the build-kit block / extend the Advanced section,
+ * - `buildKitEnabled`: the master on/off, for callers that flag the panel while it is live,
* - `buildKitSection`: the drawer block (one tool list — platform tools with a switch each, the
* Agenta-owned embeds locked on — plus sandbox permissions) under the master enable switch,
* - `permissionOverrideHint`: the inline warning to show above SandboxPermissionControl when the
@@ -186,6 +187,8 @@ export function useBuildKit({
return {
hasBuildKitOverlay,
+ // The master on/off, so the Advanced rail can flag the panel while the overlay is live.
+ buildKitEnabled,
buildKitSection,
permissionOverrideHint,
}
diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
index 3712ce51e9e..1ef2d9f25d0 100644
--- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
+++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
@@ -2,7 +2,7 @@
* useModelHarness — the Model + Advanced sections (the panel's most stateful part). One
* hook because the model/connection state feeds both; returns each section's summary + bodies.
*/
-import {useCallback, useEffect, useMemo, type ReactNode} from "react"
+import {useCallback, useEffect, useMemo, useState, type ReactNode} from "react"
import {
customSecretsAtom,
@@ -21,12 +21,13 @@ import {normalizeProviderFamily} from "@agenta/shared/utils"
import {ConfigAccordionSection} from "@agenta/ui/components/presentational"
import {useDrillInUI} from "@agenta/ui/drill-in"
import {SelectLLMProviderBase} from "@agenta/ui/select-llm-provider"
-import {Cube, ShieldCheck} from "@phosphor-icons/react"
+import {Cube, ShieldCheck, Wrench} from "@phosphor-icons/react"
import {atom, useAtomValue, useSetAtom} from "jotai"
import {useHasChangedUnder, useRevertUnder} from "../../../drawers/shared/ChangedPathsContext"
import {useFocusPaths, useHasFocusUnder} from "../../../drawers/shared/FocusPathsContext"
-import {RailField} from "../../../drawers/shared/RailField"
+import {FieldLayoutProvider, RailField} from "../../../drawers/shared/RailField"
+import {SectionRail, type SectionRailItem} from "../../../drawers/shared/SectionRail"
import {ClaudePermissionsControl} from "../ClaudePermissionsControl"
import type {PickerSelection} from "../connectionPicker"
import {
@@ -69,6 +70,13 @@ const vaultLoadedAtom = atom((get) => Array.isArray(get(vaultSecretsQueryAtom).d
// Shared with the chat composer's model palette so a hidden harness stays hidden everywhere.
+/** One Advanced rail panel: its nav item, an optional header, and the controls it shows. */
+interface AdvancedPanel {
+ item: SectionRailItem
+ header?: {title: string; caption: string; extra?: ReactNode}
+ body: ReactNode
+}
+
export function useModelHarness({
schema,
config,
@@ -755,12 +763,107 @@ export function useModelHarness({
>
)
- // The stacked sections carry their own dividers; drop the trailing one on whichever section
- // renders last (they're conditional, so target the last child rather than a fixed section).
- const advancedDrawerBody = (
+ // One rail panel per Advanced group, schema-gated: no group, no rail item and no panel.
+ const advancedPanels: AdvancedPanel[] = (
+ [
+ hasPermissionsGroup && {
+ item: {
+ value: "permissions",
+ label: "Permissions",
+ icon: ,
+ status: permissionsChanged ? ("warning" as const) : undefined,
+ },
+ header: {
+ title: "Permissions",
+ caption: "What the agent may do on its own before it must ask.",
+ extra: revertAction(permissionsChanged ? revertPermissions : null),
+ },
+ body: permissionsBody,
+ },
+ hasExecutionGroup && {
+ item: {
+ value: "execution",
+ label: "Execution",
+ icon: ,
+ status: sandboxChanged ? ("warning" as const) : undefined,
+ },
+ header: {
+ title: "Execution environment",
+ caption:
+ "Where the agent's tools and code run, and what that sandbox may touch.",
+ extra: revertAction(revertSandbox),
+ },
+ body: executionBody,
+ },
+ hasBuildKitOverlay && {
+ item: {
+ value: "build-kit",
+ label: "Build kit",
+ icon: ,
+ },
+ // The block carries its own title + enable switch, so it needs no panel header.
+ body: buildKitSection,
+ },
+ ] as (AdvancedPanel | false)[]
+ ).filter((panel): panel is AdvancedPanel => Boolean(panel))
+
+ // Land on the first panel that owns an uncommitted change, else the first one.
+ const initialAdvancedPanel =
+ advancedPanels.find((panel) => panel.item.status === "warning" && panel.header)?.item
+ .value ??
+ advancedPanels[0]?.item.value ??
+ ""
+ const [advancedPanelValue, setAdvancedPanelValue] = useState(initialAdvancedPanel)
+ const activeAdvancedPanel =
+ advancedPanels.find((panel) => panel.item.value === advancedPanelValue) ?? advancedPanels[0]
+
+ const activeAdvancedPanelBody = (
+
+ )
+
+ // One panel needs no nav — a single-item rail is chrome around nothing.
+ const advancedRailBody =
+ advancedPanels.length > 1 ? (
+ panel.item)}
+ value={activeAdvancedPanel?.item.value ?? ""}
+ onChange={setAdvancedPanelValue}
+ >
+ {activeAdvancedPanelBody}
+
+ ) : (
+ activeAdvancedPanelBody
+ )
+
+ // Under a focus filter the body is already narrowed, so it keeps the stack over the rail.
+ const advancedDrawerBody = focus.active ? (
{advancedControls}
+ ) : (
+
+
{advancedRailBody}
+
)
return {
@@ -779,5 +882,7 @@ export function useModelHarness({
hasAdvanced,
advancedSummary,
advancedDrawerBody,
+ // Rail + one panel at a time: no wider than the Model drawer.
+ advancedDrawerWidth: 560,
}
}
diff --git a/web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx b/web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx
index 2af8451449f..4349ac9ee4c 100644
--- a/web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx
+++ b/web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx
@@ -9,7 +9,7 @@
* The content is capped at `max-w-prose` so inputs in a section panel keep a readable width instead
* of stretching the full drawer. Styling uses antd semantic tokens (`--ag-color*`) only — dark-safe.
*/
-import type {ReactNode} from "react"
+import {createContext, useContext, type ReactNode} from "react"
import {cn} from "@agenta/ui/styles"
import {
@@ -27,6 +27,19 @@ import {ArrowCounterClockwise, Info} from "@phosphor-icons/react"
import {useChangedDetail, useChangedPath, useRevertPath} from "./ChangedPathsContext"
import {useIsPathVisible} from "./FocusPathsContext"
+/** How a {@link RailField} lays its label out: two-column rail, or label above the control. */
+const FieldLayoutContext = createContext<"rail" | "stacked">("rail")
+
+export function FieldLayoutProvider({
+ layout,
+ children,
+}: {
+ layout: "rail" | "stacked"
+ children: ReactNode
+}) {
+ return {children}
+}
+
export interface RailFieldProps {
label: ReactNode
/** Vertical alignment of the label against the content. @default "top" */
@@ -120,12 +133,53 @@ function ChangedDetail({
}
export function RailField({label, align = "top", path, wide, children}: RailFieldProps) {
+ const layout = useContext(FieldLayoutContext)
const changed = useChangedPath(path)
const detail = useChangedDetail(path)
const revert = useRevertPath(path)
// Focus filter (see FocusPathsContext): each row self-filters on its own `path`.
const visible = useIsPathVisible(path)
if (!visible) return null
+ const stacked = layout === "stacked"
+ // The change shows as emphasis + a colorInfo dotted underline, so both states share one box.
+ const labelNode = changed ? (
+
+
+ {/* A real button, so the keyboard can reach the change detail and its Restore. */}
+
+
+ {/* antd Popover chrome: elevated panel with 12px inner padding. */}
+
+
+
+
+ ) : (
+ label
+ )
+
+ // Stacked = the trigger drawers' flat field stack; `align`/`wide` are rail-only knobs.
+ if (stacked) {
+ return (
+
+
+ {labelNode}
+
+
{children}
+
+ )
+ }
+
return (
- {/* The label carries the change via emphasis (colorTextSecondary → colorText) plus a
- colorInfo dotted underline — no marker glyph, so changed and unchanged rows share
- the same box. */}
- {changed ? (
-
-
- {/* role=button makes the Radix aria-haspopup/expanded attrs valid here. */}
-
- {label}
-
-
- {/* antd Popover chrome: elevated panel with 12px inner padding. */}
-
-
-
-
- ) : (
- label
- )}
+ {labelNode}
diff --git a/web/packages/agenta-entity-ui/src/drawers/shared/index.ts b/web/packages/agenta-entity-ui/src/drawers/shared/index.ts
index 3c4b63f92fe..2fc47ed24cb 100644
--- a/web/packages/agenta-entity-ui/src/drawers/shared/index.ts
+++ b/web/packages/agenta-entity-ui/src/drawers/shared/index.ts
@@ -3,7 +3,7 @@
* config drawers. Re-exported here so app-layer drawers (e.g. the agent-home template setup drawer)
* can share the exact same field/rail layout instead of re-implementing it.
*/
-export {RailField, railInfoLabel, type RailFieldProps} from "./RailField"
+export {RailField, FieldLayoutProvider, railInfoLabel, type RailFieldProps} from "./RailField"
export {SectionRail, type SectionRailItem, type SectionRailProps} from "./SectionRail"
export {DrawerFooter} from "./DrawerFooter"
export {RowRemoveButton} from "./RowRemoveButton"
diff --git a/web/packages/agenta-entity-ui/src/gatewayTool/components/schemaFormControls.tsx b/web/packages/agenta-entity-ui/src/gatewayTool/components/schemaFormControls.tsx
index e6ad866e474..0e98a7e7b89 100644
--- a/web/packages/agenta-entity-ui/src/gatewayTool/components/schemaFormControls.tsx
+++ b/web/packages/agenta-entity-ui/src/gatewayTool/components/schemaFormControls.tsx
@@ -7,7 +7,7 @@
* Every control takes plain `value`/`onChange`/`disabled` so it works both under antd
* `Form.Item` cloning and as an explicitly controlled leaf.
*/
-import {useId, useRef, useState} from "react"
+import {useEffect, useId, useMemo, useRef, useState} from "react"
import {dayjs} from "@agenta/shared/utils"
import {
@@ -19,6 +19,9 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
+ Popover,
+ PopoverAnchor,
+ PopoverContent,
Switch,
Select,
SelectContent,
@@ -234,8 +237,10 @@ export function MultiSelect({
}
/**
- * Free chip input (antd `Select mode="tags"` with `open={false}`): type + Enter adds a
- * chip, Backspace on an empty input removes the last one.
+ * Free chip input (antd `Select mode="tags"`): type + Enter chips, Backspace on an empty input
+ * removes the last. `options` suggest; any typed value still lands.
+ *
+ * Suggestions use the `Combobox` pattern, not a `