d05cb0f9d4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
/**
|
|
* PendingEditRegistry — the seam's bookkeeping (spec §6.2, INV-9). Before
|
|
* applyAgentEdit applies a WorkspaceEdit, it registers the exact expected
|
|
* change; the controller's change handler consumes a matching registration to
|
|
* attribute that change to the agent. Anything unmatched is human typing.
|
|
* vscode-free and unit-testable.
|
|
*/
|
|
import type { Provenance } from "./model";
|
|
|
|
export interface PendingEdit {
|
|
docPath: string;
|
|
/** half-open [start, end) offsets of the replaced range. */
|
|
start: number;
|
|
end: number;
|
|
newText: string;
|
|
provenance: Provenance;
|
|
turnId?: string;
|
|
}
|
|
|
|
export class PendingEditRegistry {
|
|
private pending: PendingEdit[] = [];
|
|
|
|
register(edit: PendingEdit): void {
|
|
this.pending.push(edit);
|
|
}
|
|
|
|
/**
|
|
* Remove a registration that failed to apply. Identity-based (`===`), so
|
|
* callers must keep the exact registered object; it is a no-op when `match`
|
|
* already consumed the registration.
|
|
*/
|
|
unregister(edit: PendingEdit): void {
|
|
this.pending = this.pending.filter((p) => p !== edit);
|
|
}
|
|
|
|
/**
|
|
* Find-and-consume the registration exactly matching a change event
|
|
* (same doc, same replaced range, same inserted text). Null → human edit.
|
|
*/
|
|
match(docPath: string, change: { start: number; end: number; text: string }): PendingEdit | null {
|
|
const i = this.pending.findIndex(
|
|
(p) =>
|
|
p.docPath === docPath &&
|
|
p.start === change.start &&
|
|
p.end === change.end &&
|
|
p.newText === change.text,
|
|
);
|
|
if (i === -1) return null;
|
|
const [hit] = this.pending.splice(i, 1);
|
|
return hit;
|
|
}
|
|
}
|