Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,8 @@
"turnsPerDay": "托管运行次数/天",
"userRunCeiling": "运行次数/天",
"contextInvalid": "请检查上下文限制。",
"profileUnavailable": "此智能体在这个 Pod 中的策略暂时无法修改。",
"profileUnavailableSave": "此智能体在这个 Pod 中的策略暂时无法修改,更改未保存。",
"loadError": "无法加载策略数据。",
"saveError": "无法保存更改。",
"saved": "已保存"
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/v2/__tests__/V2AgentPolicy.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/v2/agents/V2AgentPolicy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const V2AgentPolicy: React.FC<Props> = ({ 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) {
Expand Down Expand Up @@ -149,6 +152,9 @@ const V2AgentPolicy: React.FC<Props> = ({ agentName, instanceId, installation, u

return <section className="v2-agent-policy" aria-labelledby="agent-policy-title">
<h2 id="agent-policy-title">{t('agentPolicy.title')}</h2>
{!installation?.profile && feedback?.group !== 'policy' && (
<p role="alert" className="v2-agent-policy__error">{t('agentPolicy.profileUnavailable')}</p>
)}
<div className="v2-agent-policy__group">
<h3>{t('agentPolicy.approvals')}</h3>
<p className="v2-agent-policy__muted">{t('agentPolicy.commaHint')}</p>
Expand All @@ -168,7 +174,7 @@ const V2AgentPolicy: React.FC<Props> = ({ agentName, instanceId, installation, u
<label><input type="checkbox" checked={includeSkills} onChange={(event) => setIncludeSkills(event.target.checked)} />{t('agentPolicy.includeSkills')}</label>
<label><input type="checkbox" checked={includeSummaries} onChange={(event) => setIncludeSummaries(event.target.checked)} />{t('agentPolicy.includeSummaries')}</label>
</div>
<button type="button" disabled={busy || !installation?.profile} onClick={savePolicy}>{t('agentPolicy.savePolicy')}</button>
<button type="button" disabled={busy} onClick={savePolicy}>{t('agentPolicy.savePolicy')}</button>
{groupFeedback('policy')}
</div>
{canSetTrust && <div className="v2-agent-policy__group">
Expand Down
Loading