feat(automation): support include with multiple node IDs#349
feat(automation): support include with multiple node IDs#349andypalmi wants to merge 16 commits into
Conversation
Add automation/align-selection action that aligns nodes on the canvas
using Node-RED core alignment actions (grid, left, right, top, bottom,
middle, center).
The action resolves node IDs, reveals the target workspace, selects
the nodes, verifies the selection, and invokes the corresponding
core:align-selection-to-{direction} action.
Config nodes are automatically excluded from the selection since they
have no canvas position and break alignment. The result returns a
structured object with alignedNodes and excludedConfigNodes so the
caller knows which nodes were skipped and does not attempt to
manually reposition them.
Workspace existence and lock checks are performed before alignment.
Non-grid directions require at least 2 non-config nodes.
The getNodes and selectNodes methods now support the include parameter (upstream, downstream, connected) when called with an array of IDs. Previously include was ignored for arrays. For each node in the array, connected nodes are retrieved and merged with deduplication. Extracted _getConnectedNodes helper to share the traversal logic between single-node and multi-node code paths. getNodes now always returns an array and throws if any node ID is not found, replacing the previous null-return behavior. The invokeAction cases retain defensive guards for both null and empty results.
Steve-Mcl
left a comment
There was a problem hiding this comment.
As discussed, I still really dislike this. I think the existing arrangement of splitting the action at the MCP server into 2 distinct tools of select_node (with include) and select_nodes (for selecting a list of ids) is the better approach - but I wont block.
1 last consideration - have you checked the affect of throwing errors has (instead of the original behaviour of returning null/empty array) on the existing expert action links (e.g. [function 1](nr-assistant://automation/select-nodes?id=709483aa8eeae4e5e) / [function 2](nr-assistant://automation/open-node-edit?id=709483aa8eeae4e5e)
| const ids = Array.isArray(nodeId) ? nodeId : [nodeId] | ||
| const nodes = ids.map(id => { | ||
| const node = this.RED.nodes.node(id) | ||
| if (!node) throw new Error(`Node ${id} not found`) | ||
| return node | ||
| }) | ||
| if (!include) { | ||
| return nodes | ||
| } | ||
| return null | ||
| const seen = new Set() | ||
| const result = [] | ||
| for (const node of nodes) { | ||
| const connected = this._getConnectedNodes(node, include) | ||
| for (const n of connected) { | ||
| if (!seen.has(n.id)) { | ||
| seen.add(n.id) | ||
| result.push(n) | ||
| } | ||
| } | ||
| } | ||
| return result |
There was a problem hiding this comment.
This will return an odd shaped array.
Also, IIRC, get upstream includes the source node so we might get duplicates in the result.
I think a better approach is to
- loop
ids- for eachid- if
idis already collected,continue(skip performing the include op altogether for this node) - get the
include(if any) - loop the result of
_getConnectedNodes& only add if not existing
- if
Add full parameter to get-nodes action. When false (default), returns slim summaries (id, type, name, x, y, z, wires, valid) instead of full exported node data.
Instead of throwing when a node ID is not found, getNodes now filters out missing nodes and returns the rest. This prevents errors when action links reference nodes that have since been removed. The invokeAction layer still throws if no nodes at all are found. Also adds deduplication optimization for the include traversal.
When get-nodes or select-nodes receives IDs that don't exist, the result now includes a warning listing the missing IDs alongside any nodes that were found, instead of throwing an error. This gives the agent actionable feedback without breaking the response.
When get-nodes or select-nodes is called with include, the result now groups connected nodes under each source node using the direction as key (e.g. "downstream": [...]), instead of returning a flat deduplicated array. This makes it clear which connections belong to which source node.
When include is 'connected', the grouped result now separates nodes into 'upstream' and 'downstream' keys instead of a single 'connected' array. This gives the caller clear directionality for each source node's connections.
Replace getAllFlowNodes with BFS using getNodeLinks for upstream and downstream traversal. The core getAllFlowNodes only applies direction on the first hop then follows all links, which causes sibling nodes to appear in directional results.
|
Regarding splitting into two MCP tools - I don't think that makes sense from the agent's perspective. It would end up with two tools that do basically the same thing, and it would need to decide which one to use each time. A single I've addressed the throwing issue - Applied your suggestions on the inline code:
I also changed the result shape when Calling {
"success": true,
"nodes": [
{
"id": "function1", "type": "function",
"upstream": [
{ "id": "inject1", "type": "inject" }
],
"downstream": [
{ "id": "debug1", "type": "debug" },
{ "id": "function2", "type": "function" }
]
},
{
"id": "function2", "type": "function",
"upstream": [
{ "id": "inject2", "type": "inject" },
{ "id": "function1", "type": "function" }
],
"downstream": [
{ "id": "debug2", "type": "debug" }
]
}
]
}Each source node lists its connections split by direction, so the agent can clearly see the flow topology. A node can appear in multiple source nodes' connections (e.g. function1 and function2 are connected to each other). |
…t-nodes levels param groups connected nodes by depth (1 = direct, 2 = two hops), making parallel vs serial topology visible. full param on select-nodes returns summarized nodes by default, matching get-nodes behavior.
Config nodes (category: config) are filtered out since they are not canvas nodes. Link nodes (link in/out/call) are excluded to avoid resolving virtual cross-tab links during traversal.
Source node is now included in direction arrays for non-leveled results and at level 0 for leveled results, matching Node-RED's selection behavior.
Link nodes connected by a wire are included in connection results. Traversal stops at them so virtual cross-tab links are not resolved.
Connection results always use leveled format (grouped by depth) when include is specified. levels defaults to 0 (all levels). Junction nodes are now resolved via RED.nodes.junction fallback since they are not in the regular node registry.
The validator rejects integer type for JavaScript numbers. Using number type instead since JSON has no distinct integer type.
|
Closing — decided to rely on Node-RED core actions instead of maintaining custom traversal code |
Summary
Test plan
Closes #348