Skip to content

Commit 368cc97

Browse files
committed
fix(subagent): count mailbox spill remaining as omitted body
1 parent 24f7772 commit 368cc97

3 files changed

Lines changed: 101 additions & 7 deletions

File tree

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,13 @@ function clipDigestSection(text: string): string {
211211
return `${text.slice(0, MAILBOX_DIGEST_SECTION_CHARS - 1).trimEnd()}…`;
212212
}
213213

214-
function mailboxReportSpillNotice(text: string, uri: string): string {
214+
function mailboxSpillNotice(text: string, uri: string): string {
215+
// Digest inlines the notice only, so remaining is the omitted body — not
216+
// length minus a 2048-char prefix that was never kept. A short spill that
217+
// claimed "0 more chars omitted" looks complete and the parent skips read_file.
215218
return truncationNotice({
216219
maxChars: MAILBOX_DIGEST_SECTION_CHARS,
217-
remaining: Math.max(0, text.length - MAILBOX_DIGEST_SECTION_CHARS),
220+
remaining: text.length,
218221
fullLength: text.length,
219222
contentType: "text/plain",
220223
uri,
@@ -287,8 +290,14 @@ export async function digestCollectedReport(
287290
report.report === undefined
288291
? undefined
289292
: reportUri !== undefined
290-
? mailboxReportSpillNotice(report.report, reportUri)
293+
? mailboxSpillNotice(report.report, reportUri)
291294
: await clipField(report.report, report.agent_id, "report");
295+
const errorInline =
296+
report.error === undefined
297+
? undefined
298+
: errorUri !== undefined
299+
? mailboxSpillNotice(report.error, errorUri)
300+
: clipDigestSection(report.error);
292301
return {
293302
agent_id: report.agent_id,
294303
status: report.status,
@@ -300,9 +309,7 @@ export async function digestCollectedReport(
300309
...(blockers !== undefined ? { blockers } : {}),
301310
...(reportInline !== undefined ? { report: reportInline } : {}),
302311
...(reportUri !== undefined ? { report_uri: reportUri } : {}),
303-
...(report.error !== undefined
304-
? { error: clipDigestSection(report.error) }
305-
: {}),
312+
...(errorInline !== undefined ? { error: errorInline } : {}),
306313
...(errorUri !== undefined ? { error_uri: errorUri } : {}),
307314
...(report.hint !== undefined ? { hint: report.hint } : {}),
308315
...(report.provider_failure === true ? { provider_failure: true } : {}),

‎src/subagent/mailbox-mail-drive.test.ts‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
driveMailboxMail,
66
latchMailboxMailDrive,
77
MAILBOX_MAIL_WAKE_PREFIX,
8+
mailboxMailErrorUriHint,
89
mailboxMailReportUriHint,
910
mailboxMailWakeLine,
1011
occupancyShouldYieldWait,
@@ -14,6 +15,7 @@ import {
1415
digestCollectedReports,
1516
fleetDrySpillKey,
1617
FLEET_DRY_REPORT_CHARS,
18+
MAILBOX_DIGEST_SECTION_CHARS,
1719
type FleetDryMailboxRecord,
1820
} from "./fleet-dry-drive.js";
1921
import {
@@ -125,6 +127,22 @@ describe("buildMailboxMailPrompt", () => {
125127
expect(prompt).toContain(uri);
126128
});
127129

130+
test("names error_uri with a read_file truncation notice", () => {
131+
const uri = `tool-output:///${fleetDrySpillKey("worker-1", "error")}`;
132+
const prompt = buildMailboxMailPrompt([
133+
{
134+
agent_id: "worker-1",
135+
status: "failed",
136+
error_uri: uri,
137+
},
138+
]);
139+
expect(prompt).toContain(mailboxMailErrorUriHint());
140+
expect(prompt).toContain(
141+
"use read_file with that URI (offset/limit supported)",
142+
);
143+
expect(prompt).toContain(uri);
144+
});
145+
128146
test("does not paste a duplicate agent_id", () => {
129147
const prompt = buildMailboxMailPrompt([
130148
{ agent_id: "w1", status: "done" },
@@ -163,6 +181,10 @@ describe("digestCollectedReports", () => {
163181
"use read_file with that URI (offset/limit supported)",
164182
);
165183
expect(digest?.report).not.toContain("SECRET_FINDINGS_BODY");
184+
expect(digest?.report).toContain(
185+
`${ENVELOPE_REPORT.length.toLocaleString()} more chars omitted here`,
186+
);
187+
expect(digest?.report).not.toMatch(/— 0 more chars omitted/);
166188
expect(
167189
new TextDecoder().decode(
168190
store.blobs.get(fleetDrySpillKey("worker-1", "report"))?.bytes ??
@@ -222,6 +244,63 @@ describe("digestCollectedReports", () => {
222244
expect(digest?.report).toContain("SECRET_FINDINGS_BODY");
223245
expect(digest?.summary).toBe("Shipped the digest.");
224246
});
247+
248+
test("spill notice remaining is the omitted body when no prefix is kept", async () => {
249+
const store = fakeBlobStore();
250+
const short = "short spilled body";
251+
const [digest] = await digestCollectedReports(
252+
[{ agent_id: "short", status: "done", report: short }],
253+
store.writeBlob,
254+
);
255+
expect(short.length).toBeLessThan(MAILBOX_DIGEST_SECTION_CHARS);
256+
expect(digest?.report).toContain(
257+
`${short.length.toLocaleString()} more chars omitted here`,
258+
);
259+
expect(digest?.report).not.toMatch(/— 0 more chars omitted/);
260+
expect(digest?.report).not.toContain(short);
261+
expect(digest?.report_uri).toBe(
262+
`tool-output:///${fleetDrySpillKey("short", "report")}`,
263+
);
264+
});
265+
266+
test("oversized spilled report remaining is the full body, not length minus the digest cap", async () => {
267+
const store = fakeBlobStore();
268+
const original = "x".repeat(MAILBOX_DIGEST_SECTION_CHARS + 80);
269+
const [digest] = await digestCollectedReports(
270+
[{ agent_id: "long", status: "done", report: original }],
271+
store.writeBlob,
272+
);
273+
expect(digest?.report).toContain(
274+
`${original.length.toLocaleString()} more chars omitted here`,
275+
);
276+
expect(digest?.report).not.toContain(
277+
`${(original.length - MAILBOX_DIGEST_SECTION_CHARS).toLocaleString()} more chars omitted here`,
278+
);
279+
});
280+
281+
test("spilled error names error_uri with truncation-notice language", async () => {
282+
const store = fakeBlobStore();
283+
const error = "provider boom";
284+
const [digest] = await digestCollectedReports(
285+
[{ agent_id: "fail", status: "failed", error }],
286+
store.writeBlob,
287+
);
288+
const errorUri = `tool-output:///${fleetDrySpillKey("fail", "error")}`;
289+
expect(digest?.error_uri).toBe(errorUri);
290+
expect(digest?.error).toContain("[output truncated");
291+
expect(digest?.error).toContain(errorUri);
292+
expect(digest?.error).toContain(
293+
`${error.length.toLocaleString()} more chars omitted here`,
294+
);
295+
expect(digest?.error).not.toMatch(/— 0 more chars omitted/);
296+
expect(digest?.error).not.toContain("provider boom");
297+
expect(
298+
new TextDecoder().decode(
299+
store.blobs.get(fleetDrySpillKey("fail", "error"))?.bytes ??
300+
new Uint8Array(),
301+
),
302+
).toBe(error);
303+
});
225304
});
226305

227306
describe("driveMailboxMail", () => {

‎src/subagent/mailbox-mail-drive.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export function mailboxMailReportUriHint(): string {
3535
return "report_uri names a spilled full report — use read_file with that URI (offset/limit supported) to see the rest.";
3636
}
3737

38+
/** Names error_uri with truncation-notice language so the parent may fetch it. */
39+
export function mailboxMailErrorUriHint(): string {
40+
return "error_uri names a spilled full error — use read_file with that URI (offset/limit supported) to see the rest.";
41+
}
42+
3843
/**
3944
* Whether inbound text is occupancy's mailbox mail. Internal runtime→agent
4045
* traffic — the fleet board already owns worker status and the payload is
@@ -85,13 +90,16 @@ export function occupancyShouldYieldWait(
8590
}
8691

8792
export function buildMailboxMailPrompt<
88-
T extends { agent_id: string; report_uri?: string },
93+
T extends { agent_id: string; report_uri?: string; error_uri?: string },
8994
>(reports: readonly T[]): string {
9095
const unique = dedupeByAgentId(reports);
9196
const lines = [mailboxMailWakeLine()];
9297
if (unique.some((report) => report.report_uri !== undefined)) {
9398
lines.push(mailboxMailReportUriHint());
9499
}
100+
if (unique.some((report) => report.error_uri !== undefined)) {
101+
lines.push(mailboxMailErrorUriHint());
102+
}
95103
lines.push(JSON.stringify(unique));
96104
return lines.join("\n");
97105
}

0 commit comments

Comments
 (0)