mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
fix: preserve external settings.json edits on reload (#1046)
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
This commit is contained in:
parent
c808de605a
commit
bac57f81be
3 changed files with 170 additions and 5 deletions
|
|
@ -123,6 +123,8 @@ export class SettingsManager {
|
|||
private inMemoryProjectSettings: Settings; // For in-memory mode
|
||||
private settings: Settings;
|
||||
private persist: boolean;
|
||||
private modifiedFields = new Set<keyof Settings>(); // Track fields modified during session
|
||||
private modifiedNestedFields = new Map<keyof Settings, Set<string>>(); // Track nested field modifications
|
||||
|
||||
private constructor(
|
||||
settingsPath: string | null,
|
||||
|
|
@ -231,6 +233,17 @@ export class SettingsManager {
|
|||
this.settings = deepMergeSettings(this.settings, overrides);
|
||||
}
|
||||
|
||||
/** Mark a field as modified during this session */
|
||||
private markModified(field: keyof Settings, nestedKey?: string): void {
|
||||
this.modifiedFields.add(field);
|
||||
if (nestedKey) {
|
||||
if (!this.modifiedNestedFields.has(field)) {
|
||||
this.modifiedNestedFields.set(field, new Set());
|
||||
}
|
||||
this.modifiedNestedFields.get(field)!.add(nestedKey);
|
||||
}
|
||||
}
|
||||
|
||||
private save(): void {
|
||||
if (this.persist && this.settingsPath) {
|
||||
try {
|
||||
|
|
@ -239,13 +252,33 @@ export class SettingsManager {
|
|||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// Re-read current file to preserve any settings added externally while running
|
||||
// Re-read current file to get latest external changes
|
||||
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)
|
||||
// Start with file settings as base - preserves external edits
|
||||
const mergedSettings: Settings = { ...currentFileSettings };
|
||||
|
||||
// Only override with in-memory values for fields that were explicitly modified during this session
|
||||
for (const field of this.modifiedFields) {
|
||||
const value = this.globalSettings[field];
|
||||
|
||||
// Handle nested objects specially - merge at nested level to preserve unmodified nested keys
|
||||
if (this.modifiedNestedFields.has(field) && typeof value === "object" && value !== null) {
|
||||
const nestedModified = this.modifiedNestedFields.get(field)!;
|
||||
const baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};
|
||||
const inMemoryNested = value as Record<string, unknown>;
|
||||
const mergedNested = { ...baseNested };
|
||||
for (const nestedKey of nestedModified) {
|
||||
mergedNested[nestedKey] = inMemoryNested[nestedKey];
|
||||
}
|
||||
(mergedSettings as Record<string, unknown>)[field] = mergedNested;
|
||||
} else {
|
||||
// For top-level primitives and arrays, use the modified value directly
|
||||
(mergedSettings as Record<string, unknown>)[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.globalSettings = mergedSettings;
|
||||
writeFileSync(this.settingsPath, JSON.stringify(this.globalSettings, null, 2), "utf-8");
|
||||
} catch (error) {
|
||||
console.error(`Warning: Could not save settings file: ${error}`);
|
||||
|
|
@ -284,6 +317,7 @@ export class SettingsManager {
|
|||
|
||||
setLastChangelogVersion(version: string): void {
|
||||
this.globalSettings.lastChangelogVersion = version;
|
||||
this.markModified("lastChangelogVersion");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -297,17 +331,21 @@ export class SettingsManager {
|
|||
|
||||
setDefaultProvider(provider: string): void {
|
||||
this.globalSettings.defaultProvider = provider;
|
||||
this.markModified("defaultProvider");
|
||||
this.save();
|
||||
}
|
||||
|
||||
setDefaultModel(modelId: string): void {
|
||||
this.globalSettings.defaultModel = modelId;
|
||||
this.markModified("defaultModel");
|
||||
this.save();
|
||||
}
|
||||
|
||||
setDefaultModelAndProvider(provider: string, modelId: string): void {
|
||||
this.globalSettings.defaultProvider = provider;
|
||||
this.globalSettings.defaultModel = modelId;
|
||||
this.markModified("defaultProvider");
|
||||
this.markModified("defaultModel");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -317,6 +355,7 @@ export class SettingsManager {
|
|||
|
||||
setSteeringMode(mode: "all" | "one-at-a-time"): void {
|
||||
this.globalSettings.steeringMode = mode;
|
||||
this.markModified("steeringMode");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +365,7 @@ export class SettingsManager {
|
|||
|
||||
setFollowUpMode(mode: "all" | "one-at-a-time"): void {
|
||||
this.globalSettings.followUpMode = mode;
|
||||
this.markModified("followUpMode");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +375,7 @@ export class SettingsManager {
|
|||
|
||||
setTheme(theme: string): void {
|
||||
this.globalSettings.theme = theme;
|
||||
this.markModified("theme");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +385,7 @@ export class SettingsManager {
|
|||
|
||||
setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): void {
|
||||
this.globalSettings.defaultThinkingLevel = level;
|
||||
this.markModified("defaultThinkingLevel");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -356,6 +398,7 @@ export class SettingsManager {
|
|||
this.globalSettings.compaction = {};
|
||||
}
|
||||
this.globalSettings.compaction.enabled = enabled;
|
||||
this.markModified("compaction", "enabled");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -390,6 +433,7 @@ export class SettingsManager {
|
|||
this.globalSettings.retry = {};
|
||||
}
|
||||
this.globalSettings.retry.enabled = enabled;
|
||||
this.markModified("retry", "enabled");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -407,6 +451,7 @@ export class SettingsManager {
|
|||
|
||||
setHideThinkingBlock(hide: boolean): void {
|
||||
this.globalSettings.hideThinkingBlock = hide;
|
||||
this.markModified("hideThinkingBlock");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -416,6 +461,7 @@ export class SettingsManager {
|
|||
|
||||
setShellPath(path: string | undefined): void {
|
||||
this.globalSettings.shellPath = path;
|
||||
this.markModified("shellPath");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -425,6 +471,7 @@ export class SettingsManager {
|
|||
|
||||
setQuietStartup(quiet: boolean): void {
|
||||
this.globalSettings.quietStartup = quiet;
|
||||
this.markModified("quietStartup");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -434,6 +481,7 @@ export class SettingsManager {
|
|||
|
||||
setShellCommandPrefix(prefix: string | undefined): void {
|
||||
this.globalSettings.shellCommandPrefix = prefix;
|
||||
this.markModified("shellCommandPrefix");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -443,6 +491,7 @@ export class SettingsManager {
|
|||
|
||||
setCollapseChangelog(collapse: boolean): void {
|
||||
this.globalSettings.collapseChangelog = collapse;
|
||||
this.markModified("collapseChangelog");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -452,6 +501,7 @@ export class SettingsManager {
|
|||
|
||||
setPackages(packages: PackageSource[]): void {
|
||||
this.globalSettings.packages = packages;
|
||||
this.markModified("packages");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -468,6 +518,7 @@ export class SettingsManager {
|
|||
|
||||
setExtensionPaths(paths: string[]): void {
|
||||
this.globalSettings.extensions = paths;
|
||||
this.markModified("extensions");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -484,6 +535,7 @@ export class SettingsManager {
|
|||
|
||||
setSkillPaths(paths: string[]): void {
|
||||
this.globalSettings.skills = paths;
|
||||
this.markModified("skills");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -500,6 +552,7 @@ export class SettingsManager {
|
|||
|
||||
setPromptTemplatePaths(paths: string[]): void {
|
||||
this.globalSettings.prompts = paths;
|
||||
this.markModified("prompts");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -516,6 +569,7 @@ export class SettingsManager {
|
|||
|
||||
setThemePaths(paths: string[]): void {
|
||||
this.globalSettings.themes = paths;
|
||||
this.markModified("themes");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -532,6 +586,7 @@ export class SettingsManager {
|
|||
|
||||
setEnableSkillCommands(enabled: boolean): void {
|
||||
this.globalSettings.enableSkillCommands = enabled;
|
||||
this.markModified("enableSkillCommands");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -548,6 +603,7 @@ export class SettingsManager {
|
|||
this.globalSettings.terminal = {};
|
||||
}
|
||||
this.globalSettings.terminal.showImages = show;
|
||||
this.markModified("terminal", "showImages");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -560,6 +616,7 @@ export class SettingsManager {
|
|||
this.globalSettings.images = {};
|
||||
}
|
||||
this.globalSettings.images.autoResize = enabled;
|
||||
this.markModified("images", "autoResize");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -572,6 +629,7 @@ export class SettingsManager {
|
|||
this.globalSettings.images = {};
|
||||
}
|
||||
this.globalSettings.images.blockImages = blocked;
|
||||
this.markModified("images", "blockImages");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -581,6 +639,7 @@ export class SettingsManager {
|
|||
|
||||
setEnabledModels(patterns: string[] | undefined): void {
|
||||
this.globalSettings.enabledModels = patterns;
|
||||
this.markModified("enabledModels");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -590,6 +649,7 @@ export class SettingsManager {
|
|||
|
||||
setDoubleEscapeAction(action: "fork" | "tree"): void {
|
||||
this.globalSettings.doubleEscapeAction = action;
|
||||
this.markModified("doubleEscapeAction");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -599,6 +659,7 @@ export class SettingsManager {
|
|||
|
||||
setShowHardwareCursor(enabled: boolean): void {
|
||||
this.globalSettings.showHardwareCursor = enabled;
|
||||
this.markModified("showHardwareCursor");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
@ -608,6 +669,7 @@ export class SettingsManager {
|
|||
|
||||
setEditorPaddingX(padding: number): void {
|
||||
this.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));
|
||||
this.markModified("editorPaddingX");
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue