feat(session-title): integrate queries ACP and TUI
This commit is contained in:
@@ -39,6 +39,7 @@ import { errorChain } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
import { SessionId, type Session, type SessionEvent, type TodoItem } from '@deepseek-ai/dsh-session'
|
||||
import { foldSessionTitle } from '@deepseek-ai/dsh-session-title'
|
||||
import type {
|
||||
FileDiff,
|
||||
TerminalCallView,
|
||||
@@ -286,7 +287,7 @@ function textBlocks(content: readonly ContentBlock[], type: 'text' | 'reasoning'
|
||||
class HeaderComponent implements Component {
|
||||
constructor(
|
||||
private readonly agent: Agent,
|
||||
private readonly welcome: string,
|
||||
private readonly subtitle: () => string,
|
||||
private readonly palette: Palette,
|
||||
) {}
|
||||
|
||||
@@ -299,7 +300,7 @@ class HeaderComponent implements Component {
|
||||
const detail = `${model} • ${displayText(this.agent.session.id)}`
|
||||
const top = this.palette.accent(`╭${'─'.repeat(Math.max(0, width - 2))}╮`)
|
||||
const bottom = this.palette.accent(`╰${'─'.repeat(Math.max(0, width - 2))}╯`)
|
||||
const lines = [title, this.palette.muted(displayText(this.welcome)), this.palette.dim(detail)]
|
||||
const lines = [title, this.palette.muted(displayText(this.subtitle())), this.palette.dim(detail)]
|
||||
.flatMap(line => wrapTextWithAnsi(line, usable))
|
||||
.map((line) => {
|
||||
const clipped = truncateToWidth(line, usable, '')
|
||||
@@ -858,7 +859,8 @@ export function createTuiChat(
|
||||
let activeQuestion: PendingQuestion | undefined
|
||||
|
||||
const welcome = config.welcome ?? 'ready.'
|
||||
const header = new HeaderComponent(agent, welcome, palette)
|
||||
let sessionTitle = foldSessionTitle(agent.session.events)?.title
|
||||
const header = new HeaderComponent(agent, () => sessionTitle ?? welcome, palette)
|
||||
const footer = new FooterComponent(agent, palette, () => toolsExpanded, () => showReasoning, () => tokens)
|
||||
ui.addChild(header)
|
||||
ui.addChild(chat)
|
||||
@@ -868,7 +870,12 @@ export function createTuiChat(
|
||||
ui.addChild(editor)
|
||||
ui.addChild(footer)
|
||||
ui.setFocus(editor)
|
||||
runtime.terminal.setTitle(displayText(resolved.title))
|
||||
const updateTerminalTitle = (): void => {
|
||||
runtime.terminal.setTitle(displayText(
|
||||
sessionTitle === undefined ? resolved.title : `${sessionTitle} — ${resolved.title}`,
|
||||
))
|
||||
}
|
||||
updateTerminalTitle()
|
||||
|
||||
const requestRender = (): void => {
|
||||
footer.invalidate()
|
||||
@@ -997,6 +1004,11 @@ export function createTuiChat(
|
||||
case 'todo/write':
|
||||
todo.update(event.data.todos)
|
||||
break
|
||||
case 'session/title':
|
||||
sessionTitle = event.data.title
|
||||
header.invalidate()
|
||||
updateTerminalTitle()
|
||||
break
|
||||
case 'turn/end':
|
||||
clearStreaming()
|
||||
if (event.data.reason.kind === 'error') {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Context } from 'cordis'
|
||||
import type { Terminal } from '@earendil-works/pi-tui'
|
||||
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-session-title'
|
||||
import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
@@ -146,6 +147,34 @@ describe('TUI config', () => {
|
||||
})
|
||||
|
||||
describe('pi-tui chat lifecycle and transcript', () => {
|
||||
it('uses the latest log-backed title for the header subtitle and terminal window', async () => {
|
||||
const result = await setup({
|
||||
beforeMount(session) {
|
||||
session.append('session/title', {
|
||||
title: 'Restored session title',
|
||||
messageSeqs: [1],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.terminal.title).toBe('Restored session title — DeepSeek Harness')
|
||||
expect(result.terminal.output).toContain('Restored session title')
|
||||
expect(result.terminal.output).not.toContain('Coding agent ready.')
|
||||
|
||||
result.session.append('session/title', {
|
||||
title: 'Live title \u001B]0;unsafe\u0007',
|
||||
messageSeqs: [1, 5],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
await tick()
|
||||
|
||||
expect(result.terminal.title).toContain('Live title \\x1b]0;unsafe\\x07 — DeepSeek Harness')
|
||||
expect(result.terminal.title).not.toContain('\u001B')
|
||||
expect(result.terminal.output).toContain('Live title \\x1b]0;unsafe\\x07')
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('renders its header, footer, replay, streaming answer, todos, and status', async () => {
|
||||
const result = await setup({
|
||||
beforeMount(session) {
|
||||
|
||||
Reference in New Issue
Block a user