Browse Source

fix(core): use file times for tool output cleanup (#42450)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] 3 days ago
parent
commit
49d07ffe5f
2 changed files with 18 additions and 9 deletions
  1. 14 6
      packages/core/src/tool-output.ts
  2. 4 3
      packages/core/test/tool-output.test.ts

+ 14 - 6
packages/core/src/tool-output.ts

@@ -2,7 +2,7 @@ export * as ToolOutput from "./tool-output.js"
 
 
 import path from "path"
 import path from "path"
 import type { Tool } from "@opencode-ai/schema/tool"
 import type { Tool } from "@opencode-ai/schema/tool"
-import { Context, Duration, Effect, Layer, Schedule } from "effect"
+import { Context, Duration, Effect, Layer, Option, Schedule } from "effect"
 import { makeGlobalNode, makeLocationNode } from "@opencode-ai/util/effect/app-node"
 import { makeGlobalNode, makeLocationNode } from "@opencode-ai/util/effect/app-node"
 import { FSUtil } from "@opencode-ai/util/fs-util"
 import { FSUtil } from "@opencode-ai/util/fs-util"
 import { Global } from "@opencode-ai/util/global"
 import { Global } from "@opencode-ai/util/global"
@@ -24,15 +24,23 @@ export interface Interface {
 export class Service extends Context.Service<Service, Interface>()("@opencode/ToolOutput") {}
 export class Service extends Context.Service<Service, Interface>()("@opencode/ToolOutput") {}
 
 
 const cleanup = Effect.fn("ToolOutput.cleanup")(function* (fs: FSUtil.Interface, directory: string) {
 const cleanup = Effect.fn("ToolOutput.cleanup")(function* (fs: FSUtil.Interface, directory: string) {
-  const cutoff = Identifier.timestamp(Identifier.create("tool", "ascending", Date.now() - Duration.toMillis(RETENTION)))
+  const cutoff = Date.now() - Duration.toMillis(RETENTION)
   const entries = yield* fs.readDirectory(directory).pipe(
   const entries = yield* fs.readDirectory(directory).pipe(
     Effect.map((entries) => entries.filter((entry) => /^tool_[0-9a-f]{12}/.test(entry))),
     Effect.map((entries) => entries.filter((entry) => /^tool_[0-9a-f]{12}/.test(entry))),
     Effect.catch(() => Effect.succeed([])),
     Effect.catch(() => Effect.succeed([])),
   )
   )
-  for (const entry of entries) {
-    if (Identifier.timestamp(entry) >= cutoff) continue
-    yield* fs.remove(path.join(directory, entry)).pipe(Effect.catch(() => Effect.void))
-  }
+  yield* Effect.forEach(
+    entries,
+    (entry) =>
+      Effect.gen(function* () {
+        const file = path.join(directory, entry)
+        const info = yield* fs.stat(file).pipe(Effect.catch(() => Effect.succeed(undefined)))
+        const mtime = info && Option.getOrUndefined(info.mtime)
+        if (!mtime || mtime.getTime() >= cutoff) return
+        yield* fs.remove(file).pipe(Effect.catch(() => Effect.void))
+      }),
+    { concurrency: 8, discard: true },
+  )
 })
 })
 
 
 const layer = Layer.effect(
 const layer = Layer.effect(

+ 4 - 3
packages/core/test/tool-output.test.ts

@@ -143,15 +143,16 @@ describe("ToolOutput", () => {
     ),
     ),
   )
   )
 
 
-  it.live("removes expired managed files", () =>
+  it.live("uses file modification time when IDs wrap", () =>
     withStore((output, fs, root) =>
     withStore((output, fs, root) =>
       Effect.gen(function* () {
       Effect.gen(function* () {
         const directory = path.join(root, ToolOutput.DIRECTORY)
         const directory = path.join(root, ToolOutput.DIRECTORY)
-        const old = path.join(directory, Identifier.create("tool", "ascending", Date.now() - 8 * 24 * 60 * 60 * 1_000))
-        const recent = path.join(directory, Identifier.ascending("tool"))
+        const old = path.join(directory, Identifier.create("tool", "ascending", 2 ** 36 - 1))
+        const recent = path.join(directory, Identifier.create("tool", "ascending", 2 ** 36 + 1))
         yield* fs.ensureDir(directory)
         yield* fs.ensureDir(directory)
         yield* fs.writeFileString(old, "old")
         yield* fs.writeFileString(old, "old")
         yield* fs.writeFileString(recent, "recent")
         yield* fs.writeFileString(recent, "recent")
+        yield* fs.utimes(old, new Date(), new Date(Date.now() - 8 * 24 * 60 * 60 * 1_000))
         yield* output.cleanup()
         yield* output.cleanup()
         expect(yield* fs.exists(old)).toBe(false)
         expect(yield* fs.exists(old)).toBe(false)
         expect(yield* fs.exists(recent)).toBe(true)
         expect(yield* fs.exists(recent)).toBe(true)