mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +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
|
|
@ -1,11 +1,14 @@
|
|||
import { forceRedraw } from '@hermes/ink'
|
||||
|
||||
import { NO_CONFIRM_DESTRUCTIVE } from '../../../config/env.js'
|
||||
import { dailyFortune, randomFortune } from '../../../content/fortunes.js'
|
||||
import { HOTKEYS } from '../../../content/hotkeys.js'
|
||||
import { isSectionName, nextDetailsMode, parseDetailsMode, SECTION_NAMES } from '../../../domain/details.js'
|
||||
import { SECTION_NAMES, isSectionName, nextDetailsMode, parseDetailsMode } from '../../../domain/details.js'
|
||||
import type {
|
||||
ConfigGetValueResponse,
|
||||
ConfigSetResponse,
|
||||
SessionSaveResponse,
|
||||
SessionStatusResponse,
|
||||
SessionSteerResponse,
|
||||
SessionTitleResponse,
|
||||
SessionUndoResponse
|
||||
|
|
@ -112,16 +115,17 @@ export const coreCommands: SlashCommand[] = [
|
|||
aliases: ['new'],
|
||||
help: 'start a new session',
|
||||
name: 'clear',
|
||||
run: (_arg, ctx, cmd) => {
|
||||
run: (arg, ctx, cmd) => {
|
||||
if (ctx.session.guardBusySessionSwitch('switch sessions')) {
|
||||
return
|
||||
}
|
||||
|
||||
const isNew = cmd.startsWith('/new')
|
||||
const requestedTitle = isNew ? arg.trim() : ''
|
||||
|
||||
const commit = () => {
|
||||
patchUiState({ status: 'forging session…' })
|
||||
ctx.session.newSession(isNew ? 'new session started' : undefined)
|
||||
ctx.session.newSession(isNew ? 'new session started' : undefined, requestedTitle || undefined)
|
||||
}
|
||||
|
||||
if (NO_CONFIRM_DESTRUCTIVE) {
|
||||
|
|
@ -141,6 +145,30 @@ export const coreCommands: SlashCommand[] = [
|
|||
}
|
||||
},
|
||||
|
||||
{
|
||||
help: 'force a full UI repaint',
|
||||
name: 'redraw',
|
||||
run: (_arg, ctx) => {
|
||||
forceRedraw(process.stdout)
|
||||
ctx.transcript.sys('ui redrawn')
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
help: 'show live session info',
|
||||
name: 'status',
|
||||
run: (_arg, ctx) => {
|
||||
if (!ctx.sid) {
|
||||
return ctx.transcript.sys('no active session')
|
||||
}
|
||||
|
||||
ctx.gateway
|
||||
.rpc<SessionStatusResponse>('session.status', { session_id: ctx.sid })
|
||||
.then(ctx.guarded<SessionStatusResponse>(r => ctx.transcript.page(r.output || '(no status)', 'Status')))
|
||||
.catch(ctx.guardedErr)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
help: 'resume a prior session',
|
||||
name: 'resume',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type {
|
||||
BrowserManageResponse,
|
||||
CommandsCatalogResponse,
|
||||
DelegationPauseResponse,
|
||||
ProcessStopResponse,
|
||||
ReloadEnvResponse,
|
||||
|
|
@ -56,6 +57,10 @@ interface SkillsBrowseResponse {
|
|||
total_pages?: number
|
||||
}
|
||||
|
||||
interface SkillsReloadResponse {
|
||||
output?: string
|
||||
}
|
||||
|
||||
export const opsCommands: SlashCommand[] = [
|
||||
{
|
||||
help: 'stop background processes',
|
||||
|
|
@ -435,10 +440,44 @@ export const opsCommands: SlashCommand[] = [
|
|||
}
|
||||
},
|
||||
|
||||
{
|
||||
aliases: ['reload_skills'],
|
||||
help: 're-scan installed skills in the live TUI gateway',
|
||||
name: 'reload-skills',
|
||||
run: (_arg, ctx) => {
|
||||
ctx.gateway
|
||||
.rpc<SkillsReloadResponse>('skills.reload', {})
|
||||
.then(
|
||||
ctx.guarded<SkillsReloadResponse>(r => {
|
||||
ctx.transcript.page(r.output || 'skills reloaded', 'Reload Skills')
|
||||
ctx.gateway
|
||||
.rpc<CommandsCatalogResponse>('commands.catalog', {})
|
||||
.then(
|
||||
ctx.guarded<CommandsCatalogResponse>(catalog => {
|
||||
if (!catalog?.pairs) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.local.setCatalog({
|
||||
canon: (catalog.canon ?? {}) as Record<string, string>,
|
||||
categories: catalog.categories ?? [],
|
||||
pairs: catalog.pairs as [string, string][],
|
||||
skillCount: (catalog.skill_count ?? 0) as number,
|
||||
sub: (catalog.sub ?? {}) as Record<string, string[]>
|
||||
})
|
||||
})
|
||||
)
|
||||
.catch(() => {})
|
||||
})
|
||||
)
|
||||
.catch(ctx.guardedErr)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
help: 'browse, inspect, install skills',
|
||||
name: 'skills',
|
||||
run: (arg, ctx) => {
|
||||
run: (arg, ctx, cmd) => {
|
||||
const text = arg.trim()
|
||||
|
||||
if (!text) {
|
||||
|
|
@ -449,6 +488,22 @@ export const opsCommands: SlashCommand[] = [
|
|||
const query = rest.join(' ').trim()
|
||||
const { rpc } = ctx.gateway
|
||||
const { panel, sys } = ctx.transcript
|
||||
const runViaSlashWorker = () => {
|
||||
ctx.gateway.gw
|
||||
.request<SlashExecResponse>('slash.exec', { command: cmd.slice(1), session_id: ctx.sid })
|
||||
.then(r => {
|
||||
if (ctx.stale()) {
|
||||
return
|
||||
}
|
||||
|
||||
const body = r?.output || '/skills: no output'
|
||||
const formatted = r?.warning ? `warning: ${r.warning}\n${body}` : body
|
||||
const long = formatted.length > 180 || formatted.split('\n').filter(Boolean).length > 2
|
||||
|
||||
long ? ctx.transcript.page(formatted, 'Skills') : ctx.transcript.sys(formatted)
|
||||
})
|
||||
.catch(ctx.guardedErr)
|
||||
}
|
||||
|
||||
if (sub === 'list') {
|
||||
rpc<SkillsListResponse>('skills.manage', { action: 'list' })
|
||||
|
|
@ -593,7 +648,7 @@ export const opsCommands: SlashCommand[] = [
|
|||
return
|
||||
}
|
||||
|
||||
sys('usage: /skills [list | inspect <n> | install <n> | search <q> | browse [page]]')
|
||||
runViaSlashWorker()
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue