Compare commits

...
Author SHA1 Message Date
paseo-bot[bot] ea76f7501d Add cold-load case: an 80 MiB provider history of ordinary rows opens 2026-09-24 00:48:28 +02:00
paseo-bot[bot] daaffe6109 Reproduce 1006 disconnect when an agent's latest timeline page exceeds the socket bound (#2610)
The first case passes: an 80 MiB history of ordinary rows opens with a
40-row page. The second fails: 40 file writes of 2 MiB each make one
fetch_agent_timeline_response larger than the 64 MiB physical socket
bound, and the daemon closes the connection (1006).
2026-09-24 00:46:49 +02:00
@@ -0,0 +1,155 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { appendFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import {
createDaemonTestContext,
type DaemonTestContext,
} from "../test-utils/index.js";
const MIB = 1024 * 1024;
const APP_PAGE_SIZE = 40;
describe("daemon E2E - large agent history (#2610)", () => {
let ctx: DaemonTestContext;
let cwd: string;
beforeEach(async () => {
ctx = await createDaemonTestContext();
cwd = mkdtempSync(path.join(tmpdir(), "daemon-e2e-"));
});
afterEach(async () => {
await ctx.cleanup();
rmSync(cwd, { recursive: true, force: true });
}, 60_000);
async function createAgentWithHistory(
rows: number,
rowBytes: number
): Promise<string> {
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Large history",
});
const body = "x".repeat(rowBytes);
for (let index = 0; index < rows; index += 1) {
await ctx.daemon.daemon.agentManager.appendTimelineItem(agent.id, {
type: "assistant_message",
text: `${index} ${body}`,
});
await ctx.daemon.daemon.agentManager.appendTimelineItem(agent.id, {
type: "user_message",
text: `next ${index}`,
});
}
return agent.id;
}
test("opens an agent whose history totals more than 64 MiB of ordinary rows", async () => {
const agentId = await createAgentWithHistory(20_000, 4_000);
const page = await ctx.client.fetchAgentTimeline(agentId, {
direction: "tail",
limit: APP_PAGE_SIZE,
projection: "projected",
timeout: 60_000,
});
expect(page.error).toBeNull();
expect(page.entries).toHaveLength(APP_PAGE_SIZE);
expect(page.hasOlder).toBe(true);
}, 300_000);
test("opens an agent whose latest file writes total more than 64 MiB", async () => {
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Large writes",
});
const content = "x".repeat(2 * MIB);
for (let index = 0; index < APP_PAGE_SIZE; index += 1) {
await ctx.daemon.daemon.agentManager.appendTimelineItem(agent.id, {
type: "tool_call",
callId: `write_${index}`,
name: "Write",
status: "completed",
error: null,
detail: {
type: "write",
filePath: `fixtures/data-${index}.json`,
content,
},
});
}
const page = await ctx.client.fetchAgentTimeline(agent.id, {
direction: "tail",
limit: APP_PAGE_SIZE,
projection: "projected",
timeout: 60_000,
});
expect(page.error).toBeNull();
expect(page.entries.length).toBeGreaterThan(0);
}, 300_000);
test("opens an agent cold-loaded from a provider history of more than 64 MiB", async () => {
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Cold load",
});
await ctx.client.sendMessage(agent.id, "Respond with exactly: READY");
const finished = await ctx.client.waitForFinish(agent.id, 5_000);
const handle = finished.final!.persistence as PersistenceHandle;
const historyPath = path.join(
tmpdir(),
"paseo-fake-provider-history",
"codex",
`${handle.sessionId}.jsonl`
);
const body = "x".repeat(4_000);
const lines: string[] = [];
for (let index = 0; index < 20_000; index += 1) {
lines.push(
JSON.stringify({ type: "turn_started", provider: "codex" }),
JSON.stringify({
type: "timeline",
provider: "codex",
item: { type: "user_message", text: `question ${index}` },
}),
JSON.stringify({
type: "timeline",
provider: "codex",
item: { type: "assistant_message", text: `${index} ${body}` },
}),
JSON.stringify({
type: "turn_completed",
provider: "codex",
usage: { inputTokens: 1, outputTokens: 1 },
})
);
}
appendFileSync(historyPath, lines.join("\n") + "\n");
await ctx.cleanup();
ctx = await createDaemonTestContext();
const resumed = await ctx.client.resumeAgent(handle);
const page = await ctx.client.fetchAgentTimeline(resumed.id, {
direction: "tail",
limit: APP_PAGE_SIZE,
projection: "projected",
timeout: 60_000,
});
expect(page.error).toBeNull();
expect(page.entries).toHaveLength(APP_PAGE_SIZE);
expect(page.entries.at(-1)?.item).toMatchObject({
type: "assistant_message",
});
}, 300_000);
});