diff --git a/ui-tui/src/app/createSlashHandler.ts b/ui-tui/src/app/createSlashHandler.ts index c3ddab7a29..de77075db1 100644 --- a/ui-tui/src/app/createSlashHandler.ts +++ b/ui-tui/src/app/createSlashHandler.ts @@ -4,15 +4,17 @@ import { asCommandDispatch, rpcErrorMessage } from '../lib/rpc.js' import type { SlashHandlerContext } from './interfaces.js' import { findSlashCommand } from './slash/registry.js' -import { createSlashShared } from './slash/shared.js' import type { SlashRunCtx } from './slash/types.js' import { getUiState } from './uiStore.js' +const titleCase = (name: string) => name.charAt(0).toUpperCase() + name.slice(1) + +const isLong = (text: string) => text.length > 180 || text.split('\n').filter(Boolean).length > 2 + export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => boolean { const { gw } = ctx.gateway const { catalog } = ctx.local - const { send, sys } = ctx.transcript - const shared = createSlashShared({ ...ctx.transcript, gw, slashFlightRef: ctx.slashFlightRef }) + const { page, send, sys } = ctx.transcript const handler = (cmd: string): boolean => { const flight = ++ctx.slashFlightRef.current @@ -37,7 +39,7 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b } } - const runCtx: SlashRunCtx = { ...ctx, flight, guarded, guardedErr, shared, sid, stale, ui } + const runCtx: SlashRunCtx = { ...ctx, flight, guarded, guardedErr, sid, stale, ui } const found = findSlashCommand(parsed.name) @@ -75,11 +77,10 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b return } - sys( - r?.warning - ? `warning: ${r.warning}\n${r?.output || `/${parsed.name}: no output`}` - : r?.output || `/${parsed.name}: no output` - ) + const body = r?.output || `/${parsed.name}: no output` + const text = r?.warning ? `warning: ${r.warning}\n${body}` : body + + isLong(text) ? page(text, titleCase(parsed.name)) : sys(text) }) .catch(() => { gw.request('command.dispatch', { arg: parsed.arg, name: parsed.name, session_id: sid }) diff --git a/ui-tui/src/app/slash/commands/ops.ts b/ui-tui/src/app/slash/commands/ops.ts index 3ea300ebed..c1f6c6d83b 100644 --- a/ui-tui/src/app/slash/commands/ops.ts +++ b/ui-tui/src/app/slash/commands/ops.ts @@ -1,365 +1,49 @@ -import type { - AgentsListResponse, - BrowserManageResponse, - ConfigShowResponse, - CronListResponse, - PluginsListResponse, - RollbackActionResponse, - RollbackListResponse, - SkillsBrowseResponse, - SkillsListResponse, - SlashExecResponse, - ToolsConfigureResponse, - ToolsetsListResponse, - ToolsListResponse, - ToolsShowResponse -} from '../../../gatewayTypes.js' -import type { PanelSection } from '../../../types.js' -import type { SlashCommand, SlashRunCtx } from '../types.js' - -const passthroughSlash = (ctx: SlashRunCtx, cmd: string, fallback: string) => - ctx.gateway.gw - .request('slash.exec', { command: cmd.slice(1), session_id: ctx.sid }) - .then(r => { - if (ctx.stale()) { - return - } - - ctx.transcript.sys(r?.warning ? `warning: ${r.warning}\n${r?.output || fallback}` : r?.output || fallback) - }) - .catch(ctx.guardedErr) - -const clip = (s: string, max: number) => (s.length > max ? `${s.slice(0, max)}…` : s) +import type { ToolsConfigureResponse } from '../../../gatewayTypes.js' +import type { SlashCommand } from '../types.js' export const opsCommands: SlashCommand[] = [ { - help: 'list or restore checkpoints', - name: 'rollback', - run: (arg, ctx) => { - const [sub, ...rest] = (arg || 'list').split(/\s+/) - - if (!sub || sub === 'list') { - return ctx.gateway.rpc('rollback.list', { session_id: ctx.sid }).then( - ctx.guarded(r => { - if (!r.checkpoints?.length) { - return ctx.transcript.sys('no checkpoints') - } - - ctx.transcript.panel('Checkpoints', [ - { - rows: r.checkpoints.map( - (c, i) => [`${i + 1} ${c.hash?.slice(0, 8) ?? ''}`, c.message ?? ''] as [string, string] - ) - } - ]) - }) - ) - } - - const isRestoreOrDiff = sub === 'restore' || sub === 'diff' - const hash = isRestoreOrDiff ? rest[0] : sub - const filePath = (isRestoreOrDiff ? rest.slice(1) : rest).join(' ').trim() - const method = sub === 'diff' ? 'rollback.diff' : 'rollback.restore' - - ctx.gateway - .rpc(method, { - hash, - session_id: ctx.sid, - ...(sub === 'diff' || !filePath ? {} : { file_path: filePath }) - }) - .then(ctx.guarded(r => ctx.transcript.sys(r.rendered || r.diff || r.message || 'done'))) - } - }, - - { - help: 'manage browser connection', - name: 'browser', - run: (arg, ctx) => { - const [action, url] = (arg || 'status').split(/\s+/) - - ctx.gateway - .rpc('browser.manage', { action, ...(url ? { url } : {}) }) - .then( - ctx.guarded(r => - ctx.transcript.sys(r.connected ? `browser: ${r.url}` : 'browser: disconnected') - ) - ) - } - }, - - { - help: 'list installed plugins', - name: 'plugins', - run: (_arg, ctx) => { - ctx.gateway.rpc('plugins.list', {}).then( - ctx.guarded(r => { - if (!r.plugins?.length) { - return ctx.transcript.sys('no plugins') - } - - ctx.transcript.panel('Plugins', [ - { items: r.plugins.map(p => `${p.name} v${p.version}${p.enabled ? '' : ' (disabled)'}`) } - ]) - }) - ) - } - }, - - { - help: 'list or browse skills', - name: 'skills', - run: (arg, ctx, cmd) => { - const [sub, ...rest] = (arg || '').split(/\s+/).filter(Boolean) - - if (!sub || sub === 'list') { - return ctx.gateway.rpc('skills.manage', { action: 'list' }).then( - ctx.guarded(r => { - if (!r.skills || !Object.keys(r.skills).length) { - return ctx.transcript.sys('no skills installed') - } - - ctx.transcript.panel( - 'Installed Skills', - Object.entries(r.skills).map(([title, items]) => ({ items, title })) - ) - }) - ) - } - - if (sub === 'browse') { - const pageNumber = parseInt(rest[0] ?? '1', 10) || 1 - - return ctx.gateway.rpc('skills.manage', { action: 'browse', page: pageNumber }).then( - ctx.guarded(r => { - if (!r.items?.length) { - return ctx.transcript.sys('no skills found in the hub') - } - - const page = r.page ?? 1 - const totalPages = r.total_pages ?? 1 - - const sections: PanelSection[] = [ - { - rows: r.items.map(s => [s.name ?? '', clip(s.description ?? '', 60)] as [string, string]) - } - ] - - if (page < totalPages) { - sections.push({ text: `/skills browse ${page + 1} → next page` }) - } - - if (page > 1) { - sections.push({ text: `/skills browse ${page - 1} → prev page` }) - } - - ctx.transcript.panel(`Skills Hub (page ${page}/${totalPages}, ${r.total ?? 0} total)`, sections) - }) - ) - } - - passthroughSlash(ctx, cmd, '/skills: no output') - } - }, - - { - aliases: ['tasks'], - help: 'running agents', - name: 'agents', - run: (_arg, ctx) => { - ctx.gateway - .rpc('agents.list', {}) - .then( - ctx.guarded(r => { - const processes = r.processes ?? [] - const running = processes.filter(p => p.status === 'running') - const finished = processes.filter(p => p.status !== 'running') - const sections: PanelSection[] = [] - - if (running.length) { - sections.push({ - rows: running.map(p => [p.session_id.slice(0, 8), p.command ?? '']), - title: `Running (${running.length})` - }) - } - - if (finished.length) { - sections.push({ - rows: finished.map(p => [p.session_id.slice(0, 8), p.command ?? '']), - title: `Finished (${finished.length})` - }) - } - - if (!sections.length) { - sections.push({ text: 'No active processes' }) - } - - ctx.transcript.panel('Agents', sections) - }) - ) - .catch(ctx.guardedErr) - } - }, - - { - help: 'list or manage cron jobs', - name: 'cron', - run: (arg, ctx, cmd) => { - if (arg && arg !== 'list') { - return passthroughSlash(ctx, cmd, '(no output)') - } - - ctx.gateway - .rpc('cron.manage', { action: 'list' }) - .then( - ctx.guarded(r => { - const jobs = r.jobs ?? [] - - if (!jobs.length) { - return ctx.transcript.sys('no scheduled jobs') - } - - ctx.transcript.panel('Cron', [ - { - rows: jobs.map( - j => - [j.name || j.job_id?.slice(0, 12) || '', `${j.schedule ?? ''} · ${j.state ?? 'active'}`] as [ - string, - string - ] - ) - } - ]) - }) - ) - .catch(ctx.guardedErr) - } - }, - - { - help: 'show configuration', - name: 'config', - run: (_arg, ctx) => { - ctx.gateway - .rpc('config.show', {}) - .then( - ctx.guarded(r => - ctx.transcript.panel( - 'Config', - (r.sections ?? []).map(s => ({ rows: s.rows, title: s.title })) - ) - ) - ) - .catch(ctx.guardedErr) - } - }, - - { - help: 'list, enable, disable tools', + help: 'enable or disable tools (client-side history reset on change)', name: 'tools', run: (arg, ctx) => { const [subcommand, ...names] = arg.trim().split(/\s+/).filter(Boolean) - if (!subcommand) { - return ctx.gateway - .rpc('tools.show', { session_id: ctx.sid }) - .then(r => { - if (ctx.stale()) { - return - } - - if (!r?.sections?.length) { - return ctx.transcript.sys('no tools') - } - - ctx.transcript.panel( - `Tools${typeof r.total === 'number' ? ` (${r.total})` : ''}`, - r.sections.map(section => ({ - rows: section.tools.map(tool => [tool.name, tool.description] as [string, string]), - title: section.name - })) - ) - }) - .catch(ctx.guardedErr) + if (subcommand !== 'disable' && subcommand !== 'enable') { + return // py prints lists / show / usage } - if (subcommand === 'list') { - return ctx.gateway - .rpc('tools.list', { session_id: ctx.sid }) - .then(r => { - if (ctx.stale()) { - return - } + if (!names.length) { + ctx.transcript.sys(`usage: /tools ${subcommand} [name ...]`) + ctx.transcript.sys(`built-in toolset: /tools ${subcommand} web`) + ctx.transcript.sys(`MCP tool: /tools ${subcommand} github:create_issue`) - if (!r?.toolsets?.length) { - return ctx.transcript.sys('no tools') - } - - ctx.transcript.panel( - 'Tools', - r.toolsets.map(ts => ({ - items: ts.tools, - title: `${ts.enabled ? '*' : ' '} ${ts.name} [${ts.tool_count} tools]` - })) - ) - }) - .catch(ctx.guardedErr) + return } - if (subcommand === 'disable' || subcommand === 'enable') { - if (!names.length) { - ctx.transcript.sys(`usage: /tools ${subcommand} [name ...]`) - ctx.transcript.sys(`built-in toolset: /tools ${subcommand} web`) - ctx.transcript.sys(`MCP tool: /tools ${subcommand} github:create_issue`) - - return - } - - return ctx.gateway - .rpc('tools.configure', { action: subcommand, names, session_id: ctx.sid }) - .then( - ctx.guarded(r => { - if (r.info) { - ctx.session.setSessionStartedAt(Date.now()) - ctx.session.resetVisibleHistory(r.info) - } - - r.changed?.length && - ctx.transcript.sys(`${subcommand === 'disable' ? 'disabled' : 'enabled'}: ${r.changed.join(', ')}`) - r.unknown?.length && ctx.transcript.sys(`unknown toolsets: ${r.unknown.join(', ')}`) - r.missing_servers?.length && ctx.transcript.sys(`missing MCP servers: ${r.missing_servers.join(', ')}`) - r.reset && ctx.transcript.sys('session reset. new tool configuration is active.') - }) - ) - .catch(ctx.guardedErr) - } - - ctx.transcript.sys('usage: /tools [list|disable|enable] ...') - } - }, - - { - help: 'list toolsets', - name: 'toolsets', - run: (_arg, ctx) => { ctx.gateway - .rpc('toolsets.list', { session_id: ctx.sid }) + .rpc('tools.configure', { action: subcommand, names, session_id: ctx.sid }) .then( - ctx.guarded(r => { - if (!r.toolsets?.length) { - return ctx.transcript.sys('no toolsets') + ctx.guarded(r => { + if (r.info) { + ctx.session.setSessionStartedAt(Date.now()) + ctx.session.resetVisibleHistory(r.info) } - ctx.transcript.panel('Toolsets', [ - { - rows: r.toolsets.map( - ts => - [`${ts.enabled ? '(*)' : ' '} ${ts.name}`, `[${ts.tool_count}] ${ts.description}`] as [ - string, - string - ] - ) - } - ]) + if (r.changed?.length) { + ctx.transcript.sys(`${subcommand === 'disable' ? 'disabled' : 'enabled'}: ${r.changed.join(', ')}`) + } + + if (r.unknown?.length) { + ctx.transcript.sys(`unknown toolsets: ${r.unknown.join(', ')}`) + } + + if (r.missing_servers?.length) { + ctx.transcript.sys(`missing MCP servers: ${r.missing_servers.join(', ')}`) + } + + if (r.reset) { + ctx.transcript.sys('session reset. new tool configuration is active.') + } }) ) .catch(ctx.guardedErr) diff --git a/ui-tui/src/app/slash/commands/session.ts b/ui-tui/src/app/slash/commands/session.ts index c8dfa587a9..224e50aaa1 100644 --- a/ui-tui/src/app/slash/commands/session.ts +++ b/ui-tui/src/app/slash/commands/session.ts @@ -2,52 +2,18 @@ import { imageTokenMeta, introMsg, toTranscriptMessages } from '../../../domain/ import type { BackgroundStartResponse, BtwStartResponse, - ConfigGetValueResponse, ConfigSetResponse, ImageAttachResponse, - InsightsResponse, - ReloadMcpResponse, SessionBranchResponse, SessionCompressResponse, - SessionHistoryResponse, - SessionSaveResponse, - SessionTitleResponse, - SessionUsageResponse, - SlashExecResponse, VoiceToggleResponse } from '../../../gatewayTypes.js' import { fmtK } from '../../../lib/text.js' -import type { PanelSection } from '../../../types.js' import { patchOverlayState } from '../../overlayStore.js' import { patchUiState } from '../../uiStore.js' import type { SlashCommand } from '../types.js' -const PAGE_TITLES: Record = { - debug: 'Debug', - fast: 'Fast', - platforms: 'Platforms', - snapshot: 'Snapshot' -} - -const passthrough = (name: string): SlashCommand => ({ - name, - run: (_arg, ctx, cmd) => - ctx.shared.showSlashOutput({ - command: cmd.slice(1), - flight: ctx.flight, - sid: ctx.sid, - title: PAGE_TITLES[name] ?? name - }) -}) - -const historyLabel = (role: string) => (role === 'assistant' ? 'Hermes' : role === 'user' ? 'You' : 'System') - export const sessionCommands: SlashCommand[] = [ - passthrough('debug'), - passthrough('fast'), - passthrough('platforms'), - passthrough('snapshot'), - { aliases: ['bg'], help: 'launch a background prompt', @@ -126,120 +92,33 @@ export const sessionCommands: SlashCommand[] = [ const meta = imageTokenMeta(r) ctx.transcript.sys(`attached image: ${r.name ?? ''}${meta ? ` · ${meta}` : ''}`) - r.remainder && ctx.composer.setInput(r.remainder) + + if (r.remainder) { + ctx.composer.setInput(r.remainder) + } }) ) } }, { - help: 'show provider details', - name: 'provider', - run: (_arg, ctx) => { - ctx.gateway.gw - .request('slash.exec', { command: 'provider', session_id: ctx.sid }) - .then(r => { - if (ctx.stale()) { - return - } - - ctx.transcript.page( - r?.warning ? `warning: ${r.warning}\n\n${r?.output || '(no output)'}` : r?.output || '(no output)', - 'Provider' - ) - }) - .catch(ctx.guardedErr) - } - }, - - { - help: 'switch theme skin', - name: 'skin', - run: (arg, ctx) => { - if (arg) { - return ctx.gateway - .rpc('config.set', { key: 'skin', value: arg }) - .then(ctx.guarded(r => r.value && ctx.transcript.sys(`skin → ${r.value}`))) - } - - ctx.gateway - .rpc('config.get', { key: 'skin' }) - .then(ctx.guarded(r => ctx.transcript.sys(`skin: ${r.value || 'default'}`))) - } - }, - - { - help: 'toggle yolo mode', - name: 'yolo', - run: (_arg, ctx) => { - ctx.gateway - .rpc('config.set', { key: 'yolo', session_id: ctx.sid }) - .then(ctx.guarded(r => ctx.transcript.sys(`yolo ${r.value === '1' ? 'on' : 'off'}`))) - } - }, - - { - help: 'inspect or set reasoning mode', - name: 'reasoning', - run: (arg, ctx) => { - if (!arg) { - return ctx.gateway - .rpc('config.get', { key: 'reasoning' }) - .then( - ctx.guarded( - r => r.value && ctx.transcript.sys(`reasoning: ${r.value} · display ${r.display || 'hide'}`) - ) - ) - } - - ctx.gateway - .rpc('config.set', { key: 'reasoning', session_id: ctx.sid, value: arg }) - .then(ctx.guarded(r => r.value && ctx.transcript.sys(`reasoning: ${r.value}`))) - } - }, - - { - help: 'cycle verbose output', - name: 'verbose', - run: (arg, ctx) => { - ctx.gateway - .rpc('config.set', { key: 'verbose', session_id: ctx.sid, value: arg || 'cycle' }) - .then(ctx.guarded(r => r.value && ctx.transcript.sys(`verbose: ${r.value}`))) - } - }, - - { - help: 'personality panel or switch', + help: 'switch or reset personality (history reset on set)', name: 'personality', run: (arg, ctx) => { - if (arg) { - return ctx.gateway - .rpc('config.set', { key: 'personality', session_id: ctx.sid, value: arg }) - .then( - ctx.guarded(r => { - r.history_reset && ctx.session.resetVisibleHistory(r.info ?? null) - ctx.transcript.sys( - `personality: ${r.value || 'default'}${r.history_reset ? ' · transcript cleared' : ''}` - ) - ctx.local.maybeWarn(r) - }) - ) + if (!arg) { + return // py handles listing } - ctx.gateway.gw - .request('slash.exec', { command: 'personality', session_id: ctx.sid }) - .then(r => { - if (ctx.stale()) { - return + ctx.gateway.rpc('config.set', { key: 'personality', session_id: ctx.sid, value: arg }).then( + ctx.guarded(r => { + if (r.history_reset) { + ctx.session.resetVisibleHistory(r.info ?? null) } - ctx.transcript.panel('Personality', [ - { - text: r?.warning ? `warning: ${r.warning}\n\n${r?.output || '(no output)'}` : r?.output || '(no output)' - } - ]) + ctx.transcript.sys(`personality: ${r.value || 'default'}${r.history_reset ? ' · transcript cleared' : ''}`) + ctx.local.maybeWarn(r) }) - .catch(ctx.guardedErr) + ) } }, @@ -260,8 +139,13 @@ export const sessionCommands: SlashCommand[] = [ ctx.transcript.setHistoryItems(r.info ? [introMsg(r.info), ...rows] : rows) } - r.info && patchUiState({ info: r.info }) - r.usage && patchUiState(state => ({ ...state, usage: { ...state.usage, ...r.usage } })) + if (r.info) { + patchUiState({ info: r.info }) + } + + if (r.usage) { + patchUiState(state => ({ ...state, usage: { ...state.usage, ...r.usage } })) + } if ((r.removed ?? 0) <= 0) { return ctx.transcript.sys('nothing to compress') @@ -275,18 +159,6 @@ export const sessionCommands: SlashCommand[] = [ } }, - { - help: 'stop background processes', - name: 'stop', - run: (_arg, ctx) => { - ctx.gateway - .rpc<{ killed?: number }>('process.stop', {}) - .then( - ctx.guarded<{ killed?: number }>(r => ctx.transcript.sys(`killed ${r.killed ?? 0} registered process(es)`)) - ) - } - }, - { aliases: ['fork'], help: 'branch the session', @@ -310,121 +182,6 @@ export const sessionCommands: SlashCommand[] = [ } }, - { - aliases: ['reload_mcp'], - help: 'reload MCP servers', - name: 'reload-mcp', - run: (_arg, ctx) => - ctx.gateway - .rpc('reload.mcp', { session_id: ctx.sid }) - .then(ctx.guarded(() => ctx.transcript.sys('MCP reloaded'))) - }, - - { - help: 'inspect or set session title', - name: 'title', - run: (arg, ctx) => { - ctx.gateway - .rpc('session.title', { session_id: ctx.sid, ...(arg ? { title: arg } : {}) }) - .then(ctx.guarded(r => ctx.transcript.sys(`title: ${r.title || '(none)'}`))) - } - }, - - { - help: 'session usage', - name: 'usage', - run: (_arg, ctx) => { - ctx.gateway.rpc('session.usage', { session_id: ctx.sid }).then(r => { - if (ctx.stale()) { - return - } - - if (r) { - patchUiState({ - usage: { calls: r.calls ?? 0, input: r.input ?? 0, output: r.output ?? 0, total: r.total ?? 0 } - }) - } - - if (!r?.calls) { - return ctx.transcript.sys('no API calls yet') - } - - const f = (v: number | undefined) => (v ?? 0).toLocaleString() - const cost = r.cost_usd != null ? `${r.cost_status === 'estimated' ? '~' : ''}$${r.cost_usd.toFixed(4)}` : null - - const rows: [string, string][] = [ - ['Model', r.model ?? ''], - ['Input tokens', f(r.input)], - ['Cache read tokens', f(r.cache_read)], - ['Cache write tokens', f(r.cache_write)], - ['Output tokens', f(r.output)], - ['Total tokens', f(r.total)], - ['API calls', f(r.calls)] - ] - - const sections: PanelSection[] = [{ rows }] - - cost && rows.push(['Cost', cost]) - r.context_max && - sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` }) - r.compressions && sections.push({ text: `Compressions: ${r.compressions}` }) - - ctx.transcript.panel('Usage', sections) - }) - } - }, - - { - help: 'save transcript to disk', - name: 'save', - run: (_arg, ctx) => { - ctx.gateway - .rpc('session.save', { session_id: ctx.sid }) - .then(ctx.guarded(r => r.file && ctx.transcript.sys(`saved: ${r.file}`))) - } - }, - - { - help: 'view message history', - name: 'history', - run: (_arg, ctx) => { - ctx.gateway.rpc('session.history', { session_id: ctx.sid }).then(r => { - if (ctx.stale() || typeof r?.count !== 'number') { - return - } - - if (!r.messages?.length) { - return ctx.transcript.sys(`${r.count} messages`) - } - - const body = r.messages - .map((m, i) => - m.role === 'tool' - ? `[Tool #${i + 1}] ${m.name || 'tool'} ${m.context || ''}`.trim() - : `[${historyLabel(m.role)} #${i + 1}] ${m.text || ''}`.trim() - ) - .join('\n\n') - - ctx.transcript.page(body, `History (${r.count})`) - }) - } - }, - - { - help: 'show current profile', - name: 'profile', - run: (_arg, ctx) => { - ctx.gateway.rpc('config.get', { key: 'profile' }).then( - ctx.guarded(r => { - const text = r.display || r.home || '(unknown profile)' - const lines = text.split('\n').filter(Boolean) - - lines.length <= 2 ? ctx.transcript.panel('Profile', [{ text }]) : ctx.transcript.page(text, 'Profile') - }) - ) - } - }, - { help: 'toggle voice input', name: 'voice', @@ -438,25 +195,5 @@ export const sessionCommands: SlashCommand[] = [ }) ) } - }, - - { - help: 'view usage insights', - name: 'insights', - run: (arg, ctx) => { - ctx.gateway.rpc('insights.get', { days: parseInt(arg) || 30 }).then( - ctx.guarded(r => - ctx.transcript.panel('Insights', [ - { - rows: [ - ['Period', `${r.days ?? 0} days`], - ['Sessions', `${r.sessions ?? 0}`], - ['Messages', `${r.messages ?? 0}`] - ] - } - ]) - ) - ) - } } ] diff --git a/ui-tui/src/app/slash/shared.ts b/ui-tui/src/app/slash/shared.ts deleted file mode 100644 index c6aba712b0..0000000000 --- a/ui-tui/src/app/slash/shared.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { MutableRefObject } from 'react' - -import type { SlashExecResponse } from '../../gatewayTypes.js' -import { rpcErrorMessage } from '../../lib/rpc.js' -import { getUiState } from '../uiStore.js' - -export interface SlashShared { - showSlashOutput: (opts: { command: string; flight: number; sid: null | string; title: string }) => void -} - -interface SlashSharedDeps { - gw: { request: (method: string, params?: Record) => Promise } - page: (text: string, title?: string) => void - slashFlightRef: MutableRefObject - sys: (text: string) => void -} - -export const createSlashShared = ({ gw, page, slashFlightRef, sys }: SlashSharedDeps): SlashShared => ({ - showSlashOutput: ({ command, flight, sid, title }) => { - const stale = () => flight !== slashFlightRef.current || getUiState().sid !== sid - - gw.request('slash.exec', { command, session_id: sid }) - .then(r => { - if (stale()) { - return - } - - const text = r?.warning ? `warning: ${r.warning}\n${r?.output || '(no output)'}` : r?.output || '(no output)' - - text.split('\n').filter(Boolean).length > 2 || text.length > 180 ? page(text, title) : sys(text) - }) - .catch((e: unknown) => { - if (!stale()) { - sys(`error: ${rpcErrorMessage(e)}`) - } - }) - } -}) diff --git a/ui-tui/src/app/slash/types.ts b/ui-tui/src/app/slash/types.ts index 4fa6e0b595..bbd187a23b 100644 --- a/ui-tui/src/app/slash/types.ts +++ b/ui-tui/src/app/slash/types.ts @@ -2,13 +2,10 @@ import type { MutableRefObject } from 'react' import type { SlashHandlerContext, UiState } from '../interfaces.js' -import type { SlashShared } from './shared.js' - export interface SlashRunCtx extends SlashHandlerContext { flight: number guarded: (fn: (r: T) => void) => (r: null | T) => void guardedErr: (e: unknown) => void - shared: SlashShared sid: null | string slashFlightRef: MutableRefObject stale: () => boolean diff --git a/ui-tui/src/gatewayTypes.ts b/ui-tui/src/gatewayTypes.ts index ee0e431230..1c991baeb4 100644 --- a/ui-tui/src/gatewayTypes.ts +++ b/ui-tui/src/gatewayTypes.ts @@ -110,11 +110,6 @@ export interface SessionUndoResponse { removed?: number } -export interface SessionHistoryResponse { - count?: number - messages?: GatewayTranscriptMessage[] -} - export interface SessionCompressResponse { info?: SessionInfo messages?: GatewayTranscriptMessage[] @@ -127,30 +122,6 @@ export interface SessionBranchResponse { title?: string } -export interface SessionTitleResponse { - title?: string -} - -export interface SessionSaveResponse { - file?: string -} - -export interface SessionUsageResponse { - cache_read?: number - cache_write?: number - calls?: number - compressions?: number - context_max?: number - context_percent?: number - context_used?: number - cost_status?: 'estimated' | 'exact' - cost_usd?: number - input?: number - model?: string - output?: number - total?: number -} - export interface SessionCloseResponse { ok?: boolean } @@ -240,34 +211,7 @@ export interface VoiceRecordResponse { text?: string } -// ── Tools / toolsets ───────────────────────────────────────────────── - -export interface ToolsetDetails { - description: string - enabled: boolean - name: string - tool_count: number - tools: string[] -} - -export interface ToolsListResponse { - toolsets?: ToolsetDetails[] -} - -export interface ToolSummary { - description: string - name: string -} - -export interface ToolsShowSection { - name: string - tools: ToolSummary[] -} - -export interface ToolsShowResponse { - sections?: ToolsShowSection[] - total?: number -} +// ── Tools (TS keeps configure since it resets local history) ───────── export interface ToolsConfigureResponse { changed?: string[] @@ -278,104 +222,7 @@ export interface ToolsConfigureResponse { unknown?: string[] } -export interface ToolsetsListResponse { - toolsets?: { - description: string - enabled: boolean - name: string - tool_count: number - }[] -} - -// ── Ops: rollback / browser / plugins / skills / agents / cron ─────── - -export interface RollbackCheckpoint { - hash?: string - message?: string -} - -export interface RollbackListResponse { - checkpoints?: RollbackCheckpoint[] -} - -export interface RollbackActionResponse { - diff?: string - message?: string - rendered?: string -} - -export interface BrowserManageResponse { - connected?: boolean - url?: string -} - -export interface PluginInfo { - enabled?: boolean - name?: string - version?: string -} - -export interface PluginsListResponse { - plugins?: PluginInfo[] -} - -export interface SkillsListResponse { - skills?: Record -} - -export interface SkillsBrowseItem { - description?: string - name?: string -} - -export interface SkillsBrowseResponse { - items?: SkillsBrowseItem[] - page?: number - total?: number - total_pages?: number -} - -export interface AgentProcess { - command?: string - session_id: string - status?: 'finished' | 'running' -} - -export interface AgentsListResponse { - processes?: AgentProcess[] -} - -export interface CronJob { - job_id?: string - name?: string - schedule?: string - state?: string -} - -export interface CronListResponse { - jobs?: CronJob[] -} - -export interface ConfigShowSection { - rows?: [string, string][] - title?: string -} - -export interface ConfigShowResponse { - sections?: ConfigShowSection[] -} - -// ── Insights / MCP ─────────────────────────────────────────────────── - -export interface InsightsResponse { - days?: number - messages?: number - sessions?: number -} - -export interface ReloadMcpResponse { - ok?: boolean -} +// ── Model picker ───────────────────────────────────────────────────── export interface ModelOptionProvider { is_current?: boolean @@ -392,6 +239,12 @@ export interface ModelOptionsResponse { providers?: ModelOptionProvider[] } +// ── MCP ────────────────────────────────────────────────────────────── + +export interface ReloadMcpResponse { + ok?: boolean +} + // ── Subagent events ────────────────────────────────────────────────── export interface SubagentEventPayload {