F4 SLICE-3: wire ProposalController — contributes, watcher fan-out, propose command, stubs, API (#12)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -30,11 +30,17 @@
|
|||||||
{ "command": "cowriting.reopenThread", "title": "Reopen Thread", "category": "Cowriting" },
|
{ "command": "cowriting.reopenThread", "title": "Reopen Thread", "category": "Cowriting" },
|
||||||
{ "command": "cowriting.toggleAttribution", "title": "Toggle Attribution", "category": "Cowriting" },
|
{ "command": "cowriting.toggleAttribution", "title": "Toggle Attribution", "category": "Cowriting" },
|
||||||
{ "command": "cowriting.applyAgentEdit", "title": "Apply Agent Edit (internal seam)", "category": "Cowriting" },
|
{ "command": "cowriting.applyAgentEdit", "title": "Apply Agent Edit (internal seam)", "category": "Cowriting" },
|
||||||
{ "command": "cowriting.editSelection", "title": "Ask Claude to Edit Selection", "category": "Cowriting" }
|
{ "command": "cowriting.editSelection", "title": "Ask Claude to Edit Selection", "category": "Cowriting" },
|
||||||
|
{ "command": "cowriting.acceptProposal", "title": "✓ Accept Proposal", "category": "Cowriting" },
|
||||||
|
{ "command": "cowriting.rejectProposal", "title": "✗ Reject Proposal", "category": "Cowriting" },
|
||||||
|
{ "command": "cowriting.proposeAgentEdit", "title": "Propose Agent Edit (internal seam)", "category": "Cowriting" }
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
"commandPalette": [
|
"commandPalette": [
|
||||||
{ "command": "cowriting.applyAgentEdit", "when": "false" }
|
{ "command": "cowriting.applyAgentEdit", "when": "false" },
|
||||||
|
{ "command": "cowriting.proposeAgentEdit", "when": "false" },
|
||||||
|
{ "command": "cowriting.acceptProposal", "when": "false" },
|
||||||
|
{ "command": "cowriting.rejectProposal", "when": "false" }
|
||||||
],
|
],
|
||||||
"editor/context": [
|
"editor/context": [
|
||||||
{
|
{
|
||||||
@@ -61,6 +67,16 @@
|
|||||||
"command": "cowriting.reopenThread",
|
"command": "cowriting.reopenThread",
|
||||||
"group": "inline",
|
"group": "inline",
|
||||||
"when": "commentController == cowriting.threads && commentThread =~ /^resolved$/"
|
"when": "commentController == cowriting.threads && commentThread =~ /^resolved$/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "cowriting.acceptProposal",
|
||||||
|
"group": "inline@1",
|
||||||
|
"when": "commentController == cowriting.proposals && commentThread =~ /^pending$/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "cowriting.rejectProposal",
|
||||||
|
"group": "inline@2",
|
||||||
|
"when": "commentController == cowriting.proposals"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-1
@@ -3,12 +3,15 @@ import { fetchSdkSummary } from "./cline";
|
|||||||
import { CoauthorStore } from "./store";
|
import { CoauthorStore } from "./store";
|
||||||
import { ThreadController } from "./threadController";
|
import { ThreadController } from "./threadController";
|
||||||
import { AttributionController } from "./attributionController";
|
import { AttributionController } from "./attributionController";
|
||||||
|
import { ProposalController } from "./proposalController";
|
||||||
|
import { buildFingerprint } from "./anchorer";
|
||||||
|
|
||||||
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
||||||
|
|
||||||
export interface CowritingApi {
|
export interface CowritingApi {
|
||||||
threadController: ThreadController;
|
threadController: ThreadController;
|
||||||
attributionController: AttributionController;
|
attributionController: AttributionController;
|
||||||
|
proposalController: ProposalController;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext): CowritingApi | undefined {
|
export function activate(context: vscode.ExtensionContext): CowritingApi | undefined {
|
||||||
@@ -55,6 +58,9 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
"cowriting.editSelection",
|
"cowriting.editSelection",
|
||||||
"cowriting.toggleAttribution",
|
"cowriting.toggleAttribution",
|
||||||
"cowriting.applyAgentEdit",
|
"cowriting.applyAgentEdit",
|
||||||
|
"cowriting.acceptProposal",
|
||||||
|
"cowriting.rejectProposal",
|
||||||
|
"cowriting.proposeAgentEdit",
|
||||||
]) {
|
]) {
|
||||||
context.subscriptions.push(vscode.commands.registerCommand(command, stub));
|
context.subscriptions.push(vscode.commands.registerCommand(command, stub));
|
||||||
}
|
}
|
||||||
@@ -68,6 +74,10 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
const attributionController = new AttributionController(store, root);
|
const attributionController = new AttributionController(store, root);
|
||||||
context.subscriptions.push(attributionController);
|
context.subscriptions.push(attributionController);
|
||||||
|
|
||||||
|
// --- F4: propose/accept (Feature #12) ---
|
||||||
|
const proposalController = new ProposalController(store, attributionController, root);
|
||||||
|
context.subscriptions.push(proposalController);
|
||||||
|
|
||||||
// One SHARED sidecar watcher for both controllers; self-writes are
|
// One SHARED sidecar watcher for both controllers; self-writes are
|
||||||
// suppressed centrally in the store (the sidecar is co-owned).
|
// suppressed centrally in the store (the sidecar is co-owned).
|
||||||
const watcher = vscode.workspace.createFileSystemWatcher("**/.threads/**/*.json");
|
const watcher = vscode.workspace.createFileSystemWatcher("**/.threads/**/*.json");
|
||||||
@@ -75,6 +85,7 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
if (store.consumeSelfWrite(uri.fsPath)) return;
|
if (store.consumeSelfWrite(uri.fsPath)) return;
|
||||||
threadController.handleExternalSidecarChange(uri);
|
threadController.handleExternalSidecarChange(uri);
|
||||||
attributionController.handleExternalSidecarChange(uri);
|
attributionController.handleExternalSidecarChange(uri);
|
||||||
|
proposalController.handleExternalSidecarChange(uri);
|
||||||
};
|
};
|
||||||
watcher.onDidChange(onSidecar);
|
watcher.onDidChange(onSidecar);
|
||||||
watcher.onDidCreate(onSidecar);
|
watcher.onDidCreate(onSidecar);
|
||||||
@@ -102,6 +113,31 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The propose ingress as a command (spec §6.4): records a pending proposal,
|
||||||
|
// NEVER touches the document (INV-10) — for the host E2E harness.
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
"cowriting.proposeAgentEdit",
|
||||||
|
(args: {
|
||||||
|
uri: string; start: number; end: number; newText: string;
|
||||||
|
model?: string; sessionId?: string; turnId?: string; instruction?: string;
|
||||||
|
}) => {
|
||||||
|
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === args.uri);
|
||||||
|
if (!doc) return Promise.resolve(undefined);
|
||||||
|
const fp = buildFingerprint(doc.getText(), { start: args.start, end: args.end });
|
||||||
|
const provenance = {
|
||||||
|
kind: "agent" as const,
|
||||||
|
id: "claude",
|
||||||
|
agent: { sdk: "@cline/sdk", model: args.model ?? "sonnet", sessionId: args.sessionId ?? "" },
|
||||||
|
};
|
||||||
|
return proposalController.propose(doc, fp, args.newText, provenance, {
|
||||||
|
turnId: args.turnId,
|
||||||
|
instruction: args.instruction,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// F3 SLICE-5: the live turn (PUC-2) — selection + instruction → one
|
// F3 SLICE-5: the live turn (PUC-2) — selection + instruction → one
|
||||||
// claude-code SDK turn (liveTurn.ts, INV-8) → the applyAgentEdit seam (INV-9).
|
// claude-code SDK turn (liveTurn.ts, INV-8) → the applyAgentEdit seam (INV-9).
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
@@ -172,12 +208,13 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
if (doc.uri.scheme === "file" && doc.uri.fsPath.startsWith(root)) {
|
if (doc.uri.scheme === "file" && doc.uri.fsPath.startsWith(root)) {
|
||||||
threadController.renderAll(doc);
|
threadController.renderAll(doc);
|
||||||
attributionController.loadAll(doc);
|
attributionController.loadAll(doc);
|
||||||
|
proposalController.renderAll(doc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
vscode.workspace.textDocuments.forEach(renderIfOpen);
|
vscode.workspace.textDocuments.forEach(renderIfOpen);
|
||||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(renderIfOpen));
|
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(renderIfOpen));
|
||||||
|
|
||||||
return { threadController, attributionController };
|
return { threadController, attributionController, proposalController };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate(): void {
|
export function deactivate(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user