Skip to content

Prd 5298 2#2817

Merged
dimaMachina merged 113 commits intoprd-5298-2from
prd-5298-2-fix
Mar 26, 2026
Merged

Prd 5298 2#2817
dimaMachina merged 113 commits intoprd-5298-2from
prd-5298-2-fix

Conversation

@dimaMachina
Copy link
Copy Markdown
Collaborator

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 24, 2026

🦋 Changeset detected

Latest commit: 8efffd5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@inkeep/agents-manage-ui Patch
@inkeep/agents-api Patch
@inkeep/agents-cli Patch
@inkeep/agents-core Patch
@inkeep/agents-email Patch
@inkeep/agents-mcp Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 26, 2026 7:20pm
agents-docs Ready Ready Preview, Comment Mar 26, 2026 7:20pm
agents-manage-ui Ready Ready Preview, Comment Mar 26, 2026 7:20pm

Request Review

…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…ive form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
…already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

Delta Review — This review covers only the 4 commits since the last automated review (2410bdf3e9edc003f67e9).

✅ What Was Fixed

The delta commits address sarah-inkeep's bug report about the "requires approval" flag not persisting:

  1. ✅ Function tool approval policies fix (validation.ts:259-303, 363-371)

    • New sharedFunctionToolPolicies map properly hydrates tempToolPolicies from API during apiToFormValues()
    • Merges policies across multiple subAgents with OR logic for needsApproval
    • This fixes the bug where "requires approval" was not persisting after save/reload
  2. ✅ Model inheritance display fixed (sub-agent-node.tsx:102-107)

    • Added getModelInheritanceStatus() call to properly determine if model is inherited
    • (inherited) text now displays correctly based on inheritance hierarchy
  3. ✅ Edge removal cleanup (page.client.tsx:378-396, 832-857)

    • Removed requestAnimationFrame deferral for form cleanup
    • Switched from setValue(..., undefined) to form.unregister() for cleaner form state management
    • This is a more correct approach for removing form fields
  4. ✅ Test coverage added (deserialize.test.ts)

    • New test "hydrates function tool approval policies back into form state" verifies the roundtrip

🟠⚠️ Major (1) 🟠⚠️

Inline Comments:

  • 🟠 Major: function-tool-node-editor.tsx:52 Bare return produces invalid React output (same pattern that was just fixed in MCP editor)

💭 Consider (1) 💭

Inline Comments:

  • 💭 Consider: sub-agent-node.tsx:81 Removed optional chaining on subAgent.models — verify this can't be nullish

🕐 Pending from Prior Reviews

These items from earlier reviews remain valid considerations for follow-up:

📋 sarah-inkeep's Feedback Status

Issue Status
"requires approval" flag not persisting Fixed in this delta
Missing "(inherited)" text on model nodes Fixed in this delta
Error removing MCP tool: "toolId: Invalid input" ⚠️ Not visible in delta — may need investigation

💡 APPROVE WITH SUGGESTIONS

Summary: This delta successfully addresses the critical bug report from sarah-inkeep about the "requires approval" flag not persisting. The fix is well-implemented with proper test coverage. The only new issue is a minor pattern inconsistency (bare return instead of return null) in the function tool editor, which is a quick fix. The model inheritance display is also now correctly implemented.

The MCP tool removal error ("toolId: Invalid input: expected string, received undefined") mentioned by sarah-inkeep is not addressed in this delta and may need separate investigation.


Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 2 1 1 0 2 3 0
Total 2 1 1 0 2 3 0

Note: Delta review — 6 files changed since last review; 2 findings are from this delta.

