With skills.write_approval on, every skill write is staged to the
file-backed pending store, but no non-CLI surface could review it:
the desktop resolver marked the whole /skills family as the sidebar's
(desktop="settings") and discarded the subcommand token, and
tui_gateway command.dispatch had no route for /skills or /memory —
a failed slash worker turned both into the routing refusal, so staged
writes accumulated silently (#98330, #123315, #118812).
- hermes_cli/commands.py: new CommandDef.desktop_subcommands field;
/skills declares its write-approval review slice
(pending/approve/reject/diff/approval) and drops desktop="settings".
command_desktop_meta serializes the allowlist (empty tuple = deny
all); gateway contract regenerated.
- apps/desktop: the resolver threads the typed arg —
desktopSubcommandAllowlist, an exec gate
(desktopSubcommandUnavailableMessage) that renders an actionable
refusal for hub mutations instead of forwarding them, completion
filtering so the popover never suggests a dead end, and the
isDesktopSlashCommand(name, arg) threading at the runExec gate.
An explicit /skills spec keeps a current Desktop able to review
staged writes even against an older backend catalog.
- tui_gateway command.dispatch: _cmd_skills answers only the
registry-declared review slice under _session_home_scope (an
unlisted subcommand keeps the 4018 routing refusal); _cmd_memory
routes the shared handler with the live agent's store or a fresh
on-disk one and a profile-scoped config setter. Gate-off still
answers while staged writes exist (gateway parity, never stranded).
- tools/write_approval: _staged()'s review hint is surface-aware —
slash-capable surfaces (CLI/TUI/desktop/chat gateway) keep
"<command> pending"; headless contexts (cron, kanban, api_server,
webhook delivery) get the pending directory path instead of a
command nobody can deliver.
Batch skill writes also rendered "(batch on ...)" for /skills diff —
skill_pending_diff had no action='batch' case; ops now diff in order
against what earlier ops of the same batch left (finn763's #123861).
Supersedes #98794 (registry-driven desktop_subcommands + resolver
allowlist), #105773, #118821 (command.dispatch handlers + arg
threading), #123861 (batch diff renderer). #107799 is superseded by
the same subcommand-aware resolution.
Co-authored-by: foma-agent <foma@bokonon.ai>
Co-authored-by: Jason Havenaar <35441875+jasonHav@users.noreply.github.com>
Co-authored-by: finn763 <165816600+finn763@users.noreply.github.com>
Co-authored-by: chelsealong <chelsealong@126.com>