diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index 02e1798789..374f87e09b 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -4699,6 +4699,51 @@ test("resumeAgentFromPersistence keeps metadata config, applies overrides, and p }); }); +test("importProviderSession preserves the native model instead of applying the new-session default", async () => { + const workdir = mkdtempSync(join(tmpdir(), "agent-manager-import-model-")); + onTestFinished(() => rmSync(workdir, { recursive: true, force: true })); + const storage = new AgentStorage(join(workdir, "agents"), logger); + const nativeModel = "native-selected-model"; + + class ImportClient extends TestAgentClient { + importConfig: AgentSessionConfig | undefined; + + async importSession(input: ImportProviderSessionInput, context: ImportProviderSessionContext) { + this.importConfig = context.config; + // A resumable provider applies explicit selections but otherwise retains its model. + const session = new TestAgentSession({ + ...context.config, + model: context.config.model ?? nativeModel, + }); + return { + session, + config: context.storedConfig, + persistence: { provider: this.provider, sessionId: input.providerHandleId }, + timeline: [], + }; + } + } + + const client = new ImportClient(); + const manager = new AgentManager({ clients: { codex: client }, registry: storage, logger }); + const imported = await manager.importProviderSession({ + provider: "codex", + providerHandleId: "native-session", + cwd: workdir, + workspaceId: "import-model", + }); + await manager.flush(); + + expect(imported.runtimeInfo?.model).toBe(nativeModel); + expect(imported.config.model).toBe(nativeModel); + expect(client.importConfig?.model).toBeUndefined(); + expect((await storage.get(imported.id))?.config.model).toBe(nativeModel); + + await manager.reloadAgentSession(imported.id); + expect(client.resumeOverrides.at(-1)?.model).toBe(nativeModel); + await manager.getAgent(imported.id)?.session?.close(); +}); + test("importProviderSession imports the selected session without listing and publishes ready state", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-import-session-")); const storagePath = join(workdir, "agents"); diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index b47e12e558..e976f40853 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -1679,6 +1679,7 @@ export class AgentManager { cwd: input.cwd, }, resolvedAgentId, + { resolveDefaultModel: false }, ); this.paseoToolPolicies.set(resolvedAgentId, paseoToolPolicy); const launchContext = await this.buildLaunchContext( @@ -1705,8 +1706,13 @@ export class AgentManager { await this.requireExternalMcpSupport(imported.session, providerLaunchConfig, launchContext); let handedToRegistration = false; try { + const runtimeInfo = await imported.session.getRuntimeInfo(); const importedConfig = await this.normalizeConfig( - stripInternalPaseoMcpServer(imported.config), + stripInternalPaseoMcpServer({ + ...imported.config, + model: runtimeInfo.model ?? imported.config.model, + }), + { resolveDefaultModel: false }, ); const timelineRows = buildImportedTimelineRows(imported.timeline); const initialTitle = resolveImportedAgentTitle(importedConfig, timelineRows); @@ -5723,11 +5729,12 @@ export class AgentManager { private async prepareSessionConfig( config: AgentSessionConfig, agentId: string, - options: { env?: Record; purpose?: AgentResumePurpose } = {}, + options: NormalizeConfigOptions = {}, ): Promise { const storedConfig = await this.normalizeConfig(stripInternalPaseoMcpServer(config), { env: options.env, purpose: options.purpose, + resolveDefaultModel: options.resolveDefaultModel, }); const paseoToolPolicy = this.paseoToolsEnabled ? this.resolvePaseoToolPolicy(storedConfig.provider) diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index 78df159fc0..40935c2668 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -1379,6 +1379,42 @@ describe("Codex app-server provider", () => { } }); + test.each([undefined, "configured-model"])( + "import resolves model %s before the first collaboration turn", + async (configuredModel) => { + const appServer = createFakeCodexAppServer({ + "config/read": () => ({ + config: { model: configuredModel, model_reasoning_effort: "low" }, + }), + "collaborationMode/list": () => ({ + data: [{ name: "Code", mode: "default", model: null, reasoning_effort: null }], + }), + }); + const provider = createProviderWithFakeAppServer(appServer); + const config = createConfig({ model: undefined, thinkingOptionId: undefined }); + const imported = await provider.importSession( + { providerHandleId: "native-thread", cwd: config.cwd! }, + { config, storedConfig: config }, + ); + + try { + await imported.session.startTurn("first follow-up after import"); + await expect(appServer.waitForTurnStart()).resolves.toMatchObject({ + threadId: "native-thread", + model: configuredModel ?? "gpt-5.4", + effort: "low", + collaborationMode: { + mode: "default", + settings: { model: configuredModel ?? "gpt-5.4", reasoning_effort: "low" }, + }, + }); + appServer.assertNoErrors(); + } finally { + await imported.session.close(); + } + }, + ); + test("provider persistence leaves options owned by the stored agent config", async () => { const appServer = createFakeCodexAppServer(); const provider = createProviderWithFakeAppServer(appServer); diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index b031ae410e..2f4ba468ba 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -5554,6 +5554,7 @@ export class CodexAppServerAgentSession implements AgentSession { this.config.model = model; this.reconcileServiceTier(); this.config.thinkingOptionId = thinkingOptionId; + this.refreshResolvedCollaborationMode(); return model; }