const [writeWithAIInstructions, setWriteWithAIInstructions] = useState('');
// if we remove function tool, functionId can be undefined
if (!functionId) {
return;
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.

🟠 MAJOR: Bare return produces invalid React output

Issue: Line 52's bare return; returns undefined, which is not valid JSX for a React component body. This is the same pattern that was just fixed in mcp-node-editor.tsx.

Why: React components must return valid JSX, null, or a React fragment. Returning undefined causes a runtime warning and can lead to unexpected rendering behavior. This could affect users navigating to a function tool node after it's been deleted or during transient form state.

Fix: Return null or proper fallback UI:

Suggested change
return;
// if we remove function tool, functionId can be undefined
if (!functionId) {
return null;
}

Or with a user-friendly message:

if (!functionId) {
  return (
    <div className="flex items-center justify-center p-4">
      <div className="text-sm text-muted-foreground">Loading function tool...</div>
    </div>
  );
}

Refs:

const projectModel = project?.models;
const modelName =
subAgent.models?.base.model ?? agentModel.base.model ?? (projectModel?.base.model as string);
subAgent.models.base.model ?? agentModel.base.model ?? projectModel?.base.model ?? '';
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.

💭 Consider: Potential runtime error if subAgent.models is nullish

Issue: Line 81 accesses subAgent.models.base.model directly. The previous code used optional chaining (subAgent.models?.base.model). If subAgent.models or subAgent.models.base is undefined/null, this will throw a TypeError.

Why: While the form schema likely guarantees models exists, this is a defensive programming concern. The change from models?. to models. removes a safety net that previously existed.

Context: Looking at the test data in the PR, subAgent form data includes models with defaults, so this may be safe in practice. However, during deserialization edge cases or form reset, models could be transiently undefined.

Refs:


const sharedExternalAgentHeaders = new Map<string, string>();
const sharedTeamAgentHeaders = new Map<string, string>();
const sharedFunctionToolPolicies = new Map<string, Record<string, { needsApproval?: boolean }>>();
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.

Good fix: This addresses sarah-inkeep's bug report

The new sharedFunctionToolPolicies map and the loop at lines 279-303 properly hydrate function tool approval policies (needsApproval) from the API response back into form state as tempToolPolicies. This fixes the bug where the "requires approval" flag was not persisting after save/reload.

How it works:

  1. Iterates through all subAgents' canUse relations
  2. Extracts toolPolicies from each relation
  3. Merges policies across multiple subAgents (uses OR logic: if any relation has needsApproval: true, the final value is true)
  4. Stores in sharedFunctionToolPolicies map keyed by toolId
  5. Applies to functionTools output at lines 363-371

The test at deserialize.test.ts confirms the roundtrip works correctly.

@github-actions github-actions Bot deleted a comment from claude Bot Mar 26, 2026
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

Delta Review — This review covers only the 1 commit since the last automated review (c003f67e9dba1bef57).

🟠⚠️ Major (1) 🟠⚠️

Inline Comments:

  • 🟠 Major: mcp-node-editor.tsx:93 Bare return produces invalid React output (returns undefined)

✅ What's Good in This Delta

  1. Race condition fix (mcp-node-editor.tsx:91-94)

    • Added guard for when mcpRelation is removed but component is still mounted
    • Correctly identifies the timing issue with headers being set to empty string
    • The fix intent is correct, but the implementation returns undefined instead of valid JSX
  2. Simplified edge removal cleanup (page.client.tsx:381-388)

    • Changed from unregistering both relationshipId and headers to only relationshipId
    • This is a sensible cleanup since headers don't need separate cleanup on edge removal

🕐 Pending from Prior Reviews

These items from earlier reviews remain valid considerations for follow-up:

📋 sarah-inkeep's Feedback Status

Issue Status
"requires approval" flag not persisting ✅ Fixed in prior delta
Missing "(inherited)" text on model nodes ✅ Fixed in prior delta
Error removing MCP tool: "toolId: Invalid input" ⚠️ This delta attempts to fix the race condition, but introduces a new issue

💡 APPROVE WITH SUGGESTIONS

Summary: This delta correctly identifies and attempts to fix a race condition when MCP relations are removed. However, the fix introduces a bare return; statement that returns undefined — the same pattern that was previously fixed elsewhere in this file. The fix is one character: change return; to return null;. The overall PR architecture is solid and this is a minor issue that doesn't block the refactor.


Discarded (0)
Location Issue Reason Discarded
No findings discarded in this delta review
Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 1 0 0 0 1 3 0
Total 1 0 0 0 1 3 0

Note: Delta review — only 2 files changed since last review; 1 finding is from this delta.

}, [liveToolData, orphanedTools, nodeId]);
// MCP was removed, fix race condition when mcpRelation.headers will be set as ''
if (!mcpRelation) {
return;
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.

🟠 MAJOR: Bare return produces invalid React output

Issue: Line 93's bare return; returns undefined, which is not valid JSX for a React component body. This is the same pattern that was just fixed at lines 96-101 (where it now properly returns JSX).

Why: React components must return valid JSX, null, or a React fragment. Returning undefined causes a runtime warning and potentially unexpected rendering behavior. This can occur when the MCP relation is removed but the component is still mounted.

Fix:

Suggested change
return;
// MCP was removed, fix race condition when mcpRelation.headers will be set as ''
if (!mcpRelation) {
return null;
}

Or combine with the subsequent guard for a cleaner single check:

if (!mcpRelation || !toolData || !tool) {
  return (
    <div className="flex items-center justify-center p-4">
      <div className="text-sm text-muted-foreground">
        {!mcpRelation ? 'Loading...' : `Tool data not found for ${toolId}.`}
      </div>
    </div>
  );
}

Refs:

@github-actions github-actions Bot deleted a comment from claude Bot Mar 26, 2026
@dimaMachina
Copy link
Copy Markdown
Collaborator Author

@claude write a detailed changeset based on changes in this PR

@inkeep-internal-ci
Copy link
Copy Markdown
Contributor

inkeep-internal-ci Bot commented Mar 26, 2026

Claude finished @dimaMachina's task in 1m 48s —— View job


Changeset Created

  • Analyze PR changes and affected packages
  • Write changeset file
  • Commit and push

Added .changeset/graph-identity-refactor.md as a patch for @inkeep/agents-manage-ui, @inkeep/agents-api, and @inkeep/agents-cli covering:

  • Graph identity system — deterministic key derivation for all node types, stable deep-links
  • RHF as single source of truth — thin node.data, removed hydrateNodesWithFormData, requireFormValue guards
  • Connection state consolidationmcpRelations/functionToolRelations form maps, synchronous form.unregister()
  • Save-cycle reconciliation — three-category ID reconciliation, MCP node rename, selection preservation
  • Bug fixes — approval policy persistence, model inheritance display, MCP/function-tool editor crash guards, race condition fix
  • Performance — targeted useWatch subscriptions, removed getFunctionIdForTool
  • Schema changesdefaultSubAgentNodeId rename, FunctionToolRelationSchema, OpenAPI refinements
  • Test coverage — 7 new test files, expanded serialize/deserialize suites

Commit: 8efffd5

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

Delta Review — This review covers only the 1 commit since the last automated review (dba1bef57428de52dd).

✅ What Was Fixed in This Delta

  1. Model inheritance display (sub-agent-node.tsx:91-111)
    • Refactored model name resolution to use a ModelName object with proper optional chaining
    • Changed from subAgent.models.base.model to subAgent.models?.base.model
    • Destructured { inheritedFrom } from getModelInheritanceStatus() for cleaner usage
    • This addresses the prior review concern about potential nullish property access

🟠⚠️ Major (1) 🟠⚠️

Inline Comments:

  • 🟠 Major: mcp-node-editor.tsx:90-91 Bare return; produces invalid React output (returns undefined)

This issue was flagged in the previous review but remains unresolved. The fix is a one-character change: return;return null;

🕐 Pending from Prior Reviews

These items from earlier reviews remain valid considerations for follow-up:


💡 APPROVE WITH SUGGESTIONS

Summary: The delta commit successfully addresses the model inheritance display issue with proper optional chaining. However, the bare return; statement in mcp-node-editor.tsx (flagged in the previous review) remains unresolved. This is a quick one-character fix (return;return null;). The overall PR architecture is solid and ready to ship once this minor issue is addressed.

The thin-node / RHF-as-single-source-of-truth refactor is a meaningful improvement. Great work on this significant architectural cleanup! 🎉


Discarded (0)
Location Issue Reason Discarded
No findings discarded in this delta review
Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 1 0 0 0 1 4 0
Total 1 0 0 0 1 4 0

Note: Delta review — 2 files changed since last review; 1 finding persists from prior review.

// MCP was removed, fix race condition when mcpRelation.headers will be set as ''
if (!mcpRelation) {
return;
}
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.

🟠 MAJOR: Bare return produces invalid React output

Issue: Line 90's bare return; returns undefined, which is not valid JSX for a React component body. This was flagged in the previous review at dba1bef57 and remains unresolved.

Why: React components must return valid JSX, null, or a React fragment. Returning undefined causes a runtime warning and potentially unexpected rendering behavior. This can occur when the MCP relation is removed but the component is still mounted.

Fix:

Suggested change
}
// MCP was removed, fix race condition when mcpRelation.headers will be set as ''
if (!mcpRelation) {
return null;
}

