feat(tui): add /status session diagnostics
This commit is contained in:
@@ -24,6 +24,8 @@ interface FakeAgent extends Agent {
|
||||
export interface TuiHarnessOptions {
|
||||
status?: AgentStatus
|
||||
config?: Config
|
||||
/** Leave the session event log empty instead of seeding one turn and step. */
|
||||
omitInitialLifecycle?: boolean
|
||||
/** Omit the harness's default `welcome`, exercising the banner sweep-reveal path. */
|
||||
omitWelcome?: boolean
|
||||
tools?: Record<string, ToolDefinition>
|
||||
@@ -120,11 +122,13 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
|
||||
sessionId,
|
||||
options.cwd === null ? undefined : { meta: { cwd: options.cwd ?? '/workspace' } },
|
||||
)
|
||||
session.append('turn/start', {
|
||||
turn: 1,
|
||||
trigger: { kind: 'message', source: { kind: 'user' } },
|
||||
})
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
if (options.omitInitialLifecycle !== true) {
|
||||
session.append('turn/start', {
|
||||
turn: 1,
|
||||
trigger: { kind: 'message', source: { kind: 'user' } },
|
||||
})
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
}
|
||||
options.beforeMount?.(session)
|
||||
const sent: ContentBlock[][] = []
|
||||
const steered: ContentBlock[][] = []
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 92x32 buffer=normal length=32 base=0 viewport=0
|
||||
lifecycle started=1 stopped=1 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor visible column=0 viewportRow=30 bufferRow=30
|
||||
cursor visible column=0 viewportRow=31 bufferRow=31
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -36,26 +36,28 @@ buffer
|
||||
style 1-88 fg=bright-black
|
||||
16| " /resume — List this workspace's resumable sessions "
|
||||
style 1-50 fg=bright-black
|
||||
17| " /tools — Expand or collapse all tool cards "
|
||||
17| " /status — Show detailed session diagnostics "
|
||||
style 1-43 fg=bright-black
|
||||
18| " /tools — Expand or collapse all tool cards "
|
||||
style 1-42 fg=bright-black
|
||||
18| " /skill:<name> [instructions] — load a skill into the conversation "
|
||||
19| " /skill:<name> [instructions] — load a skill into the conversation "
|
||||
style 1-65 fg=bright-black
|
||||
19| <blank>
|
||||
20| " provider stream failed after partial output "
|
||||
20| <blank>
|
||||
21| " provider stream failed after partial output "
|
||||
style 1-43 fg=red
|
||||
21| <blank>
|
||||
22| " The previous process ended during this turn. "
|
||||
22| <blank>
|
||||
23| " The previous process ended during this turn. "
|
||||
style 1-44 fg=yellow
|
||||
23| <blank>
|
||||
24| " Unknown command: /unknown-advanced-command "
|
||||
24| <blank>
|
||||
25| " Unknown command: /unknown-advanced-command "
|
||||
style 1-42 fg=yellow
|
||||
25| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
26| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
26| " "
|
||||
27| " "
|
||||
style 1-1 inverse
|
||||
27| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
28| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
28| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
29| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
style 0-43 dim
|
||||
style 65-91 dim
|
||||
29-31| <blank>
|
||||
30-31| <blank>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 92x32 buffer=normal length=32 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=1 viewportRow=26 bufferRow=26
|
||||
cursor hidden column=1 viewportRow=27 bufferRow=27
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -36,26 +36,28 @@ buffer
|
||||
style 1-88 fg=bright-black
|
||||
16| " /resume — List this workspace's resumable sessions "
|
||||
style 1-50 fg=bright-black
|
||||
17| " /tools — Expand or collapse all tool cards "
|
||||
17| " /status — Show detailed session diagnostics "
|
||||
style 1-43 fg=bright-black
|
||||
18| " /tools — Expand or collapse all tool cards "
|
||||
style 1-42 fg=bright-black
|
||||
18| " /skill:<name> [instructions] — load a skill into the conversation "
|
||||
19| " /skill:<name> [instructions] — load a skill into the conversation "
|
||||
style 1-65 fg=bright-black
|
||||
19| <blank>
|
||||
20| " provider stream failed after partial output "
|
||||
20| <blank>
|
||||
21| " provider stream failed after partial output "
|
||||
style 1-43 fg=red
|
||||
21| <blank>
|
||||
22| " The previous process ended during this turn. "
|
||||
22| <blank>
|
||||
23| " The previous process ended during this turn. "
|
||||
style 1-44 fg=yellow
|
||||
23| <blank>
|
||||
24| " Unknown command: /unknown-advanced-command "
|
||||
24| <blank>
|
||||
25| " Unknown command: /unknown-advanced-command "
|
||||
style 1-42 fg=yellow
|
||||
25| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
26| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
26| " "
|
||||
27| " "
|
||||
style 1-1 inverse
|
||||
27| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
28| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
28| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
29| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
style 0-43 dim
|
||||
style 65-91 dim
|
||||
29-31| <blank>
|
||||
30-31| <blank>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
terminal 92x32 buffer=normal length=32 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Inspect session diagnostics — DSH snapshot"
|
||||
cursor hidden column=1 viewportRow=27 bufferRow=27
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Inspect session diagnostics"
|
||||
style 1-27 fg=bright-black
|
||||
2| " deepseek-v4-pro • main-session"
|
||||
style 1-32 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
6| "▌ inspect this session "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
8| <blank>
|
||||
9| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
10| " Session inspected. "
|
||||
11| <blank>
|
||||
12| "▌ Session diagnostics "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-20 fg=bright-blue bold
|
||||
13| "▌ Session main-session "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
14| "▌ Title Inspect session diagnostics "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
15| "▌ Working dir /workspace/project "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
16| "▌ Model deepseek/deepseek-v4-pro "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
17| "▌ Reasoning view shown "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
18| "▌ Agent idle "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
19| "▌ Activity events 6 · turns 1 · steps 1 · tool calls 1 "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
20| "▌ Tokens input 1,250 · output 340 "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
21| "▌ Cache tokens read 3,000 · write 250 "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
22| "▌ KV cache hit 67% "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
23| "▌ Context 42,000 / 128,000 (33%) "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
24| "▌ Created 2026-07-22T09:10:11.000Z "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
25| "▌ Last active 2026-07-22T09:10:11.000Z "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-15 fg=bright-black
|
||||
26| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
27| " "
|
||||
style 1-1 inverse
|
||||
28| "────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-91 dim
|
||||
29| "deepseek-v4-pro /workspace/project ↑1.3k ↓340 cache 67% 33% context tools:collapsed"
|
||||
style 0-57 dim
|
||||
style 64-91 dim
|
||||
30-31| <blank>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mkdir, readdir, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { afterAll, describe, expect, it } from 'vitest'
|
||||
import { afterAll, describe, expect, it, vi } from 'vitest'
|
||||
import type { Context } from 'cordis'
|
||||
import { agentEvents } from '@deepseek-ai/dsh-agent'
|
||||
import { CallId, type ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
@@ -48,6 +48,7 @@ const CHECKPOINTS = [
|
||||
'errors-and-help',
|
||||
'disposed-terminal',
|
||||
'resume-sessions',
|
||||
'status-diagnostics',
|
||||
] as const
|
||||
|
||||
type Checkpoint = typeof CHECKPOINTS[number]
|
||||
@@ -637,6 +638,43 @@ describe('TUI terminal-state snapshots', () => {
|
||||
await checkpoint('resume-sessions', harness.terminal, { includeScrollback: true })
|
||||
await disposeSnapshot(harness)
|
||||
})
|
||||
|
||||
it('pins the detailed session diagnostics card', async () => {
|
||||
const dateNow = vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2026-07-22T09:10:11.000Z'))
|
||||
const harness = await setupSnapshot({
|
||||
contextWindow: 128_000,
|
||||
contextTokens: 42_000,
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-pro' },
|
||||
beforeMount(session) {
|
||||
appendUser(session, 'inspect this session')
|
||||
appendAssistant(session, [{ type: 'text', text: 'Session inspected.' }], {
|
||||
inputTokens: 1_250,
|
||||
outputTokens: 340,
|
||||
cacheReadTokens: 3_000,
|
||||
cacheWriteTokens: 250,
|
||||
})
|
||||
session.append('tool/call', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: CallId('status-call'),
|
||||
name: 'read',
|
||||
arguments: '{"path":"README.md"}',
|
||||
})
|
||||
session.append('session/title', {
|
||||
title: 'Inspect session diagnostics',
|
||||
messageSeqs: [1],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
},
|
||||
}, { columns: 92, rows: 32 })
|
||||
await renderAfter(harness, () => {
|
||||
harness.terminal.send('/status')
|
||||
harness.terminal.send('\r')
|
||||
})
|
||||
await checkpoint('status-diagnostics', harness.terminal, { includeScrollback: true })
|
||||
await disposeSnapshot(harness)
|
||||
dateNow.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@@ -845,6 +845,92 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('shows detailed session diagnostics while the agent is running', async () => {
|
||||
const timestamp = Date.parse('2026-07-22T09:10:11.000Z')
|
||||
const dateNow = vi.spyOn(Date, 'now').mockReturnValue(timestamp)
|
||||
const result = await setup({
|
||||
cwd: '/workspace/status',
|
||||
contextWindow: 128_000,
|
||||
contextTokens: 42_000,
|
||||
config: { showReasoning: false },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-pro' },
|
||||
beforeMount(session) {
|
||||
session.append('session/title', {
|
||||
title: 'Inspect status \u001B]2;unsafe\u0007',
|
||||
messageSeqs: [1],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
appendAssistant(session, [{ type: 'text', text: 'measured' }], {
|
||||
inputTokens: 1_250,
|
||||
outputTokens: 340,
|
||||
cacheReadTokens: 3_000,
|
||||
cacheWriteTokens: 250,
|
||||
})
|
||||
session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: 'status-call-1' as never, name: 'read', arguments: '{}',
|
||||
})
|
||||
session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: 'status-call-2' as never, name: 'write', arguments: '{}',
|
||||
})
|
||||
},
|
||||
})
|
||||
result.agent.status = 'running'
|
||||
agentEvents(result.ctx, result.agent).emit('agent/status', 'running')
|
||||
result.terminal.send('/status')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
|
||||
expect(result.terminal.output).toContain('Session diagnostics')
|
||||
expect(result.terminal.output).toContain('Session main-session')
|
||||
expect(result.terminal.output).toContain('Title Inspect status \\x1b]2;unsafe\\x07')
|
||||
expect(result.terminal.output).toContain('Working dir /workspace/status')
|
||||
expect(result.terminal.output).toContain('Model deepseek/deepseek-v4-pro')
|
||||
expect(result.terminal.output).toContain('Reasoning view hidden')
|
||||
expect(result.terminal.output).toContain('Agent running')
|
||||
expect(result.terminal.output).toContain('Activity events 6 · turns 1 · steps 1 · tool calls 2')
|
||||
expect(result.terminal.output).toContain('Tokens input 1,250 · output 340')
|
||||
expect(result.terminal.output).toContain('Cache tokens read 3,000 · write 250')
|
||||
expect(result.terminal.output).toContain('KV cache hit 67%')
|
||||
expect(result.terminal.output).toContain('Context 42,000 / 128,000 (33%)')
|
||||
expect(result.terminal.output).toContain('Created 2026-07-22T09:10:11.000Z')
|
||||
expect(result.terminal.output).toContain('Last active 2026-07-22T09:10:11.000Z')
|
||||
expect(result.terminal.output).not.toContain('\u001B]2;unsafe\u0007')
|
||||
|
||||
await dispose(result)
|
||||
dateNow.mockRestore()
|
||||
})
|
||||
|
||||
it('labels unavailable status diagnostics without inventing values', async () => {
|
||||
const timestamp = Date.parse('2026-07-22T10:11:12.000Z')
|
||||
const dateNow = vi.spyOn(Date, 'now').mockReturnValue(timestamp)
|
||||
const result = await setup({
|
||||
cwd: null,
|
||||
omitInitialLifecycle: true,
|
||||
contextTokens: 7,
|
||||
agentOptions: {},
|
||||
catalog: {
|
||||
providers: [],
|
||||
models: [],
|
||||
resolveModelContext: () => Promise.resolve(undefined),
|
||||
},
|
||||
})
|
||||
result.terminal.send('/status')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
|
||||
expect(result.terminal.output).toContain('Title untitled')
|
||||
expect(result.terminal.output).toContain('Model unset')
|
||||
expect(result.terminal.output).toContain('Reasoning view shown')
|
||||
expect(result.terminal.output).toContain('Agent idle')
|
||||
expect(result.terminal.output).toContain('Activity events 0 · turns 0 · steps 0 · tool calls 0')
|
||||
expect(result.terminal.output).toContain('KV cache hit n/a')
|
||||
expect(result.terminal.output).toContain('Context 7 used · capacity unknown')
|
||||
expect(result.terminal.output).toContain('Created 2026-07-22T10:11:12.000Z')
|
||||
expect(result.terminal.output).toContain('Last active 2026-07-22T10:11:12.000Z')
|
||||
await dispose(result)
|
||||
dateNow.mockRestore()
|
||||
})
|
||||
|
||||
it('sends, steers, handles commands, global keys, and disposed-agent input', async () => {
|
||||
const result = await setup()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user