feat: deploying an agent draft from review keeps its deployed identity

As for an app: an agent draft carries no identity, so the review page claims the deployed one
back, which the backend honours for an admin or wm_deployers member. The draft diff leaves the
deployed identity out, since a draft never holds one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-25 14:38:13 +02:00
co-authored by Claude Opus 5.5
parent b04de3eeda
commit 70824ea11b
2 changed files with 33 additions and 2 deletions
@@ -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
+14 -2
View File
@@ -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