Download src/agents/sessions/bash-executor.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 8.82 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sessions/bash-executor.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/sessions/bash-executor.test.ts
-
curl -L -o bash-executor.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sessions/bash-executor.test.ts
8.82 kB
| // Bash executor tests cover bounded UTF-8 output and private sanitized spills. | |
| import { readFile, rm, stat } from "node:fs/promises"; | |
| import { describe, expect, it } from "vitest"; | |
| import { executeBashWithOperations } from "./bash-executor.js"; | |
| import { | |
| expectNativeBashSpill, | |
| nativeBashSpillScenarios, | |
| } from "./bash-output-spill.test-support.js"; | |
| import type { BashOperations } from "./tools/bash-operations.js"; | |
| import { DEFAULT_MAX_BYTES } from "./tools/truncate.js"; | |
| type OutputChunk = readonly [data: Buffer, stream?: "stdout" | "stderr"]; | |
| const ESC = String.fromCharCode(27); | |
| function operationsForChunks(chunks: readonly OutputChunk[]): BashOperations { | |
| return { | |
| exec: async (_command, _cwd, options) => { | |
| for (const [data, stream] of chunks) { | |
| options.onData(data, stream); | |
| } | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| } | |
| describe("executeBashWithOperations", () => { | |
| it.runIf(process.platform !== "win32").each(nativeBashSpillScenarios)( | |
| "settles real Bash output for %s", | |
| async (scenario) => { | |
| await expectNativeBashSpill("executor", scenario); | |
| }, | |
| ); | |
| it("stores truncated full output in an owner-only temp file", async () => { | |
| const sanitizedOutput = "secret output\n".repeat(9000); | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| options.onData(Buffer.from("\u001b[31msecret output\u001b[0m\n".repeat(9000))); | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("echo secret", "/tmp", operations); | |
| expect(result.truncated).toBe(true); | |
| expect(result.fullOutputPath).toBeDefined(); | |
| // Full output can include secrets printed by a command, so the spill file | |
| // must be unreadable by group/other accounts. | |
| const mode = (await stat(result.fullOutputPath!)).mode & 0o777; | |
| expect(mode & 0o077).toBe(0); | |
| expect(await readFile(result.fullOutputPath!, "utf8")).toBe(sanitizedOutput); | |
| await rm(result.fullOutputPath!, { force: true }); | |
| }); | |
| it("preserves the exact UTF-8 tail across output chunk boundaries", async () => { | |
| const asciiChunk = "a".repeat(80 * 1024); | |
| const multibyteChunk = "界".repeat(10 * 1024); | |
| const expectedAsciiBytes = DEFAULT_MAX_BYTES - Buffer.byteLength(multibyteChunk, "utf8"); | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| options.onData(Buffer.from(asciiChunk)); | |
| options.onData(Buffer.from(multibyteChunk)); | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf output", "/tmp", operations); | |
| expect(result.truncated).toBe(true); | |
| expect(result.fullOutputPath).toBeDefined(); | |
| expect(Buffer.byteLength(result.output, "utf8")).toBe(DEFAULT_MAX_BYTES); | |
| expect(result.output).toBe("a".repeat(expectedAsciiBytes) + multibyteChunk); | |
| await rm(result.fullOutputPath!, { force: true }); | |
| }); | |
| it("sanitizes ANSI and OSC sequences split across output chunks", async () => { | |
| const chunks: string[] = []; | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| for (const chunk of [ | |
| "A\u001B]0;title", | |
| "\u0007B", | |
| "C\u001B[31", | |
| "mD", | |
| "E\u009D0;title", | |
| "\u001B\\F", | |
| "G\u009B31", | |
| "mH", | |
| ]) { | |
| options.onData(Buffer.from(chunk)); | |
| } | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf styled", "/tmp", operations, { | |
| onChunk: (chunk) => chunks.push(chunk), | |
| }); | |
| expect(chunks.join("")).toBe("ABCDEFGH"); | |
| expect(result.output).toBe("ABCDEFGH"); | |
| }); | |
| it("preserves delivery order when tagged UTF-8 chunks interleave", async () => { | |
| const chunks: string[] = []; | |
| const operations = operationsForChunks([ | |
| [Buffer.from([0xe6, 0x97]), "stdout"], // leading bytes of 日 | |
| [Buffer.from("E"), "stderr"], | |
| [Buffer.from([0xa5]), "stdout"], | |
| ]); | |
| const result = await executeBashWithOperations("printf output", "/tmp", operations, { | |
| onChunk: (chunk) => chunks.push(chunk), | |
| }); | |
| expect(chunks.join("")).toBe("E日"); | |
| expect(result.output).toBe("E日"); | |
| }); | |
| it.each([ | |
| { | |
| name: "OSC on stdout", | |
| pending: [Buffer.from(`${ESC}]0;unterminated`), "stdout"] as const, | |
| visible: [Buffer.from("stderr visible\n"), "stderr"] as const, | |
| }, | |
| { | |
| name: "CSI on stderr", | |
| pending: [Buffer.from(`${ESC}[31`), "stderr"] as const, | |
| visible: [Buffer.from("stdout visible\n"), "stdout"] as const, | |
| }, | |
| ])("does not let unterminated $name consume the other stream", async ({ pending, visible }) => { | |
| const result = await executeBashWithOperations( | |
| "printf output", | |
| "/tmp", | |
| operationsForChunks([pending, visible]), | |
| ); | |
| expect(result.output).toBe(visible[0].toString("utf8")); | |
| }); | |
| it("keeps one sanitizer lane for legacy untagged operations", async () => { | |
| const operations = operationsForChunks([[Buffer.from(`${ESC}[`)], [Buffer.from("31mlegacy")]]); | |
| const result = await executeBashWithOperations("printf output", "/tmp", operations); | |
| expect(result.output).toBe("legacy"); | |
| }); | |
| it("flushes pending UTF-8 bytes from every tagged stream", async () => { | |
| const chunks: string[] = []; | |
| const operations = operationsForChunks([ | |
| [Buffer.from([0xe6, 0x97]), "stdout"], | |
| [Buffer.from([0xe6, 0x97]), "stderr"], | |
| ]); | |
| const result = await executeBashWithOperations("printf output", "/tmp", operations, { | |
| onChunk: (chunk) => chunks.push(chunk), | |
| }); | |
| expect(chunks.join("")).toBe("��"); | |
| expect(result.output).toBe("��"); | |
| }); | |
| it("stores sanitized split ANSI output in spilled full output files", async () => { | |
| const chunks = ["A\u001B]0;title", "\u0007B\n"]; | |
| const repeatedChunks = Array.from({ length: 9000 }, (_, index) => chunks[index % 2] ?? ""); | |
| const expectedOutput = "AB\n".repeat(4500); | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| for (const chunk of repeatedChunks) { | |
| options.onData(Buffer.from(chunk)); | |
| } | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf styled", "/tmp", operations); | |
| expect(result.truncated).toBe(true); | |
| expect(result.fullOutputPath).toBeDefined(); | |
| expect(await readFile(result.fullOutputPath!, "utf8")).toBe(expectedOutput); | |
| await rm(result.fullOutputPath!, { force: true }); | |
| }); | |
| it("sanitizes split compatibility escape grammar sequences", async () => { | |
| const chunks: string[] = []; | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| options.onData(Buffer.from("A\u001B(")); | |
| options.onData(Buffer.from("BB")); | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf styled", "/tmp", operations, { | |
| onChunk: (chunk) => chunks.push(chunk), | |
| }); | |
| expect(chunks.join("")).toBe("AB"); | |
| expect(result.output).toBe("AB"); | |
| }); | |
| it("does not retain unterminated OSC payload outside accumulator limits", async () => { | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| options.onData(Buffer.from("safe\n\u001B]0;")); | |
| options.onData(Buffer.from("x".repeat(DEFAULT_MAX_BYTES * 3))); | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf styled", "/tmp", operations); | |
| expect(result.output).toBe("safe\n"); | |
| expect(result.truncated).toBe(false); | |
| expect(result.fullOutputPath).toBeDefined(); | |
| expect(await readFile(result.fullOutputPath!, "utf8")).toBe("safe\n"); | |
| await rm(result.fullOutputPath!, { force: true }); | |
| }); | |
| it.each([ | |
| { name: "success", abort: false }, | |
| { name: "abort", abort: true }, | |
| ])("flushes incomplete UTF-8 output on $name", async ({ abort }) => { | |
| const abortController = new AbortController(); | |
| const chunks: string[] = []; | |
| const operations: BashOperations = { | |
| exec: async (_command, _cwd, options) => { | |
| options.onData(Buffer.from([0xf0, 0x9f, 0x98])); | |
| if (abort) { | |
| abortController.abort(); | |
| throw new Error("aborted"); | |
| } | |
| return { exitCode: 0 }; | |
| }, | |
| }; | |
| const result = await executeBashWithOperations("printf output", "/tmp", operations, { | |
| signal: abortController.signal, | |
| onChunk: (chunk) => chunks.push(chunk), | |
| }); | |
| expect(result.output).toBe("�"); | |
| expect(chunks.join("")).toBe("�"); | |
| expect(result.cancelled).toBe(abort); | |
| expect(result.exitCode).toBe(abort ? undefined : 0); | |
| }); | |
| }); | |