Refs:

@github-actions github-actions Bot deleted a comment from claude Bot Mar 26, 2026
Co-authored-by: Dimitri POSTOLOV <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@itoqa
Copy link
Copy Markdown

itoqa Bot commented Mar 26, 2026

Ito Test Report ❌

15 test cases ran. 1 failed, 14 passed.

The unified run executed 15 test cases with 14 passing and 1 failing, showing strong overall stability across routing, graph editing integrity, validation navigation, unsaved-change guardrails, mobile usability, and security/resilience checks (including encoded query tampering and JSON/header injection payloads) with no crashes or script execution. The key issue is a confirmed medium-severity regression in browser Back/Forward behavior where sidepane selection and URL query state can desynchronize (including lost edge selection), likely because selection-sync paths use history replacement instead of preserving navigable selection history, and the failure is tied to code modified in this PR.

❌ Failed (1)
Category Summary Screenshot
Edge 🟠 Browser back/forward transitions can desynchronize or lose selection/query state instead of restoring node/edge pane state consistently. EDGE-5
🟠 Back/forward navigation desynchronizes graph selection and URL query state
  • What failed: Back/Forward does not reliably restore pane/query/selection parity and can return an inconsistent state (including lost edge selection state), instead of replaying the expected node/edge selection sequence.
  • Impact: Users cannot reliably review or compare previously selected graph objects with browser history controls. This breaks expected navigation behavior in a core editor workflow and can cause confusion or mistaken edits.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open the agent graph editor.
    2. Select Node A, then Node B, then an edge to move through node and edge panes.
    3. Press browser Back twice and Forward twice.
    4. Observe that query params, pane state, and selected graph object can become inconsistent instead of replaying the prior sequence.
  • Code analysis: The selection-to-query synchronization path in the graph editor updates URL state with explicit history replacement in multiple places, including both selection-change handling and selected-node/edge reconciliation effects. This overwrites the current entry instead of preserving a navigable stack for selection transitions.
  • Why this is likely a bug: The production selection-sync logic repeatedly replaces history entries for state transitions that users expect browser Back/Forward to replay, so the observed desynchronization is consistent with the implemented URL-history behavior rather than a test-only artifact.

