69 lines · 2.2 KB · TypeScript
RawDownload
import { Instance } from "../project/instance"import { Log } from "../util/log"import { Flag } from "../flag/flag" export namespace FileTime {  const log = Log.create({ service: "file.time" })  // Per-session read times plus per-file write locks.  // All tools that overwrite existing files should run their  // assert/read/write/update sequence inside withLock(filepath, ...)  // so concurrent writes to the same file are serialized.  export const state = Instance.state(() => {    const read: {      [sessionID: string]: {        [path: string]: Date | undefined      }    } = {}    const locks = new Map<string, Promise<void>>()    return {      read,      locks,    }  })   export function read(sessionID: string, file: string) {    log.info("read", { sessionID, file })    const { read } = state()    read[sessionID] = read[sessionID] || {}    read[sessionID][file] = new Date()  }   export function get(sessionID: string, file: string) {    return state().read[sessionID]?.[file]  }   export async function withLock<T>(filepath: string, fn: () => Promise<T>): Promise<T> {    const current = state()    const currentLock = current.locks.get(filepath) ?? Promise.resolve()    let release: () => void = () => {}    const nextLock = new Promise<void>((resolve) => {      release = resolve    })    const chained = currentLock.then(() => nextLock)    current.locks.set(filepath, chained)    await currentLock    try {      return await fn()    } finally {      release()      if (current.locks.get(filepath) === chained) {        current.locks.delete(filepath)      }    }  }   export async function assert(sessionID: string, filepath: string) {    if (Flag.OPENSCIENCE_DISABLE_FILETIME_CHECK === true) {      return    }     const time = get(sessionID, filepath)    if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`)    const stats = await Bun.file(filepath).stat()    if (stats.mtime.getTime() > time.getTime()) {      throw new Error(        `File ${filepath} has been modified since it was last read.\nLast modification: ${stats.mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`,      )    }  }}