agent-Specialization/modules/auto_approval_service.py
JOJO 2ec226b5eb fix(permission,docker): enforce docker-specific approval flow and polish permission menu layout
- Add docker risk markers config (config/docker_risk_markers.json) and load it in tools permission evaluation.

- In docker mode, pre-evaluate run_command/terminal_input: readonly-safe and no-risk -> direct run; otherwise require approval/auto-approval.

- Keep docker write_file/edit_file behavior aligned with agreed policy: approval requires review, auto_approval passes directly.

- Pass risk markers into auto approval payload and include current working directory context for approval agent.

- Fix permission dropdown layout: only use split two-column menu when execution mode section is enabled; remove empty right blank area in docker mode.

- Center mobile approval overlay to prevent sheet overflow on small screens.
2026-05-11 22:56:10 +08:00

100 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import json
from typing import Any, Dict, List, Optional
from modules.approval_agent import ApprovalAgent
from server.state import tool_approval_manager
def build_auto_approval_payload(
*,
web_terminal: Any,
recent_tool_actions: List[Dict[str, Any]],
function_name: str,
arguments: Dict[str, Any],
risk_markers: Optional[List[str]] = None,
) -> str:
history = list(getattr(getattr(web_terminal, "context_manager", None), "conversation_history", []) or [])
user_input = ""
for msg in reversed(history):
if not isinstance(msg, dict) or msg.get("role") != "user":
continue
metadata = msg.get("metadata") or {}
if bool(metadata.get("is_auto_generated")):
continue
content = msg.get("content")
if isinstance(content, str) and content.strip():
user_input = content.strip()
break
recent = recent_tool_actions[-3:] if isinstance(recent_tool_actions, list) else []
lines = [
"请按照流程审批:",
f"用户输入:{user_input or '(空)'}",
"AI运行的前三步操作",
]
if recent:
for idx, row in enumerate(recent, start=1):
lines.append(f"{idx}. {row.get('tool_name')}: {json.dumps(row.get('arguments') or {}, ensure_ascii=False)}")
else:
lines.append("(无)")
lines += [
f"当前工作目录:{('/workspace' if bool(getattr(getattr(web_terminal, 'container_session', None), 'mode', None) == 'docker') else str(getattr(getattr(web_terminal, 'context_manager', None), 'project_path', '')))}",
]
if risk_markers:
lines.append(f"当前操作中触发风险特征的内容:{', '.join([str(x) for x in risk_markers if str(x).strip()])}")
lines += [
"请审核当前操作:",
f"{function_name}: {json.dumps(arguments or {}, ensure_ascii=False)}",
"仅判断危险性和越权风险,不判断任务必要性。",
]
return "\n".join(lines)
async def run_auto_approval(
*,
web_terminal: Any,
username: str,
approval_id: str,
conversation_id: Optional[str],
recent_tool_actions: List[Dict[str, Any]],
function_name: str,
arguments: Dict[str, Any],
risk_markers: Optional[List[str]],
sender,
) -> Dict[str, Any]:
def _progress(evt: Dict[str, Any]) -> None:
sender("auto_approval_progress", {"approval_id": approval_id, "progress": evt, "conversation_id": conversation_id})
def _manual_takeover() -> Optional[Dict[str, Any]]:
row = tool_approval_manager.get(approval_id)
status = (row or {}).get("status")
if status in {"approved", "rejected"}:
return {"decision": status, "item": row, "source": "manual"}
return None
_progress({"stage": "start", "message": "自动审批开始"})
payload = build_auto_approval_payload(
web_terminal=web_terminal,
recent_tool_actions=recent_tool_actions,
function_name=function_name,
arguments=arguments,
risk_markers=risk_markers,
)
agent = ApprovalAgent(web_terminal=web_terminal)
result = await agent.review(payload_text=payload, progress_cb=_progress, cancel_check=_manual_takeover)
if not _manual_takeover():
decided = tool_approval_manager.decide(
approval_id=approval_id,
username=username,
decision=str(result.get("decision") or "rejected"),
reason=str(result.get("reason") or "").strip(),
decider="approval_agent",
)
out = {"decision": decided.get("status"), "item": decided, "source": "approval_agent"}
else:
out = _manual_takeover() or {"decision": "rejected", "reason": "人工接管失败"}
_progress({"stage": "done", "message": "自动审批结束", "decision": (out or {}).get("decision")})
return out