From 9bb18d3e0e81ebcb5474a79481ed4589eae0e265 Mon Sep 17 00:00:00 2001 From: jacobleft <88961633+jacobleft@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:05:36 +0800 Subject: [PATCH 1/3] fix(tui): update welcome banner model name on model_changed event The welcome banner (WelcomeComponent) captures the session's active model name at init time but never receives updates when the model changes. Its setModel() method already existed but was never called after construction. This wires the model_changed agent session event to a new #updateWelcomeModel() method in InteractiveMode, mirroring the existing #updateWelcomeLspServers() pattern. The banner now reflects the live model after: - delayed config/modelRoles load (the startup race where an alphabetically-first provider is picked before config resolves) - explicit /model switches - retry-fallback model swaps --- packages/coding-agent/CHANGELOG.md | 4 ++++ .../coding-agent/src/modes/interactive-mode.ts | 14 ++++++++++++++ .../test/modes/components/welcome.test.ts | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 14334fddd..ef6d273c3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the welcome banner displaying a stale model name when the session's active model changes after startup (e.g. after a delayed config load or an explicit `/model` switch). The banner now subscribes to `model_changed` events and calls `WelcomeComponent.setModel()`, which already existed but was never wired up. + ## [17.3.0] - 2026-08-13 ### Breaking Changes diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index acf8f49d4..1c700712f 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -1170,6 +1170,9 @@ export class InteractiveMode implements InteractiveModeContext { this.#eventBusUnsubscribers.push( this.session.subscribe(event => { + if (event.type === "model_changed") { + this.#updateWelcomeModel(); + } void this.#handleGoalSessionEvent(event); }), onStatusLineSessionAccentChanged(() => { @@ -4423,6 +4426,17 @@ export class InteractiveMode implements InteractiveModeContext { ); } + #updateWelcomeModel(): void { + if (!this.#welcomeComponent) { + return; + } + + this.#welcomeComponent.setModel( + this.session.model?.name ?? "Unknown", + this.session.model?.provider ?? "Unknown", + ); + } + #updateWelcomeLspServers(): void { if (!this.#welcomeComponent) { return; diff --git a/packages/coding-agent/test/modes/components/welcome.test.ts b/packages/coding-agent/test/modes/components/welcome.test.ts index f1d5997d1..5379f99b0 100644 --- a/packages/coding-agent/test/modes/components/welcome.test.ts +++ b/packages/coding-agent/test/modes/components/welcome.test.ts @@ -62,3 +62,21 @@ describe("WelcomeComponent tips", () => { expect(pickWeightedTip([], 0.5)).toBe(""); }); }); + +describe("WelcomeComponent model name", () => { + beforeAll(async () => { + await Settings.init({ inMemory: true }); + await initTheme(false); + }); + + it("updates the rendered model name after setModel()", () => { + const welcome = new WelcomeComponent("1.0.0", "DeepSeek V4 Pro", "deepseek"); + const before = welcome.render(100).join("\n"); + expect(before).toContain("DeepSeek V4 Pro"); + + welcome.setModel("GLM-5.2", "zhipu-coding-plan"); + const after = welcome.render(100).join("\n"); + expect(after).toContain("GLM-5.2"); + expect(after).not.toContain("DeepSeek V4 Pro"); + }); +}); From 272a0e8461612106645b43d63e0c9abb34a72dbd Mon Sep 17 00:00:00 2001 From: jacobleft <88961633+jacobleft@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:22:05 +0800 Subject: [PATCH 2/3] fix(tui): resync welcome banner after init-time model changes Address review feedback: init-time model switches (#reconcileModeFromSession, #enterPlanMode for plan.defaultOnStartup) fire model_changed before the subscription exists, leaving the banner stale for those cases too. Adds a catch-up #updateWelcomeModel() call right after the subscription is installed, mirroring how the sessionName listener is registered before those same init steps (line 1094-1095 comment). Also adds a regression test for the catch-up path and comments tying the component test to the wiring gap it covers. --- .../src/modes/interactive-mode.ts | 5 +++++ .../test/modes/components/welcome.test.ts | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 1c700712f..02a227156 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -1180,6 +1180,11 @@ export class InteractiveMode implements InteractiveModeContext { this.#handleSessionAccentInputsChanged(); }), ); + // Resync the welcome banner to the live model: init-time reconciliations + // (#reconcileModeFromSession, #enterPlanMode for plan.defaultOnStartup) + // can change the model before this subscription exists, so the + // model_changed events they emit are never observed by the handler above. + this.#updateWelcomeModel(); this.#eventBusUnsubscribers.push( onModelRolesChanged(() => { void this.#reapplyPlanModeModelOnRoleChange(); diff --git a/packages/coding-agent/test/modes/components/welcome.test.ts b/packages/coding-agent/test/modes/components/welcome.test.ts index 5379f99b0..bb8adfec2 100644 --- a/packages/coding-agent/test/modes/components/welcome.test.ts +++ b/packages/coding-agent/test/modes/components/welcome.test.ts @@ -63,6 +63,12 @@ describe("WelcomeComponent tips", () => { }); }); +// Regression coverage for the stale welcome banner bug: the component is +// constructed with the session model at init time, but init-time model changes +// (plan.defaultOnStartup, #reconcileModeFromSession, delayed config load) can +// swap the model before the model_changed subscription exists. The catch-up +// resync in InteractiveMode.init() calls setModel() after those steps; these +// tests verify setModel() produces the correct rendered output. describe("WelcomeComponent model name", () => { beforeAll(async () => { await Settings.init({ inMemory: true }); @@ -79,4 +85,20 @@ describe("WelcomeComponent model name", () => { expect(after).toContain("GLM-5.2"); expect(after).not.toContain("DeepSeek V4 Pro"); }); + + it("resyncs to the live model after init-time model switches (catch-up path)", () => { + // Banner constructed with the pre-config fallback model (the startup race + // picks an alphabetically-first provider before modelRoles.default loads). + const welcome = new WelcomeComponent("1.0.0", "DeepSeek V4 Pro", "deepseek"); + expect(welcome.render(100).join("\n")).toContain("DeepSeek V4 Pro"); + + // Config loads, session switches to the real default — the model_changed + // event fires before the subscription exists, so InteractiveMode.init() + // calls setModel() explicitly as a catch-up resync. + welcome.setModel("GLM-5.2", "zhipu-coding-plan"); + const rendered = welcome.render(100).join("\n"); + expect(rendered).toContain("GLM-5.2"); + expect(rendered).toContain("zhipu-coding-plan"); + expect(rendered).not.toContain("DeepSeek"); + }); }); From 7b5921abc5f86ea8da28b8e9502ed7f2f6696c64 Mon Sep 17 00:00:00 2001 From: jacobleft <88961633+jacobleft@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:34:21 +0800 Subject: [PATCH 3/3] fix(tui): request repaint after catch-up model sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setModel() invalidates the render cache but doesn't schedule a paint. The initial forced startup paint (line 1104) may have already rendered the old model, so the catch-up #updateWelcomeModel() must explicitly request a render — matching #updateWelcomeLspServers(). --- packages/coding-agent/src/modes/interactive-mode.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 02a227156..9fb2af041 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -4440,6 +4440,7 @@ export class InteractiveMode implements InteractiveModeContext { this.session.model?.name ?? "Unknown", this.session.model?.provider ?? "Unknown", ); + this.ui.requestRender(); } #updateWelcomeLspServers(): void {