F3 SLICE-5: live claude-code turn via applyAgentEdit seam + manual smoke (INV-8) (#6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,56 @@ 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).
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cowriting.editSelection", async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor || editor.selection.isEmpty || !editor.document.uri.fsPath.startsWith(root)) {
|
||||
void vscode.window.showWarningMessage("Cowriting: select some text in a workspace document first.");
|
||||
return;
|
||||
}
|
||||
const instruction = await vscode.window.showInputBox({
|
||||
prompt: "What should Claude do with the selection?",
|
||||
placeHolder: "e.g. tighten this paragraph",
|
||||
});
|
||||
if (!instruction) return;
|
||||
const document = editor.document;
|
||||
const selection = editor.selection;
|
||||
const expectedVersion = document.version;
|
||||
const selectedText = document.getText(selection);
|
||||
const turnId = `turn-${Date.now().toString(36)}`;
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{ location: vscode.ProgressLocation.Notification, title: "Cowriting: asking Claude…" },
|
||||
async () => {
|
||||
const { runEditTurn } = await import("./liveTurn");
|
||||
const turn = await runEditTurn(instruction, selectedText);
|
||||
const ok = await attributionController.applyAgentEdit(
|
||||
document,
|
||||
new vscode.Range(selection.start, selection.end),
|
||||
turn.replacement,
|
||||
{
|
||||
kind: "agent",
|
||||
id: "claude",
|
||||
agent: { sdk: "@cline/sdk", model: turn.model, sessionId: turn.sessionId },
|
||||
},
|
||||
{ expectedVersion, turnId },
|
||||
);
|
||||
if (!ok) {
|
||||
void vscode.window.showWarningMessage(
|
||||
"Cowriting: the document changed while Claude was editing — nothing was applied.",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
void vscode.window.showErrorMessage(`Cowriting: Claude edit failed — ${message}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Render threads + attributions for already-open editors, and on future opens.
|
||||
const renderIfOpen = (doc: vscode.TextDocument) => {
|
||||
if (doc.uri.scheme === "file" && doc.uri.fsPath.startsWith(root)) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* LiveTurn — the minimal machine-edit ingress (spec §6.2): one @cline/sdk
|
||||
* Agent turn on the built-in `claude-code` provider, which rides the
|
||||
* operator's local Claude Code Pro/Max login — the extension holds NO
|
||||
* credentials of any kind (INV-8). vscode-free; the editSelection command
|
||||
* (extension.ts) feeds the result into the applyAgentEdit seam (INV-9).
|
||||
*
|
||||
* Like src/cline.ts: @cline/sdk is ESM-only, loaded via dynamic import(),
|
||||
* never bundled (esbuild keeps it external).
|
||||
*/
|
||||
|
||||
export interface EditTurnResult {
|
||||
replacement: string;
|
||||
model: string;
|
||||
/** the SDK run id — recorded as provenance sessionId. */
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT = [
|
||||
"You are a precise text editor embedded in VS Code.",
|
||||
"You will be given a piece of text and an instruction.",
|
||||
"Respond with ONLY the edited replacement text — no explanation, no preamble,",
|
||||
"no markdown code fences. Preserve the original's leading/trailing whitespace",
|
||||
"unless the instruction says otherwise.",
|
||||
].join(" ");
|
||||
|
||||
/** Strip a wrapping markdown code fence if the model added one anyway. */
|
||||
export function extractReplacement(outputText: string): string {
|
||||
const m = outputText.match(/^\s*```[^\n]*\n([\s\S]*?)\n?```\s*$/);
|
||||
return m ? m[1] : outputText;
|
||||
}
|
||||
|
||||
export async function runEditTurn(
|
||||
instruction: string,
|
||||
selectedText: string,
|
||||
opts?: { modelId?: string },
|
||||
): Promise<EditTurnResult> {
|
||||
const sdk = await import("@cline/sdk");
|
||||
const modelId = opts?.modelId ?? "sonnet";
|
||||
const agent = new sdk.Agent({
|
||||
providerId: "claude-code",
|
||||
modelId,
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
});
|
||||
const result = await agent.run(
|
||||
`<instruction>\n${instruction}\n</instruction>\n<text>\n${selectedText}\n</text>`,
|
||||
);
|
||||
// The SDK's AgentRunResult.status union is "completed" | "aborted" | "failed"
|
||||
// (@cline/shared agent.d.ts) — "completed" is the success status.
|
||||
if (result.status !== "completed") {
|
||||
throw new Error(
|
||||
`claude-code turn ${result.status}: ${result.error?.message ?? "unknown error"} ` +
|
||||
"(is Claude Code installed and signed in?)",
|
||||
);
|
||||
}
|
||||
return { replacement: extractReplacement(result.outputText), model: modelId, sessionId: result.runId };
|
||||
}
|
||||
Reference in New Issue
Block a user