feat: comments-first ask + comment→reply→offer→proposal loop (D19/D10/D8, PUC-8); sunset the input webview (spec §6.10)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,12 @@ suite("coediting registry (PUC-7)", () => {
|
||||
// rendered thread (not the sidecar-backed data); re-entering restores it.
|
||||
test("non-entered doc gets no surfaces; exit detaches; re-enter restores threads", async () => {
|
||||
const api = await activateApi();
|
||||
// Task 6 (D10): createThreadOnSelection now also fires the respond-in-
|
||||
// thread turn once the doc is coediting — stub it off (not under test
|
||||
// here; this suite asserts render/hide/restore, not the reply loop).
|
||||
api.threadController.setTurnRunnerForTest(async () => {
|
||||
throw new Error("coediting suite: reply-loop turn stubbed off");
|
||||
});
|
||||
const doc = await vscode.workspace.openTextDocument({ language: "markdown", content: "# a\n\npara\n" });
|
||||
const ed = await vscode.window.showTextDocument(doc);
|
||||
// not entered → no thread creation
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as assert from "assert";
|
||||
import * as vscode from "vscode";
|
||||
import { activateApi, settleUntil } from "./helpers";
|
||||
|
||||
// Task 6 (D19/D10/D8, PUC-8): the comments-first ask + comment→reply→offer→
|
||||
// proposal loop, with a stubbed reply turn (no real @cline/sdk call). Runs on
|
||||
// an untitled buffer (F8 routes it to the global sidecar) so this suite has no
|
||||
// workspace-folder dependency.
|
||||
suite("comment → reply → offer → proposal (PUC-8, D10/D19)", () => {
|
||||
test("reply on a coedited doc summons a machine reply + offer; accept lands pending proposals", async () => {
|
||||
const api = await activateApi();
|
||||
const doc = await vscode.workspace.openTextDocument({
|
||||
language: "markdown",
|
||||
content: "# T\n\nThe quick brown fox jumps over the lazy dog paragraph.\n",
|
||||
});
|
||||
const ed = await vscode.window.showTextDocument(doc);
|
||||
await vscode.commands.executeCommand("cowriting.coeditDocument");
|
||||
api.threadController.setTurnRunnerForTest(async () => ({
|
||||
replacement: "I would tighten this sentence.",
|
||||
model: "stub",
|
||||
sessionId: "s1",
|
||||
}));
|
||||
api.editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "The quick fox jumps the lazy dog.",
|
||||
model: "stub",
|
||||
sessionId: "s1",
|
||||
}));
|
||||
ed.selection = new vscode.Selection(2, 0, 2, 20);
|
||||
const threadId = (await api.threadController.createThreadOnSelection("tighten this"))!;
|
||||
// reply-loop fires on the human message; wait for the machine message to persist
|
||||
await settleUntil(() => {
|
||||
const t = api.sidecarRouter.load(api.proposalController.keyFor(doc))?.threads.find((x) => x.id === threadId);
|
||||
return (t?.messages.length ?? 0) >= 2 && t!.messages[1].author.kind === "agent";
|
||||
}, 10000);
|
||||
const ids = await api.threadController.makeThreadEdit(threadId, api.proposalController.keyFor(doc));
|
||||
assert.ok(ids.length >= 1);
|
||||
assert.ok(api.proposalController.listProposals(doc).length >= 1); // pending, INV-5
|
||||
});
|
||||
|
||||
test("a comment on a NON-coedited doc summons nothing (INV-10)", async () => {
|
||||
const api = await activateApi();
|
||||
const doc = await vscode.workspace.openTextDocument({ language: "markdown", content: "plain\n" });
|
||||
await vscode.window.showTextDocument(doc);
|
||||
const id = await api.threadController.createThreadOnSelection("hello");
|
||||
assert.strictEqual(id, undefined); // gate refuses thread creation entirely
|
||||
});
|
||||
});
|
||||
@@ -57,6 +57,13 @@ suite("F5 cross-rung round-trip (host E2E)", () => {
|
||||
const DOC = "docs/crossrung.md";
|
||||
const doc = await open(DOC);
|
||||
const api = await getApi();
|
||||
// Task 6 (D10): createThreadOnSelection also fires the respond-in-thread
|
||||
// turn now — stub it off so it can't race the forge stand-in's reply
|
||||
// below (this test asserts an EXACT 2-message sequence: editor note +
|
||||
// forge reply).
|
||||
api.threadController.setTurnRunnerForTest(async () => {
|
||||
throw new Error("crossrung suite: reply-loop turn stubbed off");
|
||||
});
|
||||
const target = "portable record";
|
||||
const start = doc.getText().indexOf(target);
|
||||
const editor = vscode.window.activeTextEditor!;
|
||||
|
||||
@@ -41,6 +41,12 @@ suite("F8 out-of-workspace authoring (host E2E — programmatic seam, no LLM)",
|
||||
await vscode.commands.executeCommand("cowriting.coeditDocument");
|
||||
await settle();
|
||||
const api = await getApi();
|
||||
// Task 6 (D10): createThreadOnSelection (used later in this test) also
|
||||
// fires the respond-in-thread turn now — stub it off (this "no LLM" suite
|
||||
// asserts the programmatic propose/accept seam, not the reply loop).
|
||||
api.threadController.setTurnRunnerForTest(async () => {
|
||||
throw new Error("out-of-workspace suite: reply-loop turn stubbed off");
|
||||
});
|
||||
const key = uri.toString();
|
||||
|
||||
const id = await proposeViaCommand(key, doc.getText(), TARGET, "The sibling sentence CLAUDE REWROTE.", "turn-oow1");
|
||||
|
||||
@@ -41,6 +41,17 @@ async function getApi(): Promise<CowritingApi> {
|
||||
return api;
|
||||
}
|
||||
|
||||
// Task 6 (D10): every human comment on a coedited doc now also fires the
|
||||
// respond-in-thread turn (createThreadOnSelection/reply). This suite predates
|
||||
// that loop and asserts exact message sequences unrelated to it — stub the
|
||||
// turn to reject so the loop runs harmlessly (no real @cline/sdk call, and no
|
||||
// extra machine message lands in the sidecar this suite's assertions read).
|
||||
function stubReplyLoop(api: CowritingApi): void {
|
||||
api.threadController.setTurnRunnerForTest(async () => {
|
||||
throw new Error("F2 suite: reply-loop turn stubbed off (not under test here)");
|
||||
});
|
||||
}
|
||||
|
||||
// Reaches the controller's internal docs map for reply/resolve handlers (which
|
||||
// expect a live vscode.CommentThread). Spec §6.8 "drive the ThreadController and
|
||||
// assert Comments-controller state" fallback.
|
||||
@@ -67,6 +78,7 @@ suite("F2 region-anchored threads (host E2E)", () => {
|
||||
test("create on selection persists a thread sidecar", async () => {
|
||||
const doc = await openSample();
|
||||
const api = await getApi();
|
||||
stubReplyLoop(api);
|
||||
const start = doc.getText().indexOf("quick brown fox");
|
||||
const editor = vscode.window.activeTextEditor!;
|
||||
editor.selection = new vscode.Selection(doc.positionAt(start), doc.positionAt(start + "quick brown fox".length));
|
||||
@@ -89,6 +101,7 @@ suite("F2 region-anchored threads (host E2E)", () => {
|
||||
|
||||
test("reply appends and resolve flips status, both persisted", async () => {
|
||||
const api = await getApi();
|
||||
stubReplyLoop(api);
|
||||
const art0 = readSidecar();
|
||||
const threadId = art0.threads[0].id;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user