Skip to content

feat: confirm prompt for destructive slash commands (#4069) - #22687

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-08dd3c21
May 9, 2026
Merged

feat: confirm prompt for destructive slash commands (#4069)#22687
teknium1 merged 1 commit into
mainfrom
hermes/hermes-08dd3c21

Conversation

@teknium1

@teknium1 teknium1 commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

/clear, /new, /reset, and /undo now ask the user to confirm before discarding conversation state — same three-option prompt (Approve Once / Always Approve / Cancel) on every surface, with native yes/no buttons on platforms that support them.

Closes #4069.

How it works

Routes through the existing tools.slash_confirm primitive (already wired for /reload-mcp):

  • Telegram / Discord / Slack — native inline yes/no buttons via each adapter's send_slash_confirm override
  • Other gateway platforms — text-fallback prompt; user replies /approve, /always, or /cancel
  • Classic prompt_toolkit CLI — local _prompt_text_input 1/2/3 prompt, mirrors _confirm_and_reload_mcp
  • TUI — keeps its existing modal overlay (feat(tui): UX pack — stable picker keys, /clear confirm, light-theme preset #12312) untouched

Changes

  • hermes_cli/config.py: new approvals.destructive_slash_confirm key (default true)
  • cli.py: _confirm_destructive_slash helper; /clear, /new, /undo dispatch sites gated
  • gateway/run.py: _maybe_confirm_destructive_slash wraps _handle_reset_command and _handle_undo_command; _request_slash_confirm made tolerant of bare-runner test harnesses
  • Tests: 18 new unit tests (config defaults, CLI helper, gateway wrapper paths) + 2 existing test fixtures opted out of the gate (focused tests stay focused)

Validation

Before After
/clear, /new, /undo on CLI wipe history immediately three-option confirm
/new, /undo in messengers wipe immediately native buttons (TG/Discord/Slack) or text fallback
approvals.destructive_slash_confirm: false (key didn't exist) runs immediately, no prompt
Picking "Always Approve" n/a persists false to config; subsequent calls silent
  • scripts/run_tests.sh tests/cli/test_destructive_slash_confirm.py tests/gateway/test_destructive_slash_confirm.py tests/hermes_cli/test_destructive_slash_confirm_gate.py — 18/18 passing
  • scripts/run_tests.sh tests/cli/ — 647/647 passing
  • scripts/run_tests.sh tests/gateway/ — 5114/5118 passing (4 pre-existing flakes unrelated to this change, verified on origin/main)
  • E2E: real HERMES_HOME, real config loader, real tools.slash_confirm round-trip — all four resolution paths (initial prompt, once, always→persists→silences, cancel) verified

Out of scope

  • /cron remove — separate domain (scheduled jobs, not session history); the issue mentioned it but it's a different command surface
  • TUI overlay env-var (HERMES_TUI_NO_CONFIRM) left as-is; can be unified with the new config key in a follow-up if desired

/clear, /new, /reset, and /undo now ask the user to confirm before
discarding conversation state — three-option prompt routed through the
existing tools.slash_confirm primitive.

Native yes/no buttons render on Telegram, Discord, and Slack (their
adapters already implement send_slash_confirm); other platforms get a
text-fallback prompt and reply with /approve, /always, or /cancel.

The classic prompt_toolkit CLI uses the same three-option flow via the
established _prompt_text_input pattern (see _confirm_and_reload_mcp).
TUI keeps its existing modal overlay (#12312).

Gated by new config key approvals.destructive_slash_confirm (default
true). Picking 'Always Approve' flips the gate to false so subsequent
destructive commands run silently — matches the established
mcp_reload_confirm UX.

Out of scope: /cron remove (separate domain — scheduled jobs, not
session history). Existing TUI overlay env-var (HERMES_TUI_NO_CONFIRM)
left unchanged; cosmetic unification can come later.

Closes #4069.
@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-08dd3c21 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 7897 on HEAD, 7887 on base (🆕 +10)

🆕 New issues (7):

Rule Count
invalid-argument-type 3
unresolved-import 1
not-subscriptable 1
invalid-return-type 1
unresolved-attribute 1
First entries
tests/gateway/test_destructive_slash_confirm.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_destructive_slash_confirm_gate.py:26: [not-subscriptable] not-subscriptable: Cannot subscript object of type `int` with no `__getitem__` method
cli.py:6909: [invalid-return-type] invalid-return-type: Return type does not match returned value: expected `bool`, found `None`
tests/hermes_cli/test_destructive_slash_confirm_gate.py:26: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["destructive_slash_confirm"]` on object of type `str`
tests/hermes_cli/test_destructive_slash_confirm_gate.py:26: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["destructive_slash_confirm"]` on object of type `list[Unknown]`
tests/hermes_cli/test_destructive_slash_confirm_gate.py:26: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["destructive_slash_confirm"]` on object of type `list[str]`
tests/hermes_cli/test_destructive_slash_confirm_gate.py:32: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `int` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 25 union elements`

✅ Fixed issues: none

Unchanged: 4174 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UX] No confirmation for destructive slash commands (/clear, /reset, /new, /undo)

1 participant