diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 5824c870cb..ea49991968 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2447,6 +2447,8 @@ "turnsPerDay": "Hosted turns per day", "userRunCeiling": "Runs per day", "contextInvalid": "Check the context limits.", + "profileUnavailable": "This agent's policy can't be changed in this pod yet.", + "profileUnavailableSave": "This agent's policy can't be changed in this pod yet, so nothing was saved.", "loadError": "Could not load policy data.", "saveError": "Could not save changes.", "saved": "Saved" diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index a74df2e5d7..dff99eddb5 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2438,6 +2438,8 @@ "turnsPerDay": "托管运行次数/天", "userRunCeiling": "运行次数/天", "contextInvalid": "请检查上下文限制。", + "profileUnavailable": "此智能体在这个 Pod 中的策略暂时无法修改。", + "profileUnavailableSave": "此智能体在这个 Pod 中的策略暂时无法修改,更改未保存。", "loadError": "无法加载策略数据。", "saveError": "无法保存更改。", "saved": "已保存" diff --git a/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx b/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx index 69e4ef420a..a14de9f2b7 100644 --- a/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx @@ -53,6 +53,28 @@ it('writes approval and context policy through the shared settings hook', async })); }); +it('saves an edit immediately on the first click and confirms it', async () => { + renderPolicy(); + fireEvent.change(screen.getByLabelText('Allowed tools'), { target: { value: 'commonly, Read' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save policy' })); + + expect(update).toHaveBeenCalledTimes(1); + expect(update).toHaveBeenCalledWith(expect.objectContaining({ + toolPolicy: { allowed: ['commonly', 'Read'], blocked: [], requireApproval: ['Bash'] }, + })); + expect(await screen.findByRole('status')).toHaveTextContent('Saved'); +}); + +it('explains why an installation without a profile cannot save policy', async () => { + renderPolicy({ ...installation, profile: null }); + expect(screen.getByRole('alert')).toHaveTextContent("This agent's policy can't be changed in this pod yet."); + fireEvent.change(screen.getByLabelText('Allowed tools'), { target: { value: 'Read' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save policy' })); + + expect(update).not.toHaveBeenCalled(); + expect(await screen.findByRole('alert')).toHaveTextContent('nothing was saved'); +}); + it('writes only the chosen trust mode and edits memory with its revision', async () => { renderPolicy(); await screen.findByDisplayValue('Original'); diff --git a/frontend/src/v2/agents/V2AgentPolicy.tsx b/frontend/src/v2/agents/V2AgentPolicy.tsx index 78720c4529..debfe64854 100644 --- a/frontend/src/v2/agents/V2AgentPolicy.tsx +++ b/frontend/src/v2/agents/V2AgentPolicy.tsx @@ -93,6 +93,9 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u }, [agentName, instanceId, memoryUrl, t]); const savePolicy = async () => { + if (!installation?.profile) { + setFeedback({ group: 'policy', kind: 'error', text: t('agentPolicy.profileUnavailableSave') }); return; + } if (!Number.isInteger(maxTokens) || maxTokens < 1 || !Number.isInteger(compactionThreshold) || compactionThreshold < 1 || compactionThreshold > maxTokens || !Number.isInteger(summaryHours) || summaryHours < 1) { @@ -149,6 +152,9 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u return

{t('agentPolicy.title')}

+ {!installation?.profile && feedback?.group !== 'policy' && ( +

{t('agentPolicy.profileUnavailable')}

+ )}

{t('agentPolicy.approvals')}

{t('agentPolicy.commaHint')}

@@ -168,7 +174,7 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u
- + {groupFeedback('policy')} {canSetTrust &&