From f82f9221e6c68c2d082c6b29bfa6df406dbb1ddd Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 7 Jan 2026 15:04:29 -0600 Subject: [PATCH] core: improve file cleanup by extracting timestamp logic from ID and using Bun.Glob for efficient file scanning --- packages/opencode/src/id/id.ts | 8 ++++ packages/opencode/src/tool/truncation.ts | 22 +++++------ .../opencode/test/tool/truncation.test.ts | 39 ++++++++++++++++++- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index b1c310e4d6..7c81c5ed62 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -71,4 +71,12 @@ export namespace Identifier { return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12) } + + /** Extract timestamp from an ascending ID. Does not work with descending IDs. */ + export function timestamp(id: string): number { + const prefix = id.split("_")[0] + const hex = id.slice(prefix.length + 1, prefix.length + 13) + const encoded = BigInt("0x" + hex) + return Number(encoded / BigInt(0x1000)) + } } diff --git a/packages/opencode/src/tool/truncation.ts b/packages/opencode/src/tool/truncation.ts index 3dc5a5e8a0..628685a0b6 100644 --- a/packages/opencode/src/tool/truncation.ts +++ b/packages/opencode/src/tool/truncation.ts @@ -2,7 +2,6 @@ import fs from "fs/promises" import path from "path" import { Global } from "../global" import { Identifier } from "../id/id" -import { iife } from "../util/iife" import { lazy } from "../util/lazy" import { PermissionNext } from "../permission/next" import type { Agent } from "../agent/agent" @@ -25,20 +24,17 @@ export namespace Truncate { direction?: "head" | "tail" } - const init = lazy(async () => { - const cutoff = Date.now() - RETENTION_MS - const entries = await fs.readdir(DIR).catch(() => [] as string[]) + export async function cleanup() { + const cutoff = Identifier.timestamp(Identifier.create("tool", false, Date.now() - RETENTION_MS)) + const glob = new Bun.Glob("tool_*") + const entries = await Array.fromAsync(glob.scan({ cwd: DIR, onlyFiles: true })).catch(() => [] as string[]) for (const entry of entries) { - if (!entry.startsWith("tool_")) continue - const timestamp = iife(() => { - const hex = entry.slice(5, 17) - const now = BigInt("0x" + hex) - return Number(now / BigInt(0x1000)) - }) - if (timestamp >= cutoff) continue - await fs.rm(path.join(DIR, entry), { force: true }).catch(() => {}) + if (Identifier.timestamp(entry) >= cutoff) continue + await fs.unlink(path.join(DIR, entry)).catch(() => {}) } - }) + } + + const init = lazy(cleanup) function hasTaskTool(agent?: Agent.Info): boolean { if (!agent?.permission) return false diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts index 9560f53891..bb6a43b70f 100644 --- a/packages/opencode/test/tool/truncation.test.ts +++ b/packages/opencode/test/tool/truncation.test.ts @@ -1,5 +1,7 @@ -import { describe, test, expect } from "bun:test" +import { describe, test, expect, afterAll } from "bun:test" import { Truncate } from "../../src/tool/truncation" +import { Identifier } from "../../src/id/id" +import fs from "fs/promises" import path from "path" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") @@ -117,4 +119,39 @@ describe("Truncate", () => { expect(result.outputPath).toBeUndefined() }) }) + + describe("cleanup", () => { + const DAY_MS = 24 * 60 * 60 * 1000 + let oldFile: string + let recentFile: string + + afterAll(async () => { + await fs.unlink(oldFile).catch(() => {}) + await fs.unlink(recentFile).catch(() => {}) + }) + + test("deletes files older than 7 days and preserves recent files", async () => { + await fs.mkdir(Truncate.DIR, { recursive: true }) + + // Create an old file (10 days ago) + const oldTimestamp = Date.now() - 10 * DAY_MS + const oldId = Identifier.create("tool", false, oldTimestamp) + oldFile = path.join(Truncate.DIR, oldId) + await Bun.write(Bun.file(oldFile), "old content") + + // Create a recent file (3 days ago) + const recentTimestamp = Date.now() - 3 * DAY_MS + const recentId = Identifier.create("tool", false, recentTimestamp) + recentFile = path.join(Truncate.DIR, recentId) + await Bun.write(Bun.file(recentFile), "recent content") + + await Truncate.cleanup() + + // Old file should be deleted + expect(await Bun.file(oldFile).exists()).toBe(false) + + // Recent file should still exist + expect(await Bun.file(recentFile).exists()).toBe(true) + }) + }) })