test: match native continuation replies across whitespace

Use one explicit author-bound reply predicate for continuation readiness and evidence. Preserve literal matching elsewhere and reject wrong markers and output tokens. Keep original failed live measurements unchanged.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-30 17:10:23 -05:00
1 parent 0b6afc499f
commit af9e2d157d
6 files changed
+71 -12

No files matched your search

+2
View File
@@ -25,6 +25,8 @@ The five-minute outage is a resilience test, not an HTTP downtime allowance.
This suite gives its Daytona sandbox a 15-minute provider auto-stop interval,
leaving headroom for the outage and replacement startup. Native agent idle timing,
ownership/reconnect limits, archive/delete backstops and owned cleanup remain unchanged.
The retained continuation accepts whitespace between its exact marker and the
exact `queue_r` output token; readiness and final grading use the same matcher.
The explicit-only [GitHub review bot journey](GITHUB-REVIEW-JOURNEY.md) tests
attended bot setup and an automatic PR review against an attached deployment.
+1 -1
View File
@@ -61,7 +61,7 @@ describe("runner E2E catalog", () => {
}
expect(baseConfig.autoStopInterval).toBe(5);
expect(daytonaWarmEnvironment.buildEnvironment(environmentInput).config).toMatchObject({ autoStopInterval: 5 });
expect(suite.definitionMetadata).toMatchObject({ version: 4, controllerDowntimeMs: 300_000, autoStopIntervalMinutes: 15 });
expect(suite.definitionMetadata).toMatchObject({ version: 5, controllerDowntimeMs: 300_000, autoStopIntervalMinutes: 15 });
});
it.each([
+1 -1
View File
@@ -1173,7 +1173,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
})),
tasks: everydayTasks.filter(task => task.id === "recover-controller")
.map(task => ({ ...task, expectedRunCount: task.expectedRunCount + 1, automaticRetryPolicy: "single_attempt" as const })), expectedMatrixSize: 2,
definitionMetadata: { version: 4, scheduling: "explicit-only", controllerDowntimeMs: 300_000, autoStopIntervalMinutes: 15, retainedControllerContinuation: true,
definitionMetadata: { version: 5, scheduling: "explicit-only", controllerDowntimeMs: 300_000, autoStopIntervalMinutes: 15, retainedControllerContinuation: true,
maximumAttemptsPerCell: 1, budgetMonthlyCents: 1000, instructions: "production", grading: "same-active-process-lease-native-prefix-and-workspace-receipt",
authentication: "api-key", stagingAuthentication: "subscription", stagingQualification: false },
},
+8 -6
View File
@@ -45,6 +45,8 @@ import {
storyLifecycleChecks,
storyRepliesConsumed,
storyHasAgentReply,
storyAgentReplyMatches,
retainedControllerReplyMatcher,
storyHasPendingHumanInteraction,
storyReviewContinuationTimeoutDetail,
storyHasStrandedBlockedLeaf,
@@ -60,6 +62,7 @@ import {
type StoryCheck,
type StoryIssue,
type StoryRun,
type StoryReplyExpectation,
} from "./everyday-observations.js";
type Row = Record<string, any>;
@@ -325,7 +328,7 @@ export async function runEverydayFlow(input: Input) {
sameConnections: false,
};
}
async function settled(expectedAgentReply?: string) {
async function settled(expectedAgentReply?: StoryReplyExpectation) {
const settledState = await pollUntil({
label: `everyday ${caseId} settled`,
deadlineAt: input.deadlineAt,
@@ -390,9 +393,7 @@ export async function runEverydayFlow(input: Input) {
if (expectedAgentReply) {
const issue = settledState.issues.find((candidate) => candidate.id === parent?.id);
const reply = issue?.comments?.find(
(comment) =>
comment.authorAgentId === fixtures.agent.id &&
String(comment.body ?? "").includes(expectedAgentReply),
(comment) => storyAgentReplyMatches(comment, fixtures.agent.id, expectedAgentReply),
);
if (reply) {
// Keep the exact snapshot that satisfied the readiness predicate. A
@@ -1455,15 +1456,16 @@ export async function runEverydayFlow(input: Input) {
const outage = await input.restart();
await openParent();
const requestedAt = new Date().toISOString(), marker = `CONTINUED_${nonce}`;
const expectedReply = retainedControllerReplyMatcher(marker);
await reply(`Run the existing delivered slugify.py on " Queue--Ready!! " with --separator _ and --max-length 7. Report its output after the marker ${marker}. Use the saved project; do not rebuild it.`);
await settled(`${marker} queue_r`);
await settled(expectedReply);
const after = ev.runs.filter(run => !priorIds.has(run.id) && run.status === "succeeded" && run.agentId === fixtures.agent.id)
.sort((a, b) => Date.parse(b.finishedAt ?? "") - Date.parse(a.finishedAt ?? ""))[0];
if (!after) throw new Error("Retained continuation produced no new successful run");
const events = await collectRunEvents<Row>((afterSeq, limit) => api.get(`/api/heartbeat-runs/${after.id}/events?afterSeq=${afterSeq}&limit=${limit}`));
const evidence = { before, after: retainedControllerRun(after), outage: outage || undefined, requestedAt,
completedTool: events.some(row => row.payload?.prpEvent?.eventType === "tool.execution.completed" && row.payload.prpEvent.payload?.status === "completed"),
usefulReply: settledAgentReply?.authorAgentId === fixtures.agent.id && String(settledAgentReply.body ?? "").includes(`${marker} queue_r`) };
usefulReply: storyAgentReplyMatches(settledAgentReply, fixtures.agent.id, expectedReply) };
ev.checks.push(...gradeRetainedControllerContinuation(evidence));
await input.evidence("retained-controller-continuation.json", evidence);
}
+21 -4
View File
@@ -344,17 +344,34 @@ export function storyRepliesConsumed(
);
}
export type StoryReplyExpectation = string | ((body: string) => boolean);
export function storyAgentReplyMatches(
comment: StoryComment | undefined,
agentId: string,
expected: StoryReplyExpectation,
): boolean {
if (!comment || comment.authorAgentId !== agentId) return false;
const body = String(comment.body ?? "");
return typeof expected === "string" ? body.includes(expected) : expected(body);
}
/** The requested output is one exact token after this continuation's marker. */
export function retainedControllerReplyMatcher(marker: string): (body: string) => boolean {
const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`(?:^|\\s)${escaped}\\s+queue_r\\s*$`);
return body => pattern.test(body);
}
/** A terminal task is only ready for response assertions after its agent reply is visible. */
export function storyHasAgentReply(
issue: StoryIssue | undefined,
agentId: string,
expectedText: string,
expectedText: StoryReplyExpectation,
): boolean {
return (
issue?.comments?.some(
(comment) =>
comment.authorAgentId === agentId &&
String(comment.body ?? "").includes(expectedText),
(comment) => storyAgentReplyMatches(comment, agentId, expectedText),
) ?? false
);
}
+38
View File
@@ -9,6 +9,8 @@ import {
storyLifecycleChecks,
storyRepliesConsumed,
storyHasAgentReply,
storyAgentReplyMatches,
retainedControllerReplyMatcher,
storyHasPendingAgentReview,
storyHasPendingAgentWake,
storyHasPendingAgentWork,
@@ -385,6 +387,42 @@ describe("lifecycle oracle calibrated failures", () => {
});
describe("reply completion boundary", () => {
it("accepts an explicit reply matcher for continuation output on a new line", () => {
const issue = { id: "parent", companyId: "company", title: "Continue", status: "done",
comments: [{ authorAgentId: "lead", body: "CONTINUED_nonce\nqueue_r" }] };
const expected = (body: string) => body === "CONTINUED_nonce\nqueue_r";
expect(storyHasAgentReply(issue, "lead", expected)).toBe(true);
});
it.each([" ", "\n", "\r\n", "\t", " \n"])("accepts continuation output separated by %j", separator => {
const expected = retainedControllerReplyMatcher("CONTINUED_nonce.1");
const comment = { authorAgentId: "lead", body: `CONTINUED_nonce.1${separator}queue_r` };
expect(storyHasAgentReply({ id: "task", companyId: "company", title: "Continue", status: "done", comments: [comment] }, "lead", expected)).toBe(true);
expect(storyAgentReplyMatches(comment, "lead", expected)).toBe(true);
});
it.each([
"CONTINUED_nonce.1 queue_ready", "CONTINUED_nonce.1 queue_r_extra", "CONTINUED_nonce.1 wrong",
"CONTINUED_other queue_r", "CONTINUED_nonceX1 queue_r", "queue_r", "CONTINUED_nonce.1queue_r",
"OTHER_CONTINUED_nonce.1 queue_r", "CONTINUED_nonce.1 queue_r\nqueue_ready",
])("rejects an incorrect continuation reply %j", body => {
const expected = retainedControllerReplyMatcher("CONTINUED_nonce.1");
const comment = { authorAgentId: "lead", body };
expect(storyHasAgentReply({ id: "task", companyId: "company", title: "Continue", status: "done", comments: [comment] }, "lead", expected)).toBe(false);
expect(storyAgentReplyMatches(comment, "lead", expected)).toBe(false);
});
it("preserves literal string expectations and continuation author checks", () => {
const expected = retainedControllerReplyMatcher("CONTINUED_nonce");
const comment = { authorAgentId: "other", body: "CONTINUED_nonce\nqueue_r" };
expect(storyAgentReplyMatches(comment, "lead", expected)).toBe(false);
const issue = { id: "task", companyId: "company", title: "Continue", status: "done", comments: [comment] };
expect(storyHasAgentReply(issue, "lead", expected)).toBe(false);
expect(storyAgentReplyMatches({ ...comment, authorAgentId: "lead" }, "lead", "CONTINUED_nonce queue_r")).toBe(false);
expect(storyAgentReplyMatches({ authorAgentId: "lead", body: "a.b" }, "lead", "a.b")).toBe(true);
expect(storyAgentReplyMatches({ authorAgentId: "lead", body: "axb" }, "lead", "a.b")).toBe(false);
});
const run: StoryRun = {
id: "old",
companyId: "company",