mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-05 02:31:47 +00:00
feat: just more cleaning
This commit is contained in:
parent
46cef4b7fa
commit
4b4b4d47bc
24 changed files with 2852 additions and 829 deletions
|
|
@ -1,4 +1,12 @@
|
|||
import { HOTKEYS } from '../constants.js'
|
||||
import type {
|
||||
BackgroundStartResponse,
|
||||
SessionHistoryResponse,
|
||||
SlashExecResponse,
|
||||
ToolsConfigureResponse,
|
||||
ToolsListResponse,
|
||||
ToolsShowResponse
|
||||
} from '../gatewayTypes.js'
|
||||
import { writeOsc52Clipboard } from '../lib/osc52.js'
|
||||
import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
|
||||
import { fmtK } from '../lib/text.js'
|
||||
|
|
@ -12,7 +20,7 @@ import { getUiState, patchUiState } from './uiStore.js'
|
|||
export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => boolean {
|
||||
const { enqueue, hasSelection, paste, queueRef, selection, setInput } = ctx.composer
|
||||
const { gw, rpc } = ctx.gateway
|
||||
const { catalog, lastUserMsg, maybeWarn, messages } = ctx.local
|
||||
const { catalog, getHistoryItems, getLastUserMsg, maybeWarn } = ctx.local
|
||||
|
||||
const {
|
||||
closeSession,
|
||||
|
|
@ -24,9 +32,24 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
setSessionStartedAt
|
||||
} = ctx.session
|
||||
|
||||
const { page, panel, send, setHistoryItems, setMessages, sys, trimLastExchange } = ctx.transcript
|
||||
const { page, panel, send, setHistoryItems, sys, trimLastExchange } = ctx.transcript
|
||||
const { setVoiceEnabled } = ctx.voice
|
||||
|
||||
const showSlashOutput = (title: string, command: string) => {
|
||||
gw.request<SlashExecResponse>('slash.exec', { command, session_id: getUiState().sid })
|
||||
.then(r => {
|
||||
const text = r?.warning ? `warning: ${r.warning}\n${r?.output || '(no output)'}` : r?.output || '(no output)'
|
||||
const lines = text.split('\n').filter(Boolean)
|
||||
|
||||
if (lines.length > 2 || text.length > 180) {
|
||||
page(text, title)
|
||||
} else {
|
||||
sys(text)
|
||||
}
|
||||
})
|
||||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
}
|
||||
|
||||
const handler = (cmd: string): boolean => {
|
||||
const ui = getUiState()
|
||||
const detailsMode = ui.detailsMode
|
||||
|
|
@ -160,7 +183,7 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
}
|
||||
}
|
||||
|
||||
const all = messages.filter((m: any) => m.role === 'assistant')
|
||||
const all = getHistoryItems().filter((m: any) => m.role === 'assistant')
|
||||
|
||||
if (arg && Number.isNaN(parseInt(arg, 10))) {
|
||||
sys('usage: /copy [number]')
|
||||
|
|
@ -244,7 +267,6 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
}
|
||||
|
||||
if (r.removed > 0) {
|
||||
setMessages((prev: any[]) => trimLastExchange(prev))
|
||||
setHistoryItems((prev: any[]) => trimLastExchange(prev))
|
||||
sys(`undid ${r.removed} messages`)
|
||||
} else {
|
||||
|
|
@ -253,8 +275,9 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
})
|
||||
|
||||
return true
|
||||
case 'retry': {
|
||||
const lastUserMsg = getLastUserMsg()
|
||||
|
||||
case 'retry':
|
||||
if (!lastUserMsg) {
|
||||
sys('nothing to retry')
|
||||
|
||||
|
|
@ -273,7 +296,6 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
return
|
||||
}
|
||||
|
||||
setMessages((prev: any[]) => trimLastExchange(prev))
|
||||
setHistoryItems((prev: any[]) => trimLastExchange(prev))
|
||||
send(lastUserMsg)
|
||||
})
|
||||
|
|
@ -284,6 +306,7 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
send(lastUserMsg)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
case 'background':
|
||||
|
||||
|
|
@ -294,13 +317,15 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
return true
|
||||
}
|
||||
|
||||
rpc('prompt.background', { session_id: sid, text: arg }).then((r: any) => {
|
||||
if (!r?.task_id) {
|
||||
rpc<BackgroundStartResponse>('prompt.background', { session_id: sid, text: arg }).then(r => {
|
||||
const taskId = r?.task_id
|
||||
|
||||
if (!taskId) {
|
||||
return
|
||||
}
|
||||
|
||||
patchUiState(state => ({ ...state, bgTasks: new Set(state.bgTasks).add(r.task_id) }))
|
||||
sys(`bg ${r.task_id} started`)
|
||||
patchUiState(state => ({ ...state, bgTasks: new Set(state.bgTasks).add(taskId) }))
|
||||
sys(`bg ${taskId} started`)
|
||||
})
|
||||
|
||||
return true
|
||||
|
|
@ -483,7 +508,6 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
|
||||
if (Array.isArray(r.messages)) {
|
||||
const resumed = toTranscriptMessages(r.messages)
|
||||
setMessages(resumed)
|
||||
setHistoryItems(r.info ? [introMsg(r.info), ...resumed] : resumed)
|
||||
}
|
||||
|
||||
|
|
@ -526,7 +550,6 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
patchUiState({ sid: r.session_id })
|
||||
setSessionStartedAt(Date.now())
|
||||
setHistoryItems([])
|
||||
setMessages([])
|
||||
sys(`branched → ${r.title}`)
|
||||
}
|
||||
})
|
||||
|
|
@ -547,6 +570,26 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
|
||||
return true
|
||||
|
||||
case 'fast':
|
||||
showSlashOutput('Fast', cmd.slice(1))
|
||||
|
||||
return true
|
||||
|
||||
case 'debug':
|
||||
showSlashOutput('Debug', cmd.slice(1))
|
||||
|
||||
return true
|
||||
|
||||
case 'snapshot':
|
||||
showSlashOutput('Snapshot', cmd.slice(1))
|
||||
|
||||
return true
|
||||
|
||||
case 'platforms':
|
||||
showSlashOutput('Platforms', cmd.slice(1))
|
||||
|
||||
return true
|
||||
|
||||
case 'title':
|
||||
rpc('session.title', { session_id: sid, ...(arg ? { title: arg } : {}) }).then((r: any) => {
|
||||
if (!r) {
|
||||
|
|
@ -618,12 +661,28 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
return true
|
||||
|
||||
case 'history':
|
||||
rpc('session.history', { session_id: sid }).then((r: any) => {
|
||||
rpc<SessionHistoryResponse>('session.history', { session_id: sid }).then(r => {
|
||||
if (typeof r?.count !== 'number') {
|
||||
return
|
||||
}
|
||||
|
||||
sys(`${r.count} messages`)
|
||||
if (!r.messages?.length) {
|
||||
sys(`${r.count} messages`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const text = r.messages
|
||||
.map((msg, index) => {
|
||||
if (msg.role === 'tool') {
|
||||
return `[Tool #${index + 1}] ${msg.name || 'tool'} ${msg.context || ''}`.trim()
|
||||
}
|
||||
|
||||
return `[${msg.role === 'assistant' ? 'Hermes' : msg.role === 'user' ? 'You' : 'System'} #${index + 1}] ${msg.text || ''}`.trim()
|
||||
})
|
||||
.join('\n\n')
|
||||
|
||||
page(text, `History (${r.count})`)
|
||||
})
|
||||
|
||||
return true
|
||||
|
|
@ -917,29 +976,98 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
|
||||
return true
|
||||
case 'tools': {
|
||||
const [subcommand, ...names] = arg.trim().split(/\s+/).filter(Boolean)
|
||||
|
||||
case 'tools':
|
||||
rpc('tools.list', { session_id: sid })
|
||||
.then((r: any) => {
|
||||
if (!r) {
|
||||
return
|
||||
}
|
||||
if (!subcommand) {
|
||||
rpc<ToolsShowResponse>('tools.show', { session_id: sid })
|
||||
.then(r => {
|
||||
if (!r?.sections?.length) {
|
||||
return sys('no tools')
|
||||
}
|
||||
|
||||
if (!r.toolsets?.length) {
|
||||
return sys('no tools')
|
||||
}
|
||||
panel(
|
||||
`Tools${typeof r.total === 'number' ? ` (${r.total})` : ''}`,
|
||||
r.sections.map(section => ({
|
||||
title: section.name,
|
||||
rows: section.tools.map(tool => [tool.name, tool.description] as [string, string])
|
||||
}))
|
||||
)
|
||||
})
|
||||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
|
||||
panel(
|
||||
'Tools',
|
||||
r.toolsets.map((ts: any) => ({
|
||||
title: `${ts.enabled ? '*' : ' '} ${ts.name} [${ts.tool_count} tools]`,
|
||||
items: ts.tools
|
||||
}))
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
if (subcommand === 'list') {
|
||||
rpc<ToolsListResponse>('tools.list', { session_id: sid })
|
||||
.then(r => {
|
||||
if (!r?.toolsets?.length) {
|
||||
return sys('no tools')
|
||||
}
|
||||
|
||||
panel(
|
||||
'Tools',
|
||||
r.toolsets.map(ts => ({
|
||||
title: `${ts.enabled ? '*' : ' '} ${ts.name} [${ts.tool_count} tools]`,
|
||||
items: ts.tools
|
||||
}))
|
||||
)
|
||||
})
|
||||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (subcommand === 'disable' || subcommand === 'enable') {
|
||||
if (!names.length) {
|
||||
sys(`usage: /tools ${subcommand} <name> [name ...]`)
|
||||
sys(`built-in toolset: /tools ${subcommand} web`)
|
||||
sys(`MCP tool: /tools ${subcommand} github:create_issue`)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
rpc<ToolsConfigureResponse>('tools.configure', {
|
||||
action: subcommand,
|
||||
names,
|
||||
session_id: sid
|
||||
})
|
||||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
.then(r => {
|
||||
if (!r) {
|
||||
return
|
||||
}
|
||||
|
||||
if (r.info) {
|
||||
setSessionStartedAt(Date.now())
|
||||
resetVisibleHistory(r.info)
|
||||
}
|
||||
|
||||
if (r.changed?.length) {
|
||||
sys(`${subcommand === 'disable' ? 'disabled' : 'enabled'}: ${r.changed.join(', ')}`)
|
||||
}
|
||||
|
||||
if (r.unknown?.length) {
|
||||
sys(`unknown toolsets: ${r.unknown.join(', ')}`)
|
||||
}
|
||||
|
||||
if (r.missing_servers?.length) {
|
||||
sys(`missing MCP servers: ${r.missing_servers.join(', ')}`)
|
||||
}
|
||||
|
||||
if (r.reset) {
|
||||
sys('session reset. new tool configuration is active.')
|
||||
}
|
||||
})
|
||||
.catch((e: unknown) => sys(`error: ${rpcErrorMessage(e)}`))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
sys('usage: /tools [list|disable|enable] ...')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
case 'toolsets':
|
||||
rpc('toolsets.list', { session_id: sid })
|
||||
|
|
@ -969,6 +1097,28 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
|
|||
return true
|
||||
|
||||
default:
|
||||
if (catalog?.canon) {
|
||||
const needle = `/${name}`.toLowerCase()
|
||||
|
||||
const matches = [
|
||||
...new Set(
|
||||
Object.entries(catalog.canon)
|
||||
.filter(([alias]) => alias.startsWith(needle))
|
||||
.map(([, canon]) => canon)
|
||||
)
|
||||
]
|
||||
|
||||
if (matches.length === 1 && matches[0]!.toLowerCase() !== needle) {
|
||||
return handler(`${matches[0]}${arg ? ' ' + arg : ''}`)
|
||||
}
|
||||
|
||||
if (matches.length > 1) {
|
||||
sys(`ambiguous command: ${matches.slice(0, 6).join(', ')}${matches.length > 6 ? ', …' : ''}`)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
gw.request('slash.exec', { command: cmd.slice(1), session_id: sid })
|
||||
.then((r: any) => {
|
||||
sys(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue