ofiere-openclaw-plugin 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/tools.ts +190 -152
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 10 meta-tools with 13-action workflow mastery covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, and constellation agent architecture",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/tools.ts
CHANGED
|
@@ -1141,6 +1141,17 @@ function registerKnowledgeOps(
|
|
|
1141
1141
|
});
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
1144
|
+
// ─── Workflow Mutation Serialization Queue ────────────────────────────────────
|
|
1145
|
+
// Prevents parallel mutations on the same workflow from racing (last-write-wins).
|
|
1146
|
+
// Each workflow gets a sequential promise chain — mutations queue behind previous ones.
|
|
1147
|
+
const _wfLockChain = new Map<string, Promise<any>>();
|
|
1148
|
+
function sequentialWorkflowOp<T>(wfId: string, fn: () => Promise<T>): Promise<T> {
|
|
1149
|
+
const prev = _wfLockChain.get(wfId) || Promise.resolve();
|
|
1150
|
+
const next = prev.catch(() => {}).then(fn);
|
|
1151
|
+
_wfLockChain.set(wfId, next.catch(() => {}));
|
|
1152
|
+
return next;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1144
1155
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1145
1156
|
// META-TOOL 6: OFIERE_WORKFLOW_OPS — Workflow Management & Execution
|
|
1146
1157
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1219,7 +1230,7 @@ function registerWorkflowOps(
|
|
|
1219
1230
|
id: { type: "string", description: "Node ID (auto-generated if omitted)" },
|
|
1220
1231
|
type: { type: "string", enum: ["manual_trigger", "webhook_trigger", "agent_step", "formatter_step", "http_request", "task_call", "variable_set", "condition", "human_approval", "delay", "loop", "convergence", "output", "checkpoint", "note"] },
|
|
1221
1232
|
position: { type: "object", properties: { x: { type: "number" }, y: { type: "number" } } },
|
|
1222
|
-
data: { type: "object", description: "Node config — always include a 'label' field. See NODE TYPES above for all configurable fields per type." },
|
|
1233
|
+
data: { type: "object", additionalProperties: true, description: "Node config — always include a 'label' field. See NODE TYPES above for all configurable fields per type." },
|
|
1223
1234
|
},
|
|
1224
1235
|
},
|
|
1225
1236
|
description: "Workflow graph nodes",
|
|
@@ -1247,11 +1258,11 @@ function registerWorkflowOps(
|
|
|
1247
1258
|
description: "Single node definition for insert_node_between. { type, data: { label, ... } }",
|
|
1248
1259
|
properties: {
|
|
1249
1260
|
type: { type: "string" },
|
|
1250
|
-
data: { type: "object" },
|
|
1261
|
+
data: { type: "object", additionalProperties: true },
|
|
1251
1262
|
position: { type: "object", properties: { x: { type: "number" }, y: { type: "number" } } },
|
|
1252
1263
|
},
|
|
1253
1264
|
},
|
|
1254
|
-
data: { type: "object", description: "Data fields to merge into a node (for update_node). Only specified fields are changed." },
|
|
1265
|
+
data: { type: "object", additionalProperties: true, description: "Data fields to merge into a node (for update_node). Only specified fields are changed." },
|
|
1255
1266
|
source_node_id: { type: "string", description: "Source node ID for insert_node_between" },
|
|
1256
1267
|
target_node_id: { type: "string", description: "Target node ID for insert_node_between" },
|
|
1257
1268
|
steps: { type: "array", items: { type: "object" }, description: "Legacy V1 step definitions" },
|
|
@@ -1454,6 +1465,7 @@ function registerWorkflowOps(
|
|
|
1454
1465
|
const { error } = await supabase.from("workflow_runs").insert({
|
|
1455
1466
|
id: runId,
|
|
1456
1467
|
workflow_id: wfId,
|
|
1468
|
+
user_id: userId,
|
|
1457
1469
|
status: "running",
|
|
1458
1470
|
started_at: new Date().toISOString(),
|
|
1459
1471
|
trigger_type: "agent",
|
|
@@ -1470,40 +1482,40 @@ function registerWorkflowOps(
|
|
|
1470
1482
|
const newNodes = params.nodes as any[];
|
|
1471
1483
|
if (!newNodes || !Array.isArray(newNodes) || newNodes.length === 0) return err("Missing required: nodes[] (array of node definitions)");
|
|
1472
1484
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1485
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1486
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1487
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1475
1488
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1489
|
+
const existingNodes = (wf.nodes as any[]) || [];
|
|
1490
|
+
const existingEdges = (wf.edges as any[]) || [];
|
|
1478
1491
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
});
|
|
1492
|
+
const maxY = existingNodes.reduce((max: number, n: any) => Math.max(max, n.position?.y || 0), 0);
|
|
1493
|
+
const normalized = newNodes.map((n, i) => {
|
|
1494
|
+
const node = normalizeNode(n, existingNodes.length + i);
|
|
1495
|
+
if (!n.position) {
|
|
1496
|
+
node.position = { x: 250, y: maxY + 120 + i * 150 };
|
|
1497
|
+
}
|
|
1498
|
+
return node;
|
|
1499
|
+
});
|
|
1488
1500
|
|
|
1489
|
-
|
|
1501
|
+
const allNodes = [...existingNodes, ...normalized];
|
|
1490
1502
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1503
|
+
let allEdges = existingEdges;
|
|
1504
|
+
if (params.edges && Array.isArray(params.edges)) {
|
|
1505
|
+
const newEdges = (params.edges as any[]).map((e: any, i: number) => normalizeEdge(e, existingEdges.length + i));
|
|
1506
|
+
allEdges = [...existingEdges, ...newEdges];
|
|
1507
|
+
}
|
|
1497
1508
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1509
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, allNodes, allEdges);
|
|
1510
|
+
if (saveErr) return err(saveErr);
|
|
1500
1511
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1512
|
+
return ok({
|
|
1513
|
+
message: `Added ${normalized.length} node(s) to workflow`,
|
|
1514
|
+
added_nodes: normalized.map((n: any) => ({ id: n.id, type: n.type, label: n.data?.label })),
|
|
1515
|
+
total_nodes: allNodes.length,
|
|
1516
|
+
total_edges: allEdges.length,
|
|
1517
|
+
workflow: saved,
|
|
1518
|
+
});
|
|
1507
1519
|
});
|
|
1508
1520
|
}
|
|
1509
1521
|
|
|
@@ -1515,30 +1527,31 @@ function registerWorkflowOps(
|
|
|
1515
1527
|
const dataUpdate = params.data as Record<string, any>;
|
|
1516
1528
|
if (!dataUpdate || typeof dataUpdate !== "object") return err("Missing required: data (object with fields to update)");
|
|
1517
1529
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1530
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1531
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1532
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1520
1533
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1534
|
+
const nodes = (wf.nodes as any[]) || [];
|
|
1535
|
+
const nodeIndex = nodes.findIndex((n: any) => n.id === nodeId);
|
|
1536
|
+
if (nodeIndex === -1) return err(`Node "${nodeId}" not found in workflow. Use action "get" to see all node IDs.`);
|
|
1524
1537
|
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1538
|
+
// Merge new data into existing node data (preserves untouched fields)
|
|
1539
|
+
const existingData = nodes[nodeIndex].data || {};
|
|
1540
|
+
nodes[nodeIndex].data = { ...existingData, ...dataUpdate };
|
|
1528
1541
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1542
|
+
if (params.node && typeof params.node === "object" && (params.node as any).position) {
|
|
1543
|
+
nodes[nodeIndex].position = (params.node as any).position;
|
|
1544
|
+
}
|
|
1533
1545
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1546
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, nodes, wf.edges || []);
|
|
1547
|
+
if (saveErr) return err(saveErr);
|
|
1536
1548
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1549
|
+
return ok({
|
|
1550
|
+
message: `Node "${nodeId}" updated`,
|
|
1551
|
+
node: { id: nodes[nodeIndex].id, type: nodes[nodeIndex].type, data: nodes[nodeIndex].data },
|
|
1552
|
+
fields_updated: Object.keys(dataUpdate),
|
|
1553
|
+
workflow: saved,
|
|
1554
|
+
});
|
|
1542
1555
|
});
|
|
1543
1556
|
}
|
|
1544
1557
|
|
|
@@ -1548,26 +1561,33 @@ function registerWorkflowOps(
|
|
|
1548
1561
|
const nodeIds = params.node_ids as string[];
|
|
1549
1562
|
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) return err("Missing required: node_ids[] (array of node IDs to delete)");
|
|
1550
1563
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1564
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1565
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1566
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1553
1567
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1568
|
+
// Track which IDs actually exist vs. not found
|
|
1569
|
+
const existingNodeIdSet = new Set(((wf.nodes as any[]) || []).map((n: any) => n.id));
|
|
1570
|
+
const actuallyDeletedIds = nodeIds.filter(id => existingNodeIdSet.has(id));
|
|
1571
|
+
const notFoundIds = nodeIds.filter(id => !existingNodeIdSet.has(id));
|
|
1558
1572
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1573
|
+
const nodeIdSet = new Set(nodeIds);
|
|
1574
|
+
const nodes = ((wf.nodes as any[]) || []).filter((n: any) => !nodeIdSet.has(n.id));
|
|
1575
|
+
// Also remove edges connected to deleted nodes
|
|
1576
|
+
const edges = ((wf.edges as any[]) || []).filter((e: any) => !nodeIdSet.has(e.source) && !nodeIdSet.has(e.target));
|
|
1561
1577
|
|
|
1562
|
-
|
|
1563
|
-
if (saveErr) return err(saveErr);
|
|
1578
|
+
const removedEdgeCount = ((wf.edges as any[]) || []).length - edges.length;
|
|
1564
1579
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1580
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, nodes, edges);
|
|
1581
|
+
if (saveErr) return err(saveErr);
|
|
1582
|
+
|
|
1583
|
+
return ok({
|
|
1584
|
+
message: `Deleted ${actuallyDeletedIds.length} node(s) and ${removedEdgeCount} connected edge(s)`,
|
|
1585
|
+
deleted_node_ids: actuallyDeletedIds,
|
|
1586
|
+
not_found_ids: notFoundIds.length > 0 ? notFoundIds : undefined,
|
|
1587
|
+
remaining_nodes: nodes.length,
|
|
1588
|
+
remaining_edges: edges.length,
|
|
1589
|
+
workflow: saved,
|
|
1590
|
+
});
|
|
1571
1591
|
});
|
|
1572
1592
|
}
|
|
1573
1593
|
|
|
@@ -1577,33 +1597,49 @@ function registerWorkflowOps(
|
|
|
1577
1597
|
const newEdges = params.edges as any[];
|
|
1578
1598
|
if (!newEdges || !Array.isArray(newEdges) || newEdges.length === 0) return err("Missing required: edges[] (array of edge definitions)");
|
|
1579
1599
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1600
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1601
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1602
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1582
1603
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1604
|
+
const existingEdges = (wf.edges as any[]) || [];
|
|
1605
|
+
const nodesList = (wf.nodes as any[]) || [];
|
|
1606
|
+
const nodeIdSet = new Set(nodesList.map((n: any) => n.id));
|
|
1607
|
+
const nodesMap = new Map(nodesList.map((n: any) => [n.id, n]));
|
|
1585
1608
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1609
|
+
try {
|
|
1610
|
+
// Validate source/target existence AND sourceHandle semantics
|
|
1611
|
+
const normalized = newEdges.map((e: any, i: number) => {
|
|
1612
|
+
if (!nodeIdSet.has(e.source)) throw new Error(`Source node "${e.source}" not found in workflow`);
|
|
1613
|
+
if (!nodeIdSet.has(e.target)) throw new Error(`Target node "${e.target}" not found in workflow`);
|
|
1614
|
+
|
|
1615
|
+
// Validate that condition/loop handles are only used on the correct node types
|
|
1616
|
+
if (e.sourceHandle) {
|
|
1617
|
+
const srcNode = nodesMap.get(e.source);
|
|
1618
|
+
if (e.sourceHandle.startsWith("condition-") && srcNode?.type !== "condition") {
|
|
1619
|
+
throw new Error(`sourceHandle "${e.sourceHandle}" is only valid on condition nodes, but source "${e.source}" is type "${srcNode?.type}"`);
|
|
1620
|
+
}
|
|
1621
|
+
if ((e.sourceHandle === "loop_body" || e.sourceHandle === "done") && srcNode?.type !== "loop") {
|
|
1622
|
+
throw new Error(`sourceHandle "${e.sourceHandle}" is only valid on loop nodes, but source "${e.source}" is type "${srcNode?.type}"`);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
return normalizeEdge(e, existingEdges.length + i);
|
|
1627
|
+
});
|
|
1592
1628
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
if (saveErr) return err(saveErr);
|
|
1629
|
+
const allEdges = [...existingEdges, ...normalized];
|
|
1630
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, wf.nodes || [], allEdges);
|
|
1631
|
+
if (saveErr) return err(saveErr);
|
|
1597
1632
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1633
|
+
return ok({
|
|
1634
|
+
message: `Added ${normalized.length} edge(s)`,
|
|
1635
|
+
added_edges: normalized.map((e: any) => ({ id: e.id, source: e.source, target: e.target })),
|
|
1636
|
+
total_edges: allEdges.length,
|
|
1637
|
+
workflow: saved,
|
|
1638
|
+
});
|
|
1639
|
+
} catch (e: any) {
|
|
1640
|
+
return err(e.message);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1607
1643
|
}
|
|
1608
1644
|
|
|
1609
1645
|
case "delete_edges": {
|
|
@@ -1612,21 +1648,28 @@ function registerWorkflowOps(
|
|
|
1612
1648
|
const edgeIds = params.edge_ids as string[];
|
|
1613
1649
|
if (!edgeIds || !Array.isArray(edgeIds) || edgeIds.length === 0) return err("Missing required: edge_ids[] (array of edge IDs to delete)");
|
|
1614
1650
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1651
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1652
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1653
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1617
1654
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1655
|
+
// Track which IDs actually exist vs. not found
|
|
1656
|
+
const existingEdgeIds = new Set(((wf.edges as any[]) || []).map((e: any) => e.id));
|
|
1657
|
+
const actuallyDeletedIds = edgeIds.filter(id => existingEdgeIds.has(id));
|
|
1658
|
+
const notFoundIds = edgeIds.filter(id => !existingEdgeIds.has(id));
|
|
1621
1659
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1660
|
+
const edgeIdSet = new Set(edgeIds);
|
|
1661
|
+
const edges = ((wf.edges as any[]) || []).filter((e: any) => !edgeIdSet.has(e.id));
|
|
1624
1662
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1663
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, wf.nodes || [], edges);
|
|
1664
|
+
if (saveErr) return err(saveErr);
|
|
1665
|
+
|
|
1666
|
+
return ok({
|
|
1667
|
+
message: `Deleted ${actuallyDeletedIds.length} edge(s)`,
|
|
1668
|
+
deleted_edge_ids: actuallyDeletedIds,
|
|
1669
|
+
not_found_ids: notFoundIds.length > 0 ? notFoundIds : undefined,
|
|
1670
|
+
remaining_edges: edges.length,
|
|
1671
|
+
workflow: saved,
|
|
1672
|
+
});
|
|
1630
1673
|
});
|
|
1631
1674
|
}
|
|
1632
1675
|
|
|
@@ -1640,66 +1683,61 @@ function registerWorkflowOps(
|
|
|
1640
1683
|
if (!targetId) return err("Missing required: target_node_id");
|
|
1641
1684
|
if (!newNodeDef || typeof newNodeDef !== "object") return err("Missing required: node (the node definition to insert)");
|
|
1642
1685
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1686
|
+
return sequentialWorkflowOp(wfId, async () => {
|
|
1687
|
+
const { wf, error: fetchErr } = await fetchWorkflow(wfId);
|
|
1688
|
+
if (fetchErr || !wf) return err(fetchErr || "Workflow not found");
|
|
1645
1689
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1690
|
+
const nodes = (wf.nodes as any[]) || [];
|
|
1691
|
+
const edges = (wf.edges as any[]) || [];
|
|
1648
1692
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
if (!targetNode) return err(`Target node "${targetId}" not found. Use action "get" to see node IDs.`);
|
|
1693
|
+
const sourceNode = nodes.find((n: any) => n.id === sourceId);
|
|
1694
|
+
const targetNode = nodes.find((n: any) => n.id === targetId);
|
|
1695
|
+
if (!sourceNode) return err(`Source node "${sourceId}" not found. Use action "get" to see node IDs.`);
|
|
1696
|
+
if (!targetNode) return err(`Target node "${targetId}" not found. Use action "get" to see node IDs.`);
|
|
1654
1697
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
if (!connectingEdge) return err(`No edge found from "${sourceId}" to "${targetId}". They may not be directly connected.`);
|
|
1698
|
+
const connectingEdge = edges.find((e: any) => e.source === sourceId && e.target === targetId);
|
|
1699
|
+
if (!connectingEdge) return err(`No edge found from "${sourceId}" to "${targetId}". They may not be directly connected.`);
|
|
1658
1700
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
);
|
|
1701
|
+
const midX = ((sourceNode.position?.x || 0) + (targetNode.position?.x || 0)) / 2;
|
|
1702
|
+
const midY = ((sourceNode.position?.y || 0) + (targetNode.position?.y || 0)) / 2;
|
|
1703
|
+
const newNode = normalizeNode(
|
|
1704
|
+
{ ...newNodeDef, position: newNodeDef.position || { x: midX, y: midY } },
|
|
1705
|
+
nodes.length,
|
|
1706
|
+
);
|
|
1666
1707
|
|
|
1667
|
-
|
|
1668
|
-
const updatedEdges = edges.filter((e: any) => e.id !== connectingEdge.id);
|
|
1708
|
+
const updatedEdges = edges.filter((e: any) => e.id !== connectingEdge.id);
|
|
1669
1709
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
// Preserve the targetHandle from the original edge
|
|
1683
|
-
...(connectingEdge.targetHandle ? { targetHandle: connectingEdge.targetHandle } : {}),
|
|
1684
|
-
};
|
|
1710
|
+
const edgeIn = {
|
|
1711
|
+
id: `edge-${Date.now()}-in`,
|
|
1712
|
+
source: sourceId,
|
|
1713
|
+
target: newNode.id,
|
|
1714
|
+
...(connectingEdge.sourceHandle ? { sourceHandle: connectingEdge.sourceHandle } : {}),
|
|
1715
|
+
};
|
|
1716
|
+
const edgeOut = {
|
|
1717
|
+
id: `edge-${Date.now()}-out`,
|
|
1718
|
+
source: newNode.id,
|
|
1719
|
+
target: targetId,
|
|
1720
|
+
...(connectingEdge.targetHandle ? { targetHandle: connectingEdge.targetHandle } : {}),
|
|
1721
|
+
};
|
|
1685
1722
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1723
|
+
updatedEdges.push(edgeIn, edgeOut);
|
|
1724
|
+
nodes.push(newNode);
|
|
1688
1725
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1726
|
+
const { wf: saved, error: saveErr } = await saveGraph(wfId, nodes, updatedEdges);
|
|
1727
|
+
if (saveErr) return err(saveErr);
|
|
1691
1728
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1729
|
+
return ok({
|
|
1730
|
+
message: `Inserted "${newNode.data.label}" (${newNode.type}) between "${sourceNode.data?.label || sourceId}" and "${targetNode.data?.label || targetId}"`,
|
|
1731
|
+
inserted_node: { id: newNode.id, type: newNode.type, label: newNode.data.label, position: newNode.position },
|
|
1732
|
+
new_edges: [
|
|
1733
|
+
{ id: edgeIn.id, from: sourceId, to: newNode.id },
|
|
1734
|
+
{ id: edgeOut.id, from: newNode.id, to: targetId },
|
|
1735
|
+
],
|
|
1736
|
+
removed_edge: connectingEdge.id,
|
|
1737
|
+
total_nodes: nodes.length,
|
|
1738
|
+
total_edges: updatedEdges.length,
|
|
1739
|
+
workflow: saved,
|
|
1740
|
+
});
|
|
1703
1741
|
});
|
|
1704
1742
|
}
|
|
1705
1743
|
|