mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Closes #10164.
A user `!` command can discard earlier chunks but report `truncated:
false` when the retained tail fits within the output limits. The model
then receives no truncation notice or full-log path.
Track discarded chunks and include that flag in the executor's result
for both completion and cancellation. This applies to the current shared
finalization path and preserves streaming ANSI handling. Output limits
and message format stay unchanged.
Related: #2852. Here the complete log is already saved; the incorrect
flag hides its path from the model.
Validation on main `f1b2e77f5`:
- Two deterministic 64 KiB + 44 KiB cases fail without the fix. All
three regressions pass with it, checking completion, cancellation, the
complete log, model-facing path, and short-output behavior.
- Regressions are in `test/bash-executor-truncation.test.ts`; that file
and `test/tools.test.ts` pass all 88 tests.
- `npm run check`, offline build, and `./test.sh` pass.
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { readFile, rm } from "node:fs/promises";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { executeBashWithOperations } from "../src/core/bash-executor.ts";
|
|
import { bashExecutionToText } from "../src/core/messages.ts";
|
|
|
|
describe("user bash output truncation (#10164)", () => {
|
|
const tempFiles: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(tempFiles.splice(0).map((path) => rm(path, { force: true })));
|
|
});
|
|
|
|
// #10164: the retained tail can fit even though earlier output was discarded.
|
|
it.each([false, true])("reports discarded chunks when cancelled=%s", async (cancelled) => {
|
|
const first = "error: missing build dependency\n".padEnd(64 * 1024, "x");
|
|
const last = "remaining build output\n".padEnd(44 * 1024, "y");
|
|
const controller = new AbortController();
|
|
const result = await executeBashWithOperations(
|
|
"cat build.log",
|
|
process.cwd(),
|
|
{
|
|
exec: async (_command, _cwd, { onData }) => {
|
|
onData(Buffer.from(first));
|
|
onData(Buffer.from(last));
|
|
if (cancelled) {
|
|
controller.abort();
|
|
throw new Error("aborted");
|
|
}
|
|
return { exitCode: 0 };
|
|
},
|
|
},
|
|
{ signal: controller.signal },
|
|
);
|
|
if (result.fullOutputPath) tempFiles.push(result.fullOutputPath);
|
|
// The executor closes its file stream without awaiting its finish event.
|
|
await vi.waitFor(async () => {
|
|
expect(await readFile(result.fullOutputPath!, "utf-8")).toBe(first + last);
|
|
});
|
|
|
|
expect(result.cancelled).toBe(cancelled);
|
|
expect(result.output).toBe(last);
|
|
expect(result.truncated).toBe(true);
|
|
expect(
|
|
bashExecutionToText({ role: "bashExecution", command: "cat build.log", timestamp: 0, ...result }),
|
|
).toContain(`[Output truncated. Full output: ${result.fullOutputPath}]`);
|
|
});
|
|
|
|
it("does not mark short output as truncated", async () => {
|
|
const result = await executeBashWithOperations("echo done", process.cwd(), {
|
|
exec: async (_command, _cwd, { onData }) => {
|
|
onData(Buffer.from("done\n"));
|
|
return { exitCode: 0 };
|
|
},
|
|
});
|
|
expect(result.output).toBe("done\n");
|
|
expect(result.truncated).toBe(false);
|
|
expect(result.fullOutputPath).toBeUndefined();
|
|
});
|
|
});
|