@@ -56,10 +56,17 @@ type editContext struct {
5656 ctx context.Context
5757 permissions permission.Service
5858 files history.Service
59+ filetracker filetracker.Service
5960 workingDir string
6061}
6162
62−func NewEditTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, files history.Service, workingDir string) fantasy.AgentTool {
63+func NewEditTool(
64+ lspClients *csync.Map[string, *lsp.Client],
65+ permissions permission.Service,
66+ files history.Service,
67+ filetracker filetracker.Service,
68+ workingDir string,
69+) fantasy.AgentTool {
6370 return fantasy.NewAgentTool(
6471 EditToolName,
6572 string(editDescription),
@@ -73,7 +80,7 @@ func NewEditTool(lspClients *csync.Map[string, *lsp.Client], permissions permiss
7380 var response fantasy.ToolResponse
7481 var err error
7582
76− editCtx := editContext{ctx, permissions, files, workingDir}
83+ editCtx := editContext{ctx, permissions, files, filetracker, workingDir}
7784
7885 if params.OldString == "" {
7986 response, err = createNewFile(editCtx, params.FilePath, params.NewString, call)
@@ -168,8 +175,7 @@ func createNewFile(edit editContext, filePath, content string, call fantasy.Tool
168175 slog.Error("Error creating file history version", "error", err)
169176 }
170177
171− filetracker.RecordWrite(filePath)
172− filetracker.RecordRead(filePath)
178+ edit.filetracker.RecordRead(edit.ctx, sessionID, filePath)
173179
174180 return fantasy.WithResponseMetadata(
175181 fantasy.NewTextResponse("File created: "+filePath),
@@ -195,12 +201,17 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
195201 return fantasy.NewTextErrorResponse(fmt.Sprintf("path is a directory, not a file: %s", filePath)), nil
196202 }
197203
198− if filetracker.LastReadTime(filePath).IsZero() {
204+ sessionID := GetSessionFromContext(edit.ctx)
205+ if sessionID == "" {
206+ return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for deleting content")
207+ }
208+
209+ lastRead := edit.filetracker.LastReadTime(edit.ctx, sessionID, filePath)
210+ if lastRead.IsZero() {
199211 return fantasy.NewTextErrorResponse("you must read the file before editing it. Use the View tool first"), nil
200212 }
201213
202− modTime := fileInfo.ModTime()
203− lastRead := filetracker.LastReadTime(filePath)
214+ modTime := fileInfo.ModTime().Truncate(time.Second)
204215 if modTime.After(lastRead) {
205216 return fantasy.NewTextErrorResponse(
206217 fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
@@ -236,12 +247,6 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
236247 newContent = oldContent[:index] + oldContent[index+len(oldString):]
237248 }
238249
239− sessionID := GetSessionFromContext(edit.ctx)
240−
241− if sessionID == "" {
242− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for deleting content")
243− }
244−
245250 _, additions, removals := diff.GenerateDiff(
246251 oldContent,
247252 newContent,
@@ -301,8 +306,7 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
301306 slog.Error("Error creating file history version", "error", err)
302307 }
303308
304− filetracker.RecordWrite(filePath)
305− filetracker.RecordRead(filePath)
309+ edit.filetracker.RecordRead(edit.ctx, sessionID, filePath)
306310
307311 return fantasy.WithResponseMetadata(
308312 fantasy.NewTextResponse("Content deleted from file: "+filePath),
@@ -328,12 +332,17 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
328332 return fantasy.NewTextErrorResponse(fmt.Sprintf("path is a directory, not a file: %s", filePath)), nil
329333 }
330334
331− if filetracker.LastReadTime(filePath).IsZero() {
335+ sessionID := GetSessionFromContext(edit.ctx)
336+ if sessionID == "" {
337+ return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for edit a file")
338+ }
339+
340+ lastRead := edit.filetracker.LastReadTime(edit.ctx, sessionID, filePath)
341+ if lastRead.IsZero() {
332342 return fantasy.NewTextErrorResponse("you must read the file before editing it. Use the View tool first"), nil
333343 }
334344
335− modTime := fileInfo.ModTime()
336− lastRead := filetracker.LastReadTime(filePath)
345+ modTime := fileInfo.ModTime().Truncate(time.Second)
337346 if modTime.After(lastRead) {
338347 return fantasy.NewTextErrorResponse(
339348 fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
@@ -369,11 +378,6 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
369378 if oldContent == newContent {
370379 return fantasy.NewTextErrorResponse("new content is the same as old content. No changes made."), nil
371380 }
372− sessionID := GetSessionFromContext(edit.ctx)
373−
374− if sessionID == "" {
375− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
376− }
377381 _, additions, removals := diff.GenerateDiff(
378382 oldContent,
379383 newContent,
@@ -433,8 +437,7 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
433437 slog.Error("Error creating file history version", "error", err)
434438 }
435439
436− filetracker.RecordWrite(filePath)
437− filetracker.RecordRead(filePath)
440+ edit.filetracker.RecordRead(edit.ctx, sessionID, filePath)
438441
439442 return fantasy.WithResponseMetadata(
440443 fantasy.NewTextResponse("Content replaced in file: "+filePath),