Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/constants/toolLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions src/services/tools/bash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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);
Expand Down
17 changes: 9 additions & 8 deletions src/services/tools/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down