mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
fix(claude): replay large child histories without spread
Streaming removes whole-transcript duplication, but folding many visible child events still passed the complete aggregate as arguments to Array.push. Large histories exceed the V8 argument limit, the loader catches a RangeError, and history remains empty. Append replay observations iteratively and cover 160,000 aggregate child events. Adapted from #7511, independently reproduced with the public resumeSession and streamHistory interfaces. Co-authored-by: Alin Jiang <zonglinj@amazon.com>
This commit is contained in:
committed by
paseo-bot[bot]
parent
8440cb2743
commit
6bfd3e8bfa
@@ -466,6 +466,46 @@ describe("ClaudeAgentSession persisted subagent replay", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("replays more child events than V8 permits in one spread call", async () => {
|
||||
const childCount = 16;
|
||||
const entriesPerChild = 10_000;
|
||||
const children = Array.from({ length: childCount }, (_, index) => ({
|
||||
agentId: `large-child-${index}`,
|
||||
toolUseId: `large-task-${index}`,
|
||||
}));
|
||||
const parentLines = children.flatMap(({ agentId, toolUseId }) => [
|
||||
parentEntry([{ type: "tool_use", id: toolUseId, name: "Task", input: {} }]),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
message: {
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: toolUseId,
|
||||
content: `agentId: ${agentId}\ndone`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const subagentDir = writeParentSession(parentLines);
|
||||
for (const { agentId, toolUseId } of children) {
|
||||
writeSubagent({
|
||||
subagentDir,
|
||||
agentId,
|
||||
meta: JSON.stringify({ toolUseId }),
|
||||
sidechainLines: Array.from({ length: entriesPerChild }, () => sidechainEntry({ agentId })),
|
||||
});
|
||||
}
|
||||
|
||||
const events = await replayDescriptors();
|
||||
const timelineCount = events.filter((event) => event.event.type === "timeline").length;
|
||||
expect(timelineCount).toBe(childCount * entriesPerChild);
|
||||
expect(upserts(events).filter((event) => event.status === "completed")).toHaveLength(
|
||||
childCount,
|
||||
);
|
||||
}, 120_000);
|
||||
|
||||
test("limits retained child shell output to the timeline's existing content limit", async () => {
|
||||
const output = "x".repeat(128 * 1024);
|
||||
writeSession({
|
||||
|
||||
@@ -5261,15 +5261,13 @@ class ClaudeAgentSession implements AgentSession {
|
||||
};
|
||||
if (observations.length === 0) return replay;
|
||||
|
||||
this.persistedProviderSubagentEvents.push(
|
||||
...foldSubagentObservations(observations).map(
|
||||
(event): Extract<AgentStreamEvent, { type: "provider_subagent" }> => ({
|
||||
type: "provider_subagent",
|
||||
provider: "claude",
|
||||
event,
|
||||
}),
|
||||
),
|
||||
);
|
||||
for (const event of foldSubagentObservations(observations)) {
|
||||
this.persistedProviderSubagentEvents.push({
|
||||
type: "provider_subagent",
|
||||
provider: "claude",
|
||||
event,
|
||||
});
|
||||
}
|
||||
this.historyPending = true;
|
||||
return replay;
|
||||
}
|
||||
@@ -5327,12 +5325,12 @@ class ClaudeAgentSession implements AgentSession {
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
timeline.push(
|
||||
...items.map((item) => ({
|
||||
for (const item of items) {
|
||||
timeline.push({
|
||||
item,
|
||||
timestamp: historyTimestamp ?? undefined,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ function observeSubagent(
|
||||
lastTimestamp,
|
||||
);
|
||||
if (subtitle) observations.push(subtitle);
|
||||
observations.push(...timeline);
|
||||
for (const observation of timeline) observations.push(observation);
|
||||
const terminalStatus = link.status ?? childTerminalStatus;
|
||||
if (terminalStatus) {
|
||||
observations.push({
|
||||
@@ -346,9 +346,15 @@ export function observeReplaySubagents(input: {
|
||||
const { ownerId, parent, link } = resolved;
|
||||
|
||||
// Only proven descendants may own notifications; ambient sidecars cannot claim them.
|
||||
observations.push(
|
||||
...observeSubagent(subagent, parent, input.convertEntry, toolOwners, ownerId),
|
||||
);
|
||||
for (const observation of observeSubagent(
|
||||
subagent,
|
||||
parent,
|
||||
input.convertEntry,
|
||||
toolOwners,
|
||||
ownerId,
|
||||
)) {
|
||||
observations.push(observation);
|
||||
}
|
||||
if (subagent.parentFacts) resolvedParents.set(link.id, subagent.parentFacts);
|
||||
unresolved.splice(index, 1);
|
||||
madeProgress = true;
|
||||
|
||||
+9
-9
@@ -79,15 +79,15 @@ export function observeReplayWorkflows(input: {
|
||||
});
|
||||
}
|
||||
|
||||
observations.push(
|
||||
...observeWorkflowTimeline({
|
||||
id,
|
||||
result: workflow.result,
|
||||
entries: input.entriesByRunId?.get(workflow.runId) ?? [],
|
||||
convertEntry: input.convertEntry,
|
||||
finishedAt,
|
||||
}),
|
||||
);
|
||||
for (const observation of observeWorkflowTimeline({
|
||||
id,
|
||||
result: workflow.result,
|
||||
entries: input.entriesByRunId?.get(workflow.runId) ?? [],
|
||||
convertEntry: input.convertEntry,
|
||||
finishedAt,
|
||||
})) {
|
||||
observations.push(observation);
|
||||
}
|
||||
|
||||
const subtitle = buildClaudeSubagentSubtitle({
|
||||
title: "Workflow",
|
||||
|
||||
Reference in New Issue
Block a user