feat(app-instance): 集成Beaver后端并更新配置管理

集成新的Beaver后端服务到应用实例中,替换原有的nanobot实现。

主要变更包括:
- 在Dockerfile和环境配置中添加Beaver相关路径和配置变量
- 更新工作目录结构从.nanobot到.beaver
- 实现Beaver引擎加载器,支持配置文件加载和工具组装
- 添加内置工具如ListDirectoryTool、ReadFileTool、SearchFilesTool
- 更新消息处理流程,支持通道适配器和网关模式
- 重构技能系统,支持显式工具提示和嵌入式检索
- 改进错误处理和生命周期管理

此变更使应用实例能够使用统一的Beaver后端进行AI代理运行时管理。
This commit is contained in:
2026-04-27 17:37:40 +08:00
parent 36882a7d7b
commit 5ba5c7e4c1
47 changed files with 2821 additions and 462 deletions

View File

@ -0,0 +1,129 @@
from __future__ import annotations
import asyncio
import json
import os
from pathlib import Path
from beaver.tools import ObjectBackedTool, ToolContext
from beaver.tools.builtins import ListDirectoryTool, ReadFileTool, SearchFilesTool
def _run_tool(tool, arguments: dict, workspace: Path):
return asyncio.run(
ObjectBackedTool(tool).invoke(arguments, ToolContext(workspace=str(workspace)))
)
def _payload(result):
return json.loads(result.content)
def test_list_directory_is_workspace_scoped(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "README.md").write_text("# Hello\n", encoding="utf-8")
(workspace / "src").mkdir()
result = _run_tool(ListDirectoryTool(), {"path": "."}, workspace)
payload = _payload(result)
assert result.success is True
assert payload["success"] is True
assert [entry["path"] for entry in payload["entries"]] == ["src", "README.md"]
def test_read_file_returns_limited_text(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "notes.txt").write_text("one\ntwo\nthree\n", encoding="utf-8")
result = _run_tool(ReadFileTool(), {"path": "notes.txt", "start_line": 2, "max_lines": 1}, workspace)
payload = _payload(result)
assert result.success is True
assert payload["success"] is True
assert payload["content"] == "two"
assert payload["start_line"] == 2
assert payload["end_line"] == 2
assert payload["truncated"] is True
def test_search_files_finds_paths_and_content(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "Dockerfile").write_text("FROM python:3.12\n", encoding="utf-8")
(workspace / "src").mkdir()
(workspace / "src" / "app.py").write_text("print('docker log')\n", encoding="utf-8")
result = _run_tool(SearchFilesTool(), {"query": "docker", "max_results": 10}, workspace)
payload = _payload(result)
assert result.success is True
assert payload["success"] is True
assert ("Dockerfile", "path") in {
(item["path"], item["match_type"]) for item in payload["results"]
}
assert ("src/app.py", "content") in {
(item["path"], item["match_type"]) for item in payload["results"]
}
def test_read_file_rejects_relative_path_escape(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(tmp_path / "secret.txt").write_text("secret\n", encoding="utf-8")
result = _run_tool(ReadFileTool(), {"path": "../secret.txt"}, workspace)
payload = _payload(result)
assert result.success is False
assert payload["success"] is False
assert "escapes workspace" in payload["error"]
def test_read_file_rejects_absolute_path_escape(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = tmp_path / "outside.txt"
outside.write_text("secret\n", encoding="utf-8")
result = _run_tool(ReadFileTool(), {"path": str(outside)}, workspace)
payload = _payload(result)
assert result.success is False
assert payload["success"] is False
assert "escapes workspace" in payload["error"]
def test_read_file_rejects_symlink_escape(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = tmp_path / "outside.txt"
outside.write_text("secret\n", encoding="utf-8")
link = workspace / "outside-link.txt"
try:
os.symlink(outside, link)
except (OSError, NotImplementedError):
return
result = _run_tool(ReadFileTool(), {"path": "outside-link.txt"}, workspace)
payload = _payload(result)
assert result.success is False
assert payload["success"] is False
assert "escapes workspace" in payload["error"]
def test_read_file_rejects_binary_files(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "blob.bin").write_bytes(b"abc\x00def")
result = _run_tool(ReadFileTool(), {"path": "blob.bin"}, workspace)
payload = _payload(result)
assert result.success is False
assert payload["success"] is False
assert "binary" in payload["error"]