F4 SLICE-4: editSelection is propose-by-default — the turn ends in a proposal, the seam fires on accept (#12)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 22:05:44 -07:00
parent 4688ac8bbd
commit 452c071efb
+24 -10
View File
@@ -138,8 +138,9 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
), ),
); );
// F3 SLICE-5: the live turn (PUC-2) — selection + instruction → one // F3 SLICE-5 → F4 SLICE-4: the live turn — selection + instruction → one
// claude-code SDK turn (liveTurn.ts, INV-8) → the applyAgentEdit seam (INV-9). // claude-code SDK turn (liveTurn.ts, INV-8) → a PENDING PROPOSAL (F4,
// INV-10); the applyAgentEdit seam (INV-9) now fires only on accept.
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand("cowriting.editSelection", async () => { vscode.commands.registerCommand("cowriting.editSelection", async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
@@ -163,8 +164,13 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
} }
const document = editor.document; const document = editor.document;
const selection = editor.selection; const selection = editor.selection;
const expectedVersion = document.version;
const selectedText = document.getText(selection); const selectedText = document.getText(selection);
// Capture the anchor BEFORE the turn (spec §6.5 PUC-1): mid-turn edits
// can't skew it — the proposal renders wherever the target re-resolves.
const fp = buildFingerprint(document.getText(), {
start: document.offsetAt(selection.start),
end: document.offsetAt(selection.end),
});
const turnId = `turn-${Date.now().toString(36)}`; const turnId = `turn-${Date.now().toString(36)}`;
try { try {
await vscode.window.withProgress( await vscode.window.withProgress(
@@ -174,24 +180,32 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
const turn = await runEditTurn(instruction, selectedText); const turn = await runEditTurn(instruction, selectedText);
if (turn.replacement === "") { if (turn.replacement === "") {
void vscode.window.showWarningMessage( void vscode.window.showWarningMessage(
"Cowriting: Claude returned an empty replacement — nothing was applied.", "Cowriting: Claude returned an empty replacement — nothing was proposed.",
); );
return; return;
} }
const ok = await attributionController.applyAgentEdit( if (turn.replacement === selectedText) {
void vscode.window.showInformationMessage(
"Cowriting: Claude proposed no change to the selection.",
);
return;
}
// F4 (INV-10): the turn ends in a PROPOSAL, not a buffer mutation.
// The seam now fires only on accept (ProposalController, INV-9).
const id = await proposalController.propose(
document, document,
new vscode.Range(selection.start, selection.end), fp,
turn.replacement, turn.replacement,
{ {
kind: "agent", kind: "agent",
id: "claude", id: "claude",
agent: { sdk: "@cline/sdk", model: turn.model, sessionId: turn.sessionId }, agent: { sdk: "@cline/sdk", model: turn.model, sessionId: turn.sessionId },
}, },
{ expectedVersion, turnId }, { turnId, instruction },
); );
if (!ok) { if (id) {
void vscode.window.showWarningMessage( void vscode.window.showInformationMessage(
"Cowriting: the edit could not be applied (the document changed during the turn, or the editor rejected the edit) — nothing was changed.", "Cowriting: Claude proposed an edit — review the diff at the highlighted range (✓ accept / ✗ reject).",
); );
} }
}, },