From 75ebf9622ef2319377527bba37214d6a44332288 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:52:23 -0700 Subject: [PATCH 1/6] Remove unenforced agent settings and preserve OpenClaw identity Co-authored-by: kai Agent-Model: codex/gpt-6.1-sol/medium --- .../models/Session.contextSettings.test.js | 11 +++ ...gistry.agent-config-installer-gate.test.js | 56 ++++++++++++++ backend/models/Session.ts | 17 ----- backend/routes/registry/agent-config.ts | 36 ++++++++- frontend/src/i18n/locales/en.json | 17 ----- frontend/src/i18n/locales/zh-CN.json | 17 ----- .../src/v2/__tests__/V2AgentPolicy.test.tsx | 40 +++------- .../v2/__tests__/V2AgentSettingsPage.test.tsx | 73 ++++++++++++++++--- .../src/v2/agents/V2AgentIdentitySection.tsx | 19 ++--- frontend/src/v2/agents/V2AgentPolicy.tsx | 71 +----------------- frontend/src/v2/agents/useAgentSettings.ts | 3 - frontend/src/v2/agents/v2-agent-policy.css | 5 -- frontend/src/v2/agents/v2-agent-settings.css | 6 +- 13 files changed, 188 insertions(+), 183 deletions(-) create mode 100644 backend/__tests__/unit/models/Session.contextSettings.test.js diff --git a/backend/__tests__/unit/models/Session.contextSettings.test.js b/backend/__tests__/unit/models/Session.contextSettings.test.js new file mode 100644 index 0000000000..e1924847b8 --- /dev/null +++ b/backend/__tests__/unit/models/Session.contextSettings.test.js @@ -0,0 +1,11 @@ +const Session = require('../../../models/Session'); + +test('session schema has no unused context settings or compaction predicate', () => { + expect(Session.schema.path('contextSettings')).toBeUndefined(); + expect(Session.schema.methods.shouldCompact).toBeUndefined(); + const session = new Session({ + sessionKey: 'agent:pod:session', + podId: '111111111111111111111111', + }); + expect(session.toObject()).not.toHaveProperty('contextSettings'); +}); diff --git a/backend/__tests__/unit/routes/registry.agent-config-installer-gate.test.js b/backend/__tests__/unit/routes/registry.agent-config-installer-gate.test.js index adfb1395bf..94689e8d22 100644 --- a/backend/__tests__/unit/routes/registry.agent-config-installer-gate.test.js +++ b/backend/__tests__/unit/routes/registry.agent-config-installer-gate.test.js @@ -42,6 +42,11 @@ jest.mock('../../../services/runtimeIdentityOwnership', () => ({ ...jest.requireActual('../../../services/runtimeIdentityOwnership'), ownsRuntimeIdentity: jest.fn().mockResolvedValue({ owned: true }), })); +jest.mock('../../../services/agentProvisionerService', () => ({ + ...jest.requireActual('../../../services/agentProvisionerService'), + readOpenClawIdentityFile: jest.fn(), + writeWorkspaceIdentityFile: jest.fn().mockResolvedValue(undefined), +})); const Pod = require('../../../models/Pod'); const { AgentInstallation } = require('../../../models/AgentRegistry'); @@ -50,6 +55,7 @@ const User = require('../../../models/User'); const AgentIdentityService = require('../../../services/agentIdentityService'); const { ownsRuntimeIdentity } = require('../../../services/runtimeIdentityOwnership'); const { podHasOutsiderWritableGrant } = require('../../../services/nativeOutsiderGrantPolicy'); +const { readOpenClawIdentityFile, writeWorkspaceIdentityFile } = require('../../../services/agentProvisionerService'); const registryRoutes = require('../../../routes/registry'); const app = express(); @@ -98,6 +104,8 @@ describe('agent config PATCH — installer gate', () => { jest.spyOn(AgentIdentityService, 'renameAgentDisplayName').mockImplementation(async (_name, _instanceId, label) => ({ displayName: label, mirrorSynced: true })); ownsRuntimeIdentity.mockResolvedValue({ owned: true }); podHasOutsiderWritableGrant.mockResolvedValue(false); + readOpenClawIdentityFile.mockResolvedValue(''); + writeWorkspaceIdentityFile.mockResolvedValue(undefined); setCallerRole('user'); }); afterEach(() => jest.restoreAllMocks()); @@ -233,6 +241,54 @@ describe('agent config PATCH — installer gate', () => { ); }); + it('keeps the existing OpenClaw persona in IDENTITY.md on a name-only edit', async () => { + const primary = installation({ installedBy: MEMBER, displayName: 'Old name' }); + setPod(); + AgentInstallation.findOne.mockResolvedValue(primary); + AgentInstallation.find.mockResolvedValue([primary]); + readOpenClawIdentityFile.mockResolvedValue('# IDENTITY.md\n\n- **Name:** Old name\n- **Vibe:** Precise\n- **Domain:** Research\n\n## Notes\n\nKeep detail\n'); + + const res = await request(app) + .patch('/api/registry/pods/pod-1/agents/openclaw') + .send({ instanceId: 'curator', displayName: 'New name' }); + + expect(res.status).toBe(200); + await new Promise((resolve) => setImmediate(resolve)); + expect(readOpenClawIdentityFile).toHaveBeenCalledWith('curator'); + expect(writeWorkspaceIdentityFile).toHaveBeenCalledWith('curator', '# IDENTITY.md\n\n- **Name:** New name\n- **Vibe:** Precise\n- **Domain:** Research\n\n## Notes\n\nKeep detail\n'); + expect(AgentProfile.updateMany).toHaveBeenCalledWith( + expect.any(Object), + { $set: { name: 'New name' } }, + { runValidators: true }, + ); + }); + + it('keeps the runtime Name on an OpenClaw Persona-only edit', async () => { + const primary = installation({ installedBy: MEMBER, displayName: 'Scout the researcher' }); + setPod(); + AgentInstallation.findOne.mockResolvedValue(primary); + AgentInstallation.find.mockResolvedValue([primary]); + readOpenClawIdentityFile.mockResolvedValue('# IDENTITY.md\n\n- **Name:** Scout the researcher\n- **Vibe:** Warm\n'); + + const persona = { tone: 'professional', specialties: ['research'], boundaries: [], customInstructions: 'Be precise' }; + const res = await request(app) + .patch('/api/registry/pods/pod-1/agents/openclaw') + .send({ instanceId: 'curator', persona }); + + expect(res.status).toBe(200); + await new Promise((resolve) => setImmediate(resolve)); + expect(AgentIdentityService.renameAgentDisplayName).not.toHaveBeenCalled(); + expect(primary.displayName).toBe('Scout the researcher'); + expect(writeWorkspaceIdentityFile).toHaveBeenCalledWith( + 'curator', + expect.stringContaining('- **Name:** Scout the researcher\n'), + ); + expect(writeWorkspaceIdentityFile).toHaveBeenCalledWith( + 'curator', + expect.stringContaining('- **Domain:** research\n'), + ); + }); + it('refuses an identity rename by an installer who shares that identity', async () => { const primary = installation({ installedBy: MEMBER }); const peer = installation({ podId: 'pod-2', installedBy: 'other-installer' }); diff --git a/backend/models/Session.ts b/backend/models/Session.ts index dffd61ee0f..1499316ec3 100644 --- a/backend/models/Session.ts +++ b/backend/models/Session.ts @@ -41,16 +41,9 @@ export interface ISession extends Document { }; state: SessionState; lastActivityAt: Date; - contextSettings: { - maxTokens: number; - compactionThreshold: number; - includeMemory: boolean; - includeSkills: boolean; - }; createdAt: Date; updatedAt: Date; appendTranscript(entry: ITranscriptEntry): Promise; - shouldCompact(): boolean; shouldReset(): boolean; compact(summaryContent: string): Promise; reset(): Promise; @@ -114,12 +107,6 @@ const SessionSchema = new Schema( default: 'active', }, lastActivityAt: { type: Date, default: Date.now }, - contextSettings: { - maxTokens: { type: Number, default: 8000 }, - compactionThreshold: { type: Number, default: 6000 }, - includeMemory: { type: Boolean, default: true }, - includeSkills: { type: Boolean, default: true }, - }, }, { timestamps: true }, ); @@ -159,10 +146,6 @@ SessionSchema.methods.appendTranscript = async function (entry: ITranscriptEntry return this.save(); }; -SessionSchema.methods.shouldCompact = function (): boolean { - return this.tokenUsage.context >= this.contextSettings.compactionThreshold; -}; - SessionSchema.methods.shouldReset = function (): boolean { const now = new Date(); diff --git a/backend/routes/registry/agent-config.ts b/backend/routes/registry/agent-config.ts index f9d6ef407b..ad16487548 100644 --- a/backend/routes/registry/agent-config.ts +++ b/backend/routes/registry/agent-config.ts @@ -7,6 +7,7 @@ const AgentProfile = require('../../models/AgentProfile'); const AgentIdentityService = require('../../services/agentIdentityService'); const Pod = require('../../models/Pod'); const { + readOpenClawIdentityFile, writeWorkspaceIdentityFile, syncOpenClawSkills, } = require('../../services/agentProvisionerService'); @@ -421,10 +422,37 @@ agentConfigRouter.patch('/pods/:podId/agents/:name', auth, async (req: any, res: ); if ((persona !== undefined || displayName) && normalizedInstanceId && name.toLowerCase() === 'openclaw') { - const identityContent = buildIdentityContent(effectiveDisplayName || normalizedInstanceId, persona || {}); - writeWorkspaceIdentityFile(normalizedInstanceId, identityContent).catch((err: any) => { - console.warn('[registry] Failed to sync IDENTITY.md for', normalizedInstanceId, err.message); - }); + if (persona !== undefined) { + // Persona-only edits do not rename the portable identity. Retain + // the runtime's current Name; a missing file falls back to the + // installation's saved display name before the instance id. + readOpenClawIdentityFile(normalizedInstanceId).then((existingContent: string) => { + const existingName = existingContent.match(/^- \*\*Name:\*\* ([^\r\n]*)/m)?.[1]?.trim(); + const identityName = effectiveDisplayName || existingName || installation.displayName + || normalizedInstanceId; + return writeWorkspaceIdentityFile( + normalizedInstanceId, + buildIdentityContent(identityName, persona), + ); + }).catch((err: any) => { + console.warn('[registry] Failed to sync IDENTITY.md for', normalizedInstanceId, err.message); + }); + } else { + // A name-only edit must not rebuild the file with an empty persona: + // OpenClaw reads its existing vibe, domain and notes from IDENTITY.md. + readOpenClawIdentityFile(normalizedInstanceId).then((identityContent: string) => { + const renamed = identityContent.replace( + /^(- \*\*Name:\*\* )[^\r\n]*/m, + (_line: string, prefix: string) => `${prefix}${effectiveDisplayName || normalizedInstanceId}`, + ); + if (renamed !== identityContent) { + return writeWorkspaceIdentityFile(normalizedInstanceId, renamed); + } + return undefined; + }).catch((err: any) => { + console.warn('[registry] Failed to sync IDENTITY.md name for', normalizedInstanceId, err.message); + }); + } } } } catch (projectionError) { diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 552be1f348..2fd230163a 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2364,7 +2364,6 @@ "agentSettings": { "identityTitle": "Identity", "displayName": "Display name", - "instructions": "Instructions", "persona": "Persona", "tone": "Tone", "toneOptions": { "friendly": "Friendly", "professional": "Professional", "casual": "Casual", "formal": "Formal", "technical": "Technical" }, @@ -2448,19 +2447,6 @@ }, "agentPolicy": { "title": "Policy", - "approvals": "Tool approvals", - "commaHint": "Separate tool names with commas.", - "allowed": "Allowed tools", - "blocked": "Blocked tools", - "requireApproval": "Require approval", - "context": "Context", - "maxTokens": "Maximum tokens", - "compactionThreshold": "Compact after", - "summaryHours": "Summary window (hours)", - "includeMemory": "Include memory", - "includeSkills": "Include skills", - "includeSummaries": "Include summaries", - "savePolicy": "Save policy", "trust": "Trust mode", "repoAccess": "Repository access", "chooseAccess": "Choose access", @@ -2475,9 +2461,6 @@ "quotas": "Quotas", "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 a390d68913..0cd2cb0782 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2355,7 +2355,6 @@ "agentSettings": { "identityTitle": "身份", "displayName": "显示名称", - "instructions": "指令", "persona": "角色设定", "tone": "语气", "toneOptions": { "friendly": "友好", "professional": "专业", "casual": "随和", "formal": "正式", "technical": "技术型" }, @@ -2439,19 +2438,6 @@ }, "agentPolicy": { "title": "策略", - "approvals": "工具审批", - "commaHint": "用逗号分隔工具名称。", - "allowed": "允许的工具", - "blocked": "禁用的工具", - "requireApproval": "需要审批", - "context": "上下文", - "maxTokens": "最大 Token 数", - "compactionThreshold": "压缩阈值", - "summaryHours": "摘要范围(小时)", - "includeMemory": "包含记忆", - "includeSkills": "包含技能", - "includeSummaries": "包含摘要", - "savePolicy": "保存策略", "trust": "信任模式", "repoAccess": "仓库访问权限", "chooseAccess": "选择权限", @@ -2466,9 +2452,6 @@ "quotas": "配额", "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 a14de9f2b7..505d1df6ef 100644 --- a/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentPolicy.test.tsx @@ -40,39 +40,19 @@ beforeEach(() => { axios.patch.mockResolvedValue({ data: { section: 'long_term', content: 'Changed' } }); }); -it('writes approval and context policy through the shared settings hook', async () => { +it('hides stored approval and context settings that no runtime enforces', async () => { renderPolicy(); await screen.findByDisplayValue('Original'); - fireEvent.change(screen.getByLabelText('Allowed tools'), { target: { value: 'commonly, Read, Read' } }); - fireEvent.click(screen.getByLabelText('Include summaries')); - fireEvent.click(screen.getByRole('button', { name: 'Save policy' })); - await waitFor(() => expect(update).toHaveBeenCalledWith({ - toolPolicy: { allowed: ['commonly', 'Read'], blocked: [], requireApproval: ['Bash'] }, - contextPolicy: { maxTokens: 8000, compactionThreshold: 6000, summaryHours: 24, - includeMemory: true, includeSkills: true, includeSummaries: false }, - })); -}); - -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(screen.queryByText('Tool approvals')).not.toBeInTheDocument(); + expect(screen.queryByText('Context')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Maximum tokens')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Compact after')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Summary window (hours)')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Include memory')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Include skills')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Include summaries')).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Save policy' })).not.toBeInTheDocument(); 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 () => { diff --git a/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx b/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx index 95e209d7df..619ebbec1a 100644 --- a/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx @@ -140,23 +140,78 @@ test('identity edits go through the shared installation PATCH with the exact ins } }); if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: { name: 'maya-claude', instanceId: 'default', displayName: 'Maya', - profile: { instructions: 'Old', persona: { tone: 'friendly', specialties: ['writing'] } }, + profile: { + instructions: 'Old', persona: { tone: 'friendly', specialties: ['writing'] }, + toolPolicy: { requireApproval: ['search'] }, contextPolicy: { maxTokens: 8000 }, + }, } } }); return Promise.resolve({ data: [] }); }); axios.patch.mockResolvedValue({ data: { success: true } }); renderPage(); - await screen.findByRole('button', { name: 'Save identity' }); + const save = await screen.findByRole('button', { name: 'Save identity' }); + expect(save).toBeDisabled(); fireEvent.change(screen.getByRole('textbox', { name: 'Display name' }), { target: { value: 'Maya 2' } }); - fireEvent.change(screen.getByRole('textbox', { name: 'Instructions' }), { target: { value: 'New instructions' } }); - fireEvent.change(screen.getByRole('combobox', { name: 'Tone' }), { target: { value: 'technical' } }); - fireEvent.click(screen.getByRole('button', { name: 'Save identity' })); + expect(screen.queryByRole('textbox', { name: 'Instructions' })).not.toBeInTheDocument(); + expect(screen.queryByRole('group', { name: 'Persona' })).not.toBeInTheDocument(); + expect(screen.queryByRole('combobox', { name: 'Tone' })).not.toBeInTheDocument(); + expect(save).toBeEnabled(); + fireEvent.click(save); await waitFor(() => expect(axios.patch).toHaveBeenCalledWith( '/api/registry/pods/pod-1/agents/maya-claude', - expect.objectContaining({ - instanceId: 'default', displayName: 'Maya 2', instructions: 'New instructions', - persona: expect.objectContaining({ tone: 'technical', specialties: ['writing'] }), - }), + { instanceId: 'default', displayName: 'Maya 2' }, + )); +}); + +test('OpenClaw identity keeps the live Persona editor and saves its changes', async () => { + axios.get.mockImplementation((url) => { + if (url.startsWith('/api/agent-memory/')) return Promise.resolve({ data: { + displayName: 'Scout', pods: [{ id: 'pod-1', name: 'Workspace' }], + } }); + if (url.endsWith('/manage-settings')) return Promise.resolve({ data: { + writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }], + } }); + if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: { + name: 'openclaw', instanceId: 'scout', displayName: 'Scout', + profile: { persona: { tone: 'friendly', specialties: ['writing'], customInstructions: 'Stay clear' } }, + } } }); + return Promise.resolve({ data: [] }); + }); + axios.patch.mockResolvedValue({ data: { success: true } }); + renderPage('/v2/agents/openclaw/scout'); + expect(await screen.findByRole('group', { name: 'Persona' })).toBeInTheDocument(); + fireEvent.change(screen.getByRole('textbox', { name: 'Persona notes' }), { target: { value: 'Keep detail' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save identity' })); + await waitFor(() => expect(axios.patch).toHaveBeenCalledWith( + '/api/registry/pods/pod-1/agents/openclaw', + { instanceId: 'scout', persona: { + tone: 'friendly', specialties: ['writing'], boundaries: [], customInstructions: 'Keep detail', + } }, + )); +}); + +test('OpenClaw name-only edits leave Persona out of the PATCH', async () => { + axios.get.mockImplementation((url) => { + if (url.startsWith('/api/agent-memory/')) return Promise.resolve({ data: { + displayName: 'Scout', pods: [{ id: 'pod-1', name: 'Workspace' }], + } }); + if (url.endsWith('/manage-settings')) return Promise.resolve({ data: { + writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }], + } }); + if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: { + name: 'openclaw', instanceId: 'scout', displayName: 'Scout', + profile: { persona: { tone: 'friendly', specialties: ['writing'] } }, + } } }); + return Promise.resolve({ data: [] }); + }); + axios.patch.mockResolvedValue({ data: { success: true } }); + renderPage('/v2/agents/openclaw/scout'); + expect(await screen.findByRole('group', { name: 'Persona' })).toBeInTheDocument(); + fireEvent.change(screen.getByRole('textbox', { name: 'Display name' }), { target: { value: 'Scout 2' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save identity' })); + await waitFor(() => expect(axios.patch).toHaveBeenCalledWith( + '/api/registry/pods/pod-1/agents/openclaw', + { instanceId: 'scout', displayName: 'Scout 2' }, )); }); diff --git a/frontend/src/v2/agents/V2AgentIdentitySection.tsx b/frontend/src/v2/agents/V2AgentIdentitySection.tsx index 8fb3e38fd0..a5359922b8 100644 --- a/frontend/src/v2/agents/V2AgentIdentitySection.tsx +++ b/frontend/src/v2/agents/V2AgentIdentitySection.tsx @@ -31,9 +31,11 @@ const asPersona = (value: unknown): PersonaFields => { const V2AgentIdentitySection: React.FC = ({ installation, update }) => { const { t } = useTranslation(); const profile = installation.profile; + const supportsPersona = installation.name.toLowerCase() === 'openclaw'; const [displayName, setDisplayName] = useState(installation.displayName || ''); - const [instructions, setInstructions] = useState(profile?.instructions || ''); const [persona, setPersona] = useState(() => asPersona(profile?.persona)); + const personaChanged = supportsPersona && Object.entries(asPersona(profile?.persona)) + .some(([key, value]) => persona[key as keyof PersonaFields] !== value); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); @@ -68,7 +70,6 @@ const V2AgentIdentitySection: React.FC = ({ installation, update }) => { useEffect(() => { setDisplayName(installation.displayName || ''); - setInstructions(profile?.instructions || ''); setPersona(asPersona(profile?.persona)); setError(null); setSaved(false); @@ -87,13 +88,12 @@ const V2AgentIdentitySection: React.FC = ({ installation, update }) => { try { await update({ ...(name !== installation.displayName ? { displayName: name } : {}), - instructions, - persona: { + ...(personaChanged ? { persona: { tone: persona.tone, specialties: commaList(persona.specialties), boundaries: commaList(persona.boundaries), customInstructions: persona.customInstructions, - }, + } } : {}), }); setSaved(true); } catch (requestError) { @@ -122,10 +122,7 @@ const V2AgentIdentitySection: React.FC = ({ installation, update }) => { -