fix(coding-agent): prevented duplicate artifact saves and mismatched ids in bash output

This commit is contained in:
can1357
2026-07-28 02:05:43 +02:00
parent cfd335d2b1
commit bbe0236a0f
5 changed files with 88 additions and 17 deletions
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import {
enforceInlineByteCap,
formatHeadTruncationNotice,
formatMiddleElisionMarker,
formatTailTruncationNotice,
@@ -686,6 +687,36 @@ describe("OutputSink head-retain mode", () => {
// Counters realign to the authoritative buffer + the subsequent push.
expect(dumped.totalBytes).toBe(byteLength("OK\n[raw output: artifact://8]\n"));
});
test("middle-elided dump body fits the inline budget (no double truncation)", async () => {
// Regression: the head and tail windows each had their own full budget,
// so an elided dump body could reach headBytes + spillThreshold and
// re-trip enforceInlineByteCap at the tool-result boundary — truncating
// a second time and saving a duplicate artifact whose id disagreed with
// the truncation notice's `Read artifact://N for full output`.
const spillThreshold = 1000;
const sink = new OutputSink({ spillThreshold, headBytes: 400 });
const lines = Array.from({ length: 400 }, (_, i) => `line ${i}`).join("\n");
sink.push(lines);
const dumped = await sink.dump();
expect(dumped.truncated).toBe(true);
expect(dumped.elidedLines ?? 0).toBeGreaterThan(0);
// Head window + elision marker + tail window share the one budget
// (small slack for the marker and separators).
expect(byteLength(dumped.output)).toBeLessThanOrEqual(spillThreshold + 64);
let saved: string | undefined;
const capped = await enforceInlineByteCap(dumped.output, {
maxBytes: spillThreshold + 2048,
saveArtifact: full => {
saved = full;
return "duplicate";
},
});
expect(capped).toBe(dumped.output);
expect(saved).toBeUndefined();
});
});
describe("OutputSink maxColumns (per-line cap)", () => {