feat(FN-3009): migrate remote settings to global scope
This merge migrates Fusion's settings architecture to a unified global scope (FN-3009), consolidating settings types, defaults, merge semantics, updater logic, and routing into a centralized system that aligns dashboard remote settings with global configuration. Secondary changes include adding the Fusion-Task-Id: FN-3009
This commit is contained in:
@@ -1636,24 +1636,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle deep merge + targeted null clear semantics for remoteAccess
|
||||
const incomingRemoteAccess = (projectPatch as Record<string, unknown>)["remoteAccess"];
|
||||
if (incomingRemoteAccess === null) {
|
||||
delete (config.settings as unknown as Record<string, unknown>)["remoteAccess"];
|
||||
delete (projectPatch as Record<string, unknown>)["remoteAccess"];
|
||||
} else if (isPlainObject(incomingRemoteAccess)) {
|
||||
const existingRemoteAccess = (config.settings as unknown as Record<string, unknown>)["remoteAccess"];
|
||||
const mergedRemoteAccess = deepMergeWithNullDelete(existingRemoteAccess, incomingRemoteAccess);
|
||||
|
||||
if (mergedRemoteAccess === undefined) {
|
||||
delete (config.settings as unknown as Record<string, unknown>)["remoteAccess"];
|
||||
delete (projectPatch as Record<string, unknown>)["remoteAccess"];
|
||||
} else {
|
||||
(config.settings as unknown as Record<string, unknown>)["remoteAccess"] = mergedRemoteAccess;
|
||||
(projectPatch as Record<string, unknown>)["remoteAccess"] = mergedRemoteAccess;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle null values for other top-level keys (non-promptOverrides)
|
||||
for (const key of Object.keys(projectPatch)) {
|
||||
if ((projectPatch as Record<string, unknown>)[key] === null) {
|
||||
@@ -1662,32 +1644,6 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle experimentalFeatures merging (similar to promptOverrides)
|
||||
const incomingExperimentalFeatures = (projectPatch as Record<string, unknown>)["experimentalFeatures"];
|
||||
if (
|
||||
incomingExperimentalFeatures !== undefined &&
|
||||
typeof incomingExperimentalFeatures === "object" &&
|
||||
incomingExperimentalFeatures !== null &&
|
||||
!Array.isArray(incomingExperimentalFeatures)
|
||||
) {
|
||||
// experimentalFeatures: { key: value } → merge with existing
|
||||
const incomingMap = incomingExperimentalFeatures as Record<string, unknown>;
|
||||
const existingMap = ((config.settings as unknown as Record<string, unknown>)["experimentalFeatures"] as Record<string, boolean>) ?? {};
|
||||
const mergedMap: Record<string, boolean> = { ...existingMap };
|
||||
|
||||
for (const [key, value] of Object.entries(incomingMap)) {
|
||||
// null values remove the feature
|
||||
if (value === null) {
|
||||
delete mergedMap[key];
|
||||
} else if (typeof value === "boolean") {
|
||||
mergedMap[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
(config.settings as unknown as Record<string, unknown>)["experimentalFeatures"] = mergedMap;
|
||||
(projectPatch as Record<string, unknown>)["experimentalFeatures"] = mergedMap;
|
||||
}
|
||||
|
||||
const globalSettings = await this.globalSettingsStore.getSettings();
|
||||
const previousMerged: Settings = { ...DEFAULT_SETTINGS, ...globalSettings, ...config.settings } as Settings;
|
||||
const updatedProjectSettings = { ...config.settings, ...projectPatch };
|
||||
@@ -1727,7 +1683,48 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
const config = this.readConfigFast();
|
||||
const previous: Settings = { ...DEFAULT_SETTINGS, ...previousGlobal, ...config.settings } as Settings;
|
||||
|
||||
const updatedGlobal = await this.globalSettingsStore.updateSettings(patch);
|
||||
const globalPatch: Partial<GlobalSettings> = { ...patch };
|
||||
|
||||
// Handle deep merge + targeted null clear semantics for remoteAccess
|
||||
const incomingRemoteAccess = (globalPatch as Record<string, unknown>)["remoteAccess"];
|
||||
if (incomingRemoteAccess === null) {
|
||||
(globalPatch as Record<string, unknown>)["remoteAccess"] = null;
|
||||
} else if (isPlainObject(incomingRemoteAccess)) {
|
||||
const existingRemoteAccess = (previousGlobal as Record<string, unknown>)["remoteAccess"];
|
||||
const mergedRemoteAccess = deepMergeWithNullDelete(existingRemoteAccess, incomingRemoteAccess);
|
||||
|
||||
if (mergedRemoteAccess === undefined) {
|
||||
(globalPatch as Record<string, unknown>)["remoteAccess"] = null;
|
||||
} else {
|
||||
(globalPatch as Record<string, unknown>)["remoteAccess"] = mergedRemoteAccess;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle experimentalFeatures merging (similar to promptOverrides)
|
||||
const incomingExperimentalFeatures = (globalPatch as Record<string, unknown>)["experimentalFeatures"];
|
||||
if (incomingExperimentalFeatures === null) {
|
||||
(globalPatch as Record<string, unknown>)["experimentalFeatures"] = null;
|
||||
} else if (
|
||||
incomingExperimentalFeatures !== undefined &&
|
||||
typeof incomingExperimentalFeatures === "object" &&
|
||||
!Array.isArray(incomingExperimentalFeatures)
|
||||
) {
|
||||
const incomingMap = incomingExperimentalFeatures as Record<string, unknown>;
|
||||
const existingMap = ((previousGlobal as Record<string, unknown>)["experimentalFeatures"] as Record<string, boolean>) ?? {};
|
||||
const mergedMap: Record<string, boolean> = { ...existingMap };
|
||||
|
||||
for (const [key, value] of Object.entries(incomingMap)) {
|
||||
if (value === null) {
|
||||
delete mergedMap[key];
|
||||
} else if (typeof value === "boolean") {
|
||||
mergedMap[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
(globalPatch as Record<string, unknown>)["experimentalFeatures"] = mergedMap;
|
||||
}
|
||||
|
||||
const updatedGlobal = await this.globalSettingsStore.updateSettings(globalPatch);
|
||||
const merged: Settings = { ...DEFAULT_SETTINGS, ...updatedGlobal, ...config.settings } as Settings;
|
||||
|
||||
// Emit settings:updated so SSE listeners pick up the change
|
||||
|
||||
Reference in New Issue
Block a user