diff --git a/frontend/src/settings/ToolConfig.test.tsx b/frontend/src/settings/ToolConfig.test.tsx index 4786e102..4a5467b8 100644 --- a/frontend/src/settings/ToolConfig.test.tsx +++ b/frontend/src/settings/ToolConfig.test.tsx @@ -567,6 +567,30 @@ describe('ToolConfig', () => { expect(credential && disabled(credential)).toBe(true); }); + it('locks fixed values for anyone but the owner', async () => { + // Even an editor who may change credentials: the server refuses a fixed + // value from anyone but the owner. + await render({ + ...userTool, + access: 'editor', + allowed_actions: ['edit', 'edit_credentials', 'use', 'use_in_own'], + } as UserToolType); + await act(async () => { + ( + container.querySelector('[class*="cursor-pointer"]') as HTMLElement + ).click(); + }); + const filled = container.querySelector( + '[aria-label="settings.tools.filledByLLM"]', + ); + expect(filled?.hasAttribute('disabled')).toBe(true); + const tableInputs = Array.from( + container.querySelectorAll('table input[data-slot]'), + ); + // The description stays editable; the value is the fixed one. + expect(tableInputs.at(-1)?.disabled).toBe(true); + }); + it('says why the credentials are locked for an editor without edit_credentials', async () => { await render({ ...configTool, ...editorNoCreds } as UserToolType); const note = container.querySelector('[data-slot="alert"]'); diff --git a/frontend/src/settings/ToolConfig.tsx b/frontend/src/settings/ToolConfig.tsx index c2e51a79..76cf5a33 100644 --- a/frontend/src/settings/ToolConfig.tsx +++ b/frontend/src/settings/ToolConfig.tsx @@ -58,11 +58,14 @@ const BODY_TYPE_HINT_KEYS: Record = { /** * What the caller may change on the open tool (`utils/accessUtils` `can`): * `canEdit` covers the name and the actions, `canEditCredentials` the - * secrets, URLs and header / query values. + * secrets, URLs and header / query values, and `canFixValues` whether a + * parameter is filled by the AI or fixed, and its fixed value (the owner's + * alone: the server refuses anyone else). */ const ToolAccessContext = React.createContext({ canEdit: true, canEditCredentials: true, + canFixValues: true, }); /** Maps a body content type to its hint's locale key suffix (JSON by default). */ @@ -133,9 +136,10 @@ export default function ToolConfig({ can(tool, 'edit_credentials') && !sharedOAuth && !ownerOnlyConnection; // Neither: the tool opens as a read-only view with no Save. const readOnly = !canEdit && !canEditCredentials; + const canFixValues = isOwner(tool); const access = React.useMemo( - () => ({ canEdit, canEditCredentials }), - [canEdit, canEditCredentials], + () => ({ canEdit, canEditCredentials, canFixValues }), + [canEdit, canEditCredentials, canFixValues], ); const toggleUserActionExpand = (index: number) => { @@ -681,6 +685,7 @@ export default function ToolConfig({ { setTool({ @@ -1293,7 +1301,8 @@ function APIActionTable({ ) => void; }) { const { t } = useTranslation(); - const { canEditCredentials } = React.useContext(ToolAccessContext); + const { canEditCredentials, canFixValues } = + React.useContext(ToolAccessContext); const idPrefix = React.useId(); const [action, setAction] = React.useState(apiAction); @@ -1557,6 +1566,7 @@ function APIActionTable({ id={`${idPrefix}-${section}-${index}-filled-by-llm`} aria-label={t('settings.tools.filledByLLM')} checked={param.filled_by_llm} + disabled={!canFixValues} onCheckedChange={(checked) => handlePropertyChange( section, @@ -1588,6 +1598,7 @@ function APIActionTable({ value={param.value} disabled={ param.filled_by_llm || + !canFixValues || (section === 'query_params' && !canEditCredentials) } onChange={(e) =>