feat(v3): T28 操作审批流(dsh 式 allow-once/deny,fail-closed)

- ToolLoop 增 approval_hook:工具执行前挂起等待用户裁决,拒绝/异常折叠为
  失败结果回喂模型(可改道),fail-closed
- 策略 agent.approval_policy:off | dangerous(写/编辑/命令询问,只读放行,默认)| all;
  approval_timeout_s 超时自动拒绝(轮询实现,规避 portal 循环下 wait_for 定时器不可靠)
- POST /agent/{id}/approve 裁决端点;approval_request/decided 事件对进 SSE 与审计
- 前端:运行中审批卡(工具名+参数预览+拒绝/允许一次),composer 审批策略 chip
- 测试 +8(策略矩阵/拒绝回喂/允许执行/fail-closed/超时/端点分支),全量 289 passed
This commit is contained in:
tzt
2026-09-01 23:12:26 +08:00
parent 7d11ae2644
commit ca8add02e2
16 changed files with 487 additions and 21 deletions
+19
View File
@@ -602,6 +602,8 @@ try:
executor_chat=executor_chat,
max_handoffs=int(agent_cfg.get("max_handoffs", 2)),
session=session,
approval_policy=str(agent_cfg.get("approval_policy", "dangerous")),
approval_timeout_s=int(agent_cfg.get("approval_timeout_s", 120)),
)
except Exception as exc:
import traceback
@@ -638,6 +640,23 @@ try:
service._write_status(info)
return {"ok": True}
@app.post("/agent/{request_id}/approve", tags=["agent"])
async def agent_approve(request_id: str, req: dict):
"""裁决待审批操作:{"approval_id", "allowed"}dsh 式 allow-once / deny)。"""
from gateway.agent import get_agent_service
info = get_agent_service().get(request_id)
if info is None:
raise HTTPException(status_code=404, detail=f"智能体任务不存在: {request_id}")
approval_id = str((req or {}).get("approval_id") or "")
allowed = bool((req or {}).get("allowed", False))
mgr = getattr(info, "_approval_manager", None)
if mgr is None:
raise HTTPException(status_code=409, detail="该任务无审批流程")
ok = mgr.decide(approval_id, allowed)
if not ok:
raise HTTPException(status_code=404, detail=f"审批单不存在或已裁决: {approval_id}")
return {"ok": True, "approval_id": approval_id, "allowed": allowed}
# ---------------- 会话(dsh 式多轮对话) ----------------
@app.post("/agent/sessions", tags=["agent"])
async def agent_session_create(req: dict = None):