fix(coding-agent): stream bash output incrementally (#4165)
fixes #4145
This commit is contained in:
@@ -3,7 +3,7 @@ import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { executeBashWithOperations } from "../src/core/bash-executor.js";
|
||||
import { createBashTool, createLocalBashOperations } from "../src/core/tools/bash.js";
|
||||
import { type BashOperations, createBashTool, createLocalBashOperations } from "../src/core/tools/bash.js";
|
||||
import { computeEditsDiff } from "../src/core/tools/edit-diff.js";
|
||||
import {
|
||||
createEditTool,
|
||||
@@ -449,6 +449,42 @@ describe("Coding Agent Tools", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should include full output path for truncated timeout and abort errors", async () => {
|
||||
for (const testCase of [
|
||||
{ error: "timeout:5", expected: "Command timed out after 5 seconds" },
|
||||
{ error: "aborted", expected: "Command aborted" },
|
||||
]) {
|
||||
const operations: BashOperations = {
|
||||
exec: async (_command, _cwd, { onData }) => {
|
||||
for (let i = 1; i <= 3000; i++) {
|
||||
onData(Buffer.from(`${i}\n`, "utf-8"));
|
||||
}
|
||||
throw new Error(testCase.error);
|
||||
},
|
||||
};
|
||||
const bash = createBashTool(testDir, { operations });
|
||||
|
||||
let error: unknown;
|
||||
try {
|
||||
await bash.execute(`test-call-${testCase.error}`, { command: "chatty-fail" });
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
const message = (error as Error).message;
|
||||
expect(message).toContain(testCase.expected);
|
||||
expect(message).toMatch(/\[Showing lines \d+-\d+ of \d+\. Full output: /);
|
||||
expect(message).not.toContain("Full output: undefined");
|
||||
const fullOutputPath = message.match(/Full output: ([^\]\n]+)/)?.[1];
|
||||
expect(fullOutputPath).toBeDefined();
|
||||
expect(existsSync(fullOutputPath!)).toBe(true);
|
||||
const fullOutput = readFileSync(fullOutputPath!, "utf-8");
|
||||
expect(fullOutput).toContain("1\n2\n3");
|
||||
expect(fullOutput).toContain("2998\n2999\n3000");
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw error when cwd does not exist", async () => {
|
||||
const nonexistentCwd = "/this/directory/definitely/does/not/exist/12345";
|
||||
|
||||
@@ -517,6 +553,42 @@ describe("Coding Agent Tools", () => {
|
||||
expect(getTextOutput(result).trim()).toBe("no-prefix");
|
||||
});
|
||||
|
||||
it("should coalesce streaming updates for chatty output", async () => {
|
||||
const operations: BashOperations = {
|
||||
exec: async (_command, _cwd, { onData }) => {
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
onData(Buffer.from(`line ${i}\n`, "utf-8"));
|
||||
}
|
||||
return { exitCode: 0 };
|
||||
},
|
||||
};
|
||||
const updates: Array<{ content: Array<{ type: string; text?: string }>; details?: unknown }> = [];
|
||||
const bash = createBashTool(testDir, { operations });
|
||||
|
||||
const result = await bash.execute("test-call-chatty-updates", { command: "chatty" }, undefined, (update) =>
|
||||
updates.push(update),
|
||||
);
|
||||
|
||||
expect(updates.length).toBeLessThan(25);
|
||||
expect(getTextOutput(result)).toContain("line 4999");
|
||||
});
|
||||
|
||||
it("should decode UTF-8 characters split across output chunks", async () => {
|
||||
const euro = Buffer.from("€\n", "utf-8");
|
||||
const operations: BashOperations = {
|
||||
exec: async (_command, _cwd, { onData }) => {
|
||||
onData(euro.subarray(0, 1));
|
||||
onData(euro.subarray(1));
|
||||
return { exitCode: 0 };
|
||||
},
|
||||
};
|
||||
const bash = createBashTool(testDir, { operations });
|
||||
|
||||
const result = await bash.execute("test-call-split-utf8", { command: "split-utf8" });
|
||||
|
||||
expect(getTextOutput(result).trim()).toBe("€");
|
||||
});
|
||||
|
||||
it("should expose local bash operations for extension reuse", async () => {
|
||||
const ops = createLocalBashOperations();
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
Reference in New Issue
Block a user