From bad70f5e24b8e5622ae42ff4e2afdf88d799975d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 24 Mar 2026 17:47:08 +0900 Subject: [PATCH] fix(plugin): preserve selected model across messages Reuse the current session's selected model during config-time agent rebuilds when config.model is missing, so desktop sessions do not snap back to the default model after each send. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../agent-config-handler.test.ts | 58 +++++++++++++++++++ src/plugin-handlers/agent-config-handler.ts | 26 ++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/plugin-handlers/agent-config-handler.test.ts b/src/plugin-handlers/agent-config-handler.test.ts index d0d01a897..58cf1b4ed 100644 --- a/src/plugin-handlers/agent-config-handler.test.ts +++ b/src/plugin-handlers/agent-config-handler.test.ts @@ -7,8 +7,10 @@ import * as shared from "../shared" import * as sisyphusJunior from "../agents/sisyphus-junior" import type { OhMyOpenCodeConfig } from "../config" import * as agentLoader from "../features/claude-code-agent-loader" +import * as sessionState from "../features/claude-code-session-state" import * as skillLoader from "../features/opencode-skill-loader" import { getAgentDisplayName } from "../shared/agent-display-names" +import * as sessionModelState from "../shared/session-model-state" import { applyAgentConfig } from "./agent-config-handler" import type { PluginComponents } from "./plugin-components-loader" @@ -53,6 +55,8 @@ describe("applyAgentConfig builtin override protection", () => { let discoverOpencodeProjectSkillsSpy: ReturnType let loadUserAgentsSpy: ReturnType let loadProjectAgentsSpy: ReturnType + let getMainSessionIDSpy: ReturnType + let getSessionModelSpy: ReturnType let migrateAgentConfigSpy: ReturnType let logSpy: ReturnType @@ -123,6 +127,8 @@ describe("applyAgentConfig builtin override protection", () => { loadUserAgentsSpy = spyOn(agentLoader, "loadUserAgents").mockReturnValue({}) loadProjectAgentsSpy = spyOn(agentLoader, "loadProjectAgents").mockReturnValue({}) + getMainSessionIDSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(undefined) + getSessionModelSpy = spyOn(sessionModelState, "getSessionModel").mockReturnValue(undefined) migrateAgentConfigSpy = spyOn(shared, "migrateAgentConfig").mockImplementation( (config: Record) => config, @@ -140,6 +146,8 @@ describe("applyAgentConfig builtin override protection", () => { discoverOpencodeProjectSkillsSpy.mockRestore() loadUserAgentsSpy.mockRestore() loadProjectAgentsSpy.mockRestore() + getMainSessionIDSpy.mockRestore() + getSessionModelSpy.mockRestore() migrateAgentConfigSpy.mockRestore() logSpy.mockRestore() }) @@ -166,6 +174,56 @@ describe("applyAgentConfig builtin override protection", () => { expect(result[BUILTIN_SISYPHUS_DISPLAY_NAME]).toEqual(builtinSisyphusConfig) }) + test("reuses the main session model when config.model is missing", async () => { + // given + const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as { + mock: { calls: unknown[][] } + } + getMainSessionIDSpy.mockReturnValue("ses_main") + getSessionModelSpy.mockReturnValue({ providerID: "openai", modelID: "gpt-5.4" }) + + const config: Record = { + agent: {}, + } + + // when + await applyAgentConfig({ + config, + pluginConfig: createPluginConfig(), + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(createBuiltinAgentsMock.mock.calls).toHaveLength(1) + expect(createBuiltinAgentsMock.mock.calls[0]?.[3]).toBe("openai/gpt-5.4") + expect(createBuiltinAgentsMock.mock.calls[0]?.[9]).toBe("openai/gpt-5.4") + }) + + test("prefers config.model over the persisted main session model when both exist", async () => { + // given + const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as { + mock: { calls: unknown[][] } + } + getMainSessionIDSpy.mockReturnValue("ses_main") + getSessionModelSpy.mockReturnValue({ providerID: "openai", modelID: "gpt-5.4" }) + + const config = createBaseConfig() + + // when + await applyAgentConfig({ + config, + pluginConfig: createPluginConfig(), + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(createBuiltinAgentsMock.mock.calls).toHaveLength(1) + expect(createBuiltinAgentsMock.mock.calls[0]?.[3]).toBe("anthropic/claude-opus-4-6") + expect(createBuiltinAgentsMock.mock.calls[0]?.[9]).toBe("anthropic/claude-opus-4-6") + }) + test("filters user agents whose key differs from a builtin key only by case", async () => { // given loadUserAgentsSpy.mockReturnValue({ diff --git a/src/plugin-handlers/agent-config-handler.ts b/src/plugin-handlers/agent-config-handler.ts index 33f15f233..1c8a54eb4 100644 --- a/src/plugin-handlers/agent-config-handler.ts +++ b/src/plugin-handlers/agent-config-handler.ts @@ -2,8 +2,10 @@ import { createBuiltinAgents } from "../agents"; import { createSisyphusJuniorAgentWithOverrides } from "../agents/sisyphus-junior"; import type { OhMyOpenCodeConfig } from "../config"; import { log, migrateAgentConfig } from "../shared"; +import { getMainSessionID } from "../features/claude-code-session-state"; import { AGENT_NAME_MAP } from "../shared/migration"; import { getAgentDisplayName } from "../shared/agent-display-names"; +import { getSessionModel } from "../shared/session-model-state"; import { discoverConfigSourceSkills, discoverOpencodeGlobalSkills, @@ -27,6 +29,28 @@ type AgentConfigRecord = Record | undefined> & { plan?: Record; }; +function resolveCurrentModel(config: Record): string | undefined { + const configModel = config.model; + if (typeof configModel === "string") { + const trimmedModel = configModel.trim(); + if (trimmedModel.length > 0) { + return trimmedModel; + } + } + + const mainSessionID = getMainSessionID(); + if (!mainSessionID) { + return undefined; + } + + const sessionModel = getSessionModel(mainSessionID); + if (!sessionModel?.providerID || !sessionModel.modelID) { + return undefined; + } + + return `${sessionModel.providerID}/${sessionModel.modelID}`; +} + function getConfiguredDefaultAgent(config: Record): string | undefined { const defaultAgent = config.default_agent; if (typeof defaultAgent !== "string") return undefined; @@ -77,7 +101,7 @@ export async function applyAgentConfig(params: { const browserProvider = params.pluginConfig.browser_automation_engine?.provider ?? "playwright"; - const currentModel = params.config.model as string | undefined; + const currentModel = resolveCurrentModel(params.config); const disabledSkills = new Set(params.pluginConfig.disabled_skills ?? []); const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false; const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false;