Relevant code:

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/agents/[agentId]/page.client.tsx (lines 560-575)

const onSelectionChange: OnSelectionChangeFunc = ({ nodes, edges }) => {
  const node = nodes.length === 1 ? nodes[0] : null;
  const edge =
    edges.length === 1 &&
    (edges[0]?.type === EdgeType.A2A || edges[0]?.type === EdgeType.SelfLoop)
      ? edges[0]
      : null;
  const defaultPane = isOpen ? 'agent' : null;
  setQueryState(
    {
      pane: node ? 'node' : edge ? 'edge' : defaultPane,
      nodeId: node ? getNodeGraphKey(node) : null,
      edgeId: edge ? getEdgeGraphKey(edge, nodes) : null,
    },
    { history: 'replace' }
  );
};

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/agents/[agentId]/page.client.tsx (lines 700-728)

if (singleSelectedNode) {
  const nextNodeId = getNodeGraphKey(singleSelectedNode);
  if (nextNodeId && nextNodeId !== nodeId) {
    setQueryState(
      {
        pane: 'node',
        nodeId: nextNodeId,
        edgeId: null,
      },
      { history: 'replace' }
    );
  }
  return;
}

if (
  singleSelectedEdge &&
  (singleSelectedEdge.type === EdgeType.A2A || singleSelectedEdge.type === EdgeType.SelfLoop)
) {
  const nextEdgeId = getEdgeGraphKey(singleSelectedEdge, nodes);
  if (nextEdgeId && nextEdgeId !== edgeId) {
    setQueryState(
      {
        pane: 'edge',
        nodeId: null,
        edgeId: nextEdgeId,
      },
      { history: 'replace' }
    );
  }
}
✅ Passed (14)
Category Summary Screenshot
Adversarial Rapid double-click interactions on Save and Discard produced single outcomes without duplicate navigation or crashes. ADV-1
Adversarial Repeated connect attempts to the same tool remained deduplicated in UI and persisted state after reload. ADV-2
Adversarial Header injection payload handling remained data-only in JSON editors with no script execution behavior. ADV-3
Adversarial Encoded/malformed query payloads are handled safely without script execution or UI breakage. ADV-4
Adversarial Refresh interruption correctly surfaced guardrails, preserved edits on cancel, and allowed a valid follow-up save. ADV-5
Edge Default sub-agent deletion was blocked and the node remained present as the default after the delete attempt. EDGE-3
Edge Save in the unsaved-changes dialog correctly blocked navigation on validation failure and kept the user on the editor with errors visible. EDGE-4
Edge Mobile viewport editing, sidepane interactions, and save flow remained usable and completed successfully. EDGE-6
Logic Sub-agent ID update preserved tool relationships and relation identity remained stable after save/reload checks. LOGIC-2
Logic Tool reconnect behavior retained MCP relation configuration without wiping persisted relation form data. LOGIC-3
Logic Validation summary Go to actions navigated to the expected graph-key-backed nodes in the editor flow. LOGIC-4
Happy-path Existing sub-agent deep-link opens the Sub Agent sidepane and keeps query state synchronized. ROUTE-1
Happy-path New agent creation opens the editor with one initial Sub Agent and default-sub-agent enabled. ROUTE-2
Happy-path MCP placeholder selection correctly rewrites nodeId to mcp:* and preserves relation state. ROUTE-3

