fix(cli): keep status line on session model (#6514)

* fix(cli): keep status line on session model

Resolve status line model display drift after fast-model background subagents.

Refs #6512

* test(cli): cover preset status model fallback
This commit is contained in:
易良 2026-07-08 17:26:18 +08:00 committed by GitHub
parent 8296ce9e54
commit 51f0364d63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 26 deletions

View file

@ -69,6 +69,10 @@ const mockConfig = {
getTargetDir: vi.fn(() => '/test/dir'),
getModel: vi.fn(() => 'test-model'),
getModelDisplayName: vi.fn(() => 'Test Model'),
getModelsConfig: vi.fn(() => ({
getModelDisplayName: (model: string) =>
model === 'main-model' ? 'Main Model' : 'Test Model',
})),
getCliVersion: vi.fn(() => '1.0.0'),
getContentGeneratorConfig: vi.fn(getMockContentGeneratorConfig),
};
@ -191,6 +195,7 @@ describe('useStatusLine', () => {
mockUIState.sessionStats.metrics.files.totalLinesRemoved = 0;
mockVimMode.vimEnabled = false;
mockVimMode.vimMode = 'INSERT';
mockConfig.getModelDisplayName.mockReturnValue('Test Model');
mockConfig.getContentGeneratorConfig.mockReturnValue({
contextWindowSize: 131072,
});
@ -215,6 +220,27 @@ describe('useStatusLine', () => {
]);
});
it('falls back to config model display name when the preset current model is empty', () => {
mockUIState.currentModel = '';
const { result } = renderHook(() => useStatusLine());
expect(result.current.lines).toEqual([
'\u279c dir \u00b7 git:(main) \u00b7 Test Model \u00b7 131.1k Context 0.1% used',
]);
});
it('renders the session model when config is scoped to a fast subagent', () => {
mockUIState.currentModel = 'main-model';
mockConfig.getModelDisplayName.mockReturnValue('Fast Model');
const { result } = renderHook(() => useStatusLine());
expect(result.current.lines).toEqual([
'\u279c dir \u00b7 git:(main) \u00b7 Main Model \u00b7 131.1k Context 0.1% used',
]);
});
it('renders no lines when statusLine is explicitly null (opt-out)', () => {
setStatusLineConfig(null);
const { result } = renderHook(() => useStatusLine());
@ -715,6 +741,17 @@ describe('useStatusLine', () => {
const input = JSON.parse(stdinWrittenData);
expect(input.model.display_name).toBe('Test Model');
});
it('sends the session model when config is scoped to a fast subagent', () => {
mockUIState.currentModel = 'main-model';
mockConfig.getModelDisplayName.mockReturnValue('Fast Model');
setStatusLineConfig({ type: 'command', command: 'cat' });
renderHook(() => useStatusLine());
const input = JSON.parse(stdinWrittenData);
expect(input.model.display_name).toBe('Main Model');
});
});
// --- Stale generation handling ---

View file

@ -389,6 +389,24 @@ export function useStatusLine(): {
const doUpdate = useCallback(() => {
const preset = statusLinePresetRef.current;
const cmd = statusLineCommandRef.current;
if (!preset && !cmd) {
clearPullRequestLookup();
setOutput([]);
return;
}
const ui = uiStateRef.current;
const cfg = configRef.current;
const stats = ui.sessionStats;
const m = stats.metrics;
const contentGeneratorConfig = cfg.getContentGeneratorConfig();
const contextWindowSize = contentGeneratorConfig?.contextWindowSize || 0;
const modelDisplayName = ui.currentModel
? cfg.getModelsConfig().getModelDisplayName(ui.currentModel)
: cfg.getModelDisplayName();
const { totalInputTokens, totalOutputTokens } = aggregateModelTokens(m);
if (preset) {
if (activeChildRef.current) {
activeChildRef.current.kill();
@ -396,21 +414,13 @@ export function useStatusLine(): {
generationRef.current++;
}
const ui = uiStateRef.current;
const cfg = configRef.current;
const stats = ui.sessionStats;
const m = stats.metrics;
const currentDir = cfg.getTargetDir();
ensurePullRequestNumber(preset, currentDir, ui.branchName);
const { totalInputTokens, totalOutputTokens } = aggregateModelTokens(m);
const contentGeneratorConfig = cfg.getContentGeneratorConfig();
const contextWindowSize = contentGeneratorConfig?.contextWindowSize || 0;
const data = buildStatusLinePresetData({
sessionId: stats.sessionId,
version: cfg.getCliVersion(),
modelDisplayName: cfg.getModelDisplayName(),
modelDisplayName,
reasoning: contentGeneratorConfig?.reasoning,
currentDir,
branch: ui.branchName,
@ -429,19 +439,6 @@ export function useStatusLine(): {
clearPullRequestLookup();
const cmd = statusLineCommandRef.current;
if (!cmd) {
setOutput([]);
return;
}
const ui = uiStateRef.current;
const cfg = configRef.current;
const stats = ui.sessionStats;
const m = stats.metrics;
const contextWindowSize =
cfg.getContentGeneratorConfig()?.contextWindowSize || 0;
const usedPercentage =
contextWindowSize > 0
? Math.min(
@ -455,13 +452,11 @@ export function useStatusLine(): {
)
: 0;
const { totalInputTokens, totalOutputTokens } = aggregateModelTokens(m);
const input: StatusLineCommandInput = {
session_id: stats.sessionId,
version: cfg.getCliVersion() || 'unknown',
model: {
display_name: cfg.getModelDisplayName(),
display_name: modelDisplayName,
},
context_window: {
context_window_size: contextWindowSize,
@ -511,7 +506,7 @@ export function useStatusLine(): {
let child: ChildProcess;
try {
child = exec(
cmd,
cmd!,
{ cwd: cfg.getTargetDir(), timeout: 5000, maxBuffer: 1024 * 10 },
(error, stdout) => {
if (gen !== generationRef.current) return; // stale