Conversation
…edits - API and Chat edits that switch a basic/advanced pair by sending one side keep the side they left out, for Permission Mode and nested tool params such as Jira's project picker and manual ID. The side sent selects the mode, and a pair resent unchanged keeps its mode. - A tool-scoped mode toggle names its tool. The realtime server refuses it when that position no longer holds the tool, editors ignore a broadcast toggle for a tool they moved or removed, and removing or reordering tools always saves the list and its modes together, so a concurrent toggle can no longer land on another tool or an empty position. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
| const TOOL_IDENTITY_FIELDS = ['type', 'operation', 'toolId', 'customToolId'] as const | ||
|
|
||
| const TOOL_IDENTITY_PARAM_FIELDS = ['serverId', 'toolName', 'workflowId'] as const |
There was a problem hiding this comment.
Duplicate tools share identity
Two Jira tools can have the same type and operation while differing only by parameters such as projectId. Because those parameters are excluded from this identity and stored tools have no stable per-instance ID, swapping the two tools still satisfies the identity check. An in-flight positional mode toggle can therefore be accepted for the other Jira instance and assign its mode to the wrong tool.
| const previousIndexByIndex = new Map<number, number>() | ||
| if (originalTools) { | ||
| for (const [previousIndex, index] of matchRewrittenTools(originalTools, tools)) { | ||
| previousIndexByIndex.set(index, previousIndex) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Reordering can mix tool values
When two tools of the same type are both edited and reordered, neither may match its old entry by full content. The fallback matcher then pairs them by type and position, and this code uses those matches to restore omitted basic, advanced, or permission-mode values. As a result, an edited tool can receive inactive values from the other tool and persist a corrupted configuration.
| if (persisted?.applied !== false) { | ||
| const broadcastData = { | ||
| operation, | ||
| target, | ||
| payload, | ||
| timestamp: operationTimestamp, | ||
| senderId: socket.id, | ||
| userId: session.userId, | ||
| userName: session.userName, | ||
| metadata: { | ||
| workflowId, | ||
| operationId: generateId(), | ||
| }, | ||
| } | ||
|
|
||
| socket.to(workflowId).emit('workflow-operation', broadcastData) | ||
| socket.to(workflowId).emit('workflow-operation', broadcastData) | ||
| } |
There was a problem hiding this comment.
The sender applies a mode toggle optimistically before queueing it. When the server rejects that toggle as stale, this branch suppresses the broadcast but still confirms the operation; confirmation only removes it from the queue and does not roll back or reload state. If the competing move or removal was already applied locally before the toggle was queued, no later update corrects the optimistic value, leaving that editor inconsistent with persisted state.
Summary
Stacked on #7538. Fixes two problems found while QA-ing variable tool permission modes.
API and Chat edits lose the inactive value of a basic/advanced pair. An edit replaces the whole
toolslist, so switching a pair by sending only one side dropped the other side's stored value. This affected Permission Mode (sending onlyusageControlExpressiondropped the fixedusageControl) and nested tool params (sending only Jira'smanualProjectIddropped the pickedprojectId). The edit engine now matches each rewritten tool to the one it replaced and:Concurrent editors can put a tool mode on the wrong tool. Tool-scoped mode keys are positional (
${toolIndex}:…), and a toggle racing another editor's removal or reorder could persist at an index its tool no longer holds, so the next tool added there silently inherited Variable mode with an empty expression and runs failed. Now:toolRef(subblock, index, tool identity) and the realtime server skips it when that position holds a different tool; a skipped toggle is confirmed to its sender but not broadcast,A toggle made at the same moment as another editor's removal can still be dropped, but consistently: every editor and the saved state agree, and it can no longer land on another tool.
toolRefis optional, so older clients and servers keep the previous behavior.Type of Change
Testing
type-checkfor sim, realtime, and realtime-protocol; Biome;check:api-validation,check:boundaries, andcheck:realtime-prunepass.nonewere dropped.Reviewers:
applySuppliedToolModesinlib/workflows/editing/tool-modes.tsandisToolInputRefCurrentinpackages/realtime-protocol/src/tool-input.ts.Checklist
Screenshots/Videos
No screenshots attached.
🤖 Generated with Claude Code