diff --git a/frontend/src/lib/utils_draft_deploy.test.ts b/frontend/src/lib/utils_draft_deploy.test.ts index 5f503989c8..6eddfe093d 100644 --- a/frontend/src/lib/utils_draft_deploy.test.ts +++ b/frontend/src/lib/utils_draft_deploy.test.ts @@ -122,6 +122,25 @@ describe('deployDraft preserves on_behalf_of', () => { }) ) }) + + it('agent: claims the deployed identity, which its draft never carries', async () => { + vi.mocked(ResourceService.getResource).mockResolvedValueOnce({ + path: 'f/support/triage_agent', + resource_type: 'ai_agent', + value: { system_prompt: 'deployed', on_behalf_of: 'u/alice' }, + draft: { path: 'f/support/triage_agent', args: { system_prompt: 'drafted' } } + } as any) + + expect(await deployDraft('resource', 'f/support/triage_agent', 'ws')).toEqual({ success: true }) + expect(ResourceService.updateResource).toHaveBeenCalledWith( + expect.objectContaining({ + requestBody: expect.objectContaining({ + value: { system_prompt: 'drafted', on_behalf_of: 'u/alice' }, + preserve_on_behalf_of: true + }) + }) + ) + }) }) // The resource branch reads the item again when the deploy lands, and falls back to the deployed diff --git a/frontend/src/lib/utils_draft_deploy.ts b/frontend/src/lib/utils_draft_deploy.ts index 31b973f9a5..3614b344e2 100644 --- a/frontend/src/lib/utils_draft_deploy.ts +++ b/frontend/src/lib/utils_draft_deploy.ts @@ -233,8 +233,14 @@ function canonicalizeDraftDiffValue(kind: DraftKind, raw: any, isDraft: boolean) if (kind === 'resource') { // draft: { args, description, resource_type, labels, wsSpecific } // deployed row: { value, description, resource_type, labels, ws_specific } + // An agent's identity is in its deployed value and never in a draft, which is not an edit. + let value = (isDraft ? raw.args : raw.value) ?? {} + if (raw.resource_type === 'ai_agent') { + const { on_behalf_of: _, ...rest } = value + value = rest + } return { - value: (isDraft ? raw.args : raw.value) ?? {}, + value, description: raw.description ?? '', resource_type: raw.resource_type ?? undefined, labels: raw.labels ?? undefined, @@ -639,12 +645,18 @@ export async function deployDraft( } }) } else { + // As for an app: an agent draft carries no identity, so the deployed one is kept (the + // backend resets it to the deploying user without the flag, gated by + // can_preserve_on_behalf_of). + const agentObo = + deployed.resource_type === 'ai_agent' ? deployed.value?.on_behalf_of : undefined await ResourceService.updateResource({ workspace, path, requestBody: { path: d.path ?? path, - value: d.args ?? {}, + value: agentObo ? { ...(d.args ?? {}), on_behalf_of: agentObo } : (d.args ?? {}), + preserve_on_behalf_of: agentObo ? true : undefined, description: d.description ?? '', labels: d.labels, ws_specific: d.wsSpecific