From c010d5d99f880b5d223a269b4133a131ed7d2d64 Mon Sep 17 00:00:00 2001 From: roboomp Date: Fri, 12 Jun 2026 10:23:43 +0000 Subject: [PATCH 1/2] fix(cli): corrected model scope banner suffix Fixed the interactive Model scope formatter so unset thinking levels omit the suffix and explicit levels render as id:level. Added regression coverage for the startup banner list formatter. Fixes #2385 --- packages/coding-agent/CHANGELOG.md | 4 ++++ packages/coding-agent/src/main.ts | 8 ++------ .../coding-agent/src/model-scope-display.ts | 17 +++++++++++++++++ .../test/model-scope-display.test.ts | 14 ++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 packages/coding-agent/src/model-scope-display.ts create mode 100644 packages/coding-agent/test/model-scope-display.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 86343fe33..90dc248d4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the interactive Model scope startup banner so models without an explicit thinking level do not show `:undefined`, while scoped models with one still show `:` ([#2385](https://github.com/can1357/oh-my-pi/issues/2385)). + ## [15.11.8] - 2026-06-12 ### Added diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 2c10b2656..506f9a19a 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -51,6 +51,7 @@ import { ExtensionRunner } from "./extensibility/extensions/runner"; import type { ExtensionUIContext } from "./extensibility/extensions/types"; import { scheduleMarketplaceAutoUpdate } from "./extensibility/plugins/marketplace-auto-update"; import type { MCPManager } from "./mcp"; +import { formatModelScopeList } from "./model-scope-display"; import { InteractiveMode } from "./modes/interactive-mode"; import type { PrintModeOptions } from "./modes/print-mode"; import { CURRENT_SETUP_VERSION } from "./modes/setup-version"; @@ -1269,12 +1270,7 @@ export async function runRootCommand( const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels; if (scopedModelsForDisplay.length > 0) { - const modelList = scopedModelsForDisplay - .map(scopedModel => { - const thinkingStr = !scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; - return `${scopedModel.model.id}${thinkingStr}`; - }) - .join(", "); + const modelList = formatModelScopeList(scopedModelsForDisplay); // Routed through the TUI (not stdout): the startup capture owns the // terminal in raw mode here, and the TUI's first clearScrollback paint // would wipe a pre-TUI line anyway. diff --git a/packages/coding-agent/src/model-scope-display.ts b/packages/coding-agent/src/model-scope-display.ts new file mode 100644 index 000000000..7ac5f2c41 --- /dev/null +++ b/packages/coding-agent/src/model-scope-display.ts @@ -0,0 +1,17 @@ +/** Formats scoped model selectors for interactive startup notifications. */ +export interface ModelScopeDisplayEntry { + model: { + id: string; + }; + thinkingLevel?: string; +} + +/** Builds the compact `id[:thinking]` list shown in the Model scope banner. */ +export function formatModelScopeList(scopedModels: ReadonlyArray): string { + return scopedModels + .map(scopedModel => { + const thinkingStr = scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; + return `${scopedModel.model.id}${thinkingStr}`; + }) + .join(", "); +} diff --git a/packages/coding-agent/test/model-scope-display.test.ts b/packages/coding-agent/test/model-scope-display.test.ts new file mode 100644 index 000000000..9cbc8529d --- /dev/null +++ b/packages/coding-agent/test/model-scope-display.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "bun:test"; +import { formatModelScopeList } from "../src/model-scope-display"; + +describe("formatModelScopeList", () => { + it("omits thinking suffixes when unset and includes explicit levels", () => { + const modelList = formatModelScopeList([ + { model: { id: "openai/gpt-5.5" } }, + { model: { id: "anthropic/claude-opus-4.8" }, thinkingLevel: "high" }, + ]); + + expect(modelList).toBe("openai/gpt-5.5, anthropic/claude-opus-4.8:high"); + expect(modelList).not.toContain(":undefined"); + }); +}); From b2a79608a1c1b9197172f54bf74a44aea47d6b2c Mon Sep 17 00:00:00 2001 From: roboomp Date: Fri, 12 Jun 2026 10:28:15 +0000 Subject: [PATCH 2/2] fix(cli): preserved explicit scope thinking suffix Gated the Model scope banner suffix on the original scope's explicitThinkingLevel flag and switched the display source back to the pre-default ScopedModel[] so non-explicit scope entries no longer render the global default thinking level. Fixes #2385 --- packages/coding-agent/CHANGELOG.md | 2 +- packages/coding-agent/src/main.ts | 9 ++++++--- packages/coding-agent/src/model-scope-display.ts | 13 +++++++++++-- .../coding-agent/test/model-scope-display.test.ts | 15 +++++++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 90dc248d4..77ca410f8 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed the interactive Model scope startup banner so models without an explicit thinking level do not show `:undefined`, while scoped models with one still show `:` ([#2385](https://github.com/can1357/oh-my-pi/issues/2385)). +- Fixed the interactive Model scope startup banner so models without an explicit thinking level do not show `:undefined`, and entries that were scoped without a `:level` are no longer rendered with the global default thinking level (which `applyRootSessionOptions` pre-fills on the cycling array for Ctrl+P) ([#2385](https://github.com/can1357/oh-my-pi/issues/2385)). ## [15.11.8] - 2026-06-12 diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 506f9a19a..cf080e412 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -1268,9 +1268,12 @@ export async function runRootCommand( const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined); const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs); - const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels; - if (scopedModelsForDisplay.length > 0) { - const modelList = formatModelScopeList(scopedModelsForDisplay); + if (scopedModels.length > 0) { + // Use the original `ScopedModel[]` here (not `sessionOptions.scopedModels`, + // whose thinking levels are pre-filled with the global default for Ctrl+P + // cycling) so the banner only shows `:level` suffixes the user actually + // scoped. + const modelList = formatModelScopeList(scopedModels); // Routed through the TUI (not stdout): the startup capture owns the // terminal in raw mode here, and the TUI's first clearScrollback paint // would wipe a pre-TUI line anyway. diff --git a/packages/coding-agent/src/model-scope-display.ts b/packages/coding-agent/src/model-scope-display.ts index 7ac5f2c41..6226f06b6 100644 --- a/packages/coding-agent/src/model-scope-display.ts +++ b/packages/coding-agent/src/model-scope-display.ts @@ -1,16 +1,25 @@ -/** Formats scoped model selectors for interactive startup notifications. */ +/** + * Display shape for one entry in the interactive Model scope banner. + * + * `thinkingLevel` carries the suffix value, but it is only rendered when + * `explicitThinkingLevel` is true — the caller may have filled the field with + * the global default for runtime use (e.g. Ctrl+P cycling), and the banner + * must not present that default as if the user had requested it. + */ export interface ModelScopeDisplayEntry { model: { id: string; }; thinkingLevel?: string; + explicitThinkingLevel: boolean; } /** Builds the compact `id[:thinking]` list shown in the Model scope banner. */ export function formatModelScopeList(scopedModels: ReadonlyArray): string { return scopedModels .map(scopedModel => { - const thinkingStr = scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; + const thinkingStr = + scopedModel.explicitThinkingLevel && scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; return `${scopedModel.model.id}${thinkingStr}`; }) .join(", "); diff --git a/packages/coding-agent/test/model-scope-display.test.ts b/packages/coding-agent/test/model-scope-display.test.ts index 9cbc8529d..2c1fdd682 100644 --- a/packages/coding-agent/test/model-scope-display.test.ts +++ b/packages/coding-agent/test/model-scope-display.test.ts @@ -4,11 +4,22 @@ import { formatModelScopeList } from "../src/model-scope-display"; describe("formatModelScopeList", () => { it("omits thinking suffixes when unset and includes explicit levels", () => { const modelList = formatModelScopeList([ - { model: { id: "openai/gpt-5.5" } }, - { model: { id: "anthropic/claude-opus-4.8" }, thinkingLevel: "high" }, + { model: { id: "openai/gpt-5.5" }, explicitThinkingLevel: false }, + { model: { id: "anthropic/claude-opus-4.8" }, thinkingLevel: "high", explicitThinkingLevel: true }, ]); expect(modelList).toBe("openai/gpt-5.5, anthropic/claude-opus-4.8:high"); expect(modelList).not.toContain(":undefined"); }); + + it("hides the suffix when the level was filled from the global default", () => { + // `applyRootSessionOptions` fills `sessionOptions.scopedModels[*].thinkingLevel` + // with the global default for Ctrl+P cycling — the banner must not surface that + // default as if the user had scoped `:high`. + const modelList = formatModelScopeList([ + { model: { id: "openai/gpt-5.5" }, thinkingLevel: "high", explicitThinkingLevel: false }, + ]); + + expect(modelList).toBe("openai/gpt-5.5"); + }); });