package builtin import "sync" // DefaultFileStateCache is a thread-safe in-memory implementation of // both FileStateCacheRecorder (write side) and FileStateCacheReader // (read side). Used to enforce the Read-before-Edit hard contract: // FileReadTool records each successfully read path here, and // FileEditTool / FileWriteTool look up the same path before applying // changes. // // Mirror of Claude Code's toolUseContext.readFileState // (createFileStateCacheWithSizeLimit) in screens/REPL.tsx, but kept // schema-simpler: Flyto's FileStateCacheEntry stores // {ContentHash, Size, LineCount, ModTime, IsPartialView} instead of // the raw {timestamp, content, offset, limit, isPartialView} pair -- // the hash is enough for the content-equality fallback (see // FileEditTool.validate where mtime > recorded but ContentHash matches // is allowed through, identical to FileEditTool.ts:294-301). // // Bounded growth: this implementation keeps every recorded path until // Reset() is called or the process exits. Long sessions that touch // thousands of files should swap in an LRU implementation; the // interface contract does not assume eviction policy. Claude Code // uses a 100-entry LRU (READ_FILE_STATE_CACHE_SIZE) -- we accept the // unbounded form for v1 because typical agent runs touch tens of // files, not thousands; LRU is a deliberate v2 follow-up driven by // real long-session profiles. // // Concurrency: a single instance is safe to share across goroutines // (RWMutex). FileReadTool and FileEditTool dispatched in parallel // will not corrupt the map. // // DefaultFileStateCache 是 FileStateCacheRecorder (写侧) + Reader // (读侧) 的内存线程安全实现. 用于强制 Read-before-Edit hard contract: // FileReadTool 把每次成功读的路径写入, FileEditTool / FileWriteTool // 改文件前查同一路径. // // 对位 Claude Code screens/REPL.tsx 的 toolUseContext.readFileState // (createFileStateCacheWithSizeLimit) 但 schema 更简洁: Flyto 的 // FileStateCacheEntry 存 {ContentHash, Size, LineCount, ModTime, // IsPartialView} 而非原 {timestamp, content, offset, limit, // isPartialView}. 哈希足够做 content-equality fallback (见 FileEditTool // .validate: mtime 漂但 ContentHash 一致仍放行, 与 FileEditTool.ts: // 294-301 一致). // // 增长有界: 此实现保留每条记录直到 Reset() 或进程退出. 长会话扫上千 // 文件时建议替换为 LRU 实现; 接口契约不假设淘汰策略. Claude Code 用 // 100-entry LRU (READ_FILE_STATE_CACHE_SIZE) -- v1 接受无界因为典型 // agent 跑触十几个文件不上千, LRU 留作 v2 follow-up 由真实长会话 // profile 驱动. // // 并发: 单实例可跨 goroutine 共享 (RWMutex). FileReadTool 和 // FileEditTool 并发执行不会损坏 map. type DefaultFileStateCache struct { mu sync.RWMutex entries map[string]FileStateCacheEntry } // NewDefaultFileStateCache creates an empty cache ready to be passed // to NewFileReadToolFull / NewFileEditTool. Same instance MUST be // shared by both tools so writes from FileReadTool are visible to // FileEditTool's Read-before-Edit check. // // NewDefaultFileStateCache 创建空 cache, 可传给 NewFileReadToolFull / // NewFileEditTool. 同一实例必须给两 tool 共享, FileReadTool 的写 // 才能被 FileEditTool 的 Read-before-Edit check 看到. func NewDefaultFileStateCache() *DefaultFileStateCache { return &DefaultFileStateCache{ entries: make(map[string]FileStateCacheEntry), } } // RecordState writes / overwrites the entry for path. Idempotent; // repeated reads of the same path overwrite with fresh metadata. // // RecordState 写入 / 覆盖 path 的 entry. 幂等; 同一路径多次读会 // 用新元数据覆盖. func (c *DefaultFileStateCache) RecordState(path string, entry FileStateCacheEntry) { c.mu.Lock() c.entries[path] = entry c.mu.Unlock() } // GetState returns the entry for path and a found flag. (zero, // false) when no Read has been recorded for path. // // GetState 返回 path 的 entry 和 found 标志. 路径无 Read 记录时 // 返回 (zero, false). func (c *DefaultFileStateCache) GetState(path string) (FileStateCacheEntry, bool) { c.mu.RLock() defer c.mu.RUnlock() entry, ok := c.entries[path] return entry, ok } // Reset clears all recorded entries. Used by conversation-clear // flows (analog to Claude Code commands/clear/conversation.ts:130 // readFileState.clear()) and by tests for isolation. // // Reset 清空全部 entries. 用于 conversation-clear 流程 (对位 // Claude Code commands/clear/conversation.ts:130 readFileState.clear()) // 以及测试隔离. func (c *DefaultFileStateCache) Reset() { c.mu.Lock() c.entries = make(map[string]FileStateCacheEntry) c.mu.Unlock() } // Compile-time interface assertions. // // 编译期接口断言. var ( _ FileStateCacheRecorder = (*DefaultFileStateCache)(nil) _ FileStateCacheReader = (*DefaultFileStateCache)(nil) )