| import type { UndoManager } from "yjs"; | |
| /** | |
| * Helpers that bracket a sequence of AI-agent edits so they collapse into a | |
| * single undo step. | |
| * | |
| * Yjs UndoManager merges successive transactions that happen within the | |
| * `captureTimeout` window (500ms by default). `stopCapturing()` forces the | |
| * next transaction to start a fresh undo item, which we use as a *boundary | |
| * marker*: | |
| * | |
| * - `startAgentBatch()` is called once at the first tool call of a turn. | |
| * It separates the previous user edit from the agent batch. | |
| * - `endAgentBatch()` is called when the model finishes streaming. | |
| * It separates the agent batch from the user's next edits. | |
| * | |
| * Between the two markers, every Yjs-backed mutation issued by the agent | |
| * (replaceSelection, applyDiff, updateFrontmatter, ...) merges into one | |
| * undo item so a single `Cmd+Z` reverts the entire turn. | |
| * | |
| * The functions are no-ops when the manager is unavailable (early in the | |
| * editor lifecycle) or when the batch is already (in)active, which keeps | |
| * call sites trivial. | |
| */ | |
| export interface AgentBatchHandle { | |
| startAgentBatch(): void; | |
| endAgentBatch(): void; | |
| isActive(): boolean; | |
| } | |
| export function createAgentBatch( | |
| undoManager: UndoManager | null | undefined, | |
| ): AgentBatchHandle { | |
| let active = false; | |
| return { | |
| startAgentBatch() { | |
| if (undoManager && !active) { | |
| undoManager.stopCapturing(); | |
| active = true; | |
| } | |
| }, | |
| endAgentBatch() { | |
| if (undoManager && active) { | |
| undoManager.stopCapturing(); | |
| active = false; | |
| } | |
| }, | |
| isActive() { | |
| return active; | |
| }, | |
| }; | |
| } | |