diff --git a/src/index.ts b/src/index.ts index 1c3ca77..27a3f2f 100755 --- a/src/index.ts +++ b/src/index.ts @@ -362,6 +362,16 @@ async function main(): Promise { // Silently ignore update failures }); + // Clean up old overflow files (non-blocking, 24h retention) + const { cleanupOldOverflowFiles } = await import("./tools/impl/overflow"); + Promise.resolve().then(() => { + try { + cleanupOldOverflowFiles(process.cwd()); + } catch { + // Silently ignore cleanup failures + } + }); + // Parse command-line arguments (Bun-idiomatic approach using parseArgs) // Preprocess args to support --conv as alias for --conversation const processedArgs = process.argv.map((arg) => diff --git a/src/tests/tools/overflow.test.ts b/src/tests/tools/overflow.test.ts index 3c6959d..aa8cee4 100644 --- a/src/tests/tools/overflow.test.ts +++ b/src/tests/tools/overflow.test.ts @@ -170,6 +170,33 @@ describe("overflow utilities", () => { expect(deletedCount).toBe(0); }); + + test("skips subdirectories without crashing", () => { + // Create a test file and a subdirectory + const content = "Test content"; + const filePath = writeOverflowFile(content, testWorkingDir, "TestTool"); + + // Create a subdirectory in the overflow dir + const subDir = path.join(path.dirname(filePath), "subdir"); + fs.mkdirSync(subDir, { recursive: true }); + + // Make the file old + const oldTime = Date.now() - 48 * 60 * 60 * 1000; + fs.utimesSync(filePath, new Date(oldTime), new Date(oldTime)); + + // Cleanup should skip the directory and only delete the file + const deletedCount = cleanupOldOverflowFiles( + testWorkingDir, + 24 * 60 * 60 * 1000, + ); + + expect(deletedCount).toBe(1); + expect(fs.existsSync(filePath)).toBe(false); + expect(fs.existsSync(subDir)).toBe(true); + + // Clean up the subdir + fs.rmdirSync(subDir); + }); }); describe("getOverflowStats", () => { diff --git a/src/tools/impl/Task.ts b/src/tools/impl/Task.ts index 66b5ecc..828a00f 100644 --- a/src/tools/impl/Task.ts +++ b/src/tools/impl/Task.ts @@ -16,6 +16,7 @@ import { generateSubagentId, registerSubagent, } from "../../cli/helpers/subagentState.js"; +import { LIMITS, truncateByChars } from "./truncation.js"; import { validateRequiredParams } from "./validation"; interface TaskArgs { @@ -114,7 +115,18 @@ export async function task(args: TaskArgs): Promise { .filter(Boolean) .join(" "); - return `${header}\n\n${result.report}`; + const fullOutput = `${header}\n\n${result.report}`; + const userCwd = process.env.USER_CWD || process.cwd(); + + // Apply truncation to prevent excessive token usage (same pattern as Bash tool) + const { content: truncatedOutput } = truncateByChars( + fullOutput, + LIMITS.TASK_OUTPUT_CHARS, + "Task", + { workingDirectory: userCwd, toolName: "Task" }, + ); + + return truncatedOutput; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); completeSubagent(subagentId, { success: false, error: errorMessage }); diff --git a/src/tools/impl/overflow.ts b/src/tools/impl/overflow.ts index 9c7959b..d345ffa 100644 --- a/src/tools/impl/overflow.ts +++ b/src/tools/impl/overflow.ts @@ -113,17 +113,33 @@ export function cleanupOldOverflowFiles( return 0; } - const files = fs.readdirSync(overflowDir); + let files: string[]; + try { + files = fs.readdirSync(overflowDir); + } catch { + // Directory may have been deleted or become inaccessible + return 0; + } + const now = Date.now(); let deletedCount = 0; for (const file of files) { const filePath = path.join(overflowDir, file); - const stats = fs.statSync(filePath); + try { + const stats = fs.statSync(filePath); - if (now - stats.mtimeMs > maxAgeMs) { - fs.unlinkSync(filePath); - deletedCount++; + // Skip directories (shouldn't exist, but be safe) + if (stats.isDirectory()) { + continue; + } + + if (now - stats.mtimeMs > maxAgeMs) { + fs.unlinkSync(filePath); + deletedCount++; + } + } catch { + // File may have been deleted, or permission error - skip it } } diff --git a/src/tools/impl/truncation.ts b/src/tools/impl/truncation.ts index 00e3360..0be2acd 100644 --- a/src/tools/impl/truncation.ts +++ b/src/tools/impl/truncation.ts @@ -10,6 +10,7 @@ import { OVERFLOW_CONFIG, writeOverflowFile } from "./overflow.js"; export const LIMITS = { // Command output limits BASH_OUTPUT_CHARS: 30_000, // 30K characters for bash/shell output + TASK_OUTPUT_CHARS: 30_000, // 30K characters for subagent task output // File reading limits READ_MAX_LINES: 2_000, // Max lines per file read