diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a843be21..0cf98e80 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -24,6 +24,7 @@ ### Fixed +- Fixed `SettingsManager.inMemory()` initial settings being lost after reloads triggered by SDK resource loading ([#3616](https://github.com/badlogic/pi-mono/issues/3616)) - Fixed `models.json` provider compatibility to accept `compat.supportsLongCacheRetention`, allowing proxies to opt out of long-retention cache fields when needed while long retention is enabled by default when requested ([#3543](https://github.com/badlogic/pi-mono/issues/3543)) - Fixed `--thinking xhigh` for `openai-codex` `gpt-5.5` so it is no longer downgraded to `high`. - Fixed git package installs with custom `npmCommand` values such as `pnpm` by avoiding npm-specific production flags in that compatibility path ([#3604](https://github.com/badlogic/pi-mono/issues/3604)) diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index cd2339ac..d54b7753 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -289,7 +289,9 @@ export class SettingsManager { /** Create an in-memory SettingsManager (no file I/O) */ static inMemory(settings: Partial = {}): SettingsManager { const storage = new InMemorySettingsStorage(); - return new SettingsManager(storage, settings, {}); + const initialSettings = SettingsManager.migrateSettings(structuredClone(settings) as Record); + storage.withLock("global", () => JSON.stringify(initialSettings, null, 2)); + return SettingsManager.fromStorage(storage); } private static loadFromStorage(storage: SettingsStorage, scope: SettingsScope): Settings { diff --git a/packages/coding-agent/test/suite/regressions/3616-settings-inmemory-reload.test.ts b/packages/coding-agent/test/suite/regressions/3616-settings-inmemory-reload.test.ts new file mode 100644 index 00000000..daa2c030 --- /dev/null +++ b/packages/coding-agent/test/suite/regressions/3616-settings-inmemory-reload.test.ts @@ -0,0 +1,86 @@ +import { existsSync, mkdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { DefaultResourceLoader } from "../../../src/core/resource-loader.js"; +import { SettingsManager } from "../../../src/core/settings-manager.js"; + +describe("regression #3616: in-memory settings survive reload", () => { + let tempDir: string; + let agentDir: string; + + beforeEach(() => { + tempDir = join(tmpdir(), `pi-settings-inmemory-${Date.now()}-${Math.random().toString(36).slice(2)}`); + agentDir = join(tempDir, "agent"); + mkdirSync(agentDir, { recursive: true }); + }); + + afterEach(() => { + if (tempDir && existsSync(tempDir)) { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it("preserves initial settings after direct reload", async () => { + const settingsManager = SettingsManager.inMemory({ + defaultThinkingLevel: "high", + images: { autoResize: false }, + compaction: { enabled: false }, + }); + + await settingsManager.reload(); + + expect(settingsManager.getDefaultThinkingLevel()).toBe("high"); + expect(settingsManager.getImageAutoResize()).toBe(false); + expect(settingsManager.getCompactionEnabled()).toBe(false); + expect(settingsManager.getGlobalSettings()).toEqual({ + defaultThinkingLevel: "high", + images: { autoResize: false }, + compaction: { enabled: false }, + }); + }); + + it("preserves initial settings when DefaultResourceLoader reloads", async () => { + const settingsManager = SettingsManager.inMemory({ + defaultThinkingLevel: "high", + images: { autoResize: false }, + compaction: { enabled: false }, + }); + const resourceLoader = new DefaultResourceLoader({ + cwd: tempDir, + agentDir, + settingsManager, + noExtensions: true, + noSkills: true, + noPromptTemplates: true, + noThemes: true, + noContextFiles: true, + }); + + await resourceLoader.reload(); + + expect(settingsManager.getDefaultThinkingLevel()).toBe("high"); + expect(settingsManager.getImageAutoResize()).toBe(false); + expect(settingsManager.getCompactionEnabled()).toBe(false); + }); + + it("preserves initial settings after an unrelated setter, flush, and reload", async () => { + const settingsManager = SettingsManager.inMemory({ + images: { autoResize: false }, + compaction: { enabled: false }, + }); + + settingsManager.setTheme("dark"); + await settingsManager.flush(); + await settingsManager.reload(); + + expect(settingsManager.getTheme()).toBe("dark"); + expect(settingsManager.getImageAutoResize()).toBe(false); + expect(settingsManager.getCompactionEnabled()).toBe(false); + expect(settingsManager.getGlobalSettings()).toEqual({ + images: { autoResize: false }, + compaction: { enabled: false }, + theme: "dark", + }); + }); +});