mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 20:03:05 +00:00
fix: preserve externally-added settings when saving
When a user edits settings.json while pi is running (e.g., adding enabledModels), those settings would be lost when pi saved other changes (e.g., changing thinking level via Shift+Tab). The fix re-reads the file before saving and merges the current file contents with in-memory changes, so external edits are preserved. Adds test coverage for SettingsManager.
This commit is contained in:
parent
1f2dbb57f6
commit
e0dbdc56d6
2 changed files with 115 additions and 1 deletions
|
|
@ -178,7 +178,13 @@ export class SettingsManager {
|
|||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// Save only global settings (project settings are read-only)
|
||||
// Re-read current file to preserve any settings added externally while running
|
||||
const currentFileSettings = SettingsManager.loadFromFile(this.settingsPath);
|
||||
// Merge: file settings as base, globalSettings (in-memory changes) as overrides
|
||||
const mergedSettings = deepMergeSettings(currentFileSettings, this.globalSettings);
|
||||
this.globalSettings = mergedSettings;
|
||||
|
||||
// Save merged settings (project settings are read-only)
|
||||
writeFileSync(this.settingsPath, JSON.stringify(this.globalSettings, null, 2), "utf-8");
|
||||
} catch (error) {
|
||||
console.error(`Warning: Could not save settings file: ${error}`);
|
||||
|
|
|
|||
108
packages/coding-agent/test/settings-manager.test.ts
Normal file
108
packages/coding-agent/test/settings-manager.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||
|
||||
describe("SettingsManager", () => {
|
||||
const testDir = join(process.cwd(), "test-settings-tmp");
|
||||
const agentDir = join(testDir, "agent");
|
||||
const projectDir = join(testDir, "project");
|
||||
|
||||
beforeEach(() => {
|
||||
// Clean up and create fresh directories
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true });
|
||||
}
|
||||
mkdirSync(agentDir, { recursive: true });
|
||||
mkdirSync(join(projectDir, ".pi"), { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("preserves externally added settings", () => {
|
||||
it("should preserve enabledModels when changing thinking level", () => {
|
||||
// Create initial settings file
|
||||
const settingsPath = join(agentDir, "settings.json");
|
||||
writeFileSync(
|
||||
settingsPath,
|
||||
JSON.stringify({
|
||||
theme: "dark",
|
||||
defaultModel: "claude-sonnet",
|
||||
}),
|
||||
);
|
||||
|
||||
// Create SettingsManager (simulates pi starting up)
|
||||
const manager = SettingsManager.create(projectDir, agentDir);
|
||||
|
||||
// Simulate user editing settings.json externally to add enabledModels
|
||||
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
currentSettings.enabledModels = ["claude-opus-4-5", "gpt-5.2-codex"];
|
||||
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||
|
||||
// User changes thinking level via Shift+Tab
|
||||
manager.setDefaultThinkingLevel("high");
|
||||
|
||||
// Verify enabledModels is preserved
|
||||
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
expect(savedSettings.enabledModels).toEqual(["claude-opus-4-5", "gpt-5.2-codex"]);
|
||||
expect(savedSettings.defaultThinkingLevel).toBe("high");
|
||||
expect(savedSettings.theme).toBe("dark");
|
||||
expect(savedSettings.defaultModel).toBe("claude-sonnet");
|
||||
});
|
||||
|
||||
it("should preserve custom settings when changing theme", () => {
|
||||
const settingsPath = join(agentDir, "settings.json");
|
||||
writeFileSync(
|
||||
settingsPath,
|
||||
JSON.stringify({
|
||||
defaultModel: "claude-sonnet",
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = SettingsManager.create(projectDir, agentDir);
|
||||
|
||||
// User adds custom settings externally
|
||||
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
currentSettings.shellPath = "/bin/zsh";
|
||||
currentSettings.extensions = ["/path/to/extension.ts"];
|
||||
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||
|
||||
// User changes theme
|
||||
manager.setTheme("light");
|
||||
|
||||
// Verify all settings preserved
|
||||
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
expect(savedSettings.shellPath).toBe("/bin/zsh");
|
||||
expect(savedSettings.extensions).toEqual(["/path/to/extension.ts"]);
|
||||
expect(savedSettings.theme).toBe("light");
|
||||
});
|
||||
|
||||
it("should let in-memory changes override file changes for same key", () => {
|
||||
const settingsPath = join(agentDir, "settings.json");
|
||||
writeFileSync(
|
||||
settingsPath,
|
||||
JSON.stringify({
|
||||
theme: "dark",
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = SettingsManager.create(projectDir, agentDir);
|
||||
|
||||
// User externally sets thinking level to "low"
|
||||
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
currentSettings.defaultThinkingLevel = "low";
|
||||
writeFileSync(settingsPath, JSON.stringify(currentSettings, null, 2));
|
||||
|
||||
// But then changes it via UI to "high"
|
||||
manager.setDefaultThinkingLevel("high");
|
||||
|
||||
// In-memory change should win
|
||||
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||
expect(savedSettings.defaultThinkingLevel).toBe("high");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue