core/agent+tools: apply a batch's same-file writes in call order

This commit is contained in:
2026-10-16 19:37:29 +08:00
parent ab6b5d590c
commit 85b0318b77
7 changed files with 208 additions and 17 deletions
+38
View File
@@ -1,11 +1,18 @@
from __future__ import annotations
import asyncio
import importlib
from pathlib import Path
from typing import TYPE_CHECKING
from plyngent.agent import ToolRegistry
from plyngent.tools.danger import classify_danger
from plyngent.tools.file import edit_replace, listdir, read_file, write_file
from tests.test_tools.helpers import call_sync
if TYPE_CHECKING:
from collections.abc import Mapping
get_truncated_module = importlib.import_module("plyngent.tools.file.get_truncated")
@@ -37,6 +44,37 @@ def test_write_file_refuses_existing_file_without_overwrite(workspace: Path) ->
assert (workspace / "keep.txt").read_text(encoding="utf-8") == "second"
async def test_same_file_writes_in_one_batch_keep_call_order(workspace: Path) -> None:
"""The second call writes onto the first one's text, not onto the stale text."""
target = workspace / "notes.txt"
_ = target.write_text("one", encoding="utf-8")
confirmed = asyncio.Event()
async def on_confirm(name: str, args: Mapping[str, object], reason: str) -> bool:
del name, args, reason
await confirmed.wait()
return True
registry = ToolRegistry([write_file, edit_replace], danger=classify_danger, on_confirm=on_confirm)
rewrite = asyncio.create_task(
registry.execute("write_file", '{"path": "notes.txt", "content": "two", "overwrite": true}')
)
await asyncio.sleep(0) # the rewrite is waiting on its overwrite confirm
edit = asyncio.create_task(
registry.execute(
"edit_replace",
'{"path": "notes.txt", "old_string": "two", "new_string": "three"}',
)
)
await asyncio.sleep(0) # the edit waits for the file instead of editing stale text
assert target.read_text(encoding="utf-8") == "one"
confirmed.set()
rewrite_out, edit_out = await asyncio.gather(rewrite, edit)
assert "wrote" in rewrite_out
assert "replaced" in edit_out
assert target.read_text(encoding="utf-8") == "three"
def test_read_file_max_chars_embeds_token(workspace: Path) -> None:
(workspace / "big.txt").write_text("a" * 1000, encoding="utf-8")
out = call_sync(read_file, "big.txt", max_chars=200)