fix(approval): restore session approval for Tirith-flagged commands
Adds an allow_session flag to the gateway approval payload so adapters can render the session tier independently of the permanent tier. Matrix gains a session reaction (🌀) and a reaction legend; pure-tirith prompts now offer once/session/deny instead of collapsing to once/deny. Salvaged from PR #67312, adapted to the allow_permanent semantics that landed in #68597 (Always offered when any dangerous-pattern warning is persistable; pure-tirith prompts stay session-max).
This commit is contained in:
parent
6b54582438
commit
a3297bd232
|
|
@ -0,0 +1 @@
|
|||
faikwo
|
||||
|
|
@ -359,6 +359,7 @@ def _format_exec_approval_fallback(
|
|||
command_prefix: str,
|
||||
*,
|
||||
allow_permanent: bool = True,
|
||||
allow_session: bool = True,
|
||||
smart_denied: bool = False,
|
||||
) -> str:
|
||||
"""Render the text fallback from approval capabilities, not platform names."""
|
||||
|
|
@ -368,7 +369,7 @@ def _format_exec_approval_fallback(
|
|||
heading = "⚠️ **Smart DENY — owner override for one operation:**"
|
||||
|
||||
choices = [f"Reply `{command_prefix}approve` to execute this one operation"]
|
||||
if not smart_denied:
|
||||
if not smart_denied and allow_session:
|
||||
choices.append(
|
||||
f"`{command_prefix}approve session` to approve this pattern for the session"
|
||||
)
|
||||
|
|
@ -20706,6 +20707,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
description=desc,
|
||||
metadata=_status_thread_metadata,
|
||||
allow_permanent=approval_data.get("allow_permanent", True),
|
||||
allow_session=approval_data.get("allow_session", True),
|
||||
smart_denied=approval_data.get("smart_denied", False),
|
||||
),
|
||||
_loop_for_step,
|
||||
|
|
@ -20736,6 +20738,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
desc,
|
||||
_p,
|
||||
allow_permanent=approval_data.get("allow_permanent", True),
|
||||
allow_session=approval_data.get("allow_session", True),
|
||||
smart_denied=approval_data.get("smart_denied", False),
|
||||
)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -996,6 +996,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
# Matrix reaction-based dangerous command approvals.
|
||||
self._approval_reaction_map = {
|
||||
"✅": "once",
|
||||
"🌀": "session",
|
||||
"♾️": "always",
|
||||
"♾": "always",
|
||||
"\u267e\ufe0f": "always",
|
||||
|
|
@ -2063,6 +2064,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
description: str = "dangerous command",
|
||||
metadata: Optional[dict] = None,
|
||||
allow_permanent: bool = True,
|
||||
allow_session: bool = True,
|
||||
smart_denied: bool = False,
|
||||
) -> SendResult:
|
||||
"""Send a reaction-based exec approval prompt for Matrix."""
|
||||
|
|
@ -2075,17 +2077,24 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
if smart_denied:
|
||||
scope_choices = "Smart DENY: owner override applies to this one operation only.\n"
|
||||
else:
|
||||
scope_choices = "Reply `!approve session` to approve this pattern for the session, "
|
||||
scope_choices = ""
|
||||
if allow_session:
|
||||
scope_choices += "Reply `!approve session` to approve this pattern for the session, "
|
||||
if allow_permanent:
|
||||
scope_choices += "`!approve always` to approve permanently, "
|
||||
reaction_legend_parts = ["✅ = approve once"]
|
||||
if allow_session:
|
||||
reaction_legend_parts.append("🌀 = approve for this session")
|
||||
if allow_permanent:
|
||||
reaction_legend_parts.append("♾️ = approve always")
|
||||
reaction_legend_parts.append("❎ = deny")
|
||||
text = (
|
||||
"⚠️ **Dangerous command requires approval**\n"
|
||||
f"```\n{cmd_preview}\n```\n"
|
||||
f"Reason: {description}\n\n"
|
||||
f"{scope_choices}Reply `!approve` to execute once, or `!deny` to cancel.\n\n"
|
||||
"You can also click the reaction to approve:\n"
|
||||
"✅ = approve\n"
|
||||
"❎ = deny"
|
||||
+ "\n".join(reaction_legend_parts)
|
||||
)
|
||||
|
||||
result = await self.send(chat_id, text, metadata=metadata)
|
||||
|
|
@ -2105,7 +2114,12 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
self._approval_prompts_by_event[result.message_id] = prompt
|
||||
self._approval_prompt_by_session[session_key] = result.message_id
|
||||
|
||||
reactions = ("✅", "❌") if smart_denied or not allow_permanent else ("✅", "♾️", "❌")
|
||||
if not allow_session:
|
||||
reactions = ("✅", "❌")
|
||||
elif not allow_permanent:
|
||||
reactions = ("✅", "🌀", "❌")
|
||||
else:
|
||||
reactions = ("✅", "🌀", "♾️", "❌")
|
||||
for emoji in reactions:
|
||||
try:
|
||||
reaction_result = await self._send_reaction(chat_id, result.message_id, emoji)
|
||||
|
|
|
|||
|
|
@ -2769,6 +2769,7 @@ def _run_approval_gate(
|
|||
"pattern_keys": [pattern_key],
|
||||
"description": redact_sensitive_text(description),
|
||||
"allow_permanent": True,
|
||||
"allow_session": True,
|
||||
}
|
||||
decision = _await_gateway_decision(
|
||||
session_key, notify_cb, approval_data, surface="gateway"
|
||||
|
|
@ -3464,6 +3465,11 @@ def check_all_command_guards(command: str, env_type: str,
|
|||
# whenever any dangerous-pattern warning can actually be
|
||||
# persisted (pure-tirith prompts stay session-max).
|
||||
"allow_permanent": has_permanent_capable and not smart_denied_for_owner,
|
||||
# Session approval is safe for every non-Smart-DENY prompt —
|
||||
# including pure-tirith ones, where the persistence layer
|
||||
# already caps scope at session. Adapters use this to render
|
||||
# a session tier independently of the permanent tier.
|
||||
"allow_session": not smart_denied_for_owner,
|
||||
}
|
||||
if smart_denied_for_owner:
|
||||
approval_data["smart_denied"] = True
|
||||
|
|
@ -3795,6 +3801,7 @@ def check_execute_code_guard(code: str, env_type: str,
|
|||
"pattern_keys": [pattern_key],
|
||||
"description": display_description,
|
||||
"allow_permanent": not smart_denied_for_owner,
|
||||
"allow_session": not smart_denied_for_owner,
|
||||
}
|
||||
if smart_denied_for_owner:
|
||||
approval_data["smart_denied"] = True
|
||||
|
|
|
|||
Loading…
Reference in New Issue