fix(#576): surface command.dispatch 4018 errors instead of silent dead-tap (#603)

* fix(#576): surface command.dispatch 4018 errors instead of silent dead-tap

* fix(#576): fall back to slash.exec for command.dispatch 4018 registry misses

* fix(#576): block cli_only/TUI-only commands at dispatch (blocklist deliverable)

* review(#603): address agy findings — tighten 4018 match, case-insensitive filter, add double-fault + blank-output tests

* fix(#576): surface command dispatch outputs and update tests

---------

Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-15 23:43:00 +03:00
1 parent e273488adc
commit d9f20f8eb2
5 files changed
+281 -93

No files matched your search

@@ -24,3 +24,66 @@ data class CommandCategory(
val name: String,
val pairs: List<List<String>>,
)
/**
* Desktop/CLI-only and TUI-only slash commands that do NOT function on mobile
* (issue #574). Source of truth for the backend `cli_only` / TUI-only flags
* returned by `commands.catalog`.
*
* Two consumers:
* 1. The slash-command suggestion menu hides these (so users don't discover
* dead commands).
* 2. [com.m57.hermescontrol.ui.chat.ChatViewModel.handleSlashCommand] blocks
* them at dispatch time (issue #576, deliverable #3) — a user who types one
* anyway gets a clear "not supported on mobile" message instead of a doomed
* RPC that produces TUI-only output or a confusing error.
*/
object CommandBlocklist {
val UNSUPPORTED: Set<String> =
setOf(
"/clear",
"/redraw",
"/history",
"/save",
"/prompt",
"/snapshot",
"/handoff",
"/journey",
"/config",
"/statusbar",
"/timestamps",
"/verbose",
"/skin",
"/indicator",
"/busy",
"/tools",
"/toolsets",
"/skills",
"/pet",
"/hatch",
"/cron",
"/reload",
"/browser",
"/plugins",
"/billing",
"/platforms",
"/copy",
"/paste",
"/image",
"/quit",
// TUI-only extras (meaningless outside the TUI)
"/compact",
"/logs",
"/mouse",
)
/**
* True if [command] (e.g. "/clear" or "/CLEAR arg") is blocked on mobile.
* Matches on the exact command name (the token before the first space),
* so "/reload" is blocked while "/reload-mcp" (a supported command) is not.
*/
fun contains(command: String): Boolean {
val name = command.split(" ", limit = 2)[0].lowercase()
return name in UNSUPPORTED
}
}