fix(desktop): surface skill & quick-command slash commands in the palette (#38531)
The desktop chat app's slash curation (desktop-slash-commands.ts) only suggested the ~19 curated built-ins. isDesktopSlashSuggestion required membership in DESKTOP_COMMANDS, so every skill-derived command and user quick_command was silently dropped from both completion paths (commands.catalog empty-query + complete.slash typed-query) and from filterDesktopCommandsCatalog — even though isDesktopSlashCommand let them EXECUTE when typed in full. The tui_gateway backend already includes skills in both RPCs; the gap was purely renderer-side. Add isDesktopSlashExtensionCommand() (= not-a-known-Hermes-built-in, the same predicate that already gates execution) and let extensions through the suggestion path. The catalog filter routes through isDesktopSlashSuggestion, so skill/quick-command categories and pairs are kept automatically.
This commit is contained in:
15
AGENTS.md
15
AGENTS.md
@ -283,6 +283,21 @@ The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes
|
||||
|
||||
**Structured React UI around the TUI is allowed when it is not a second chat surface.** Sidebar widgets, inspectors, summaries, status panels, and similar supporting views (e.g. `ChatSidebar`, `ModelPickerDialog`, `ToolCall`) are fine when they complement the embedded TUI rather than replacing the transcript / composer / terminal. Keep their state independent of the PTY child's session and surface their failures non-destructively so the terminal pane keeps working unimpaired.
|
||||
|
||||
### Electron Desktop Chat App (`apps/desktop/`)
|
||||
|
||||
A **separate** chat surface from both the classic CLI and the dashboard's embedded TUI. It is an Electron + React + nanostore renderer (`@assistant-ui/react`) that talks to a `tui_gateway` backend over JSON-RPC (`requestGateway(method, params)`). It does NOT embed `hermes --tui` — it has its own composer, transcript, and slash-command pipeline. Route desktop bugs to the `hermes-desktop-app-work` skill, not `hermes-dashboard-work`.
|
||||
|
||||
**Slash commands in the desktop app are curated client-side, then dispatched to the backend.** The pipeline:
|
||||
|
||||
- **Backend already provides everything.** `tui_gateway/server.py` `commands.catalog` (empty-query list) and `complete.slash` (typed-query completions) both include built-in commands, user `quick_commands`, AND skill-derived commands (`scan_skill_commands()` / `get_skill_commands()`). The desktop app does not need a new RPC to see skills.
|
||||
- **The renderer curates via `apps/desktop/src/lib/desktop-slash-commands.ts`.** This is the load-bearing file. It holds `DESKTOP_COMMANDS` (the ~19 built-ins shown in the palette) plus block-lists for terminal-only / messaging-only / picker-owned / settings-owned / advanced commands that should NOT clutter the desktop popover.
|
||||
- `isDesktopSlashCommand(name)` — gates **execution**. Returns true for built-ins AND for any non-built-in (skill / quick command), so typed extension commands run.
|
||||
- `isDesktopSlashSuggestion(name)` — gates **discovery/completion**. Used by BOTH completion paths in `app/chat/composer/hooks/use-slash-completions.ts` (empty-query catalog filter + typed-query `complete.slash` filter) and by `filterDesktopCommandsCatalog`.
|
||||
- `isDesktopSlashExtensionCommand(name)` — true when the command is NOT a known Hermes built-in (i.e. a skill or user quick command). Both suggestion and catalog-filter paths allow extensions through so skill commands surface in the palette. (Added when fixing "skill commands missing from the desktop slash palette" — the curated allow-list was silently dropping every skill/quick command from completions even though they executed fine when typed.)
|
||||
- **Dispatch** lives in `app/session/hooks/use-prompt-actions.ts` (`runSlash`): built-ins that the desktop owns (`/skin`, `/help`, `/new`, …) are handled locally or via `commands.catalog`; everything else goes to `slash.exec`, falling back to `command.dispatch` (which the gateway resolves into skill / alias / exec directives). A skill command resolves to `{type: "skill", message}` and is submitted as a normal prompt.
|
||||
|
||||
**Rule:** the desktop slash palette's curation is about hiding noise (terminal-only / messaging-only built-ins), NOT about hiding user-activated extensions. Skill commands and `quick_commands` are extensions the backend surfaces — they belong in completions. If you tighten `desktop-slash-commands.ts`, keep `isDesktopSlashExtensionCommand` flowing into both the suggestion and catalog-filter paths. Tests: `apps/desktop/src/lib/desktop-slash-commands.test.ts` (run via the repo-root `vitest`, since `apps/desktop` resolves deps from the root workspace install).
|
||||
|
||||
---
|
||||
|
||||
## Adding New Tools
|
||||
|
||||
@ -17,8 +17,9 @@ describe('desktop slash command curation', () => {
|
||||
expect(isDesktopSlashSuggestion('/usage')).toBe(true)
|
||||
})
|
||||
|
||||
it('lets explicitly typed extension commands run without suggesting them', () => {
|
||||
expect(isDesktopSlashSuggestion('/my-skill')).toBe(false)
|
||||
it('surfaces skill and quick commands (extensions) in suggestions and lets them run', () => {
|
||||
expect(isDesktopSlashSuggestion('/my-skill')).toBe(true)
|
||||
expect(isDesktopSlashSuggestion('/gif-search')).toBe(true)
|
||||
expect(isDesktopSlashCommand('/my-skill')).toBe(true)
|
||||
})
|
||||
|
||||
@ -38,7 +39,7 @@ describe('desktop slash command curation', () => {
|
||||
expect(isDesktopSlashCommand('/reset')).toBe(true)
|
||||
})
|
||||
|
||||
it('filters command catalogs down to core desktop commands', () => {
|
||||
it('filters built-in catalog noise but keeps skill / quick-command extensions', () => {
|
||||
const filtered = filterDesktopCommandsCatalog({
|
||||
categories: [
|
||||
{
|
||||
@ -61,8 +62,14 @@ describe('desktop slash command curation', () => {
|
||||
skill_count: 2
|
||||
})
|
||||
|
||||
expect(filtered.categories).toEqual([{ name: 'Session', pairs: [['/new', 'Start a new desktop chat']] }])
|
||||
expect(filtered.pairs).toEqual([['/new', 'Start a new desktop chat']])
|
||||
expect(filtered.categories).toEqual([
|
||||
{ name: 'Session', pairs: [['/new', 'Start a new desktop chat']] },
|
||||
{ name: 'User commands', pairs: [['/ship-it', 'Run release checklist']] }
|
||||
])
|
||||
expect(filtered.pairs).toEqual([
|
||||
['/new', 'Start a new desktop chat'],
|
||||
['/ship-it', 'Run release checklist']
|
||||
])
|
||||
expect(filtered.skill_count).toBe(2)
|
||||
})
|
||||
|
||||
|
||||
@ -150,10 +150,35 @@ export function isDesktopSlashCommand(command: string): boolean {
|
||||
return DESKTOP_COMMANDS.has(canonical) || !isKnownHermesSlashCommand(normalized)
|
||||
}
|
||||
|
||||
/**
|
||||
* An "extension" command is anything the backend surfaces that is NOT one of
|
||||
* Hermes' built-in slash commands — i.e. skill commands (`/gif-search`,
|
||||
* `/codex`, …) and user-defined quick commands. These are user-activated, so
|
||||
* they should appear in the desktop slash palette even though they aren't in
|
||||
* the curated `DESKTOP_COMMANDS` allow-list. This mirrors the predicate in
|
||||
* `isDesktopSlashCommand` that already lets them EXECUTE when typed.
|
||||
*/
|
||||
export function isDesktopSlashExtensionCommand(command: string): boolean {
|
||||
const normalized = normalizeCommand(command)
|
||||
|
||||
if (!normalized || normalized === '/') {
|
||||
return false
|
||||
}
|
||||
|
||||
return !isKnownHermesSlashCommand(normalized)
|
||||
}
|
||||
|
||||
export function isDesktopSlashSuggestion(command: string): boolean {
|
||||
const normalized = normalizeCommand(command)
|
||||
const canonical = canonicalDesktopSlashCommand(normalized)
|
||||
|
||||
// Surface skill / quick commands (extensions the backend provides) alongside
|
||||
// the curated built-ins. Built-in aliases stay hidden so the popover isn't
|
||||
// cluttered with duplicates.
|
||||
if (isDesktopSlashExtensionCommand(normalized)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return DESKTOP_COMMANDS.has(canonical) && !DESKTOP_ALIASES.has(normalized)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user