From 452c071efb3b548c5274a95a4b6a7e337ca47b7c Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Wed, 10 Jun 2026 22:05:44 -0700 Subject: [PATCH] =?UTF-8?q?F4=20SLICE-4:=20editSelection=20is=20propose-by?= =?UTF-8?q?-default=20=E2=80=94=20the=20turn=20ends=20in=20a=20proposal,?= =?UTF-8?q?=20the=20seam=20fires=20on=20accept=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- src/extension.ts | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 58c4d7c..a42d6b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -138,8 +138,9 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef ), ); - // F3 SLICE-5: the live turn (PUC-2) — selection + instruction → one - // claude-code SDK turn (liveTurn.ts, INV-8) → the applyAgentEdit seam (INV-9). + // F3 SLICE-5 → F4 SLICE-4: the live turn — selection + instruction → one + // 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( vscode.commands.registerCommand("cowriting.editSelection", async () => { const editor = vscode.window.activeTextEditor; @@ -163,8 +164,13 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef } const document = editor.document; const selection = editor.selection; - const expectedVersion = document.version; 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)}`; try { await vscode.window.withProgress( @@ -174,24 +180,32 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef const turn = await runEditTurn(instruction, selectedText); if (turn.replacement === "") { void vscode.window.showWarningMessage( - "Cowriting: Claude returned an empty replacement — nothing was applied.", + "Cowriting: Claude returned an empty replacement — nothing was proposed.", ); 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, - new vscode.Range(selection.start, selection.end), + fp, turn.replacement, { kind: "agent", id: "claude", agent: { sdk: "@cline/sdk", model: turn.model, sessionId: turn.sessionId }, }, - { expectedVersion, turnId }, + { turnId, instruction }, ); - if (!ok) { - void vscode.window.showWarningMessage( - "Cowriting: the edit could not be applied (the document changed during the turn, or the editor rejected the edit) — nothing was changed.", + if (id) { + void vscode.window.showInformationMessage( + "Cowriting: Claude proposed an edit — review the diff at the highlighted range (✓ accept / ✗ reject).", ); } },