mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 10:30:37 +09:00
Merge pull request #3232 from officialasishkumar/fix/clear-empty-agent-env-bindings
fix(ui): persist cleared agent env bindings on save
This commit is contained in:
commit
62d05a7ae2
3 changed files with 185 additions and 55 deletions
|
|
@ -49,6 +49,7 @@ import { shouldShowLegacyWorkingDirectoryField } from "../lib/legacy-agent-confi
|
|||
import { listAdapterOptions, listVisibleAdapterTypes } from "../adapters/metadata";
|
||||
import { getAdapterLabel } from "../adapters/adapter-display-registry";
|
||||
import { useDisabledAdaptersSync } from "../adapters/use-disabled-adapters";
|
||||
import { buildAgentUpdatePatch, type AgentConfigOverlay } from "../lib/agent-config-patch";
|
||||
|
||||
/* ---- Create mode values ---- */
|
||||
|
||||
|
|
@ -89,15 +90,7 @@ type AgentConfigFormProps = {
|
|||
|
||||
/* ---- Edit mode overlay (dirty tracking) ---- */
|
||||
|
||||
interface Overlay {
|
||||
identity: Record<string, unknown>;
|
||||
adapterType?: string;
|
||||
adapterConfig: Record<string, unknown>;
|
||||
heartbeat: Record<string, unknown>;
|
||||
runtime: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const emptyOverlay: Overlay = {
|
||||
const emptyOverlay: AgentConfigOverlay = {
|
||||
identity: {},
|
||||
adapterConfig: {},
|
||||
heartbeat: {},
|
||||
|
|
@ -107,7 +100,7 @@ const emptyOverlay: Overlay = {
|
|||
/** Stable empty object used as fallback for missing env config to avoid new-object-per-render. */
|
||||
const EMPTY_ENV: Record<string, EnvBinding> = {};
|
||||
|
||||
function isOverlayDirty(o: Overlay): boolean {
|
||||
function isOverlayDirty(o: AgentConfigOverlay): boolean {
|
||||
return (
|
||||
Object.keys(o.identity).length > 0 ||
|
||||
o.adapterType !== undefined ||
|
||||
|
|
@ -211,7 +204,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
});
|
||||
|
||||
// ---- Edit mode: overlay for dirty tracking ----
|
||||
const [overlay, setOverlay] = useState<Overlay>(emptyOverlay);
|
||||
const [overlay, setOverlay] = useState<AgentConfigOverlay>(emptyOverlay);
|
||||
const agentRef = useRef<Agent | null>(null);
|
||||
|
||||
// Clear overlay when agent data refreshes (after save)
|
||||
|
|
@ -227,14 +220,14 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
const isDirty = !isCreate && isOverlayDirty(overlay);
|
||||
|
||||
/** Read effective value: overlay if dirty, else original */
|
||||
function eff<T>(group: keyof Omit<Overlay, "adapterType">, field: string, original: T): T {
|
||||
function eff<T>(group: keyof Omit<AgentConfigOverlay, "adapterType">, field: string, original: T): T {
|
||||
const o = overlay[group];
|
||||
if (field in o) return o[field] as T;
|
||||
return original;
|
||||
}
|
||||
|
||||
/** Mark field dirty in overlay */
|
||||
function mark(group: keyof Omit<Overlay, "adapterType">, field: string, value: unknown) {
|
||||
function mark(group: keyof Omit<AgentConfigOverlay, "adapterType">, field: string, value: unknown) {
|
||||
setOverlay((prev) => ({
|
||||
...prev,
|
||||
[group]: { ...prev[group], [field]: value },
|
||||
|
|
@ -248,48 +241,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
|
||||
const handleSave = useCallback(() => {
|
||||
if (isCreate || !isDirty) return;
|
||||
const agent = props.agent;
|
||||
const patch: Record<string, unknown> = {};
|
||||
|
||||
if (Object.keys(overlay.identity).length > 0) {
|
||||
Object.assign(patch, overlay.identity);
|
||||
}
|
||||
if (overlay.adapterType !== undefined) {
|
||||
patch.adapterType = overlay.adapterType;
|
||||
// When adapter type changes, replace adapter-specific fields but preserve
|
||||
// adapter-agnostic fields (env, promptTemplate, etc.) that are shared
|
||||
// across all adapter types.
|
||||
const existing = (agent.adapterConfig ?? {}) as Record<string, unknown>;
|
||||
const adapterAgnosticKeys = [
|
||||
"env",
|
||||
"promptTemplate",
|
||||
"instructionsFilePath",
|
||||
"cwd",
|
||||
"timeoutSec",
|
||||
"graceSec",
|
||||
"bootstrapPromptTemplate",
|
||||
];
|
||||
const preserved: Record<string, unknown> = {};
|
||||
for (const key of adapterAgnosticKeys) {
|
||||
if (key in existing) {
|
||||
preserved[key] = existing[key];
|
||||
}
|
||||
}
|
||||
patch.adapterConfig = { ...preserved, ...overlay.adapterConfig };
|
||||
} else if (Object.keys(overlay.adapterConfig).length > 0) {
|
||||
const existing = (agent.adapterConfig ?? {}) as Record<string, unknown>;
|
||||
patch.adapterConfig = { ...existing, ...overlay.adapterConfig };
|
||||
}
|
||||
if (Object.keys(overlay.heartbeat).length > 0) {
|
||||
const existingRc = (agent.runtimeConfig ?? {}) as Record<string, unknown>;
|
||||
const existingHb = (existingRc.heartbeat ?? {}) as Record<string, unknown>;
|
||||
patch.runtimeConfig = { ...existingRc, heartbeat: { ...existingHb, ...overlay.heartbeat } };
|
||||
}
|
||||
if (Object.keys(overlay.runtime).length > 0) {
|
||||
Object.assign(patch, overlay.runtime);
|
||||
}
|
||||
|
||||
props.onSave(patch);
|
||||
props.onSave(buildAgentUpdatePatch(props.agent, overlay));
|
||||
}, [isCreate, isDirty, overlay, props]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue