F3 SLICE-3: applyAgentEdit seam + live tracking + decorations/toggle (INV-7/INV-9) (#6)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 10:01:03 -07:00
parent decdfbf31d
commit d05cb0f9d4
6 changed files with 372 additions and 26 deletions
+46 -3
View File
@@ -2,11 +2,13 @@ import * as vscode from "vscode";
import { fetchSdkSummary } from "./cline";
import { CoauthorStore } from "./store";
import { ThreadController } from "./threadController";
import { AttributionController } from "./attributionController";
const CHANNEL_NAME = "Cowriting (Cline SDK)";
export interface CowritingApi {
threadController: ThreadController;
attributionController: AttributionController;
}
export function activate(context: vscode.ExtensionContext): CowritingApi | undefined {
@@ -41,14 +43,55 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
const threadController = new ThreadController(store, root);
context.subscriptions.push(threadController);
// Render threads for already-open editors, and on future opens.
// --- F3: live attribution (Feature #6) ---
const attributionController = new AttributionController(store, root);
context.subscriptions.push(attributionController);
// One SHARED sidecar watcher for both controllers; self-writes are
// suppressed centrally in the store (the sidecar is co-owned).
const watcher = vscode.workspace.createFileSystemWatcher("**/.threads/**/*.json");
const onSidecar = (uri: vscode.Uri) => {
if (store.consumeSelfWrite(uri.fsPath)) return;
threadController.handleExternalSidecarChange(uri);
attributionController.handleExternalSidecarChange(uri);
};
watcher.onDidChange(onSidecar);
watcher.onDidCreate(onSidecar);
context.subscriptions.push(watcher);
// The seam as a command (INV-9): the only machine-edit ingress, hidden from
// the palette — for the host E2E harness and future SDK turn plumbing.
context.subscriptions.push(
vscode.commands.registerCommand(
"cowriting.applyAgentEdit",
(args: {
uri: string; start: number; end: number; newText: string;
model?: string; sessionId?: string; turnId?: string;
}) => {
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === args.uri);
if (!doc) return Promise.resolve(false);
const range = new vscode.Range(doc.positionAt(args.start), doc.positionAt(args.end));
const provenance = {
kind: "agent" as const,
id: "claude",
agent: { sdk: "@cline/sdk", model: args.model ?? "sonnet", sessionId: args.sessionId ?? "" },
};
return attributionController.applyAgentEdit(doc, range, args.newText, provenance, { turnId: args.turnId });
},
),
);
// 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)) threadController.renderAll(doc);
if (doc.uri.scheme === "file" && doc.uri.fsPath.startsWith(root)) {
threadController.renderAll(doc);
attributionController.loadAll(doc);
}
};
vscode.workspace.textDocuments.forEach(renderIfOpen);
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(renderIfOpen));
return { threadController };
return { threadController, attributionController };
}
export function deactivate(): void {