feat(settings): add a tool approval mode with an explicit off (YOLO) option (#6304)

* feat: add optional tool approval mode

Admin-configurable tool_approval_mode setting (ask/auto) for the
post-external-context tool approval gate.

- ask (default): existing approval flow, gate stays on
- auto: bypasses the approval gate for all runs - same effect as
  ODYSSEUS_TOOL_APPROVAL_GATE=0. Documented honestly in the settings
  row copy and in THREAT_MODEL.md, per review on #6304.
- value validated server-side ({ask, auto}, anything else -> 400)
- no new env var: the setting wires to the existing gate control
- tests: setting validation + _tool_approval_gate_bypassed behavior

* feat: surface deployment gate state on Tool approval row

Follow-up to StressTestor's re-review: when ODYSSEUS_TOOL_APPROVAL_GATE is
set to a falsy value, the Tool approval row still read 'Always ask' even though
the gate was off, misleading admins the other way.

GET /api/auth/settings now returns tool_approval_gate_env_disabled (derived
from TOOL_APPROVAL_GATE_ENABLED) for admins, and the settings row renders a
red notice naming the env var and explaining that the gate is off for every
run regardless of the stored setting. Non-admin/scrubbed responses do not
expose the deployment state. Test covers both env values and the scrub path.

* refactor: rename tool approval mode auto to off

The gate-off value is now "off" ("Off (YOLO)" in the UI) so the auto-approve name stays available for the future judged mode.

---------

Co-authored-by: Matej <matej@local>
This commit is contained in:
DracieVajko
2026-11-01 12:22:56 -07:00
committed by GitHub
co-authored by Matej
parent 2a413814c5
commit f7f6abf82b
9 changed files with 168 additions and 6 deletions
+34
View File
@@ -500,3 +500,37 @@ async def test_dispatcher_revalidates_sealed_workspace(monkeypatch, tmp_path):
assert result["blocked"] is True
assert result["policy"] == "exact_tool_approval"
def test_tool_approval_mode_defaults_to_ask(monkeypatch):
import src.agent_loop as agent_loop
monkeypatch.setattr(
agent_loop,
"get_setting",
lambda key, default=None: (
"ask" if key == "tool_approval_mode" else default
),
)
assert agent_loop._tool_approval_gate_bypassed() is False
def test_tool_approval_gate_respects_setting(monkeypatch):
import src.agent_loop as agent_loop
monkeypatch.setattr(
agent_loop,
"get_setting",
lambda key, default=None: (
"ask" if key == "tool_approval_mode" else default
),
)
assert agent_loop._tool_approval_gate_bypassed() is False
monkeypatch.setattr(
agent_loop,
"get_setting",
lambda key, default=None: (
"off" if key == "tool_approval_mode" else default
),
)
assert agent_loop._tool_approval_gate_bypassed() is True