improve: text_editor add mtime checks and prompts for patch read validation
linuztx committed
Feb 28, 2026 at 01:55 UTC
a5a1e02bad341b6f2f3118844579245dd76be082
3 files changed
+43
-1
plugins/text_editor/prompts/fw.text_editor.patch_need_read.md
new
+1
@@ -0,0 +1 @@
1
+error patching {{path}}: read file before patching - line numbers unknown
plugins/text_editor/prompts/fw.text_editor.patch_stale_read.md
new
+1
@@ -0,0 +1 @@
1
+error patching {{path}}: file changed since last read - re-read to get current line numbers
plugins/text_editor/tools/text_editor.py
+41
-1
@@ -10,6 +10,10 @@ from plugins.text_editor.helpers.file_ops import (
10
apply_patch,
11
)
12
13
+# Key used in agent.data to store file mtimes
14
+_MTIME_KEY = "_text_editor_mtimes"
15
+
16
+
17
18
class TextEditor(Tool):
19
@@ -49,6 +53,8 @@ class TextEditor(Tool):
53
if result.error:
54
return self._error("read", path, result.error)
55
56
+ _record_mtime(self.agent, os.path.expanduser(path))
57
+
58
# Extension point
59
ext_data = {"content": result.content, "warnings": result.warnings}
60
await call_extensions(
@@ -89,6 +95,8 @@ class TextEditor(Tool):
95
)
96
97
expanded = os.path.expanduser(path)
98
+ _record_mtime(self.agent, expanded)
99
+
100
cfg = _get_config(self.agent)
101
read_result = read_file(
102
expanded,
@@ -117,6 +125,11 @@ class TextEditor(Tool):
125
if not os.path.isfile(expanded):
126
return self._error("patch", path, "file not found")
127
128
+ stale_err = _check_mtime(self.agent, expanded)
129
+ if stale_err:
130
+ return self._error("patch", path, stale_err)
131
+
132
+
133
parsed, err = validate_edits(edits)
134
if err:
135
return self._error("patch", path, err)
@@ -138,6 +151,8 @@ class TextEditor(Tool):
151
data={"path": expanded, "total_lines": total_lines},
152
)
153
154
+ _record_mtime(self.agent, expanded)
155
+
156
patch_content = _read_patch_region(
157
expanded, ext_data["edits"], total_lines, _get_config(self.agent)
158
)
@@ -168,7 +183,6 @@ class TextEditor(Tool):
183
def _read_patch_region(
184
path: str, edits: list[dict], total_lines: int, cfg: dict
185
) -> str:
171
- """Read back the affected region after a patch so the LLM sees the result."""
186
if not edits:
187
return ""
188
@@ -194,6 +208,32 @@ def _read_patch_region(
208
)
209
return result.content
210
211
+
212
+def _record_mtime(agent, path: str):
213
+ mtimes = agent.data.setdefault(_MTIME_KEY, {})
214
+ try:
215
+ mtimes[os.path.realpath(path)] = os.path.getmtime(path)
216
+ except OSError:
217
+ pass
218
+
219
+
220
+def _check_mtime(agent, path: str) -> str:
221
+ mtimes = agent.data.get(_MTIME_KEY, {})
222
+ real = os.path.realpath(path)
223
+ if real not in mtimes:
224
+ return agent.read_prompt(
225
+ "fw.text_editor.patch_need_read.md", path=path
226
+ )
227
+ try:
228
+ current = os.path.getmtime(path)
229
+ except OSError:
230
+ return ""
231
+ if current != mtimes[real]:
232
+ return agent.read_prompt(
233
+ "fw.text_editor.patch_stale_read.md", path=path
234
+ )
235
+ return ""
236
+
237
# ------------------------------------------------------------------
238
# Config
239
# ------------------------------------------------------------------