Files
hermes-agent/hermes_cli/approval_mode.py
Teknium 14791b4d4e simplify(compat): approval — drop 43 facade re-exports + _command_detection_variants late-bind seam, repoint 30 callers + 46 test files
tools/approval.py no longer re-exports sibling names (approval_context/prompt/floors/detection/
human_wait/smart/gateway_wait); it imports only what it uses. Siblings reference sibling-defined
names directly (module-attribute reads on tools.approval_context so patching the defining module
still works); only facade-owned state (_lock, _gateway_queues, _permanent_approved, _denied,
_denial_breaker_addendum, _gateway_notify_cb) is still read back through tools.approval.
approval_detection calls its own _command_detection_variants instead of late-binding through the facade.
2026-09-03 13:49:57 -07:00

67 lines
2.6 KiB
Python

"""Shared persistent approval-mode command logic.
Approval mode is profile-scoped configuration, not conversation state. Changing it affects
subsequent terminal guard checks immediately because approval.py loads config on each check; it must
not rebuild a live agent or mutate its system prompt/tool schema, preserving the prompt-cache
prefix.
"""
from __future__ import annotations
from contextlib import redirect_stderr, redirect_stdout
from dataclasses import dataclass
from io import StringIO
from typing import Optional
VALID_APPROVAL_MODES = ("manual", "smart", "off")
@dataclass(frozen=True)
class ApprovalModeResult:
ok: bool
mode: str
changed: bool
message: str
def _effective_mode() -> str:
"""Return the exact mode enforced by the terminal approval guard."""
from tools.approval_context import _get_approval_mode
return _get_approval_mode()
def run_approval_mode_command(requested_mode: Optional[str]) -> ApprovalModeResult:
"""Inspect or persist ``approvals.mode`` through canonical config APIs."""
current = _effective_mode()
requested = (requested_mode or "").strip().lower()
if not requested:
return ApprovalModeResult(True, current, False, f"Approval mode: {current} (persistent profile setting).")
if requested not in VALID_APPROVAL_MODES:
return ApprovalModeResult(False, current, False, "Usage: /approvals [manual|smart|off]")
# set_config_value is the canonical managed-scope/write-safety chokepoint. It reports managed
# policy through stderr + SystemExit, and the fail-closed write guard raises RuntimeError on an
# unparseable config.yaml; capture both for slash-command output instead of terminating the
# interactive worker.
from hermes_cli.config import set_config_value
output = StringIO()
try:
with redirect_stdout(output), redirect_stderr(output):
set_config_value("approvals.mode", requested)
except SystemExit:
detail = output.getvalue().strip() or "Approval mode is managed and cannot be changed."
return ApprovalModeResult(False, current, False, detail)
except Exception as exc:
return ApprovalModeResult(False, current, False, f"Failed to save approval mode: {exc}")
effective = _effective_mode()
if effective != requested:
return ApprovalModeResult(
False, effective, False,
f"Approval mode remains {effective}; the requested value did not become effective.",
)
return ApprovalModeResult(
True, effective, effective != current, f"Approval mode: {effective} (persistent profile setting).",
)