Merge PR #5491: fix(models): preserve auto role thinking across restart (@roboomp)
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
|
||||
- Fixed inconsistent history rendering when toggling the display setting for compacted items
|
||||
- Fixed discovered plugin `.mcp.json` stdio servers launching relative `command`/`cwd` values against the session cwd instead of the plugin's config directory, which broke bundled ChatGPT/Codex plugins such as Computer Use (`ENOENT` spawning `./…` from an unrelated cwd). Relative `cwd` and path-like `command` (`./`, `../`) discovered by the `claude-plugins`/`omp-plugins` providers now resolve against the `.mcp.json` directory; bare executables like `npx` are left untouched. ([#5330](https://github.com/can1357/oh-my-pi/issues/5330))
|
||||
- Fixed Model Hub DEFAULT role assignments with `auto` retaining a stale concrete reasoning suffix, which restored the old level after restart ([#5326](https://github.com/can1357/oh-my-pi/issues/5326))
|
||||
- Fixed configured `retry.fallbackChains` never engaging on non-retryable provider errors (e.g. "Cloud Code Assist API returned an empty response"): a hard error on a model covered by a fallback chain now switches to the next candidate instead of failing the turn, while still never backoff-retrying the failing model itself
|
||||
- Fixed transcript rebuilds (compaction, `/compact`, and toggling history display) repainting content below stale scrollback when collapsing history; rebuilds now correctly clear the scrollback buffer when history is collapsed
|
||||
- Improved auto-compaction to automatically drop images and elide content when context is tight, and added persistent warning badges to the compaction divider when manual intervention is required
|
||||
|
||||
@@ -719,7 +719,7 @@ export class SelectorController {
|
||||
if (role === "default") {
|
||||
const { switched } = await this.ctx.session.setModel(model, role, {
|
||||
selector,
|
||||
thinkingLevel: concreteThinking,
|
||||
thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
|
||||
persist: true,
|
||||
currentContextTokens,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
||||
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { SelectorController } from "@oh-my-pi/pi-coding-agent/modes/controllers/selector-controller";
|
||||
import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
import type { ResolvedRoleModel } from "@oh-my-pi/pi-coding-agent/session/agent-session";
|
||||
import { AUTO_THINKING } from "@oh-my-pi/pi-coding-agent/thinking";
|
||||
import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state";
|
||||
|
||||
let settingsState: SettingsTestState | undefined;
|
||||
@@ -57,6 +61,98 @@ describe("selector setting side effects", () => {
|
||||
expect(requestRender).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("clears stale default role thinking when auto is selected", async () => {
|
||||
const testTheme = await getThemeByName("dark");
|
||||
if (!testTheme) throw new Error("Failed to load dark theme for model selector test");
|
||||
setThemeInstance(testTheme);
|
||||
|
||||
const previousModel = getBundledModel("openai", "gpt-5.5");
|
||||
const nextModel = getBundledModel("openai", "gpt-5.6");
|
||||
if (!previousModel || !nextModel) throw new Error("Expected bundled OpenAI models for selector test");
|
||||
|
||||
const settings = Settings.isolated({
|
||||
defaultThinkingLevel: ThinkingLevel.High,
|
||||
modelRoles: { default: `${previousModel.provider}/${previousModel.id}:high` },
|
||||
});
|
||||
const setModel = vi.fn(async () => ({ switched: true }));
|
||||
const autoApplied = Promise.withResolvers<void>();
|
||||
const setThinkingLevel = vi.fn((level: ThinkingLevel | typeof AUTO_THINKING, persist: boolean) => {
|
||||
if (level === AUTO_THINKING && persist) {
|
||||
settings.set("defaultThinkingLevel", level);
|
||||
autoApplied.resolve();
|
||||
}
|
||||
});
|
||||
let captured: unknown;
|
||||
const controller = new SelectorController({
|
||||
ui: {
|
||||
requestRender: vi.fn(),
|
||||
setFocus: vi.fn(),
|
||||
showOverlay: vi.fn((component: unknown) => {
|
||||
captured = component;
|
||||
return { hide: vi.fn() };
|
||||
}),
|
||||
terminal: { rows: 40 },
|
||||
},
|
||||
editorContainer: { clear: vi.fn(), addChild: vi.fn(), children: [] },
|
||||
editor: {},
|
||||
settings,
|
||||
session: {
|
||||
model: nextModel,
|
||||
modelRegistry: {
|
||||
getAll: () => [previousModel, nextModel],
|
||||
getAvailable: () => [previousModel, nextModel],
|
||||
getError: () => undefined,
|
||||
refresh: async () => {},
|
||||
refreshProvider: async () => {},
|
||||
getDiscoverableProviders: () => [],
|
||||
getProviderDiscoveryState: () => undefined,
|
||||
authStorage: { hasAuth: () => false },
|
||||
},
|
||||
scopedModels: [{ model: nextModel }],
|
||||
getContextUsage: () => undefined,
|
||||
setModel,
|
||||
setThinkingLevel,
|
||||
},
|
||||
statusLine: { invalidate: vi.fn() },
|
||||
updateEditorBorderColor: vi.fn(),
|
||||
keybindings: { getKeys: () => [] },
|
||||
showStatus: vi.fn(),
|
||||
showError: vi.fn(),
|
||||
} as unknown as InteractiveModeContext);
|
||||
|
||||
controller.showModelSelector();
|
||||
const hub = captured as
|
||||
| { handleInput(data: string): void; render(width: number): string[]; dispose(): void }
|
||||
| undefined;
|
||||
if (!hub) throw new Error("Expected model hub overlay to be shown");
|
||||
try {
|
||||
hub.handleInput("\x1b[A"); // All models → Roles.
|
||||
hub.handleInput("\n"); // Enter the role rows.
|
||||
hub.handleInput("\n"); // Assign DEFAULT.
|
||||
hub.handleInput("\n"); // Pick the scoped replacement model.
|
||||
|
||||
const levels = [ThinkingLevel.Inherit, ThinkingLevel.Off, AUTO_THINKING, ...getSupportedEfforts(nextModel)];
|
||||
const highIndex = levels.indexOf(ThinkingLevel.High);
|
||||
const autoIndex = levels.indexOf(AUTO_THINKING);
|
||||
if (highIndex < autoIndex) throw new Error("Expected auto before high in the thinking strip");
|
||||
for (let i = autoIndex; i < highIndex; i++) hub.handleInput("\x1b[D");
|
||||
hub.handleInput("\n");
|
||||
await autoApplied.promise;
|
||||
|
||||
expect(setModel).toHaveBeenLastCalledWith(
|
||||
nextModel,
|
||||
"default",
|
||||
expect.objectContaining({
|
||||
thinkingLevel: ThinkingLevel.Inherit,
|
||||
persist: true,
|
||||
}),
|
||||
);
|
||||
expect(setThinkingLevel).toHaveBeenLastCalledWith(AUTO_THINKING, true);
|
||||
} finally {
|
||||
hub.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it("replaces malformed default retry fallback chains from the model selector action", async () => {
|
||||
const testTheme = await getThemeByName("dark");
|
||||
if (!testTheme) throw new Error("Failed to load dark theme for model selector test");
|
||||
|
||||
Reference in New Issue
Block a user