diff --git a/ui-tui/src/__tests__/createSlashHandler.test.ts b/ui-tui/src/__tests__/createSlashHandler.test.ts index 1f2f938a9..901564f73 100644 --- a/ui-tui/src/__tests__/createSlashHandler.test.ts +++ b/ui-tui/src/__tests__/createSlashHandler.test.ts @@ -211,6 +211,42 @@ describe('createSlashHandler', () => { expect(ctx.transcript.send).toHaveBeenCalledWith(skillMessage) }) + it('/history pages the current TUI transcript (user + assistant)', () => { + const ctx = buildCtx({ + local: { + ...buildLocal(), + getHistoryItems: vi.fn(() => [ + { role: 'user', text: 'hello' }, + { role: 'system', text: 'ignore me' }, + { role: 'assistant', text: 'hi there' }, + { role: 'user', text: 'test' } + ]) + } + }) + + createSlashHandler(ctx)('/history') + expect(ctx.transcript.page).toHaveBeenCalledTimes(1) + + const [body, title] = ctx.transcript.page.mock.calls[0]! + + expect(title).toBe('History') + expect(body).toContain('[You #1]') + expect(body).toContain('hello') + expect(body).toContain('[Hermes #2]') + expect(body).toContain('hi there') + expect(body).toContain('[You #3]') + expect(body).not.toContain('ignore me') + expect(ctx.gateway.gw.request).not.toHaveBeenCalled() + }) + + it('/history reports empty state without paging', () => { + const ctx = buildCtx() + + createSlashHandler(ctx)('/history') + expect(ctx.transcript.page).not.toHaveBeenCalled() + expect(ctx.transcript.sys).toHaveBeenCalledWith('no conversation yet') + }) + it('handles send-type dispatch for /plan command', async () => { const planMessage = 'Plan skill content loaded' diff --git a/ui-tui/src/app/slash/commands/core.ts b/ui-tui/src/app/slash/commands/core.ts index 3a254b293..77eb20dec 100644 --- a/ui-tui/src/app/slash/commands/core.ts +++ b/ui-tui/src/app/slash/commands/core.ts @@ -275,6 +275,34 @@ export const coreCommands: SlashCommand[] = [ } }, + { + help: 'view current transcript (user + assistant messages)', + name: 'history', + run: (arg, ctx) => { + // The CLI-side `/history` runs in a detached slash-worker subprocess + // that never sees the TUI's turns — it only surfaces whatever was + // persisted before this process started. Render the TUI's own + // transcript so `/history` actually reflects what the user just did. + const items = ctx.local.getHistoryItems().filter(m => m.role === 'user' || m.role === 'assistant') + + if (!items.length) { + return ctx.transcript.sys('no conversation yet') + } + + const preview = Math.max(80, parseInt(arg, 10) || 400) + + const lines = items.map((m, i) => { + const tag = m.role === 'user' ? `You #${i + 1}` : `Hermes #${i + 1}` + const body = m.text.trim() || (m.tools?.length ? `(${m.tools.length} tool calls)` : '(empty)') + const clipped = body.length > preview ? `${body.slice(0, preview).trimEnd()}…` : body + + return `[${tag}]\n${clipped}` + }) + + ctx.transcript.page(lines.join('\n\n'), 'History') + } + }, + { aliases: ['sb'], help: 'toggle status bar',