fix: unify agent config save across mission control and gateway
This commit is contained in:
parent
e3c33a6a0c
commit
914a6c4cd0
|
|
@ -44,12 +44,12 @@ export async function GET(
|
|||
}
|
||||
|
||||
/**
|
||||
* PUT /api/agents/[id] - Update agent config with optional gateway write-back
|
||||
* PUT /api/agents/[id] - Update agent config with unified MC + gateway save
|
||||
*
|
||||
* Body: {
|
||||
* role?: string
|
||||
* gateway_config?: object - OpenClaw agent config fields to update
|
||||
* write_to_gateway?: boolean - If true, also write to openclaw.json
|
||||
* write_to_gateway?: boolean - Defaults to true when gateway_config exists
|
||||
* }
|
||||
*/
|
||||
export async function PUT(
|
||||
|
|
@ -86,66 +86,88 @@ export async function PUT(
|
|||
newConfig = { ...existingConfig, ...gateway_config }
|
||||
}
|
||||
|
||||
// Build update
|
||||
const fields: string[] = ['updated_at = ?']
|
||||
const values: any[] = [now]
|
||||
|
||||
if (role !== undefined) {
|
||||
fields.push('role = ?')
|
||||
values.push(role)
|
||||
const shouldWriteToGateway = Boolean(
|
||||
gateway_config &&
|
||||
(write_to_gateway === undefined || write_to_gateway === null || write_to_gateway === true)
|
||||
)
|
||||
const openclawId = existingConfig.openclawId || agent.name.toLowerCase().replace(/\s+/g, '-')
|
||||
const getWriteBackPayload = (source: Record<string, any>) => {
|
||||
const writeBack: any = { id: openclawId }
|
||||
if (source.model) writeBack.model = source.model
|
||||
if (source.identity) writeBack.identity = source.identity
|
||||
if (source.sandbox) writeBack.sandbox = source.sandbox
|
||||
if (source.tools) writeBack.tools = source.tools
|
||||
if (source.subagents) writeBack.subagents = source.subagents
|
||||
if (source.memorySearch) writeBack.memorySearch = source.memorySearch
|
||||
return writeBack
|
||||
}
|
||||
|
||||
if (gateway_config) {
|
||||
fields.push('config = ?')
|
||||
values.push(JSON.stringify(newConfig))
|
||||
}
|
||||
|
||||
values.push(agent.id, workspaceId)
|
||||
db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ? AND workspace_id = ?`).run(...values)
|
||||
|
||||
// Write back to openclaw.json if requested
|
||||
if (write_to_gateway && gateway_config) {
|
||||
// Unified save: gateway first, then DB. If DB fails after gateway write, attempt rollback.
|
||||
if (shouldWriteToGateway) {
|
||||
try {
|
||||
const openclawId = existingConfig.openclawId || agent.name.toLowerCase().replace(/\s+/g, '-')
|
||||
|
||||
// Build the config to write back (full OpenClaw format)
|
||||
const writeBack: any = { id: openclawId }
|
||||
if (gateway_config.model) writeBack.model = gateway_config.model
|
||||
if (gateway_config.identity) writeBack.identity = gateway_config.identity
|
||||
if (gateway_config.sandbox) writeBack.sandbox = gateway_config.sandbox
|
||||
if (gateway_config.tools) writeBack.tools = gateway_config.tools
|
||||
if (gateway_config.subagents) writeBack.subagents = gateway_config.subagents
|
||||
if (gateway_config.memorySearch) writeBack.memorySearch = gateway_config.memorySearch
|
||||
|
||||
await writeAgentToConfig(writeBack)
|
||||
|
||||
const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'
|
||||
logAuditEvent({
|
||||
action: 'agent_config_writeback',
|
||||
actor: auth.user.username,
|
||||
actor_id: auth.user.id,
|
||||
target_type: 'agent',
|
||||
target_id: agent.id,
|
||||
detail: { agent_name: agent.name, openclaw_id: openclawId, fields: Object.keys(gateway_config) },
|
||||
ip_address: ipAddress,
|
||||
})
|
||||
await writeAgentToConfig(getWriteBackPayload(gateway_config))
|
||||
} catch (err: any) {
|
||||
// Config update succeeded in DB but gateway write failed
|
||||
return NextResponse.json({
|
||||
warning: `Agent updated in MC but gateway write failed: ${err.message}`,
|
||||
agent: { ...agent, config: newConfig, role: role || agent.role, updated_at: now },
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: `Save failed: unable to update gateway config: ${err.message}` },
|
||||
{ status: 502 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Build update
|
||||
const fields: string[] = ['updated_at = ?']
|
||||
const values: any[] = [now]
|
||||
|
||||
if (role !== undefined) {
|
||||
fields.push('role = ?')
|
||||
values.push(role)
|
||||
}
|
||||
|
||||
if (gateway_config) {
|
||||
fields.push('config = ?')
|
||||
values.push(JSON.stringify(newConfig))
|
||||
}
|
||||
|
||||
values.push(agent.id, workspaceId)
|
||||
db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ? AND workspace_id = ?`).run(...values)
|
||||
} catch (err: any) {
|
||||
if (shouldWriteToGateway) {
|
||||
try {
|
||||
// Best-effort rollback to preserve consistency if DB update fails after gateway write.
|
||||
await writeAgentToConfig(getWriteBackPayload(existingConfig))
|
||||
} catch (rollbackErr: any) {
|
||||
logger.error({ err: rollbackErr, agent: agent.name }, 'Failed to rollback gateway config after DB failure')
|
||||
return NextResponse.json(
|
||||
{ error: `Save failed after gateway update and rollback failed: ${err.message}` },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
return NextResponse.json({ error: `Save failed: ${err.message}` }, { status: 500 })
|
||||
}
|
||||
|
||||
if (shouldWriteToGateway) {
|
||||
const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'
|
||||
logAuditEvent({
|
||||
action: 'agent_config_writeback',
|
||||
actor: auth.user.username,
|
||||
actor_id: auth.user.id,
|
||||
target_type: 'agent',
|
||||
target_id: agent.id,
|
||||
detail: { agent_name: agent.name, openclaw_id: openclawId, fields: Object.keys(gateway_config || {}) },
|
||||
ip_address: ipAddress,
|
||||
})
|
||||
}
|
||||
|
||||
// Log activity
|
||||
db_helpers.logActivity(
|
||||
'agent_config_updated',
|
||||
'agent',
|
||||
agent.id,
|
||||
auth.user.username,
|
||||
`Config updated for agent ${agent.name}${write_to_gateway ? ' (+ gateway)' : ''}`,
|
||||
{ fields: Object.keys(gateway_config || {}), write_to_gateway },
|
||||
`Config updated for agent ${agent.name}${shouldWriteToGateway ? ' (+ gateway)' : ''}`,
|
||||
{ fields: Object.keys(gateway_config || {}), write_to_gateway: shouldWriteToGateway },
|
||||
workspaceId
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1337,7 +1337,7 @@ export function ConfigTab({
|
|||
})
|
||||
}
|
||||
|
||||
const handleSave = async (writeToGateway: boolean = false) => {
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
setError(null)
|
||||
try {
|
||||
|
|
@ -1352,12 +1352,11 @@ export function ConfigTab({
|
|||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
gateway_config: showJson ? JSON.parse(jsonInput) : config,
|
||||
write_to_gateway: writeToGateway,
|
||||
write_to_gateway: true,
|
||||
}),
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to save')
|
||||
if (data.warning) setError(data.warning)
|
||||
setEditing(false)
|
||||
onSave()
|
||||
} catch (err: any) {
|
||||
|
|
@ -1759,18 +1758,11 @@ export function ConfigTab({
|
|||
{editing && (
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
onClick={() => handleSave(false)}
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="flex-1 bg-primary text-primary-foreground py-2 rounded-md hover:bg-primary/90 disabled:opacity-50 transition-smooth"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save to MC'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSave(true)}
|
||||
disabled={saving}
|
||||
className="flex-1 bg-green-600 text-white py-2 rounded-md hover:bg-green-700 disabled:opacity-50 transition-smooth"
|
||||
>
|
||||
Save to Gateway
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue