Skip to content

Commit 008bd59

Browse files
committed
Reuse leisure truncation notices for fleet-dry spills
1 parent 1b6e314 commit 008bd59

3 files changed

Lines changed: 7 additions & 122 deletions

File tree

src/plugins/result-truncation-plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function spillBlobKey(callId: string): string {
5555
return `${callId}:full`;
5656
}
5757

58-
function truncationNotice(args: {
58+
export function truncationNotice(args: {
5959
maxChars: number;
6060
remaining: number;
6161
fullLength: number;
@@ -92,7 +92,7 @@ function truncationNotice(args: {
9292
* remaining/fullLength (and optional absolutePath), so shrink kept until the
9393
* assembled result fits.
9494
*/
95-
function truncateWithReservedNotice(
95+
export function truncateWithReservedNotice(
9696
text: string,
9797
maxChars: number,
9898
buildNotice: (keptLen: number) => string,

src/subagent/fleet-dry-drive.test.ts

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -557,86 +557,6 @@ describe("driveOpenTasksAfterFleetDry", () => {
557557
).toBe(original);
558558
});
559559

560-
test("send waits for async writeBlob before naming the URI", async () => {
561-
const original = `head-${"x".repeat(FLEET_DRY_REPORT_CHARS)}TAIL-MARKER`;
562-
const records = new Map<string, FleetDryMailboxRecord>([
563-
["big", { status: "done", report: original }],
564-
]);
565-
const store = fakeBlobStore();
566-
let resolveWrite: (() => void) | undefined;
567-
const writeBlob = (key: string, bytes: Uint8Array, contentType: string) =>
568-
new Promise<void>((resolve) => {
569-
resolveWrite = () => {
570-
store.writeBlob(key, bytes, contentType);
571-
resolve();
572-
};
573-
});
574-
const sent: string[] = [];
575-
const drivenP = driveOpenTasksAfterFleetDry({
576-
previousRunning: 1,
577-
running: 0,
578-
openTasks: [openTask],
579-
parentProcessing: false,
580-
mailbox: peekMailbox(records),
581-
lanes: [],
582-
writeBlob,
583-
beginSystemContinuation: (prompt) => {
584-
sent.push(prompt);
585-
},
586-
send: () => undefined,
587-
});
588-
await Promise.resolve();
589-
expect(sent).toEqual([]);
590-
resolveWrite?.();
591-
expect(await drivenP).toBe(true);
592-
const uri = `tool-output:///${fleetDrySpillKey("big", "report")}`;
593-
expect(sent[0]).toContain(uri);
594-
expect(sent[0]).not.toContain("TAIL-MARKER");
595-
const recovered = new TextDecoder().decode(
596-
await createBlobReader(store).read(uri),
597-
);
598-
expect(recovered).toBe(original);
599-
});
600-
601-
test("rejected writeBlob send is NOT retrievable and names no URI", async () => {
602-
const original = "x".repeat(FLEET_DRY_REPORT_CHARS + 40);
603-
const records = new Map<string, FleetDryMailboxRecord>([
604-
["big", { status: "done", report: original }],
605-
]);
606-
const rejections: unknown[] = [];
607-
const onUnhandled = (reason: unknown) => {
608-
rejections.push(reason);
609-
};
610-
process.on("unhandledRejection", onUnhandled);
611-
try {
612-
const sent: string[] = [];
613-
const driven = await driveOpenTasksAfterFleetDry({
614-
previousRunning: 1,
615-
running: 0,
616-
openTasks: [openTask],
617-
parentProcessing: false,
618-
mailbox: peekMailbox(records),
619-
lanes: [],
620-
writeBlob: async () => {
621-
throw new Error("disk full");
622-
},
623-
beginSystemContinuation: (prompt) => {
624-
sent.push(prompt);
625-
},
626-
send: () => undefined,
627-
});
628-
await Promise.resolve();
629-
expect(driven).toBe(true);
630-
const parsed = reportsJSONFromPrompt(sent[0] ?? "");
631-
const report = (parsed as { report?: string }[])[0]?.report ?? "";
632-
expect(report).toContain("NOT retrievable");
633-
expect(report).not.toContain("tool-output:///");
634-
expect(rejections).toEqual([]);
635-
} finally {
636-
process.off("unhandledRejection", onUnhandled);
637-
}
638-
});
639-
640560
test("dry+terminal, live+open, and parentProcessing only skip", () => {
641561
const noop = {
642562
mailbox: undefined,

src/subagent/fleet-dry-drive.ts

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
*/
66

77
import { hasActiveTasks, type Task } from "../agent/tasks.js";
8+
import {
9+
truncationNotice,
10+
truncateWithReservedNotice,
11+
} from "../plugins/result-truncation-plugin.js";
812
import { isLiveWaitStatus, type WaitJSONStatus } from "./lifecycle.js";
913

1014
/** Enough of a lane report for a parent continuation; traces stay on disk. */
@@ -83,46 +87,6 @@ export function fleetDrySpillKey(
8387
return `fleet-dry:${agentId}:${field}`;
8488
}
8589

86-
function truncationNotice(args: {
87-
maxChars: number;
88-
remaining: number;
89-
fullLength: number;
90-
uri?: string;
91-
}): string {
92-
const { maxChars, remaining, fullLength, uri } = args;
93-
if (uri === undefined) {
94-
return (
95-
`\n[output truncated at ${maxChars.toLocaleString()} chars — ` +
96-
`${remaining.toLocaleString()} chars discarded, NOT retrievable ` +
97-
`(no blob store is configured; re-running gives the same cut). ` +
98-
`Use offset/limit or a narrower query.]`
99-
);
100-
}
101-
return (
102-
`\n[output truncated at ${maxChars.toLocaleString()} chars — ` +
103-
`${remaining.toLocaleString()} more chars omitted here. The full result ` +
104-
`(${fullLength.toLocaleString()} chars, text/plain) is saved at ${uri}` +
105-
` — use read_file with that URI (offset/limit supported) to see the rest.]`
106-
);
107-
}
108-
109-
function truncateWithReservedNotice(
110-
text: string,
111-
maxChars: number,
112-
buildNotice: (keptLen: number) => string,
113-
): string {
114-
let keptLen = maxChars;
115-
for (let i = 0; i < 8; i++) {
116-
const notice = buildNotice(keptLen);
117-
const total = keptLen + notice.length;
118-
if (total <= maxChars) return text.slice(0, keptLen) + notice;
119-
keptLen -= total - maxChars;
120-
if (keptLen < 0) keptLen = 0;
121-
}
122-
const notice = buildNotice(keptLen);
123-
return (text.slice(0, keptLen) + notice).slice(0, maxChars);
124-
}
125-
12690
async function clipField(
12791
text: string | undefined,
12892
agentId: string,
@@ -146,6 +110,7 @@ async function clipField(
146110
maxChars: FLEET_DRY_REPORT_CHARS,
147111
remaining: text.length - keptLen,
148112
fullLength: text.length,
113+
contentType: "text/plain",
149114
...(uri !== undefined ? { uri } : {}),
150115
}),
151116
);

0 commit comments

Comments
 (0)