From b4574e923e52476049a8a8499d78ec3f050a6087 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 17 Sep 2026 11:30:19 -0500 Subject: [PATCH] Scope warm acceptance instructions to the active runner protocol Co-Authored-By: Paperclip --- tests/runner-e2e/catalog.test.ts | 15 +++++++++++++++ tests/runner-e2e/catalog.ts | 8 ++++++++ tests/runner-e2e/runner.spec.ts | 9 ++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/runner-e2e/catalog.test.ts b/tests/runner-e2e/catalog.test.ts index a1d03f57b8b..081605bcc2d 100644 --- a/tests/runner-e2e/catalog.test.ts +++ b/tests/runner-e2e/catalog.test.ts @@ -12,6 +12,7 @@ import { runnerSuites, runnerTasks, daytonaWarmContinuityTask, + warmPromptForGeneration, daytonaWarmEnvironment, isImmutableDaytonaImage, suiteDefinitionHash, @@ -25,6 +26,20 @@ import { } from "./selectors.js"; describe("runner E2E catalog", () => { + it("gives warm runners only their own completion protocol on all three turns", () => { + const prompts = [daytonaWarmContinuityTask.buildPrompt("nonce"), ...daytonaWarmContinuityTask.buildFollowupMessages!("nonce")]; + for (const prompt of prompts) { + const native = warmPromptForGeneration(prompt, "native"); + expect(native).toContain("paperclip_finish"); + expect(native).not.toContain("PATCH /api/issues"); + expect(native).not.toContain("In a legacy runner,"); + const legacy = warmPromptForGeneration(prompt, "legacy"); + expect(legacy).not.toContain("paperclip_finish"); + expect(legacy).toContain("issue"); + expect(legacy).toContain("Do not recreate, truncate, reorder, or duplicate prior lines."); + } + }); + it("defines sixteen local connection-review journeys without expanding the default matrix", () => { expect(connectionReviewSuite.expectedMatrixSize).toBe(16); expect(new Set(connectionReviewSuite.profiles.map(profile => profile.id))).toEqual(new Set(["runner-codex", "runner-acpx-claude", "legacy-codex", "legacy-claude"])); diff --git a/tests/runner-e2e/catalog.ts b/tests/runner-e2e/catalog.ts index f5c14fd44e0..61f37739163 100644 --- a/tests/runner-e2e/catalog.ts +++ b/tests/runner-e2e/catalog.ts @@ -799,6 +799,14 @@ const localEnvironment = runnerEnvironments.find( (environment) => environment.id === "local", )!; +export function warmPromptForGeneration(prompt: string, generation: RunnerProfileFixture["generation"]): string { + const other = generation === "native" ? "legacy" : "native"; + return [ + `You are running in a ${generation} runner. Follow the ${generation} completion instructions below.`, + ...prompt.split("\n").filter((line) => !line.startsWith(`In a ${other} runner,`)), + ].join("\n"); +} + function warmTurnMarker(turn: 1 | 2 | 3, nonce: string) { return `PAPERCLIP_E2E_WARM_T${turn}_${nonce}`; } diff --git a/tests/runner-e2e/runner.spec.ts b/tests/runner-e2e/runner.spec.ts index 313e6052616..dd6aab5068b 100644 --- a/tests/runner-e2e/runner.spec.ts +++ b/tests/runner-e2e/runner.spec.ts @@ -11,7 +11,7 @@ import path from "node:path"; import { expect, test, type Page } from "@playwright/test"; import { RunnerApi, pollUntil } from "./api.js"; import { buildRuntimeUsage, summarizeExecutionBilling } from "./billing.js"; -import { runnerExecutionById } from "./catalog.js"; +import { runnerExecutionById, warmPromptForGeneration } from "./catalog.js"; import { classifyFailure } from "./failure-classifier.js"; import { runnerE2EServerControlPaths } from "./harness-env.js"; import { setupConnectionReview } from "./connection-reviews.js"; @@ -525,7 +525,10 @@ for (const execution of executions) { const nonce = `${randomBytes(6).toString("hex")}-${attempt}`; const marker = execution.task.buildVisibleMarker(nonce); const title = execution.task.buildTitle(nonce); - const prompt = execution.task.buildPrompt(nonce); + const rawPrompt = execution.task.buildPrompt(nonce); + const prompt = execution.task.flow === "warm_three_turn" + ? warmPromptForGeneration(rawPrompt, execution.profile.generation) + : rawPrompt; const credentials = credentialValues(); const secrets = normalizedSecrets(Object.values(credentials)); const api = new RunnerApi(request); @@ -1517,7 +1520,7 @@ for (const execution of executions) { `warm-turn-${completedTurn}.png`, ); turnSubmissionTimesMs.push( - await submitTaskRevision(page, followups[completedTurn - 1]), + await submitTaskRevision(page, warmPromptForGeneration(followups[completedTurn - 1], execution.profile.generation)), ); } warmLifecycleEvidence = { turns: turnEvidence };