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:
+84
-15
@@ -501,11 +501,13 @@ try:
|
||||
|
||||
@app.post("/agent", tags=["agent"])
|
||||
async def agent_run(req: dict):
|
||||
"""提交智能体任务:{"task": "...", "pool_id": "可选模型条目"}。
|
||||
"""提交智能体任务:{"task", "pool_id"?, "workspace"?}。
|
||||
|
||||
workspace 为用户选择的工作目录(绝对路径);缺省用设置里的 agent.workspace_dir。
|
||||
立即返回 request_id;过程事件经 GET /agent/{id}/stream (SSE) 推送。
|
||||
"""
|
||||
from gateway.agent import get_agent_service, new_request_id
|
||||
from router_system.tools import WorkspaceTools
|
||||
|
||||
task = str((req or {}).get("task") or "").strip()
|
||||
if not task:
|
||||
@@ -514,23 +516,36 @@ try:
|
||||
raise HTTPException(status_code=400, detail="task 过长(>8000)")
|
||||
pool_id = str((req or {}).get("pool_id") or "")
|
||||
|
||||
s = settings_store().to_dict()
|
||||
agent_cfg = s.get("agent", {})
|
||||
ws_raw = str((req or {}).get("workspace") or "").strip()
|
||||
if ws_raw:
|
||||
ws_path = Path(ws_raw)
|
||||
if not ws_path.exists():
|
||||
raise HTTPException(status_code=400, detail=f"工作目录不存在: {ws_raw}")
|
||||
if not ws_path.is_dir():
|
||||
raise HTTPException(status_code=400, detail=f"不是目录: {ws_raw}")
|
||||
workspace_dir = str(ws_path.resolve())
|
||||
else:
|
||||
workspace_dir = agent_cfg.get("workspace_dir", "agent_workspace")
|
||||
|
||||
chat, model, used_pool_id = _resolve_agent_chat(pool_id)
|
||||
service = get_agent_service()
|
||||
request_id = new_request_id()
|
||||
info = service.register(request_id, task, model, used_pool_id)
|
||||
info = service.register(request_id, task, model, used_pool_id,
|
||||
workspace=workspace_dir)
|
||||
if info is None:
|
||||
raise HTTPException(status_code=503, detail="智能体同时运行任务已达上限")
|
||||
|
||||
s = settings_store().to_dict()
|
||||
agent_cfg = s.get("agent", {})
|
||||
|
||||
async def _run():
|
||||
try:
|
||||
await service.run(
|
||||
info, chat,
|
||||
workspace_dir=agent_cfg.get("workspace_dir", "agent_workspace"),
|
||||
workspace_dir=workspace_dir,
|
||||
max_rounds=int(agent_cfg.get("max_rounds", 8)),
|
||||
token_cap=int(agent_cfg.get("token_cap", 20000)),
|
||||
allow_shell=bool(agent_cfg.get("allow_shell", False)),
|
||||
shell_timeout_s=int(agent_cfg.get("shell_timeout_s", 20)),
|
||||
)
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
@@ -541,7 +556,50 @@ try:
|
||||
service._write_status(info)
|
||||
|
||||
info.asyncio_task = asyncio.create_task(_run())
|
||||
return {"request_id": request_id, "status": "running", "model": model}
|
||||
return {"request_id": request_id, "status": "running", "model": model,
|
||||
"workspace": workspace_dir}
|
||||
|
||||
@app.get("/agent/fs", tags=["agent"])
|
||||
async def agent_fs_browse(path: str = ""):
|
||||
"""目录选择器:浏览本地文件系统(只列子目录,不读文件内容)。"""
|
||||
from router_system.tools import browse_directories
|
||||
return browse_directories(path)
|
||||
|
||||
@app.get("/agent/workspaces", tags=["agent"])
|
||||
async def agent_workspaces():
|
||||
"""当前工作区 + 最近打开列表。"""
|
||||
s = settings_store().to_dict()
|
||||
agent_cfg = s.get("agent", {})
|
||||
current = agent_cfg.get("workspace_dir", "agent_workspace")
|
||||
return {"current": current, "recent": list(agent_cfg.get("recent_workspaces", []))}
|
||||
|
||||
@app.post("/agent/workspaces", tags=["agent"])
|
||||
async def agent_open_workspace(req: dict):
|
||||
"""打开(或创建)一个工作目录:设为当前并记入最近列表。"""
|
||||
path = str((req or {}).get("path") or "").strip()
|
||||
create = bool((req or {}).get("create", False))
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="path 不能为空")
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
if not create:
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"目录不存在: {path}(可勾选“新建目录”)")
|
||||
try:
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise HTTPException(status_code=400, detail=f"创建失败: {e}")
|
||||
elif not p.is_dir():
|
||||
raise HTTPException(status_code=400, detail=f"不是目录: {path}")
|
||||
resolved = str(p.resolve())
|
||||
store = settings_store()
|
||||
store.update({"agent": {"workspace_dir": resolved}})
|
||||
merged = store.to_dict().get("agent", {})
|
||||
recent = [w for w in merged.get("recent_workspaces", []) if w != resolved]
|
||||
recent.insert(0, resolved)
|
||||
store.update({"agent": {"recent_workspaces": recent[:8]}})
|
||||
return {"ok": True, "current": resolved,
|
||||
"recent": store.to_dict().get("agent", {}).get("recent_workspaces", [])}
|
||||
|
||||
@app.get("/agent/{request_id}/status", tags=["agent"])
|
||||
async def agent_status(request_id: str):
|
||||
@@ -585,22 +643,22 @@ try:
|
||||
)
|
||||
|
||||
@app.get("/agent/workspace", tags=["agent"])
|
||||
async def agent_workspace(path: str = ""):
|
||||
"""列出智能体工作区(默认根目录;path 指定子目录)。越界返回 400。"""
|
||||
async def agent_workspace(path: str = "", root: str = ""):
|
||||
"""列出智能体工作区内容。root 可指定其他已选工作目录(默认用设置值)。越界/非法返回 400。"""
|
||||
from router_system.tools import ToolError, WorkspaceTools
|
||||
s = settings_store().to_dict()
|
||||
tools = WorkspaceTools(s.get("agent", {}).get("workspace_dir", "agent_workspace"))
|
||||
base = _agent_workspace_root(root)
|
||||
tools = WorkspaceTools(base)
|
||||
try:
|
||||
return tools.list_dir(path)
|
||||
except ToolError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
@app.get("/agent/file", tags=["agent"])
|
||||
async def agent_file(path: str):
|
||||
"""读取智能体工作区内文件(前端预览,越界即 400)。"""
|
||||
async def agent_file(path: str, root: str = ""):
|
||||
"""读取智能体工作区内文件(前端预览,越界即 400)。root 同 /agent/workspace。"""
|
||||
from router_system.tools import ToolError, WorkspaceTools
|
||||
s = settings_store().to_dict()
|
||||
tools = WorkspaceTools(s.get("agent", {}).get("workspace_dir", "agent_workspace"))
|
||||
base = _agent_workspace_root(root)
|
||||
tools = WorkspaceTools(base)
|
||||
try:
|
||||
result = tools.read_file(path)
|
||||
except ToolError as e:
|
||||
@@ -609,6 +667,17 @@ try:
|
||||
raise HTTPException(status_code=404, detail=result.get("error", "读取失败"))
|
||||
return result
|
||||
|
||||
def _agent_workspace_root(root: str = "") -> str:
|
||||
"""解析工作区根:显式 root(须为已存在目录)> 设置值。"""
|
||||
root = (root or "").strip()
|
||||
if root:
|
||||
p = Path(root)
|
||||
if not p.is_dir():
|
||||
raise HTTPException(status_code=400, detail=f"工作目录不存在: {root}")
|
||||
return str(p.resolve())
|
||||
s = settings_store().to_dict()
|
||||
return s.get("agent", {}).get("workspace_dir", "agent_workspace")
|
||||
|
||||
# ---------------- 模型设置(用户可调整) ----------------
|
||||
@app.get("/config", tags=["settings"])
|
||||
async def get_config():
|
||||
|
||||
Reference in New Issue
Block a user