Merge PR #8428: fix(tui): welcome banner shows stale model name after model changes (@jacobleft)
This commit is contained in:
@@ -72,6 +72,9 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed Agent Hub parking a mid-spawn child JSONL (title + session header only) so `task` then fails with `already owned by another session generation` and the row cannot be revived.
|
||||
### 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
|
||||
|
||||
|
||||
@@ -1177,6 +1177,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(() => {
|
||||
@@ -1184,6 +1187,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();
|
||||
@@ -4430,6 +4438,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
);
|
||||
}
|
||||
|
||||
#updateWelcomeModel(): void {
|
||||
if (!this.#welcomeComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#welcomeComponent.setModel(
|
||||
this.session.model?.name ?? "Unknown",
|
||||
this.session.model?.provider ?? "Unknown",
|
||||
);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
|
||||
#updateWelcomeLspServers(): void {
|
||||
if (!this.#welcomeComponent) {
|
||||
return;
|
||||
|
||||
@@ -62,3 +62,43 @@ describe("WelcomeComponent tips", () => {
|
||||
expect(pickWeightedTip([], 0.5)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// 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 });
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user