Procházet zdrojové kódy

core: add schedule cleanup module

Aiden Cline před 3 měsíci
rodič
revize
bb1d602c29

+ 4 - 0
packages/opencode/src/project/bootstrap.ts

@@ -11,6 +11,8 @@ import { Instance } from "./instance"
 import { Vcs } from "./vcs"
 import { Log } from "@/util/log"
 import { ShareNext } from "@/share/share-next"
+import { Snapshot } from "../snapshot"
+import { Truncate } from "../tool/truncation"
 
 export async function InstanceBootstrap() {
   Log.Default.info("bootstrapping", { directory: Instance.directory })
@@ -22,6 +24,8 @@ export async function InstanceBootstrap() {
   FileWatcher.init()
   File.init()
   Vcs.init()
+  Snapshot.init()
+  Truncate.init()
 
   Bus.subscribe(Command.Event.Executed, async (payload) => {
     if (payload.properties.name === Command.Default.INIT) {

+ 50 - 0
packages/opencode/src/scheduler/index.ts

@@ -0,0 +1,50 @@
+import { Instance } from "../project/instance"
+import { Log } from "../util/log"
+
+export namespace Scheduler {
+  const log = Log.create({ service: "scheduler" })
+
+  export type Task = {
+    id: string
+    interval: number
+    run: () => Promise<void>
+  }
+
+  type Timer = ReturnType<typeof setInterval>
+
+  const state = Instance.state(
+    () => {
+      const tasks = new Map<string, Task>()
+      const timers = new Map<string, Timer>()
+      return { tasks, timers }
+    },
+    async (entry) => {
+      for (const timer of entry.timers.values()) {
+        clearInterval(timer)
+      }
+      entry.tasks.clear()
+      entry.timers.clear()
+    },
+  )
+
+  export function register(task: Task) {
+    const entry = state()
+    const current = entry.timers.get(task.id)
+    if (current) clearInterval(current)
+
+    entry.tasks.set(task.id, task)
+    void run(task)
+    const timer = setInterval(() => {
+      void run(task)
+    }, task.interval)
+    timer.unref()
+    entry.timers.set(task.id, timer)
+  }
+
+  async function run(task: Task) {
+    log.info("run", { id: task.id })
+    await task.run().catch((error) => {
+      log.error("run failed", { id: task.id, error })
+    })
+  }
+}

+ 36 - 0
packages/opencode/src/snapshot/index.ts

@@ -6,9 +6,45 @@ import { Global } from "../global"
 import z from "zod"
 import { Config } from "../config/config"
 import { Instance } from "../project/instance"
+import { Scheduler } from "../scheduler"
 
 export namespace Snapshot {
   const log = Log.create({ service: "snapshot" })
+  const hour = 60 * 60 * 1000
+  const prune = "7.days"
+
+  export function init() {
+    Scheduler.register({
+      id: "snapshot.cleanup",
+      interval: hour,
+      run: cleanup,
+    })
+  }
+
+  export async function cleanup() {
+    if (Instance.project.vcs !== "git") return
+    const cfg = await Config.get()
+    if (cfg.snapshot === false) return
+    const git = gitdir()
+    const exists = await fs
+      .stat(git)
+      .then(() => true)
+      .catch(() => false)
+    if (!exists) return
+    const result = await $`git --git-dir ${git} --work-tree ${Instance.worktree} gc --prune=${prune}`
+      .quiet()
+      .cwd(Instance.directory)
+      .nothrow()
+    if (result.exitCode !== 0) {
+      log.warn("cleanup failed", {
+        exitCode: result.exitCode,
+        stderr: result.stderr.toString(),
+        stdout: result.stdout.toString(),
+      })
+      return
+    }
+    log.info("cleanup", { prune })
+  }
 
   export async function track() {
     if (Instance.project.vcs !== "git") return

+ 10 - 4
packages/opencode/src/tool/truncation.ts

@@ -2,9 +2,9 @@ import fs from "fs/promises"
 import path from "path"
 import { Global } from "../global"
 import { Identifier } from "../id/id"
-import { lazy } from "../util/lazy"
 import { PermissionNext } from "../permission/next"
 import type { Agent } from "../agent/agent"
+import { Scheduler } from "../scheduler"
 
 export namespace Truncate {
   export const MAX_LINES = 2000
@@ -12,6 +12,7 @@ export namespace Truncate {
   export const DIR = path.join(Global.Path.data, "tool-output")
   export const GLOB = path.join(DIR, "*")
   const RETENTION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
+  const HOUR_MS = 60 * 60 * 1000
 
   export type Result = { content: string; truncated: false } | { content: string; truncated: true; outputPath: string }
 
@@ -21,6 +22,14 @@ export namespace Truncate {
     direction?: "head" | "tail"
   }
 
+  export function init() {
+    Scheduler.register({
+      id: "tool.truncation.cleanup",
+      interval: HOUR_MS,
+      run: cleanup,
+    })
+  }
+
   export async function cleanup() {
     const cutoff = Identifier.timestamp(Identifier.create("tool", false, Date.now() - RETENTION_MS))
     const glob = new Bun.Glob("tool_*")
@@ -31,8 +40,6 @@ export namespace Truncate {
     }
   }
 
-  const init = lazy(cleanup)
-
   function hasTaskTool(agent?: Agent.Info): boolean {
     if (!agent?.permission) return false
     const rule = PermissionNext.evaluate("task", "*", agent.permission)
@@ -81,7 +88,6 @@ export namespace Truncate {
     const unit = hitBytes ? "bytes" : "lines"
     const preview = out.join("\n")
 
-    await init()
     const id = Identifier.ascending("tool")
     const filepath = path.join(DIR, id)
     await Bun.write(Bun.file(filepath), text)