Files
paperclip/ui/src/lib/agent-config-patch.test.ts
T
Asish Kumar 44d94d0add 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
2026-04-09 17:50:14 +00:00

109 lines
2.5 KiB
TypeScript

// @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,
});
});
});