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:
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user