@@ -3,14 +3,20 @@ import { Log } from "../util/log"
33
44export namespace FileTime {
55 const log = Log . create ( { service : "file.time" } )
6+ // Per-session read times plus per-file write locks.
7+ // All tools that overwrite existing files should run their
8+ // assert/read/write/update sequence inside withLock(filepath, ...)
9+ // so concurrent writes to the same file are serialized.
610 export const state = Instance . state ( ( ) => {
711 const read : {
812 [ sessionID : string ] : {
913 [ path : string ] : Date | undefined
1014 }
1115 } = { }
16+ const locks = new Map < string , Promise < void > > ( )
1217 return {
1318 read,
19+ locks,
1420 }
1521 } )
1622
@@ -25,6 +31,29 @@ export namespace FileTime {
2531 return state ( ) . read [ sessionID ] ?. [ file ]
2632 }
2733
34+ export async function withLock < T > ( filepath : string , fn : ( ) => Promise < T > ) : Promise < T > {
35+ const current = state ( )
36+ const currentLock = current . locks . get ( filepath ) ?? Promise . resolve ( )
37+ let release : ( ) => void = ( ) => { }
38+ const nextLock = new Promise < void > ( ( resolve ) => {
39+ release = resolve
40+ } )
41+ current . locks . set (
42+ filepath ,
43+ currentLock . then ( ( ) => nextLock ) ,
44+ )
45+ await currentLock
46+ try {
47+ const result = await fn ( )
48+ return result
49+ } finally {
50+ release ( )
51+ if ( current . locks . get ( filepath ) === nextLock ) {
52+ current . locks . delete ( filepath )
53+ }
54+ }
55+ }
56+
2857 export async function assert ( sessionID : string , filepath : string ) {
2958 const time = get ( sessionID , filepath )
3059 if ( ! time ) throw new Error ( `You must read the file ${ filepath } before overwriting it. Use the Read tool first` )
0 commit comments