Преглед на файлове

core: improve file cleanup by extracting timestamp logic from ID and using Bun.Glob for efficient file scanning

Aiden Cline преди 3 месеца
родител
ревизия
f82f9221e6
променени са 3 файла, в които са добавени 55 реда и са изтрити 14 реда
  1. 8 0
      packages/opencode/src/id/id.ts
  2. 9 13
      packages/opencode/src/tool/truncation.ts
  3. 38 1
      packages/opencode/test/tool/truncation.test.ts

+ 8 - 0
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))
+  }
 }

+ 9 - 13
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

+ 38 - 1
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)
+    })
+  })
 })