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:
tzt
2026-09-01 10:16:58 +08:00
parent 10bd4cc71d
commit 8358302002
6 changed files with 440 additions and 24 deletions
+16 -8
View File
@@ -32,10 +32,13 @@ STATE_DONE = "done"
STATE_FAILED = "failed"
AGENT_SYSTEM_PROMPT = (
"你是端云协同 LLM 系统中的智能体(Agent)。你拥有工作区文件工具:"
"list_dir(列目录)、read_file(读文件)、write_file(写文件)。"
"像程序员助手一样工作:先列目录/读文件了解现状,需要时再写文件;"
"任务完成或给出结论后,直接输出给用户的最终答复(中文,不要再调用工具)。"
"你是端云协同 LLM 系统中的智能体(Agent),正在操作用户选择的**真实项目工作目录**。"
"你拥有的工具:list_dir(列目录)、read_file(读文件)、write_file(写文件/新建)、"
"edit_file(精确替换编辑:old_string 须唯一匹配)、search_files(跨文件搜索内容)、"
"run_command(执行 shell 命令,仅当系统开启 allow_shell 时可用,否则不要尝试)。"
"像编程助手一样工作:先列目录/搜索了解项目结构,读文件核对原文后再用 edit_file 小步修改"
"(或 write_file 新建),需要时运行命令验证。任务完成或给出结论后,"
"直接输出给用户的最终答复(中文,不要再调用工具)。"
)
@@ -125,6 +128,7 @@ class AgentRunInfo:
prompt_tokens: int = 0
completion_tokens: int = 0
pool_id: str = ""
workspace: str = "" # 本次运行使用的工作区根目录(绝对路径)
asyncio_task: Optional[asyncio.Task] = field(default=None, repr=False)
def to_dict(self) -> Dict[str, Any]:
@@ -141,6 +145,7 @@ class AgentRunInfo:
"prompt_tokens": self.prompt_tokens,
"completion_tokens": self.completion_tokens,
"pool_id": self.pool_id,
"workspace": self.workspace,
}
@@ -163,12 +168,13 @@ class AgentService:
return self._dir(request_id) / "status.json"
# ---------- 注册与查询 ----------
def register(self, request_id: str, task: str, model: str, pool_id: str) -> Optional[AgentRunInfo]:
def register(self, request_id: str, task: str, model: str, pool_id: str,
workspace: str = "") -> Optional[AgentRunInfo]:
running = [r for r in self._runs.values() if r.state == STATE_RUNNING]
if len(running) >= self.max_running:
return None
info = AgentRunInfo(request_id=request_id, task=task, model=model,
pool_id=pool_id, started_at=time.time())
pool_id=pool_id, workspace=workspace, started_at=time.time())
self._runs[request_id] = info
self._dir(request_id).mkdir(parents=True, exist_ok=True)
self._write_status(info)
@@ -179,9 +185,11 @@ class AgentService:
# ---------- 执行 ----------
async def run(self, info: AgentRunInfo, chat: Any, workspace_dir: str | Path,
max_rounds: int = 8, token_cap: int = 0) -> None:
max_rounds: int = 8, token_cap: int = 0,
allow_shell: bool = False, shell_timeout_s: int = 20) -> None:
"""执行智能体任务(由调用方包成后台协程)。"""
tools = WorkspaceTools(workspace_dir)
tools = WorkspaceTools(workspace_dir, allow_shell=allow_shell,
shell_timeout_s=shell_timeout_s)
loop = ToolLoop(tools, chat, max_rounds=max_rounds, token_cap=token_cap,
on_event=self._make_event_writer(info))
try: