diff --git a/src/constants/toolLimits.ts b/src/constants/toolLimits.ts index f6ba0d18b..c8b04fc99 100644 --- a/src/constants/toolLimits.ts +++ b/src/constants/toolLimits.ts @@ -7,8 +7,9 @@ export const BASH_MAX_TOTAL_BYTES = 16 * 1024; // 16KB total output to show agen export const BASH_MAX_FILE_BYTES = 100 * 1024; // 100KB max to save to temp file // truncate policy limits (IPC - generous for UI features like code review) -export const BASH_TRUNCATE_HARD_MAX_LINES = 10_000; // 10K lines +// No line limit for IPC - only byte limit applies export const BASH_TRUNCATE_MAX_TOTAL_BYTES = 1024 * 1024; // 1MB total output +export const BASH_TRUNCATE_MAX_FILE_BYTES = 1024 * 1024; // 1MB file limit (same as total for IPC) // Shared limits export const BASH_MAX_LINE_BYTES = 1024; // 1KB per line (shared across both policies) diff --git a/src/services/tools/bash.test.ts b/src/services/tools/bash.test.ts index f8884fbb1..e0fd07a98 100644 --- a/src/services/tools/bash.test.ts +++ b/src/services/tools/bash.test.ts @@ -166,7 +166,8 @@ describe("bash tool", () => { }); const args: BashToolArgs = { - script: "for i in {1..11000}; do echo line$i; done", // Exceeds 10K line hard cap for truncate policy + // Generate ~1.5MB of output (1700 lines * 900 bytes) to exceed 1MB byte limit + script: 'perl -e \'for (1..1700) { print "A" x 900 . "\\n" }\'', timeout_secs: 5, }; @@ -177,15 +178,14 @@ describe("bash tool", () => { expect(result.truncated).toBeDefined(); if (result.truncated) { expect(result.truncated.reason).toContain("exceeded"); - // Should collect all lines up to when truncation was triggered - expect(result.truncated.totalLines).toBeGreaterThan(10000); + // Should collect lines up to ~1MB (around 1150-1170 lines with 900 bytes each) + expect(result.truncated.totalLines).toBeGreaterThan(1000); + expect(result.truncated.totalLines).toBeLessThan(1300); } - // Should contain collected lines - expect(result.output).toContain("line1"); - expect(result.output).toContain("line10000"); - // Might or might not contain line 11000 depending on when truncation triggers - expect(result.output).toContain("line11000"); + // Should contain output that's around 1MB + expect(result.output?.length).toBeGreaterThan(1000000); + expect(result.output?.length).toBeLessThan(1100000); // Should NOT create temp file with truncate policy const files = fs.readdirSync(tempDir.path); diff --git a/src/services/tools/bash.ts b/src/services/tools/bash.ts index 91b170225..7096e4884 100644 --- a/src/services/tools/bash.ts +++ b/src/services/tools/bash.ts @@ -10,8 +10,8 @@ import { BASH_MAX_LINE_BYTES, BASH_MAX_TOTAL_BYTES, BASH_MAX_FILE_BYTES, - BASH_TRUNCATE_HARD_MAX_LINES, BASH_TRUNCATE_MAX_TOTAL_BYTES, + BASH_TRUNCATE_MAX_FILE_BYTES, } from "@/constants/toolLimits"; import type { BashToolResult } from "@/types/tools"; @@ -62,13 +62,14 @@ class DisposableProcess implements Disposable { */ export const createBashTool: ToolFactory = (config: ToolConfiguration) => { // Select limits based on overflow policy - // truncate = IPC calls (generous limits for UI features) + // truncate = IPC calls (generous limits for UI features, no line limit) // tmpfile = AI agent calls (conservative limits for LLM context) const overflowPolicy = config.overflow_policy ?? "tmpfile"; const maxTotalBytes = overflowPolicy === "truncate" ? BASH_TRUNCATE_MAX_TOTAL_BYTES : BASH_MAX_TOTAL_BYTES; - const maxLines = - overflowPolicy === "truncate" ? BASH_TRUNCATE_HARD_MAX_LINES : BASH_HARD_MAX_LINES; + const maxFileBytes = + overflowPolicy === "truncate" ? BASH_TRUNCATE_MAX_FILE_BYTES : BASH_MAX_FILE_BYTES; + const maxLines = overflowPolicy === "truncate" ? Infinity : BASH_HARD_MAX_LINES; return tool({ description: TOOL_DEFINITIONS.bash.description + "\nRuns in " + config.cwd + " - no cd needed", @@ -248,9 +249,9 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => { totalBytesAccumulated += lineBytes + 1; // +1 for newline // Check file limit first (hard stop) - if (totalBytesAccumulated > BASH_MAX_FILE_BYTES) { + if (totalBytesAccumulated > maxFileBytes) { triggerFileTruncation( - `Total output exceeded file preservation limit: ${totalBytesAccumulated} bytes > ${BASH_MAX_FILE_BYTES} bytes (at line ${lines.length})` + `Total output exceeded file preservation limit: ${totalBytesAccumulated} bytes > ${maxFileBytes} bytes (at line ${lines.length})` ); return; } @@ -290,9 +291,9 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => { totalBytesAccumulated += lineBytes + 1; // +1 for newline // Check file limit first (hard stop) - if (totalBytesAccumulated > BASH_MAX_FILE_BYTES) { + if (totalBytesAccumulated > maxFileBytes) { triggerFileTruncation( - `Total output exceeded file preservation limit: ${totalBytesAccumulated} bytes > ${BASH_MAX_FILE_BYTES} bytes (at line ${lines.length})` + `Total output exceeded file preservation limit: ${totalBytesAccumulated} bytes > ${maxFileBytes} bytes (at line ${lines.length})` ); return; }