diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx index b4606a0f6..509bb5b42 100644 --- a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx +++ b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx @@ -102,6 +102,8 @@ type AgentDefinitionDialogProps = { isPending: boolean; runtimes: AcpRuntimeCatalogEntry[]; runtimeCatalogStatus?: "loading" | "ready" | "error"; + /** When true, D-fields render disabled + "Managed by team" notice; submit blocked. */ + definitionReadOnly?: boolean; onDirtyChange?: (dirty: boolean) => void; onOpenChange: (open: boolean) => void; onSubmit: ( @@ -130,6 +132,7 @@ export function AgentDefinitionDialog({ isPending, runtimes, runtimeCatalogStatus = "ready" as const, + definitionReadOnly = false, onDirtyChange, onOpenChange, onSubmit, @@ -159,15 +162,9 @@ export function AgentDefinitionDialog({ // The seed the draft is diffed against at submit: an untouched quad // submits no behavior group, keeping unrelated edits hash-quiet. const behaviorSeedRef = React.useRef(emptyPersonaBehaviorDraft); - // Tracks when the runtime was auto-seeded by the default-runtime effect in - // edit mode (i.e. the user never explicitly chose a runtime). Used to omit - // the seeded runtime from the submit payload for builtin definitions whose - // canonical runtime is null — the sync would revert it anyway. + // Tracks when the runtime was auto-seeded (not an explicit user choice). const isRuntimeAutoSeededRef = React.useRef(false); // Guards the seeding effect so it fires at most once per dialog-open. - // Without this, clearing runtime back to "" via "No preference" would re- - // trigger the effect (the `runtime` dep would pass the length guard) and - // snap the dropdown back to the default — an edit-mode regression. const hasSeededForOpenRef = React.useRef(false); const [showAdvancedFields, setShowAdvancedFields] = React.useState(false); const [isAvatarUploadPending, setIsAvatarUploadPending] = @@ -484,12 +481,8 @@ export function AgentDefinitionDialog({ const modelFieldVisible = runtime.trim().length > 0 || blankRuntimeModelProviderEditable; const isExplicitModelRequired = aiConfigurationMode === "custom"; - // Gate the provider requirement on the field's actual visibility, not the raw - // runtime capability. Codex/Claude hide the provider picker (they drive their - // own provider), so Customize must not require a provider there. But a - // runtime-less legacy/builtin definition still exposes the picker via - // blankRuntimeModelProviderEditable, so it must keep requiring a provider — - // otherwise Save could persist `provider: undefined` despite the visible field. + // Gate provider requirement on visible field (Codex/Claude hide picker), + // but a runtime-less legacy definition must still require provider. const customAiPairSatisfied = agentAiConfigurationModeSatisfied( aiConfigurationMode, { provider, model }, @@ -501,6 +494,7 @@ export function AgentDefinitionDialog({ // Gate model/provider validity through missingNormalizedFields — single // source of truth with the readiness gate so display and Save can't drift. const canSubmit = + !definitionReadOnly && canSubmitPersonaDialog({ displayName, isPending }) && (!isCreateMode || runtime.trim().length > 0) && (!isCreateMode || selectedRuntimeIsAvailable) && @@ -752,7 +746,7 @@ export function AgentDefinitionDialog({ > { setHasUserChanges(true); @@ -766,6 +760,12 @@ export function AgentDefinitionDialog({ />
+ {definitionReadOnly ? ( +

+ This agent is managed by a team. Its configuration cannot be edited + here. +

+ ) : null}