Commit: 8efffd5

View Full Run


Tell us how we did: Give Ito Feedback

github-merge-queue Bot pushed a commit that referenced this pull request Mar 26, 2026
… schemas from `agents-core` (#1799)

* upd

* schemas changes

* upd

* move to serializeJson

* upd

* upd

* all type errors are gone

* fix knip

* less diff

* less diff

* tweaks, fix tests

* tweaks, fix tests

* tweaks, fix tests

* tweaks, fix tests

* add changeset

* add changeset

* update openapi

* rollback nullable update for some fields

* fix some missing shouldDirty

* fix typecheck

* add `GenericJsonEditor`/`GenericPromptEditor`

* move

* move

* move

* move

* move

* upd

* upd

* add lintignore

* format

* Add changeset for GenericJsonEditor and GenericPromptEditor

Co-authored-by: dimaMachina <[email protected]>

* polish

* polish

* polishing

* polishing

* upd

* upd

* add tests

* upd

* format

* use GenericJsonSchemaEditor

* upd

* fix agent prompt tests

* polish tests

* remove unneeded JSON suffix

* increase timeout

* add more tests

* upd

* fixes

* upd

* fix typecheck

* fix tests

* fix

* fix missing cypress tests

* try change in core to see docs deployment

* update snapshot

* format

* feat(dashboard): refactor agent pages `subAgent`, `mcp-tool`, `function-tool`, `teamAgent`, `externalAgent` forms to use zod schemas from `agents-core`  (#2400)

* dependencies

* add generic code editor

* execute code

* migrate tempToolPolicies

* migrate external agent node editor

* migrate external agent editor

* base url

* better monaco loading for action buttons

* description and baseUrl

* format

* headers

* team agent name and id

* description and headers in team agent

* knip

* knip InputField

* mcp tool id name and headers

* polish

* update zod type

* reuse validation

* migrate config.mcp.server.url

* remove use callback

* update subagent node

* destructure is default too

* mark name and id as required for subagent

* fix subagent flow errors

* add useProcessedErrors

* polish external agent

* upd

* upd

* upd

* functional tools

* fix saving

* remove use memo

* remove error Helpers

* format

* upd use agent errors

* upd use agent errors

* rm useAgentErrors

* update agent error summary

* fix agent error summary

* remove toast error which duplicate agent summary

* rm old states

* make tools field as record

* rm

* rm

* upd

* more

* polish

* show badge in agent settings

* format

* upd

* rm PartialFullAgentDefinition

* rm PartialFullAgentDefinition

* upd

* upd

* cleanup

* fix type errors

* improve types

* fix type errors

* more fix type errors

* fix deserializeAgentData, pick only used types

* more typecheck fixes

* fix typecheck

* 2 type errors left

* fix type errors

* lint is good now

* rm unused types

* format

* polish

* remove unneeded arguments

* polish

* add go to for agent settings

* upd

* polish

* polish

* polish

* polish agent error summary

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* add ToolPoliciesSchema

* upd mcp

* upd mcp

* upd mcp

* upd mcp

* rm tempHeaders

* rm tempHeaders

* rm tempHeaders

* rm tempHeaders

* rm tempHeaders

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* cleanup

* cleanup

* fix

* fix

* fix

* fix

* fix sidepane

* imrpove AgentErrorSummary, show popup only on blur

* rm agent-error-parser.ts

* show server errors in agent error summary

* add useGroupedAgentErrors

* remove isDefault

* upd

* upd

* use animate graph

* upd

* upd

* move deleteSelected

* use useDefaultSubAgentIdRef

* upd

* upd

* cleanup

* format

* upd

* splits functions tools to functions and function tools

* fix agent summary, upd function and function tools

* update snapshot

* update snapshot

* upd

* upd

* update external agents

* update team agents

* upd

* upd

* upd

* upd flatNestedFieldMessage

* upd flatNestedFieldMessage

* upd validation

* upd isRequired in function-tool-node-editor.tsx

* rm

* update useAgentShortcuts

* cleanup serialize

* upd

* upd name and description

* trigger dirty state

* add super refine

* upd

* update onDrop

* upd

* upd

* upd

* upd

* upd

* upd

* upd mcp

* upd mcp

* upd mcp moreeeeeee

* upd

* upd validation

* sub agent editor

* external agent selector

* update team agent

* upd

* toolid

* handle tool null

* upd team agent and external agent

* upd errors styles

* upd errors styles

* upd errors styles

* upd errors styles

* upd errors styles

* upd errors styles

* upd button

* upd agent error summary

* updd

* updd

* upd typecheck

* upd typecheck

* upd skills type

* rm

* rm

* rm

* rm

* upd

* flowButton

* flowButton

* TeamRelation

* fix

* fix serialize

* deserialize

* upd

* upd

* fix form.unregister

* fix type

* upd serialize

* rm functionToolId

* knip

* openapi

* fix dirty state

* fix core tests

* fix tests

* fix cypress

* fix cypress

* fix lint

* fix some tests

* fix some tests

* fix rest of tests

* fix api tests

* fix cypress validation tests

* fix more tests

* fix edit with ai

* fix tests

* add more tests

* lint

* remove @ts-expect-error

* fix typecheck

* polish

* fix typecheck

* fix(dashboard): address review feedback for agent form refactor

* format

* apply merge fixes

* undo making inputSchema required

* chore: update OpenAPI snapshot

* fixes

* review

* show errors for skills

* refactor data components and artifacts

* refactor data components and artifacts

* upd

* upd models

* upd model section

* fix skills

* rm

* fixes

* fixes

* fixes

* fix typecheck

* Update agents-manage-ui/src/components/agent/sidepane/nodes/model-section.tsx

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* one last fix

* fix test

* style: auto-format with biome

---------

Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* fix `inkeep pull` tests

* upd

* duplicate sub agent id validation

* set subAgent only for right type

* fallbacks

* sync saved agent graph wip

* subagents, ext agents and team agents can be optional

* move error to zod superRefine

* more fallbacks

* fix defaultSubAgentId

* Prd 5298 2 (#2817)

* fix typecheck

* fix modelName

* fix

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P1] The current model hides two different identities behind id. In live form state, subAgents and defaultSubAgentId are actually keyed by node key, not persisted subagent ID; then submit-time transforms convert some of that back to persisted IDs. That translation is spread across the resolver, hydrateNodesWithFormData, and serializeAgentData, so form.reset changes semantics mid-session. See validation.ts (line 160), validation.ts (line 240), serialize.ts (line 87), serialize.ts (line 150), sub-agent-selector.tsx (line 32).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* [P2] Business data is duplicated in RHF and node.data, but renderers already trust RHF. hydrateNodesWithFormData() exists only because serializeAgentData() reads node payload fields from node.data; the UI mostly ignores those same fields and watches RHF instead. That duplication is the main drift source. See serialize.ts (line 87), deserialize.ts (line 123), sub-agent-node.tsx (line 50), function-tool-node.tsx (line 17), mcp-node.tsx (line 63).

* Group 1: Stabilize graph identity

* Group 2: Finish RHF as the business source of truth

* Group 2: Finish RHF as the business source of truth

* Group 2: Finish RHF as the business source of truth

* Group 3: Remove layout dependence on node business payload

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* upd

* renames

* Group 1: Fix remaining schema mismatches

* Group 1: Fix remaining schema mismatches

* Group 1: Fix remaining schema mismatches

* Group 1: Fix remaining schema mismatches

* upd

* upd

* upd

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* Group 3: Collapse connection state into one relation model

* more

* more

* rm

* rm

* Group 3: Finish explicit graph identity

* comment

* upd

* upd

* upd

* upd

* upd

* upd

* simplify

* lint

* update snapshot

* fix zod omit issue

* fix cypress

* style: auto-format with biome

* fix review

* Update mcp-node-editor.tsx

* apply more review

* fix: differentiate loading vs missing state in MCP node editor null guard

* Update mcp-node-editor.tsx

* fix inheritance in subagent node

* fix needs approval

* polish

* fix issue

* fix race condition

* fix error

* Add detailed changeset for graph identity refactor

Co-authored-by: Dimitri POSTOLOV <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Dimitri POSTOLOV <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>

* format

* fix typecheck

* fix conflicts

* lintfix

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: dimaMachina <[email protected]>
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Dimitri POSTOLOV <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
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