fix(tui): reconcile master's model selector and session titles with the staging footer and status line

Post-rebase reconciliation of the two TUI lines that evolved in parallel:
- header subtitle prefers the latest logged session title over the configured
  welcome; the process-local auto-title owns the whole terminal title while a
  logged session/title still wins through the suffixed form
- footer keeps staging's model/cwd/usage/cache layout and gains master's
  context-percent segment; per-step usage dedup carries cache buckets
- test harness only stubs the llm catalog when the test did not mount the
  real LlmService, and defaults the TUI clock to the real Date.now
- the plugin-shaped /reload test composes commands+llm like the shipped app
This commit is contained in:
Turtle
2026-07-22 12:38:16 +08:00
parent 2464d6169f
commit 30c7e76ea5
3 changed files with 60 additions and 35 deletions
+8 -9
View File
@@ -508,7 +508,7 @@ class HeaderComponent implements Component {
constructor(
private readonly agent: Agent,
private readonly welcome: string | undefined,
private readonly subtitle: () => string | undefined,
private readonly palette: Palette,
private readonly gradient: boolean,
private readonly currentModel: () => string | undefined,
@@ -529,9 +529,10 @@ class HeaderComponent implements Component {
const title = `${name} ${this.palette.bold('HARNESS')}`
const model = displayText(this.currentModel() ?? 'model unset')
const detail = `${model}${displayText(this.agent.session.id)}`
const subtitle = this.subtitle()
const lines = [
title,
...this.welcome === undefined ? [] : [this.palette.muted(displayText(this.welcome))],
...subtitle === undefined ? [] : [this.palette.muted(displayText(subtitle))],
this.palette.dim(detail),
]
.flatMap(line => wrapTextWithAnsi(line, usable))
@@ -1380,7 +1381,7 @@ export function createTuiChat(
let sessionTitle = foldSessionTitle(agent.session.events)?.title
const header = new HeaderComponent(
agent,
config.welcome,
() => sessionTitle ?? config.welcome,
palette,
resolved.color && resolved.truecolor,
() => target.current?.model,
@@ -1555,12 +1556,10 @@ export function createTuiChat(
const assembler = new BlockAssembler()
for await (const chunk of llm.stream(options)) assembler.push(chunk)
const title = titleLine(contentText(assembler.message().content))
if (!disposed && title.length > 0) {
sessionTitle = title
header.invalidate()
updateTerminalTitle()
requestRender()
}
// Unlike a logged `session/title` (which suffixes the product title), the
// process-local auto-title owns the whole terminal title. A logged title
// arriving later still wins through `updateTerminalTitle`.
if (!disposed && title.length > 0) runtime.terminal.setTitle(displayText(title))
}
void applyTitle().catch(ignoreTitleFailure)
}