feat(desktop): Shift+click the status-bar zap to toggle YOLO globally (#41666)

The status-bar zap currently toggles per-session approval bypass (the same
scope as the TUI's Shift+Tab). This adds a global escape hatch: Shift+clicking
the zap flips the persistent approvals.mode in config.yaml between "off"
(bypass on) and "manual" (bypass off), affecting every session, the CLI, the
TUI, and cron — and it survives restarts.

- statusbar-controls: thread the click's shiftKey through onSelect via a new
  StatusbarSelectModifiers arg.
- yolo-session: add setGlobalYolo() that calls config.set with scope="global".
- use-statusbar-items: branch toggleYolo on modifiers.shiftKey; plain click
  stays per-session, Shift+click goes global.
- tui_gateway config.set "yolo" key: add scope="global" that reads/writes
  approvals.mode through the gateway's own (mtime-cached) config view, honors
  an explicit value, and re-emits session.info to every live session so each
  window's zap reflects the flip immediately.
- i18n: tooltip copy in en/ja/zh/zh-hant notes Shift+click toggles globally.

Tests: two new tui_gateway tests cover the global toggle and explicit-value
paths; existing session/process-scope yolo tests still pass.
This commit is contained in:
brooklyn!
2026-06-07 20:57:08 -05:00
committed by GitHub
parent 30c7913617
commit fa42ac094d
9 changed files with 188 additions and 52 deletions
+55 -24
View File
@@ -5785,32 +5785,62 @@ def _(rid, params: dict) -> dict:
return _ok(rid, {"key": key, "value": nv})
if key == "yolo":
# Per-session approval bypass — same scope as the TUI's Shift+Tab. This
# toggles ONLY this session's _session_yolo flag; it never writes the
# global approvals.mode, so it cannot change CLI / TUI / cron behavior.
# Approval bypass. Two scopes:
# scope="session" (default) — same as the TUI's Shift+Tab. Toggles
# ONLY this session's _session_yolo flag; never touches global
# config, so CLI / TUI / cron behavior is unaffected.
# scope="global" (Shift+click the zap) — flips the persistent global
# approvals.mode in config.yaml between "off" (bypass on) and
# "manual" (bypass off). This DOES affect every session, the CLI,
# the TUI, and cron, and survives restarts.
scope = str(params.get("scope") or "session").strip().lower()
try:
if session:
from tools.approval import (
disable_session_yolo,
enable_session_yolo,
is_session_yolo_enabled,
)
from tools.approval import (
disable_session_yolo,
enable_session_yolo,
is_session_yolo_enabled,
)
raw = str(value or "").strip().lower()
raw = str(value or "").strip().lower()
def _resolve_toggle(current: bool) -> bool:
if raw in {"1", "on", "true", "yes"}:
return True
if raw in {"0", "off", "false", "no"}:
return False
return not current
if scope == "global":
from tools.approval import _normalize_approval_mode
cfg = _load_cfg()
appr = cfg.get("approvals") if isinstance(cfg, dict) else None
if not isinstance(appr, dict):
appr = {}
current = _normalize_approval_mode(appr.get("mode", "manual")) == "off"
enable = _resolve_toggle(current)
# Toggle between full bypass and the default manual gate. We do
# not try to restore a prior "smart"/custom mode — the zap is a
# binary on/off affordance; users with bespoke modes set them in
# config.yaml.
_write_config_key("approvals.mode", "off" if enable else "manual")
nv = "1" if enable else "0"
# Reflect the global flip in every live session's indicator.
for sid, sess in list(_sessions.items()):
agent = sess.get("agent")
if agent is not None:
_emit("session.info", sid, _session_info(agent, sess))
return _ok(rid, {"key": key, "value": nv, "scope": "global"})
if session:
current = is_session_yolo_enabled(session["session_key"])
enable = _resolve_toggle(current)
if enable:
enable_session_yolo(session["session_key"])
nv = "1"
elif raw in {"0", "off", "false", "no"}:
else:
disable_session_yolo(session["session_key"])
nv = "0"
else:
current = is_session_yolo_enabled(session["session_key"])
if current:
disable_session_yolo(session["session_key"])
nv = "0"
else:
enable_session_yolo(session["session_key"])
nv = "1"
agent = session.get("agent")
if agent is not None:
_emit(
@@ -5820,13 +5850,14 @@ def _(rid, params: dict) -> dict:
)
else:
current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
if current:
os.environ.pop("HERMES_YOLO_MODE", None)
nv = "0"
else:
enable = _resolve_toggle(current)
if enable:
os.environ["HERMES_YOLO_MODE"] = "1"
nv = "1"
return _ok(rid, {"key": key, "value": nv})
else:
os.environ.pop("HERMES_YOLO_MODE", None)
nv = "0"
return _ok(rid, {"key": key, "value": nv, "scope": "session"})
except Exception as e:
return _err(rid, 5001, str(e))