fix: include approval metadata in terminal tool results (#5141)
When a dangerous command is approved (gateway, CLI, or smart approval), the terminal tool now includes an 'approval' field in the result JSON so the model knows approval was requested and granted. Previously the model only saw normal command output with no indication that approval happened, causing it to hallucinate that the approval system didn't fire. Changes: - approval.py: Return user_approved/description in all 3 approval paths (gateway blocking, CLI interactive, smart approval) - terminal_tool.py: Capture approval metadata and inject into both foreground and background command results
This commit is contained in:
@@ -724,7 +724,8 @@ def check_all_command_guards(command: str, env_type: str,
|
||||
logger.debug("Smart approval: auto-approved '%s' (%s)",
|
||||
command[:60], combined_desc_for_llm)
|
||||
return {"approved": True, "message": None,
|
||||
"smart_approved": True}
|
||||
"smart_approved": True,
|
||||
"description": combined_desc_for_llm}
|
||||
elif verdict == "deny":
|
||||
combined_desc_for_llm = "; ".join(desc for _, desc, _ in warnings)
|
||||
return {
|
||||
@@ -819,7 +820,8 @@ def check_all_command_guards(command: str, env_type: str,
|
||||
approve_permanent(key)
|
||||
save_permanent_allowlist(_permanent_approved)
|
||||
|
||||
return {"approved": True, "message": None}
|
||||
return {"approved": True, "message": None,
|
||||
"user_approved": True, "description": combined_desc}
|
||||
|
||||
# Fallback: no gateway callback registered (e.g. cron, batch).
|
||||
# Return approval_required for backward compat.
|
||||
@@ -865,4 +867,5 @@ def check_all_command_guards(command: str, env_type: str,
|
||||
approve_permanent(key)
|
||||
save_permanent_allowlist(_permanent_approved)
|
||||
|
||||
return {"approved": True, "message": None}
|
||||
return {"approved": True, "message": None,
|
||||
"user_approved": True, "description": combined_desc}
|
||||
|
||||
@@ -1058,6 +1058,7 @@ def terminal_tool(
|
||||
|
||||
# Pre-exec security checks (tirith + dangerous command detection)
|
||||
# Skip check if force=True (user has confirmed they want to run it)
|
||||
approval_note = None
|
||||
if not force:
|
||||
approval = _check_all_guards(command, env_type)
|
||||
if not approval["approved"]:
|
||||
@@ -1084,6 +1085,13 @@ def terminal_tool(
|
||||
"error": approval.get("message", fallback_msg),
|
||||
"status": "blocked"
|
||||
}, ensure_ascii=False)
|
||||
# Track whether approval was explicitly granted by the user
|
||||
if approval.get("user_approved"):
|
||||
desc = approval.get("description", "flagged as dangerous")
|
||||
approval_note = f"Command required approval ({desc}) and was approved by the user."
|
||||
elif approval.get("smart_approved"):
|
||||
desc = approval.get("description", "flagged as dangerous")
|
||||
approval_note = f"Command was flagged ({desc}) and auto-approved by smart approval."
|
||||
|
||||
# Prepare command for execution
|
||||
if background:
|
||||
@@ -1121,6 +1129,8 @@ def terminal_tool(
|
||||
"exit_code": 0,
|
||||
"error": None,
|
||||
}
|
||||
if approval_note:
|
||||
result_data["approval"] = approval_note
|
||||
|
||||
# Transparent timeout clamping note
|
||||
max_timeout = effective_timeout
|
||||
@@ -1232,11 +1242,14 @@ def terminal_tool(
|
||||
from agent.redact import redact_sensitive_text
|
||||
output = redact_sensitive_text(output.strip()) if output else ""
|
||||
|
||||
return json.dumps({
|
||||
result_dict = {
|
||||
"output": output,
|
||||
"exit_code": returncode,
|
||||
"error": None
|
||||
}, ensure_ascii=False)
|
||||
}
|
||||
if approval_note:
|
||||
result_dict["approval"] = approval_note
|
||||
return json.dumps(result_dict, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
Reference in New Issue
Block a user