feat: 危险命令确认改为会话级——保留单次确认,新增「全部确认」选项(approve_all)授权后本会话不再询问

This commit is contained in:
ymq 2026-08-19 14:09:27 +08:00
parent bda9f31a1b
commit 41232c8295
2 changed files with 71 additions and 6 deletions

View File

@ -64,6 +64,7 @@ class AgentExecutor:
role: str = "", # 产线内角色(可选,如 develop/design驾驶舱 agent 为空)
base_url: str = "", # 请求 base_urlscheme://host/),供 slash 命令生成 widget 绝对 URL
generic: bool = False, # True = 纯通用会话(不解析项目、不挂产线能力)
session=None, # GatewaySession会话级状态approve_all/pending_confirm
):
self.config = config
self.project_id = project_id
@ -72,6 +73,7 @@ class AgentExecutor:
self.pipeline_id = "" # loaded from project context产线能力包 key
self.role = role # 产线内角色(技能/工具/prompt 按角色加载)
self.generic = generic # 是否纯通用会话
self._session = session # GatewaySessionapprove_all/pending_confirm 会话级状态)
self.space = GENERAL_SPACE # 项目空间键generic→'general',产线→真实 pipeline_id
self.workspace_dir = workspace_dir or WORKSPACE_BASE
self.model_name = model_name or config.model_name
@ -146,6 +148,39 @@ class AgentExecutor:
self._msgs.extend(hist_msgs)
self._msgs.append({"role": "user", "content": user_input})
# Step 4.5: 危险命令确认回复短路pending_confirm + 确认/全部确认/取消)
confirm_decision = self._detect_confirm_decision(user_input)
if confirm_decision and self._session and self._session.pending_confirm:
pending = self._session.pending_confirm
if confirm_decision == "cancel":
self._session.pending_confirm = None
yield json.dumps({"type": "reply", "message": "已取消该操作。"}, ensure_ascii=False) + "\n"
await self._save_turn(user_input, "已取消该操作。")
return
if confirm_decision == "approve_all":
self._session.approve_all = True
self._session.pending_confirm = None
pending_tool = pending.get("tool", "")
pending_params = pending.get("params") or {}
# 回填 assistant tool_call → 执行 → 回填 tool result随后继续 Tool Loop
fake_id = f"call_{int(time.time() * 1000)}"
self._msgs.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": fake_id,
"type": "function",
"function": {"name": pending_tool, "arguments": json.dumps(pending_params, ensure_ascii=False)},
}],
})
yield json.dumps({"type": "tool_call", "tool": pending_tool, "params": pending_params}, ensure_ascii=False) + "\n"
result = await self._execute_tool(pending_tool, pending_params)
if not result.startswith("未知工具") and not result.startswith("ERROR"):
self._tool_call_count += 1
yield json.dumps({"type": "tool_result", "tool": pending_tool, "result": result[:500]}, ensure_ascii=False) + "\n"
self._msgs.append({"role": "tool", "tool_call_id": fake_id, "content": str(result)})
# 不 return继续进入 Tool Loop让 LLM 基于工具结果继续后续步骤
# Step 5: Tool Loop
yield json.dumps({"type": "progress", "message": "思考中..."}, ensure_ascii=False) + "\n"
@ -179,8 +214,10 @@ class AgentExecutor:
except Exception:
tool_params = {}
# 需要确认的工具
# 需要确认的工具:记录待确认并暂停,等待用户确认/全部确认/取消
if self._needs_confirmation(tool_name, tool_params):
if self._session:
self._session.pending_confirm = {"tool": tool_name, "params": tool_params}
yield json.dumps({
"type": "confirm", "tool": tool_name, "params": tool_params,
}, ensure_ascii=False) + "\n"
@ -239,8 +276,10 @@ class AgentExecutor:
tool_name = act.get("tool", "")
tool_params = act.get("params", {})
# 需要确认的工具
# 需要确认的工具:记录待确认并暂停,等待用户确认/全部确认/取消
if self._needs_confirmation(tool_name, tool_params):
if self._session:
self._session.pending_confirm = {"tool": tool_name, "params": tool_params}
yield json.dumps({
"type": "confirm",
"tool": tool_name,
@ -1268,13 +1307,37 @@ class AgentExecutor:
)
return any(d in c for d in dangerous)
def _detect_confirm_decision(self, user_input: str) -> str:
"""检测用户对危险命令确认的回复。返回 approve / approve_all / cancel / ''"""
t = (user_input or "").strip().lower()
if not t:
return ""
for kw in ("全部确认", "确认全部", "全部同意", "都确认", "都同意", "一律确认", "approve all", "yes to all", "always"):
if kw in t:
return "approve_all"
for kw in ("取消", "放弃", "不执行", "别执行", "cancel", "abort", "不要"):
if kw in t:
return "cancel"
for kw in ("确认", "同意", "执行", "可以", "approve", "confirm", "yes", "ok", "继续"):
if kw in t:
return "approve"
return ""
def _needs_confirmation(self, tool_name: str, params: dict = None) -> bool:
"""检查工具是否需要用户确认。
已关闭确认机制agent 会话应能理解和接受全部确认执行任务时
不再打断用户要求确认危险命令也自动执行不再弹请回复确认
如需恢复按命令危险度确认改回读取 tool.requires_confirmation + _is_dangerous_command
会话级全部确认approve_all时直接放行否则 run_command 只对
危险命令确认普通命令自动执行其他 requires_confirmation 工具一律确认
"""
if self._session and getattr(self._session, "approve_all", False):
return False
if self._tool_registry:
tool = self._tool_registry.get(tool_name)
if tool and tool.requires_confirmation:
if tool_name == "run_command":
cmd = (params or {}).get("command", "")
return self._is_dangerous_command(cmd)
return True
return False

View File

@ -36,6 +36,8 @@ class GatewaySession:
project_name: str = ""
created_at: float = 0.0
updated_at: float = 0.0
approve_all: bool = False # 会话级"全部确认":用户评估后授权,本会话不再询问危险命令
pending_confirm: Optional[dict] = None # 待确认工具 {"tool":..., "params":...}(危险命令等待用户确认)
class Gateway:
@ -155,7 +157,7 @@ class Gateway:
config.model_name = ctx["default_llm_name"]
executor = AgentExecutor(
config=config, project_id=ctx["pid"], user_id=user_id, role=role,
base_url=base_url, generic=generic)
base_url=base_url, generic=generic, session=sess)
# 4. 转发标准化事件流
async for chunk in executor.run(content, history=history):