mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-13 03:52:00 +00:00
fix(tui): close slash parity gaps with CLI (#20339)
* fix(tui): close slash parity gaps with CLI Route unsupported /skills subcommands through slash.exec, support /new <name> titles, and handle /redraw natively so TUI behavior matches classic CLI. Also filter gateway-only commands out of the TUI catalog while keeping /status discoverable. * fix(tui): run remaining CLI parity paths natively Forward chat launch flags into the TUI runtime and handle live-session status and skill reloads in the gateway process so TUI state no longer depends on the slash worker's stale CLI instance. * fix(tui): block stale snapshot restores Prevent snapshot restore from running through the isolated slash worker because it mutates disk state without refreshing the live TUI agent. * chore: uptick * fix(tui): guard async session title updates Handle failures from the fire-and-forget session.title RPC so title-setting errors do not surface as unhandled promise rejections while preserving session-scoped messaging.
This commit is contained in:
parent
acca3ec3af
commit
794f48766c
14 changed files with 1266 additions and 284 deletions
|
|
@ -18,6 +18,27 @@ describe('createSlashHandler', () => {
|
|||
expect(getOverlayState().picker).toBe(true)
|
||||
})
|
||||
|
||||
it('handles /redraw locally without slash worker fallback', () => {
|
||||
const ctx = buildCtx()
|
||||
|
||||
expect(createSlashHandler(ctx)('/redraw')).toBe(true)
|
||||
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
|
||||
expect(ctx.transcript.sys).toHaveBeenCalledWith('ui redrawn')
|
||||
})
|
||||
|
||||
it('routes /status to live session.status instead of slash worker', async () => {
|
||||
patchUiState({ sid: 'sid-abc' })
|
||||
const rpc = vi.fn(() => Promise.resolve({ output: 'Hermes TUI Status' }))
|
||||
const ctx = buildCtx({ gateway: { ...buildGateway(), rpc } })
|
||||
|
||||
expect(createSlashHandler(ctx)('/status')).toBe(true)
|
||||
expect(rpc).toHaveBeenCalledWith('session.status', { session_id: 'sid-abc' })
|
||||
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
|
||||
await vi.waitFor(() => {
|
||||
expect(ctx.transcript.page).toHaveBeenCalledWith('Hermes TUI Status', 'Status')
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps typed /model switches session-scoped by default', async () => {
|
||||
patchUiState({ sid: 'sid-abc' })
|
||||
|
||||
|
|
@ -157,12 +178,49 @@ describe('createSlashHandler', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('shows usage for an unknown /skills subcommand', () => {
|
||||
it('delegates non-native /skills subcommands to slash.exec', () => {
|
||||
const ctx = buildCtx()
|
||||
|
||||
createSlashHandler(ctx)('/skills zzz')
|
||||
createSlashHandler(ctx)('/skills check')
|
||||
expect(ctx.gateway.rpc).not.toHaveBeenCalled()
|
||||
expect(ctx.transcript.sys).toHaveBeenCalledWith(expect.stringContaining('usage: /skills'))
|
||||
expect(ctx.gateway.gw.request).toHaveBeenCalledWith('slash.exec', {
|
||||
command: 'skills check',
|
||||
session_id: null
|
||||
})
|
||||
})
|
||||
|
||||
it('passes /new <title> through to the session lifecycle', () => {
|
||||
const ctx = buildCtx()
|
||||
|
||||
createSlashHandler(ctx)('/new sprint planning')
|
||||
getOverlayState().confirm?.onConfirm()
|
||||
|
||||
expect(ctx.session.newSession).toHaveBeenCalledWith('new session started', 'sprint planning')
|
||||
expect(ctx.gateway.rpc).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('reloads skills in the live gateway and refreshes the catalog', async () => {
|
||||
const rpc = vi.fn((method: string) => {
|
||||
if (method === 'skills.reload') {
|
||||
return Promise.resolve({ output: '42 skill(s) available' })
|
||||
}
|
||||
if (method === 'commands.catalog') {
|
||||
return Promise.resolve({ canon: { '/new-skill': '/new-skill' }, pairs: [['/new-skill', 'demo']] })
|
||||
}
|
||||
return Promise.resolve({})
|
||||
})
|
||||
const ctx = buildCtx({ gateway: { ...buildGateway(), rpc } })
|
||||
|
||||
createSlashHandler(ctx)('/reload-skills')
|
||||
|
||||
expect(rpc).toHaveBeenCalledWith('skills.reload', {})
|
||||
await vi.waitFor(() => {
|
||||
expect(ctx.transcript.page).toHaveBeenCalledWith('42 skill(s) available', 'Reload Skills')
|
||||
expect(ctx.local.setCatalog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ canon: { '/new-skill': '/new-skill' }, pairs: [['/new-skill', 'demo']] })
|
||||
)
|
||||
})
|
||||
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Regressions from Copilot review on #19835: /voice output + frontend
|
||||
|
|
@ -192,9 +250,7 @@ describe('createSlashHandler', () => {
|
|||
expect(ctx.transcript.sys).toHaveBeenCalledWith('Voice mode enabled')
|
||||
expect(ctx.transcript.sys).toHaveBeenCalledWith(' Alt+R to start/stop recording')
|
||||
})
|
||||
expect(ctx.voice.setVoiceRecordKey).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ ch: 'r', mod: 'alt' })
|
||||
)
|
||||
expect(ctx.voice.setVoiceRecordKey).toHaveBeenCalledWith(expect.objectContaining({ ch: 'r', mod: 'alt' }))
|
||||
})
|
||||
|
||||
it('/voice falls back to Ctrl+B when the gateway response omits record_key', async () => {
|
||||
|
|
@ -447,17 +503,17 @@ describe('createSlashHandler', () => {
|
|||
local: {
|
||||
catalog: {
|
||||
canon: {
|
||||
'/status': '/status',
|
||||
'/statusbar': '/statusbar'
|
||||
'/profile': '/profile',
|
||||
'/plugins': '/plugins'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(createSlashHandler(ctx)('/status')).toBe(true)
|
||||
expect(createSlashHandler(ctx)('/profile')).toBe(true)
|
||||
await vi.waitFor(() => {
|
||||
expect(ctx.gateway.gw.request).toHaveBeenCalledWith('slash.exec', {
|
||||
command: 'status',
|
||||
command: 'profile',
|
||||
session_id: null
|
||||
})
|
||||
})
|
||||
|
|
@ -675,7 +731,8 @@ const buildLocal = () => ({
|
|||
catalog: null,
|
||||
getHistoryItems: vi.fn(() => []),
|
||||
getLastUserMsg: vi.fn(() => ''),
|
||||
maybeWarn: vi.fn()
|
||||
maybeWarn: vi.fn(),
|
||||
setCatalog: vi.fn()
|
||||
})
|
||||
|
||||
const buildSession = () => ({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue