Skip to content

Feat(agentflow) Node version detection and version upgrade support #6347

Merged
j-sanaa merged 17 commits into
mainfrom
feat-agentflow-node-version
May 6, 2026
Merged

Feat(agentflow) Node version detection and version upgrade support #6347
j-sanaa merged 17 commits into
mainfrom
feat-agentflow-node-version

Conversation

@j-sanaa
Copy link
Copy Markdown
Contributor

@j-sanaa j-sanaa commented May 5, 2026

FLOWISE-411
Changes Made

  • Ported node versioning utilities (isNodeOutdated, getNodeVersionWarning, upgradeNodeData,
    getStaleEdgesAfterUpgrade) from the V2 canvas into the AgentFlow SDK
  • Added an orange Sync Nodes FAB on the canvas (top-left, matching V2's floating button placement),
  • Optimised syncNodes: Map-based component lookup, Set-based stale edge filter, early exit when nothing is outdated
  • Full test coverage for all utilities and FAB visibility branches

Current Behavior mimicking legacy UI

  • No version field — node shows a warning badge ("Node outdated — update to latest version X") but the Sync Nodes FAB
    does not appear. The node cannot be auto-upgraded without a baseline version to compare against.
  • Outdated version (node.version < server component version) — node shows a warning badge and the orange Sync Nodes
    FAB appears on the canvas. Clicking it upgrades all outdated nodes in one action, preserving user-configured inputs,
    credentials, and labels.
  • Current version — no badge, no FAB.

Demo:

Ndoe.versioning.fix.recording.mov

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a node versioning system to identify and upgrade outdated nodes in the Agentflow canvas. It introduces version tracking, warning badges for deprecated or old nodes, and a 'Sync Nodes' header action. Review feedback focuses on improving the robustness of the stale edge detection logic by using edge.target to avoid potential runtime errors and fragile string splitting. Additionally, the reviewer suggests optimizing the node data upgrade process by consolidating input property loops.

Comment thread packages/agentflow/src/core/utils/nodeVersionUtils.ts Outdated
Comment thread packages/agentflow/src/core/utils/nodeVersionUtils.ts
@j-sanaa
Copy link
Copy Markdown
Contributor Author

j-sanaa commented May 5, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a node versioning and synchronization system to detect and upgrade outdated nodes. Key additions include utility functions for version comparison, a 'Sync Nodes' button in the header, and UI indicators for version warnings. Feedback identifies a logic error in version checking that could cause persistent 'outdated' states, performance issues with status re-calculations during node drags, and a recommendation to use Sets for more efficient edge filtering.

Comment on lines +5 to +8
export function isNodeOutdated(nodeData: NodeData, componentNode: NodeDataSchema): boolean {
if (!nodeData.version) return true
return componentNode.version !== undefined && componentNode.version > nodeData.version
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic in isNodeOutdated can lead to an infinite 'outdated' state if the component definition itself lacks a version or has version 0. Specifically, if nodeData.version is missing or 0, it returns true regardless of the componentNode.version. If the component also has no version, the 'upgraded' node will still be considered outdated, causing the 'Sync Nodes' button to persist or trigger redundant updates.

It is safer to check if a target version exists first and then compare. Using a nullish check (== null) is consistent with the repository's standard idiom for checking both null and undefined.

Suggested change
export function isNodeOutdated(nodeData: NodeData, componentNode: NodeDataSchema): boolean {
if (!nodeData.version) return true
return componentNode.version !== undefined && componentNode.version > nodeData.version
}
export function isNodeOutdated(nodeData: NodeData, componentNode: NodeDataSchema): boolean {
if (componentNode.version == null) return false
if (nodeData.version == null) return true
return componentNode.version > nodeData.version
}
References
  1. In JavaScript/TypeScript, use loose equality (== null) as a standard idiom for a 'nullish' check that covers both null and undefined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isNodeOutdated was deliberately set to return false for missing nodeData.version so the Sync Nodes FAB doesn't appear for no-version nodes matching V2's implementation

Comment on lines +370 to +377
const hasOutdatedNodes = useMemo(
() =>
state.nodes.some((node) => {
const cn = state.componentNodes.find((c) => c.name === node.data.name)
return cn ? isNodeOutdated(node.data, cn) : false
}),
[state.nodes, state.componentNodes]
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This useMemo re-calculates hasOutdatedNodes on every change to state.nodes. Since state.nodes is synced from ReactFlow on every drag event, this O(N x M) calculation (where M is the number of component types) runs during every frame of a node drag. For large flows, this can cause noticeable UI stutter.

Consider optimizing this by converting state.componentNodes into a Map for O(1) lookups, or by using a more stable dependency that only changes when node versions or names change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map has to be rebuilt for each drag - optimization does not offer any significant gain

Comment thread packages/agentflow/src/infrastructure/store/AgentflowContext.tsx Outdated
@j-sanaa
Copy link
Copy Markdown
Contributor Author

j-sanaa commented May 5, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a node versioning and synchronization system to the Agentflow canvas. It enables the detection of outdated nodes by comparing their stored version against the latest component schema, displaying warning badges and a 'Sync Nodes' action button when updates are available. The implementation includes core utility functions for upgrading node data while preserving user configurations, state management updates in the context and reducer, and a new demo example. Feedback focuses on optimizing performance for node lookups using Maps, ensuring robust error handling for missing version data, and preventing potential infinite loops or redundant notifications during state updates.

Comment on lines +5 to +16
export function isNodeOutdated(nodeData: NodeData, componentNode: NodeDataSchema): boolean {
if (nodeData.version === undefined || componentNode.version === undefined) return false
return componentNode.version > nodeData.version
}

export function getNodeVersionWarning(nodeData: NodeData, componentNode: NodeDataSchema): string | null {
if (nodeData.version === undefined && componentNode.version !== undefined) {
return `Node outdated\nUpdate to latest version ${componentNode.version}`
}
if (nodeData.version !== undefined && componentNode.version !== undefined && componentNode.version > nodeData.version) {
return `Node version ${nodeData.version} outdated\nUpdate to latest version ${componentNode.version}`
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is an inconsistency between isNodeOutdated and getNodeVersionWarning regarding nodes with a missing version field. To promote fail-fast behavior and ensure data integrity when handling external data, consider throwing an error when version information is missing instead of silently skipping the check or defaulting to a value.

export function isNodeOutdated(nodeData: NodeData, componentNode: NodeDataSchema): boolean {
    if (componentNode.version === undefined) return false
    if (nodeData.version === undefined) {
        throw new Error('Node version is missing from data source')
    }
    return componentNode.version > nodeData.version
}
References
  1. When handling potentially invalid data from external sources, prefer throwing an error for invalid input types rather than silently returning a default or empty value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isNodeOutdated was deliberately set to return false for missing nodeData.version so the Sync Nodes FAB doesn't appear for no-version nodes matching V2's implementation

Comment on lines +370 to +377
const hasOutdatedNodes = useMemo(
() =>
state.nodes.some((node) => {
const cn = state.componentNodes.find((c) => c.name === node.data.name)
return cn ? isNodeOutdated(node.data, cn) : false
}),
[state.nodes, state.componentNodes]
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

hasOutdatedNodes is re-calculated on every change to state.nodes (including dragging). Since it performs a .find() on componentNodes for every node, this is $O(N \cdot M)$. While usually small, it's more efficient to create a lookup Map from componentNodes to optimize this to $O(N)$.

    const hasOutdatedNodes = useMemo(() => {
        const componentMap = new Map(state.componentNodes.map((c) => [c.name, c]))
        return state.nodes.some((node) => {
            const cn = componentMap.get(node.data.name)
            return cn ? isNodeOutdated(node.data, cn) : false
        })
    }, [state.nodes, state.componentNodes])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map has to be rebuilt for each drag - optimization does not offer any significant gain

Comment thread packages/agentflow/src/infrastructure/store/AgentflowContext.tsx
Comment on lines +109 to +113
useEffect(() => {
if (!isNodesLoading && availableNodes.length > 0) {
setComponentNodes(availableNodes)
}
}, [availableNodes, isNodesLoading, setComponentNodes])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This useEffect dispatches SET_COMPONENT_NODES whenever availableNodes changes. If the useFlowNodes hook returns a new array reference on every render, this will trigger a state update, causing a re-render of AgentflowCanvas, and potentially leading to an infinite loop. Consider adding a check to ensure the content of availableNodes has actually changed before dispatching.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availableNodes is a useState so it will not be recomputed on every render

@j-sanaa j-sanaa marked this pull request as ready for review May 5, 2026 21:43
@j-sanaa j-sanaa requested a review from jocelynlin-wd May 5, 2026 21:43
@jocelynlin-wd
Copy link
Copy Markdown
Contributor

Re: scope — demo app and public API surface

First off, thanks for tackling this — node versioning is a great feature to bring over to the SDK, and the utils port + reducer slice + auto FAB + badges + tests all look clean and faithful to legacy. 🙌

A couple of high-level thoughts on how this lands as an SDK port specifically, that I think would make the change even tighter:

1. The NodeVersioningExample demo doesn't really model consumer code. Everything version-related (badge, FAB, upgrade-on-click) is wired internally by the SDK — a real integrator's code is just <Agentflow apiBaseUrl token initialFlow /> and the feature works automatically. The demo's scenario picker / reset / description text is documentation chrome rather than an integration pattern, so there's not much for a consumer to copy from it. The unit tests in Agentflow.test.tsx > Sync Nodes FAB already cover the show/hide behavior nicely, so the demo isn't carrying QA weight either. I'd suggest dropping the demo, its App.tsx entry, and the demos/index.ts export to keep the examples app focused on integration patterns.

2. The new public API surface goes a bit beyond legacy parity. Legacy V2 (packages/ui/src/views/agentflowsv2/Canvas.jsx:441) treats syncNodes as a local function called only by the FAB's onClick — it's never exposed externally. This PR adds AgentFlowInstance.syncNodes() and hasOutdatedNodes to the public ref, plus onSyncNodes/hasOutdatedNodes to HeaderRenderProps and createHeaderProps. Without a clear consumer use case yet, I'd lean toward keeping the surface small — once these are public we own their semantics for good (when does a programmatic sync run? how does it interact with unsaved changes? what events fire?).

Suggested trim to keep the version-sync concern fully internal, matching legacy:

  • Drop syncNodes / hasOutdatedNodes from AgentFlowInstance and useAgentflow
  • Drop onSyncNodes / hasOutdatedNodes from HeaderRenderProps and createHeaderProps
  • Keep them on AgentflowContextValue — the FAB inside Agentflow.tsx is the only consumer and that's the right scope

Really nice work overall — just want to trim the public surface and the demo before merging so we ship the smallest correct version of this. 🚀

Comment thread packages/agentflow/src/core/utils/nodeVersionUtils.ts Outdated
Comment thread packages/agentflow/src/features/canvas/containers/AgentFlowNode.tsx Outdated
Comment thread packages/agentflow/src/Agentflow.tsx Outdated
Comment thread packages/agentflow/src/Agentflow.tsx Outdated
Comment thread packages/agentflow/src/infrastructure/store/agentflowReducer.ts Outdated
Comment thread packages/agentflow/src/features/canvas/components/AgentflowHeader.test.tsx Outdated
@j-sanaa j-sanaa merged commit 4b17501 into main May 6, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants