fix: wire PatternEditor to mode-aware graph mutations

Replace the old disconnected-node addAgentNodeToGraph with the
mode-aware addAgentToGraph so new agents are automatically wired
into the graph topology. Replace the graph-nuking removeAgent
path with removeAgentFromGraph for surgical node removal. Split
emitChange into graph-preserving and mode-change paths so editing
agent properties no longer discards custom positions and edges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-27 21:55:54 +01:00
co-authored by Copilot
parent bb7e5d4108
commit c1dab96bfd
3 changed files with 16 additions and 36 deletions
+11 -4
View File
@@ -18,6 +18,8 @@ import {
import type { ApprovalCheckpointKind, ApprovalPolicy } from '@shared/domain/approval';
import type { ModelDefinition } from '@shared/domain/models';
import {
addAgentToGraph,
removeAgentFromGraph,
resolvePatternGraph,
syncPatternGraph,
validatePatternDefinition,
@@ -35,7 +37,6 @@ import {
} from '@shared/domain/tooling';
import { ToggleSwitch } from '@renderer/components/ui';
import { addAgentNodeToGraph } from '@renderer/lib/patternGraph';
import { PatternGraphCanvas } from './pattern-graph/PatternGraphCanvas';
import { PatternGraphInspector } from './pattern-graph/PatternGraphInspector';
@@ -143,6 +144,10 @@ export function PatternEditor({
const graph = resolvePatternGraph(pattern);
function emitChange(nextPattern: PatternDefinition) {
onChange({ ...nextPattern, graph: resolvePatternGraph(nextPattern) });
}
function emitModeChange(nextPattern: PatternDefinition) {
onChange(syncPatternGraph(nextPattern));
}
@@ -159,7 +164,7 @@ export function PatternEditor({
model: 'gpt-5.4',
reasoningEffort: 'high',
};
const updatedGraph = addAgentNodeToGraph(graph, newAgent);
const updatedGraph = addAgentToGraph(graph, pattern.mode, newAgent);
onChange({ ...pattern, agents: [...pattern.agents, newAgent], graph: updatedGraph });
}
@@ -175,9 +180,11 @@ export function PatternEditor({
return;
}
emitChange({
const updatedGraph = removeAgentFromGraph(graph, pattern.mode, agentId);
onChange({
...pattern,
agents: pattern.agents.filter((a) => a.id !== agentId),
graph: updatedGraph,
});
setSelectedNodeId(null);
}
@@ -374,7 +381,7 @@ export function PatternEditor({
}`}
disabled={disabled}
key={mode}
onClick={() => emitChange({ ...pattern, mode })}
onClick={() => emitModeChange({ ...pattern, mode })}
type="button"
>
<div className="flex items-center gap-1.5">
-26
View File
@@ -246,32 +246,6 @@ export function removeEdge(graph: PatternGraph, removeEdgeId: string): PatternGr
return { ...graph, edges: graph.edges.filter((e) => e.id !== removeEdgeId) };
}
/* ── Add disconnected agent node ───────────────────────────── */
export function addAgentNodeToGraph(
graph: PatternGraph,
agent: PatternAgentDefinition,
): PatternGraph {
const existingAgentNodes = graph.nodes.filter((n) => n.kind === 'agent');
const nextOrder = existingAgentNodes.length;
// Place new node below existing agent nodes
const maxY = existingAgentNodes.reduce((max, n) => Math.max(max, n.position.y), 0);
const avgX = existingAgentNodes.length > 0
? Math.round(existingAgentNodes.reduce((sum, n) => sum + n.position.x, 0) / existingAgentNodes.length)
: 400;
const newNode: PatternGraphNode = {
id: `agent-node-${agent.id}`,
kind: 'agent',
agentId: agent.id,
order: nextOrder,
position: { x: avgX, y: maxY + 120 },
};
return { ...graph, nodes: [...graph.nodes, newNode] };
}
/* ── Sequential reorder ────────────────────────────────────── */
export function swapSequentialOrder(
+5 -6
View File
@@ -1,8 +1,7 @@
import { describe, expect, test } from 'bun:test';
import { createBuiltinPatterns, resolvePatternGraph, type PatternDefinition } from '@shared/domain/pattern';
import { addAgentToGraph, createBuiltinPatterns, resolvePatternGraph, type PatternDefinition } from '@shared/domain/pattern';
import {
addAgentNodeToGraph,
addEdge,
autoLayoutGraph,
canMoveSequential,
@@ -208,20 +207,20 @@ describe('pattern graph mutation helpers', () => {
expect(updated.edges.find((e) => e.id === firstEdge.id)).toBeUndefined();
});
test('addAgentNodeToGraph places a disconnected agent node on the canvas', () => {
test('addAgentToGraph inserts a wired agent node for sequential mode', () => {
const pattern = findPattern('sequential');
const graph = resolvePatternGraph(pattern);
const newAgent = { id: 'new-1', name: 'New Agent', description: '', instructions: '', model: 'gpt-5.4' };
const updated = addAgentNodeToGraph(graph, newAgent);
const updated = addAgentToGraph(graph, pattern.mode, newAgent);
expect(updated.nodes.length).toBe(graph.nodes.length + 1);
const newNode = updated.nodes.find((n) => n.agentId === 'new-1');
expect(newNode).toBeDefined();
expect(newNode!.kind).toBe('agent');
// No new edges added — node is disconnected
expect(updated.edges.length).toBe(graph.edges.length);
// New agent is wired into the chain, so edge count stays at agents+1
expect(updated.edges.length).toBe(graph.edges.length + 1);
});
});