Verify revised plan outcomes and navigate cleanly after server restart

This commit is contained in:
Dotta 2026-09-11 15:00:56 -05:00
parent 6b26534610
commit 9bbdfdcb60
2 changed files with 29 additions and 5 deletions

View File

@ -10,6 +10,7 @@ import {
readRunningChatLog,
readChatOutputDocument,
isChatClarificationReply,
assertChatExecutionOutput,
isResetRun,
type ChatIssue,
type ChatRun,
@ -56,6 +57,7 @@ describe("chat acceptance contracts", () => {
isChatClarificationReply("Tell me the intended audience and format."),
).toBe(true);
expect(isChatClarificationReply("Please share:")).toBe(false);
expect(isChatClarificationReply("Please share.")).toBe(false);
expect(
isChatClarificationReply("Asked the user clarifying questions about their club."),
).toBe(false);
@ -64,6 +66,12 @@ describe("chat acceptance contracts", () => {
).toBe(false);
});
it("rejects superseded plan requirements in executed output, independently of plan history", () => {
expect(() => assertChatExecutionOutput("Welcome CHAT123.", "CHAT123", "DRAFT123")).not.toThrow();
expect(() => assertChatExecutionOutput("Welcome DRAFT123 and CHAT123.", "CHAT123", "DRAFT123")).toThrow();
expect(() => assertChatExecutionOutput("Welcome DRAFT123.", "CHAT123", "DRAFT123")).toThrow();
});
it("keeps chat markers literal across rich-text and Markdown boundaries", () => {
for (const prefix of ["CHAT", "DRAFT", "OLDCONTEXT"] as const) {
expect(chatMarker(prefix, "abc123-1")).toBe(`${prefix}abc1231`);

View File

@ -54,7 +54,16 @@ export function isChatClarificationReply(body: string): boolean {
const request = body.match(
/\b(?:please\s+(?:share|provide|clarify|confirm)|tell me|let me know)\b([\s\S]*)/i,
);
return Boolean(request?.[1].replace(/[\s:*-]/g, ""));
return Boolean(request && /[\p{L}\p{N}]/u.test(request[1]));
}
export function assertChatExecutionOutput(
body: string,
marker: string,
supersededMarker?: string,
): void {
expect(body).toContain(marker);
if (supersededMarker) expect(body).not.toContain(supersededMarker);
}
/** A requested output document may have a descriptive key; a copied plan is not output. */
@ -315,7 +324,10 @@ export async function runChatFlow(input: {
).toContain(secret);
const count = runs.length;
await input.restart();
await page.reload({ waitUntil: "domcontentloaded", timeout: 60_000 });
// Re-enter the canonical route after the server replaces its browser
// transport; reloading the stale document can target a detached page.
await page.goto(route, { waitUntil: "domcontentloaded", timeout: 60_000 });
await expect(page.getByTestId("task-chat-composer-input")).toBeVisible();
await idle(2);
expect(runs).toHaveLength(count);
await turn(
@ -561,7 +573,8 @@ export async function runChatFlow(input: {
`/api/issues/${issue!.id}/documents/plan`,
);
expect(revised.body).toContain(marker);
expect(revised.body).not.toContain(draftMarker);
// A revision-history section may quote the superseded requirement.
// The executed output below must use only the accepted requirement.
expect(revised.latestRevisionId).not.toBe(draft.latestRevisionId);
acceptedPlan = revised;
await noTasks();
@ -642,7 +655,6 @@ export async function runChatFlow(input: {
if (plan) {
assertChatHandoff(child, plan, taskRuns, issue!);
expect(plan.body).toContain(marker);
expect(plan.body).not.toContain(draftMarker);
const sourcePlan = await api.get<Plan>(
`/api/issues/${issue!.id}/documents/plan`,
);
@ -652,7 +664,11 @@ export async function runChatFlow(input: {
);
} else assertChatTaskHandoff(child, taskRuns, issue!);
const output = await readChatOutputDocument(api, child.id, marker);
expect(output.body).toContain(marker);
assertChatExecutionOutput(
output.body,
marker,
caseId === "plan-handoff" ? draftMarker : undefined,
);
await input.evidence("chat-execution-output.json", {
taskId: child.id,
document: output,