feat(v3): T22-T23 harness 级工具扩展 + 工作区选择后端
- T22 工具内核:edit_file(old_string 唯一命中才替换,防误改)、search_files (跨文件内容搜索,跳过 .git/node_modules 与二进制大文件)、run_command (allow_shell 默认关;超时+输出截断+Windows CREATE_NO_WINDOW) - T23 工作区选择(参考 deepseek-harness 打开文件夹体验):/agent/fs 磁盘目录浏览 (空 path 列 Windows 盘符)、/agent/workspaces 最近列表持久化、 POST /agent 接受 workspace(须存在目录),运行状态记录所用工作区 - 新增测试 12 项,全量 274 passed
This commit is contained in:
@@ -174,3 +174,66 @@ def test_toolloop_chat_error(ws):
|
||||
result = asyncio_run(loop.run("任务"))
|
||||
assert result["reason"] == "error"
|
||||
assert "RuntimeError" in result["error"]
|
||||
|
||||
|
||||
# ---------------- 扩展工具(T22):edit_file / search_files / run_command ----------------
|
||||
|
||||
def test_edit_file_unique_replace(ws):
|
||||
ws.write_file("app.py", "def main():\n print('v1')\n return 0\n")
|
||||
r = ws.execute("edit_file", {"path": "app.py", "old_string": "print('v1')",
|
||||
"new_string": "print('v2 — 已修复')"})
|
||||
assert r["ok"] is True and r["replaced"] == 1
|
||||
assert "print('v2 — 已修复')" in ws.read_file("app.py")["content"]
|
||||
|
||||
|
||||
def test_edit_file_rejects_ambiguous_and_missing(ws):
|
||||
ws.write_file("dup.txt", "abc-abc")
|
||||
r1 = ws.execute("edit_file", {"path": "dup.txt", "old_string": "abc", "new_string": "x"})
|
||||
assert r1["ok"] is False and "2 次" in r1["error"]
|
||||
r2 = ws.execute("edit_file", {"path": "dup.txt", "old_string": "zzz", "new_string": "x"})
|
||||
assert r2["ok"] is False and "未在文件中找到" in r2["error"]
|
||||
assert ws.read_file("dup.txt")["content"] == "abc-abc" # 原文未被破坏
|
||||
|
||||
|
||||
def test_search_files(ws):
|
||||
ws.write_file("a.py", "DEFAULT_PORT = 8000\n")
|
||||
ws.write_file("docs/note.md", "端口 8000 是默认值\n")
|
||||
ws.write_file("node_modules/pkg/index.js", "port 8000\n") # 应被跳过
|
||||
r = ws.execute("search_files", {"query": "8000"})
|
||||
assert r["ok"] is True
|
||||
files = {m["file"] for m in r["matches"]}
|
||||
assert files == {"a.py", "docs/note.md"}
|
||||
assert all("node_modules" not in f for f in files)
|
||||
# 空查询
|
||||
assert ws.execute("search_files", {"query": ""})["ok"] is False
|
||||
|
||||
|
||||
def test_run_command_disabled_by_default(ws):
|
||||
r = ws.execute("run_command", {"command": "echo hi"})
|
||||
assert r["ok"] is False and "allow_shell" in r["error"]
|
||||
|
||||
|
||||
def test_run_command_enabled(tmp_path):
|
||||
import sys
|
||||
ws2 = WorkspaceTools(tmp_path / "ws2", allow_shell=True, shell_timeout_s=15)
|
||||
r = ws2.execute("run_command", {"command": f'"{sys.executable}" -c "print(40+2)"'})
|
||||
assert r["ok"] is True and r["exit_code"] == 0
|
||||
assert "42" in r["output"]
|
||||
|
||||
|
||||
def test_run_command_timeout(tmp_path):
|
||||
import sys
|
||||
ws2 = WorkspaceTools(tmp_path / "ws3", allow_shell=True, shell_timeout_s=2)
|
||||
r = ws2.execute("run_command",
|
||||
{"command": f'"{sys.executable}" -c "import time; time.sleep(30)"'})
|
||||
assert r["ok"] is False and "超时" in r["error"]
|
||||
|
||||
|
||||
def test_browse_directories(tmp_path):
|
||||
from router_system.tools import browse_directories
|
||||
(tmp_path / "sub").mkdir()
|
||||
(tmp_path / "file.txt").write_text("x", encoding="utf-8")
|
||||
r = browse_directories(str(tmp_path))
|
||||
assert r["ok"] is True and r["dirs"] == ["sub"] # 只列目录不列文件
|
||||
assert r["parent"] # 可以上级
|
||||
assert browse_directories(str(tmp_path / "ghost"))["ok"] is False
|
||||
|
||||
Reference in New Issue
Block a user