fix(desktop): slash commands bypass queue when busy and chip id suffix leak (#39289)

Two fixes for desktop app slash command handling:

1. Slash commands submitted while the agent is busy now execute
   immediately instead of being queued. Previously submitDraft()
   unconditionally queued any draft when busy, but slash commands
   are client-side operations or self-contained gateway RPCs that
   should run regardless of busy state (matching TUI behavior).
   executeSlashCommand already has its own per-command busy guard
   for commands that genuinely need an idle session.

2. Slash command trigger items no longer leak the "|index" suffix
   from their item.id into the serialized chip text. The
   toItem callback now sets rawText in metadata so
   hermesDirectiveFormatter.serialize takes the direct-insertion
   path instead of the legacy @type:id fallback. This also means
   slash commands enter the composer as plain text (not chips),
   matching selectSkinSlashCommand and TUI behavior.
This commit is contained in:
ethernet
2026-06-04 17:06:45 -04:00
committed by GitHub
parent acce1a2452
commit 1eeb7da2e6
2 changed files with 22 additions and 2 deletions

View File

@ -16,6 +16,7 @@ interface SlashItemMetadata extends Record<string, string> {
command: string
display: string
meta: string
rawText?: string
}
function textValue(value: unknown, fallback = ''): string {
@ -91,7 +92,13 @@ export function useSlashCompletions(options: { gateway: HermesGateway | null }):
const metadata: SlashItemMetadata = {
command,
display,
meta
meta,
// Provide rawText so hermesDirectiveFormatter.serialize uses the
// direct-insertion path instead of the legacy @type:id fallback.
// Without this, the item.id (which includes a "|index" suffix for
// trigger-adapter uniqueness) leaks into the serialized chip text
// and the submitted command.
rawText: command
}
return {

View File

@ -18,6 +18,7 @@ import { Button } from '@/components/ui/button'
import { useMediaQuery } from '@/hooks/use-media-query'
import { useResizeObserver } from '@/hooks/use-resize-observer'
import { chatMessageText } from '@/lib/chat-messages'
import { SLASH_COMMAND_RE } from '@/lib/chat-runtime'
import { DATA_IMAGE_URL_RE } from '@/lib/embedded-images'
import { triggerHaptic } from '@/lib/haptics'
import { cn } from '@/lib/utils'
@ -1037,7 +1038,19 @@ export function ChatBar({
if (queueEdit) {
exitQueuedEdit('save')
} else if (busy) {
if (hasComposerPayload) {
// Slash commands should execute immediately even while the agent is
// busy — they're client-side operations (/yolo, /skin, /new, /help,
// etc.) or self-contained gateway RPCs (/status, /compress). onSubmit
// routes them to executeSlashCommand, which has its own per-command
// busy guard for commands that genuinely need an idle session (skill
// /send directives). Queuing them would make every slash command wait
// for the current turn to finish, which is how the TUI never behaves.
if (!attachments.length && SLASH_COMMAND_RE.test(draft.trim())) {
const submitted = draft
triggerHaptic('submit')
clearDraft()
void onSubmit(submitted)
} else if (hasComposerPayload) {
queueCurrentDraft()
} else {
// Stop button: an explicit interrupt must actually halt the running