mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-24 13:38:43 +02:00
feat: enable edge deletion in group-chat mode
Extend edge deletion to group-chat orchestrator↔agent edges in addition to handoff agent↔agent edges. Structural edges (user-input/output, distributor/collector) remain non-deletable across all modes. Sequential, concurrent, and single modes keep fully structural topologies with no user-deletable edges. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -114,18 +114,11 @@ function PatternGraphCanvasInner({
|
||||
|
||||
const handleEdgesChange: OnEdgesChange = useCallback(
|
||||
(changes) => {
|
||||
// Block all edge deletions in modes that don't support it
|
||||
if (!isEdgeDeletionAllowed(pattern.mode)) {
|
||||
const filtered = changes.filter((c) => c.type !== 'remove');
|
||||
if (filtered.length > 0) {
|
||||
onEdgesChange(filtered);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For handoff mode, apply removals to the authoritative graph
|
||||
// Route edge removals through the authoritative graph.
|
||||
// Non-deletable edges are already protected by the per-edge `deletable`
|
||||
// flag, so React Flow will not emit removal changes for them.
|
||||
const removals = changes.filter((c) => c.type === 'remove');
|
||||
if (removals.length > 0) {
|
||||
if (removals.length > 0 && isEdgeDeletionAllowed(pattern.mode)) {
|
||||
let updatedGraph = graph;
|
||||
for (const removal of removals) {
|
||||
if (removal.type === 'remove') {
|
||||
|
||||
@@ -101,18 +101,32 @@ export function toCanvasNodes(
|
||||
});
|
||||
}
|
||||
|
||||
/** Determines whether user-created edges can be deleted in this mode. */
|
||||
/** Determines whether an edge can be deleted by the user.
|
||||
*
|
||||
* Deletable edges:
|
||||
* - handoff: agent ↔ agent edges (handoff routes)
|
||||
* - group-chat: orchestrator ↔ agent edges (participation links)
|
||||
*
|
||||
* Structural edges (user-input/output, distributor/collector) are never deletable.
|
||||
* Sequential, concurrent, and single modes have fully structural topologies.
|
||||
*/
|
||||
function isEdgeDeletable(edge: PatternGraphEdge, mode: OrchestrationMode, graph: PatternGraph): boolean {
|
||||
if (mode !== 'handoff') {
|
||||
const sourceNode = graph.nodes.find((n) => n.id === edge.source);
|
||||
const targetNode = graph.nodes.find((n) => n.id === edge.target);
|
||||
if (!sourceNode || !targetNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceNode = graph.nodes.find((n) => n.id === edge.source);
|
||||
const targetNode = graph.nodes.find((n) => n.id === edge.target);
|
||||
if (mode === 'handoff') {
|
||||
return sourceNode.kind === 'agent' && targetNode.kind === 'agent';
|
||||
}
|
||||
|
||||
// Only agent↔agent edges in handoff mode are user-deletable;
|
||||
// system edges (user-input → triage, agent → user-output) are structural.
|
||||
return sourceNode?.kind === 'agent' && targetNode?.kind === 'agent';
|
||||
if (mode === 'group-chat') {
|
||||
const kinds = new Set([sourceNode.kind, targetNode.kind]);
|
||||
return kinds.has('orchestrator') && kinds.has('agent');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const EDGE_COLORS = {
|
||||
@@ -201,9 +215,9 @@ export function isConnectionAllowed(
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether edges can be deleted by the user in this mode. */
|
||||
/** Whether any edges can be deleted by the user in this mode. */
|
||||
export function isEdgeDeletionAllowed(mode: OrchestrationMode): boolean {
|
||||
return mode === 'handoff';
|
||||
return mode === 'handoff' || mode === 'group-chat';
|
||||
}
|
||||
|
||||
/* ── Graph mutation helpers ─────────────────────────────────── */
|
||||
|
||||
@@ -226,11 +226,11 @@ describe('sequential reorder', () => {
|
||||
});
|
||||
|
||||
describe('edge deletion rules', () => {
|
||||
test('only handoff mode allows edge deletion', () => {
|
||||
test('handoff and group-chat modes allow edge deletion', () => {
|
||||
expect(isEdgeDeletionAllowed('handoff')).toBe(true);
|
||||
expect(isEdgeDeletionAllowed('group-chat')).toBe(true);
|
||||
expect(isEdgeDeletionAllowed('sequential')).toBe(false);
|
||||
expect(isEdgeDeletionAllowed('concurrent')).toBe(false);
|
||||
expect(isEdgeDeletionAllowed('group-chat')).toBe(false);
|
||||
expect(isEdgeDeletionAllowed('single')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -248,6 +248,20 @@ describe('edge deletion rules', () => {
|
||||
expect(nonDeletableEdges.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('group-chat marks orchestrator↔agent edges as deletable', () => {
|
||||
const pattern = findPattern('group-chat');
|
||||
const graph = resolvePatternGraph(pattern);
|
||||
const edges = toCanvasEdges(graph, 'group-chat');
|
||||
|
||||
const deletableEdges = edges.filter((e) => e.deletable);
|
||||
const nonDeletableEdges = edges.filter((e) => !e.deletable);
|
||||
|
||||
// Orchestrator↔agent edges should be deletable
|
||||
expect(deletableEdges.length).toBeGreaterThan(0);
|
||||
// Structural edges (user-input → orchestrator, orchestrator → user-output)
|
||||
expect(nonDeletableEdges.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('user-input nodes use userInputNode type and user-output use userOutputNode type', () => {
|
||||
const pattern = findPattern('sequential');
|
||||
const graph = resolvePatternGraph(pattern);
|
||||
|
||||
Reference in New Issue
Block a user