refactor(tui): drop the TUI-local auto-title; titles come from the session-title service

Master's log-backed session-title capability already titles sessions durably
(deterministic fallback in the spine, optional model providers). Remove the
TUI's own autoTitle generation — the latch, prompt, cap, and llm stream call —
and keep the terminal rename: the TUI folds the logged title on mount and sets
'<session title> — <configured title>' on every accepted session/title event.
The tui-agent example and the scripted PTY fixture mount
session-title-first-message-llm so titles stay model-made; the scripted
adapter's tool-less branch now answers that provider's auxiliary request.

See .agents/notes/implemented/simplification/2026-07-22-tui-titles-from-session-title-service.md
This commit is contained in:
Turtle
2026-07-22 15:01:54 +08:00
parent f1f35ccaef
commit 2a9f248594
19 changed files with 144 additions and 327 deletions

View File

@@ -23,6 +23,8 @@ flowchart LR
bundle_agent_core --> spine_sessions["ctx.sessions"]
bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
plugin_tui_session_title_llm["session-title-llm<br/>@deepseek-ai/dsh-session-title-first-message-llm"]
cfg --> plugin_tui_session_title_llm
plugin_tui_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
cfg --> plugin_tui_token_meter
plugin_tui_tool_result_prune["tool-result-prune<br/>@deepseek-ai/dsh-compact-tool-result-prune"]
@@ -67,6 +69,7 @@ flowchart LR
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
| `bash` | `@deepseek-ai/dsh-bash-local` |
| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
| `session-title-llm` | `@deepseek-ai/dsh-session-title-first-message-llm` |
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
| `tool-result-prune` | `@deepseek-ai/dsh-compact-tool-result-prune` |
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |

View File

@@ -45,8 +45,6 @@
ui:
showReasoning: true
maxToolOutputLines: 6
# Rename the terminal after the first message with a short model-made title.
autoTitle: true
# Keep the persona to identity and behavior; tool plugins own tool guidance.
# The loop resolves {{model}} from this agent's configuration.
persona: |
@@ -55,6 +53,19 @@
Verify your work by running the code or tests. Keep answers brief and
factual.
# Model-made session titles on the first-message cadence: replaces the spine's
# deterministic fallback title with a short model summary. The TUI renders the
# logged `session/title` as the banner subtitle and the terminal window title.
# Omitting provider/model inherits the main request's exact route.
- id: session-title-llm
name: '@deepseek-ai/dsh-session-title-first-message-llm'
config:
targetWords: 5
targetCjkCharacters: 10
maxInputBytes: 4096
maxOutputTokens: 64
timeoutMs: 60000
# Replay-aware request pressure with one service-wide context window.
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'

View File

@@ -38,8 +38,9 @@ class ScriptedTuiAdapter extends LlmAdapter {
}
override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// The TUI's auto-title request carries no tool schemas, unlike every agent
// turn; answer it with a fixed title so the PTY test can assert the OSC set.
// The session-title provider's auxiliary request carries no tool schemas,
// unlike every agent turn; answer it with a fixed title so the PTY test can
// assert the logged title reaches the terminal window title.
if ((options.tools?.length ?? 0) === 0) {
for (const chunk of textChunks(TITLE_TEXT)) yield chunk
return

View File

@@ -30,4 +30,15 @@
persona: 'Scripted model {{model}}.'
ui:
showReasoning: true
autoTitle: true
# Model-made session titles, as in the shipped cordis.yml: the scripted adapter
# answers the tool-less title request with a fixed string so the PTY test can
# assert the logged title reaches the terminal window title.
- id: session-title-llm
name: '@deepseek-ai/dsh-session-title-first-message-llm'
config:
targetWords: 5
targetCjkCharacters: 10
maxInputBytes: 4096
maxOutputTokens: 64
timeoutMs: 10000

View File

@@ -256,9 +256,11 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
expect(output).toContain('How should the scripted run proceed?')
expect(output).toContain('Safe')
expect(output).toContain('Decision received. Scripted TUI run complete.')
// Auto-title: the first user message drives a tool-less title call that the
// scripted adapter answers, and the TUI sets it via OSC 0.
expect(output).toContain('\u001B]0;scripted session title\u0007')
// Session title: the first user message drives the first-message-llm
// provider's tool-less title call; the scripted adapter answers it, the
// accepted title lands in the log, and the TUI renders the terminal window
// title as `<session title> — <configured title>` via OSC 0.
expect(output).toContain('\u001B]0;scripted session title — DeepSeek Harness\u0007')
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)

View File

@@ -268,9 +268,6 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
color: true,
showReasoning: true,
title: 'DSH TUI snapshot',
// Off so replay stays deterministic: an auto-title request is not among the
// recorded turns. Auto-title's PTY coverage lives in the keyless smoke.
autoTitle: false,
welcome: `Recorded replay: ${scenario.name}`,
maxToolOutputLines: 8,
}, {