core/tools: edit_lineno unread error names the read window

The boundary overguard (read offset=50 shows lines 51+, then editing
line 50 fails) forced blind re-reads. The error now reports the read
window ("this turn read lines 51-86") and advises reading with a margin
around the target range; read_file's description says the same.
This commit is contained in:
2026-08-15 17:38:58 +08:00
parent c9815e32bb
commit 38671482f2
3 changed files with 23 additions and 2 deletions
+9 -2
View File
@@ -111,13 +111,20 @@ async def edit_lineno(path: str, start_line: int, end_line: int, new_content: st
if err is not None:
return err
unread = _unread_lines_in_range(lineno_read_lines(str(target)), start_line, end_line, len(lines))
read_lines = lineno_read_lines(str(target))
unread = _unread_lines_in_range(read_lines, start_line, end_line, len(lines))
if unread:
preview = unread[:_UNREAD_PREVIEW]
head = ", ".join(str(i) for i in preview)
if len(unread) > _UNREAD_PREVIEW:
head += f", … ({len(unread)} total)"
return f"error: lines {head} not read; call read_file(path, with_lineno=true) first to see current line numbers"
window = ""
if read_lines:
window = f" (this turn read lines {min(read_lines)}-{max(read_lines)})"
return (
f"error: lines {head} not read{window}; call read_file(path, with_lineno=true) "
"first — read with a small margin around the lines you plan to edit"
)
recorded_mtime = lineno_read_mtime(str(target))
if recorded_mtime is not None:
+2
View File
@@ -64,6 +64,8 @@ async def read_file(
(None = rest of file). When ``with_lineno`` is true, each line is prefixed
with its 1-based file line number (`` N|…``), matching ``edit_lineno``
numbering, and those lines are marked readable for ``edit_lineno`` this turn.
Only the displayed lines become editable, so read with a small margin around
any range you plan to edit with ``edit_lineno``.
``max_chars`` caps the returned slice (default: the agent's tool-result cap,
``DEFAULT_TOOL_RESULT_MAX_CHARS``); a ``truncate_token`` is appended when cut
+12
View File
@@ -38,6 +38,18 @@ def test_edit_lineno_read_truncated_marks_only_displayed_lines(workspace: object
assert "not read" in out and "90" in out
def test_edit_lineno_error_shows_read_window(workspace: object) -> None:
"""The unread error names the read window so the model can re-read precisely."""
del workspace
_ = call_sync(write_file, "j.txt", "a\nb\nc\nd\n")
_ = reset_lineno_tracker()
_ = call_sync(read_file, "j.txt", offset=1, limit=2, with_lineno=True) # lines 2-3
out = call_sync(edit_lineno, "j.txt", 1, 1, "X\n")
assert "not read" in out
assert "read lines 2-3" in out
assert "margin" in out
def test_edit_lineno_requires_all_lines_read(workspace: object) -> None:
del workspace
_ = call_sync(write_file, "r.txt", "a\nb\nc\nd\ne\n")