feat(cli): persist resolved approval/clarify prompts in scrollback (#44702)
Some checks are pending
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
Docker Build and Publish / build-amd64 (push) Waiting to run
Docker Build and Publish / build-arm64 (push) Waiting to run
Docker Build and Publish / merge (push) Blocked by required conditions
Lint (ruff + ty) / ruff + ty diff (push) Waiting to run
Lint (ruff + ty) / ruff enforcement (blocking) (push) Waiting to run
Lint (ruff + ty) / Windows footguns (blocking) (push) Waiting to run
Nix Lockfile Fix / auto-fix-main (push) Waiting to run
Nix Lockfile Fix / fix (push) Waiting to run
Nix / nix (macos-latest) (push) Waiting to run
Nix / nix (ubuntu-latest) (push) Waiting to run
OSV-Scanner / Scan lockfiles (push) Waiting to run
Build Skills Index / build-index (push) Waiting to run
Build Skills Index / trigger-deploy (push) Blocked by required conditions
Tests / test (1) (push) Waiting to run
Tests / test (2) (push) Waiting to run
Tests / test (3) (push) Waiting to run
Tests / test (4) (push) Waiting to run
Tests / test (5) (push) Waiting to run
Tests / test (6) (push) Waiting to run
Tests / save-durations (push) Blocked by required conditions
Tests / e2e (push) Waiting to run
Typecheck / typecheck (apps/bootstrap-installer) (push) Waiting to run
Typecheck / typecheck (apps/desktop) (push) Waiting to run
Typecheck / typecheck (apps/shared) (push) Waiting to run
Typecheck / typecheck (ui-tui) (push) Waiting to run
Typecheck / typecheck (web) (push) Waiting to run
uv.lock check / uv lock --check (push) Waiting to run

Modal prompt panels (dangerous-command approval, clarify questions)
live in the prompt_toolkit layout and vanish on the next repaint,
leaving no trace of the question or the decision in chat history.

Emit a dim one-line summary after each prompt resolves:
  ⚠ Approval: <command> → allowed for session
  ? Clarify: <question> → <answer>

Gated on display.persist_prompts (default true). Detail and outcome
are whitespace-collapsed and capped at 120 chars.
This commit is contained in:
Teknium 2026-06-12 01:14:35 -07:00 committed by GitHub
parent 8e5b7592f8
commit 4474873d2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 0 deletions

33
cli.py
View file

@ -456,6 +456,9 @@ def load_cli_config() -> Dict[str, Any]:
"busy_input_mode": "interrupt",
"persistent_output": True,
"persistent_output_max_lines": 200,
# Print a one-line summary of resolved modal prompts (approval /
# clarify) into scrollback so the decision survives the repaint.
"persist_prompts": True,
"skin": "default",
},
@ -9361,6 +9364,25 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
for line in reqs["details"].split("\n"):
_cprint(f" {line}")
def _persist_prompt_summary(self, icon: str, label: str, detail: str, outcome: str) -> None:
"""Print a one-line scrollback summary of a resolved modal prompt.
Modal panels (approval / clarify) live in the prompt_toolkit layout and
vanish on the next repaint, so the question and the decision leave no
trace in the terminal scrollback. When display.persist_prompts is on
(default), emit a dim single line after the prompt resolves so the
decision survives in chat history.
"""
if not CLI_CONFIG.get("display", {}).get("persist_prompts", True):
return
detail = " ".join(detail.split())
if len(detail) > 120:
detail = detail[:119] + ""
outcome = " ".join(outcome.split())
if len(outcome) > 120:
outcome = outcome[:119] + ""
_cprint(f"\n{_DIM}{icon} {label}: {detail}{outcome}{_RST}")
def _clarify_callback(self, question, choices):
"""
Platform callback for the clarify tool. Called from the agent thread.
@ -9400,6 +9422,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
try:
result = response_queue.get(timeout=1)
self._clarify_deadline = 0
self._persist_prompt_summary("?", "Clarify", question, str(result))
return result
except queue.Empty:
remaining = self._clarify_deadline - _time.monotonic()
@ -9513,6 +9536,16 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
self._approval_state = None
self._approval_deadline = 0
self._paint_now()
_outcome_labels = {
"once": "allowed once",
"session": "allowed for session",
"always": "added to allowlist",
"deny": "denied",
}
self._persist_prompt_summary(
"", "Approval", command,
_outcome_labels.get(result, str(result)),
)
return result
except queue.Empty:
remaining = self._approval_deadline - _time.monotonic()