mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
fix(desktop): render send/prefill directive notices (/goal, /undo) (#49073)
The desktop slash dispatcher dropped the `notice` field on `send` and
never handled `prefill` directives at all. `/goal <text>` returns
{type: send, notice: "⊙ Goal set …", message} from command.dispatch —
the desktop submitted the goal text as a plain prompt with no feedback,
so the goal looked like it did nothing. `/undo` returns a prefill
directive that fell through to "invalid response".
- types: add `notice?` to SendCommandDispatchResponse; add
PrefillCommandDispatchResponse to the union.
- parseCommandDispatch: keep `notice` on send, parse prefill.
- runExec dispatcher: render the notice as a system line before acting,
and handle prefill by dropping the message into the composer for
editing (mirrors the TUI's createSlashHandler).
Tests: parseCommandDispatch send-notice / prefill cases.
This commit is contained in:
parent
e00b965406
commit
b936f92b25
4 changed files with 62 additions and 2 deletions
|
|
@ -32,6 +32,7 @@ import {
|
|||
clearComposerAttachments,
|
||||
type ComposerAttachment,
|
||||
setComposerAttachmentUploadState,
|
||||
setComposerDraft,
|
||||
terminalContextBlocksFromDraft,
|
||||
updateComposerAttachment
|
||||
} from '@/store/composer'
|
||||
|
|
@ -951,8 +952,26 @@ export function usePromptActions({
|
|||
return
|
||||
}
|
||||
|
||||
// send / prefill carry an optional `notice` (e.g. "⊙ Goal set …")
|
||||
// that the backend wants shown as a system line before the message
|
||||
// is acted on. Mirrors the TUI's createSlashHandler — without it a
|
||||
// `/goal <text>` looked like it did nothing.
|
||||
if ((dispatch.type === 'send' || dispatch.type === 'prefill') && dispatch.notice?.trim()) {
|
||||
renderSlashOutput(dispatch.notice.trim())
|
||||
}
|
||||
|
||||
const message = ('message' in dispatch ? dispatch.message : '')?.trim() ?? ''
|
||||
|
||||
// /undo returns a prefill directive: drop the backed-up message into
|
||||
// the composer for editing instead of submitting it immediately.
|
||||
if (dispatch.type === 'prefill') {
|
||||
if (message) {
|
||||
setComposerDraft(message)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
renderSlashOutput(
|
||||
`/${name}: ${dispatch.type === 'skill' ? 'skill payload missing message' : 'empty message'}`
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@ export interface SkillCommandDispatchResponse {
|
|||
export interface SendCommandDispatchResponse {
|
||||
type: 'send'
|
||||
message: string
|
||||
notice?: string
|
||||
}
|
||||
|
||||
export interface PrefillCommandDispatchResponse {
|
||||
type: 'prefill'
|
||||
message: string
|
||||
notice?: string
|
||||
}
|
||||
|
||||
export type CommandDispatchResponse =
|
||||
|
|
@ -113,6 +120,7 @@ export type CommandDispatchResponse =
|
|||
| AliasCommandDispatchResponse
|
||||
| SkillCommandDispatchResponse
|
||||
| SendCommandDispatchResponse
|
||||
| PrefillCommandDispatchResponse
|
||||
|
||||
export type SidebarNavId = 'artifacts' | 'command-center' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|||
|
||||
import type { ComposerAttachment } from '@/store/composer'
|
||||
|
||||
import { coerceThinkingText, optimisticAttachmentRef } from './chat-runtime'
|
||||
import { coerceThinkingText, optimisticAttachmentRef, parseCommandDispatch } from './chat-runtime'
|
||||
|
||||
const DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANS'
|
||||
|
||||
|
|
@ -52,3 +52,31 @@ describe('coerceThinkingText', () => {
|
|||
).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseCommandDispatch', () => {
|
||||
it('keeps the notice on a send directive (e.g. /goal set)', () => {
|
||||
// The backend's /goal set returns {type:send, notice:"⊙ Goal set …", message}.
|
||||
// Dropping the notice made /goal look like it did nothing in the desktop app.
|
||||
const parsed = parseCommandDispatch({ type: 'send', notice: '⊙ Goal set', message: 'do the thing' })
|
||||
|
||||
expect(parsed).toEqual({ type: 'send', message: 'do the thing', notice: '⊙ Goal set' })
|
||||
})
|
||||
|
||||
it('keeps message-only send directives working (no notice)', () => {
|
||||
expect(parseCommandDispatch({ type: 'send', message: 'hi' })).toEqual({
|
||||
type: 'send',
|
||||
message: 'hi',
|
||||
notice: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('parses a prefill directive with its notice (e.g. /undo)', () => {
|
||||
const parsed = parseCommandDispatch({ type: 'prefill', notice: 'backed up 1 turn', message: 'edit me' })
|
||||
|
||||
expect(parsed).toEqual({ type: 'prefill', message: 'edit me', notice: 'backed up 1 turn' })
|
||||
})
|
||||
|
||||
it('rejects a prefill directive missing its message', () => {
|
||||
expect(parseCommandDispatch({ type: 'prefill', notice: 'x' })).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -238,7 +238,12 @@ export function parseCommandDispatch(raw: unknown): CommandDispatchResponse | nu
|
|||
return typeof row.name === 'string' ? { type: 'skill', name: row.name, message: str(row.message) } : null
|
||||
|
||||
case 'send':
|
||||
return typeof row.message === 'string' ? { type: 'send', message: row.message } : null
|
||||
return typeof row.message === 'string' ? { type: 'send', message: row.message, notice: str(row.notice) } : null
|
||||
|
||||
case 'prefill':
|
||||
return typeof row.message === 'string'
|
||||
? { type: 'prefill', message: row.message, notice: str(row.notice) }
|
||||
: null
|
||||
|
||||
default:
|
||||
return null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue