displayModel gains a `config` source, ranked below any report from the running
CLI and above the launch model: custom endpoint, then statusline or screen,
then config, then launch, then nothing. The screen still wins whenever it
names a model, since that is what the running TUI uses.
- Registry data: capabilities.modelDetect gains `configResolver`, a NAMED
reader (src/model-config-resolvers.ts), like a launcher profile; dsh names
'deepseek-route' (the reader from the previous commit). `screenLine` becomes
optional; the schema refuses a modelDetect naming nothing, an unknown
reader, or screenLines without a screenLine.
- Session: the reader runs from _withPaneLifecycle's finally, so at every pane
start, attach and relaunch, with the session's own launch config
(legacyConfigForMode) and env (its clamped overrides, then the server's), so
a per-session DSH_HOME is the home read. Async; a read that lands after a
newer one or after the session stopped is dropped; a remote or docker
session reads nothing locally. A change emits displayModelChanged
(broadcast and persist). Not restored after a restart: the next attach
reads it again, and a restored screen value outranks it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A session header can only name the model a session runs if the server knows
it, so SessionState gains `displayModel: { model, source }`, resolved in a pure
module (src/session-display-model.ts), strongest first:
- custom-endpoint: a Custom Model Endpoint Profile's modelId answers the
session, whatever alias the CLI prints;
- statusline / screen: the newest report from the running CLI itself.
Claude's statusLine exporter already posts model.display_name on every
render; the status-telemetry route now records it (only for a CLI with
capabilities.statusLineTelemetry). A CLI whose registry entry declares the
new capabilities.modelDetect has its footer read off the pane capture the
idle/working probe already takes (no extra tmux call), so an in-session
/model switch is followed at the next transition;
- launch: the model the session was launched with (claude's --model or the
app-wide default, another CLI's <cli>Config.model), read where the registry
says the model param lives;
- nothing known: no field, never a placeholder.
modelDetect is registry data, measured on live panes: dsh-TUI's status line
on the row under its composer (qwen3.8-27b on the owner's route) and codex's
`<model> <effort> ·` footer on its last row. Both anchor on chrome only that
CLI draws, over the last rows of the screen only; a transcript line shaped like
the footer is never taken (fixture tests). The pattern goes through
compileVersionRegex() with exactly one capture group, checked at load time.
An unreadable or covered footer keeps the last model (unlike the watching
label: a model does not stop running when something covers its row). Model
text is untrusted: escape sequences and control characters are stripped and it
is capped at 64 characters. A change emits displayModelChanged, broadcast
(session:updated) and persisted; a restart restores a CLI-reported model until
the next report.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>