mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 03:10:38 +09:00
fix(ui): persist cleared agent env bindings on save
Agent configuration edits already had an API path for replacing the full adapterConfig, but the edit form was still sending merge-style patches. That meant clearing the last environment variable serialized as undefined, the key disappeared from JSON, and the server merged the old env bindings back into the saved config. Build adapter config save payloads as full replacement patches, strip undefined keys before send, and reuse the existing replaceAdapterConfig contract so explicit clears persist correctly. Add regression coverage for the cleared-env case and for adapter-type changes that still need to preserve adapter-agnostic fields. Fixes #3179
This commit is contained in:
parent
6d63a4df45
commit
44d94d0add
3 changed files with 185 additions and 55 deletions
108
ui/src/lib/agent-config-patch.test.ts
Normal file
108
ui/src/lib/agent-config-patch.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Agent } from "@paperclipai/shared";
|
||||
import { buildAgentUpdatePatch, type AgentConfigOverlay } from "./agent-config-patch";
|
||||
|
||||
function makeAgent(): Agent {
|
||||
return {
|
||||
id: "agent-1",
|
||||
companyId: "company-1",
|
||||
name: "Agent",
|
||||
role: "engineer",
|
||||
title: "Engineer",
|
||||
icon: null,
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
capabilities: null,
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {
|
||||
model: "claude-sonnet-4-6",
|
||||
env: {
|
||||
OPENAI_API_KEY: {
|
||||
type: "plain",
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
promptTemplate: "Work the issue.",
|
||||
},
|
||||
runtimeConfig: {
|
||||
heartbeat: {
|
||||
enabled: true,
|
||||
intervalSec: 300,
|
||||
},
|
||||
},
|
||||
budgetMonthlyCents: 0,
|
||||
spentMonthlyCents: 0,
|
||||
pauseReason: null,
|
||||
pausedAt: null,
|
||||
lastHeartbeatAt: null,
|
||||
createdAt: new Date("2026-01-01T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-01-01T00:00:00.000Z"),
|
||||
urlKey: "agent",
|
||||
permissions: {
|
||||
canCreateAgents: false,
|
||||
},
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
function makeOverlay(patch?: Partial<AgentConfigOverlay>): AgentConfigOverlay {
|
||||
return {
|
||||
identity: {},
|
||||
adapterConfig: {},
|
||||
heartbeat: {},
|
||||
runtime: {},
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildAgentUpdatePatch", () => {
|
||||
it("replaces adapter config and drops env when the last env binding is cleared", () => {
|
||||
const patch = buildAgentUpdatePatch(
|
||||
makeAgent(),
|
||||
makeOverlay({
|
||||
adapterConfig: {
|
||||
env: undefined,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(patch).toEqual({
|
||||
adapterConfig: {
|
||||
model: "claude-sonnet-4-6",
|
||||
promptTemplate: "Work the issue.",
|
||||
},
|
||||
replaceAdapterConfig: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves adapter-agnostic keys when changing adapter types", () => {
|
||||
const patch = buildAgentUpdatePatch(
|
||||
makeAgent(),
|
||||
makeOverlay({
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: {
|
||||
model: "gpt-5.4",
|
||||
dangerouslyBypassApprovalsAndSandbox: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(patch).toEqual({
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: {
|
||||
env: {
|
||||
OPENAI_API_KEY: {
|
||||
type: "plain",
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
promptTemplate: "Work the issue.",
|
||||
model: "gpt-5.4",
|
||||
dangerouslyBypassApprovalsAndSandbox: true,
|
||||
},
|
||||
replaceAdapterConfig: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue