test(e2e): exercise Daytona continuation after large tool output

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-27 15:11:24 -05:00
1 parent 0f14d26123
commit 666f44f8d3
7 files changed
+125 -4

No files matched your search

+5
View File
@@ -60,6 +60,11 @@ harness are under `tests/runner-e2e`; the package scripts are `test:e2e:runner`,
the server and embedded database, creates the company/agent/task through the
real APIs, drives Chromium, and invokes the selected local or Daytona runner.
The explicit-only `daytona-journal-continuity` Product E2E cell exercises
continuation after ordinary tool output grows the durable control-plane journal
past two MiB. Its size oracle is read-only and retains only byte counts; see the
[Product E2E fixture](../tests/runner-e2e/README.md).
The explicit-only `agent-chat-hardening` Product E2E suite covers native chat
recovery, hiring, status evidence, and review handoff on local and selected warm
Daytona paths. Its [fixture contract](../tests/runner-e2e/README.md) distinguishes
+11 -1
View File
@@ -83,7 +83,7 @@ pnpm test:e2e:runner -- --suite daytona-warm-continuity
pnpm test:e2e:runner -- --all
```
The catalog contains thirteen suites, including the explicit-only everyday and
The catalog contains fourteen suites, including the explicit-only everyday and
[lifecycle baseline](LIFECYCLE-BASELINE.md) suites. The latter adds 46 real-provider
cells pairing narrative variants and exercising durable lifecycle boundaries;
it is excluded from `--all`. `core-compatibility` (**Core Runner
@@ -134,6 +134,16 @@ runner instance, PID, and process-start identity. Each turn is bounded to ten
minutes, the cell to thirty minutes, and cleanup explicitly deletes the
sandbox rather than waiting for Daytona's idle timeout.
`daytona-journal-continuity` is one explicit-only native Codex cell. Select
`daytona-journal-continuity.runner-codex.daytona.large-journal-three-turn`.
It reuses the three-turn warm workflow after streaming four MiB of synthetic
command output through the real provider. Before the first browser follow-up,
a read-only controller journal oracle requires the exact completed run's journal
to exceed two MiB. Only byte counts enter evidence. No runner state or database
is injected or modified. The usual workspace, sandbox, provider, process,
three-run, screenshot, timeout, billing, and cleanup assertions remain required;
`--all` excludes this stress case.
`agent-chat` (**Persistent Agent Chat**) has eight workflows on `legacy-codex`,
`legacy-claude`, `runner-codex`, and `runner-acpx-claude`: **28 local cells**.
They cover continuity across server restart, fresh context after `/new`,
+16 -3
View File
@@ -13,6 +13,7 @@ import {
runnerSuites,
runnerTasks,
daytonaWarmContinuityTask,
daytonaLargeJournalTask,
daytonaWarmEnvironment,
isImmutableDaytonaImage,
suiteDefinitionHash,
@@ -71,10 +72,10 @@ describe("runner E2E catalog", () => {
expect(localIntegrityTasks).toHaveLength(2);
expect(openRouterBreadthTasks).toHaveLength(3);
expect(runnerSuites.map((suite) => suite.expectedMatrixSize)).toEqual([
8, 46, 23, 47, 52, 28, 18, 6, 6, 42, 14, 10, 2,
8, 46, 23, 47, 52, 28, 18, 6, 6, 42, 14, 10, 2, 1,
]);
expect(validateRunnerCatalog()).toHaveLength(302);
expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(302);
expect(validateRunnerCatalog()).toHaveLength(303);
expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(303);
expect(
runnerMatrix.filter((entry) => entry.suite.id === "core-compatibility"),
).toHaveLength(42);
@@ -186,6 +187,18 @@ describe("runner E2E catalog", () => {
).not.toBe(suiteDefinitionHash(suite));
});
it("keeps large-journal stress explicit-only and uses the ordinary warm workflow", () => {
const suite = runnerSuites.find((candidate) => candidate.id === "daytona-journal-continuity")!;
expect(suite.manualOnly).toBe(true);
expect(suite.expectedMatrixSize).toBe(1);
expect(suite.profiles.map((profile) => profile.id)).toEqual(["runner-codex"]);
expect(daytonaLargeJournalTask.flow).toBe("warm_three_turn");
expect(daytonaLargeJournalTask.buildPrompt("nonce")).toContain("range(64)");
expect(daytonaLargeJournalTask.buildFollowupMessages!("nonce")).toEqual(daytonaWarmContinuityTask.buildFollowupMessages!("nonce"));
expect(selectRunnerExecutions(parseRunnerSelectors(["--all"]))
.some((cell) => cell.suite.id === suite.id)).toBe(false);
});
it("derives the qualified local native OpenCode profiles from the ranked snapshot", () => {
expect(openRouterBreadthExcludedModelIds).toEqual(["xiaomi/mimo-v2.5"]);
expect(openRouterBreadthExcludedExecutionIds).toEqual([
+25
View File
@@ -874,6 +874,18 @@ export const daytonaWarmContinuityTask: RunnerTaskFixture = {
},
};
export const daytonaLargeJournalTask: RunnerTaskFixture = {
...daytonaWarmContinuityTask,
id: "large-journal-three-turn",
label: "Large journal three-turn workspace continuity",
buildTitle: (nonce) => `Runner E2E large journal continuity ${nonce}`,
buildPrompt: (nonce) => [
"First exercise ordinary command-output streaming with this bounded diagnostic. Execute this Python command exactly once, allowing it to print all 64 chunks. Do not redirect, suppress, pipe, or summarize its stdout; the transport must receive the output. The repeated text is synthetic fixture data and needs no analysis. Wait for the command to exit successfully before continuing.",
`python3 -c 'import sys,time; [(sys.stdout.write("journal-continuity-" + "x" * 65500 + "\\n"), sys.stdout.flush(), time.sleep(0.05)) for _ in range(64)]'`,
warmTurnInstructions(1, nonce),
].join("\n"),
};
const codexContinuityProfiles = runnerProfiles.filter((profile) =>
["legacy-codex", "runner-codex"].includes(profile.id),
);
@@ -1053,6 +1065,18 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
tasks: [daytonaWarmContinuityTask],
expectedMatrixSize: 2,
},
{
id: "daytona-journal-continuity",
label: "Daytona Large Journal Continuity",
manualOnly: true,
description: "Continue the same native session after real tool output grows its durable journal beyond 2 MiB.",
groups: ["daytona", "warm"],
profiles: codexContinuityProfiles.filter((profile) => profile.id === "runner-codex"),
environments: [daytonaWarmEnvironment],
tasks: [daytonaLargeJournalTask],
expectedMatrixSize: 1,
definitionMetadata: { version: 1, journalMinimumBytes: 2 * 1024 * 1024, outputChunks: 64, scheduling: "explicit-only" },
},
] as const;
export function suiteDefinitionHash(suite: RunnerSuiteFixture) {
@@ -1169,6 +1193,7 @@ export function validateRunnerCatalog(): MatrixExecution[] {
...localIntegrityTasks,
...openRouterBreadthTasks,
daytonaWarmContinuityTask,
daytonaLargeJournalTask,
];
for (const [label, values] of [
["suite", runnerSuites],
+26
View File
@@ -0,0 +1,26 @@
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { largeJournalEvidence } from "./journal-evidence.js";
describe("large journal boundary evidence", () => {
it("requires one correctly owned journal over the boundary and returns only sizes", async () => {
const root = await mkdtemp(path.join(tmpdir(), "journal-evidence-"));
const directory = path.join(root, "a".repeat(64), "control-plane");
await mkdir(directory, { recursive: true });
const filename = path.join(directory, "control-plane-state.json");
const state = { schema: "paperclip.runner.durable.control-plane-state.v1", identity: { runId: "expected-run" }, commands: [] };
try {
await writeFile(filename, JSON.stringify(state));
await expect(largeJournalEvidence({ stateRoot: root, runId: "expected-run", minimumBytes: 1024 })).rejects.toThrow("boundary not reached");
await writeFile(filename, JSON.stringify({ ...state, commands: [{ payload: "x".repeat(2048) }] }));
const evidence = await largeJournalEvidence({ stateRoot: root, runId: "expected-run", minimumBytes: 1024 });
expect(Object.keys(evidence)).toEqual(["journalBytes", "minimumBytes"]);
expect(evidence.journalBytes).toBeGreaterThan(2048);
await expect(largeJournalEvidence({ stateRoot: root, runId: "another-run", minimumBytes: 1024 })).rejects.toThrow("boundary not reached");
} finally {
await rm(root, { recursive: true, force: true });
}
});
});
+30
View File
@@ -0,0 +1,30 @@
import { lstat, readdir, readFile } from "node:fs/promises";
import path from "node:path";
// The fixture reads bounded private evidence independently of production admission.
const MAX_JOURNAL_EVIDENCE_BYTES = 192 * 1024 * 1024;
/** Read-only size oracle; retained journal contents never enter public evidence. */
export async function largeJournalEvidence(input: {
stateRoot: string;
runId: string;
minimumBytes: number;
}): Promise<{ journalBytes: number; minimumBytes: number }> {
const matches: number[] = [];
for (const entry of await readdir(input.stateRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !/^[a-f0-9]{64}$/.test(entry.name)) continue;
const filename = path.join(input.stateRoot, entry.name, "control-plane", "control-plane-state.json");
const metadata = await lstat(filename).catch(() => null);
if (!metadata?.isFile() || metadata.size > MAX_JOURNAL_EVIDENCE_BYTES) continue;
const state = JSON.parse(await readFile(filename, "utf8")) as {
schema?: string;
identity?: { runId?: string };
};
if (state.schema === "paperclip.runner.durable.control-plane-state.v1" && state.identity?.runId === input.runId) {
matches.push(metadata.size);
}
}
if (matches.length !== 1 || matches[0]! <= input.minimumBytes) {
throw new Error(`Large-journal boundary not reached: expected one matching journal above ${input.minimumBytes} bytes; observed ${JSON.stringify(matches)}`);
}
return { journalBytes: matches[0]!, minimumBytes: input.minimumBytes };
}
+12
View File
@@ -1,3 +1,4 @@
import { largeJournalEvidence } from "./journal-evidence.js";
import { observeBrowserBootstrap } from "./browser-bootstrap-diagnostics.js";
import { runAccountingFlow } from "./accounting-flow.js";
import type { Issue } from "../../packages/shared/src/types/issue.js";
@@ -1545,7 +1546,18 @@ for (const execution of executions) {
`Warm turn ${completedTurn} replaced its Daytona sandbox`,
);
}
const journalEvidence = execution.suite.id === "daytona-journal-continuity" && completedTurn === 1
? await largeJournalEvidence({
stateRoot: path.join(process.env.PAPERCLIP_HOME!, "instances", process.env.PAPERCLIP_INSTANCE_ID!, "runtime", "paperclip-runner", "durable-sessions"),
runId: chronologicalRuns.at(-1)!.id,
minimumBytes: 2 * 1024 * 1024,
})
: undefined;
if (journalEvidence) {
await writeSanitizedJson(snapshotsDir, "large-journal-boundary.json", journalEvidence, secrets);
}
turnEvidence.push({
...(journalEvidence ? { journalEvidence } : {}),
turn: completedTurn,
issue: waitingState.currentIssue,
run: chronologicalRuns.at(-1),