fix ongoing findings issue

This commit is contained in:
gsknnft
2026-04-20 17:19:50 -04:00
parent e215cae553
commit 97502dbe54
2 changed files with 187 additions and 4 deletions
+76 -4
View File
@@ -5,6 +5,7 @@ import type {
StudioFocusedPreference,
StudioGatewaySettings,
StudioGatewaySettingsPublic,
StudioGatewaySettingsPatch,
StudioOfficePreferencePatch,
StudioSettingsPublic,
StudioSettingsPatch,
@@ -267,13 +268,83 @@ const mergeOfficeFloorsPatch = (
return { ...current, ...next };
};
const mergeGatewayProfilesPatch = (
current: StudioGatewaySettingsPatch["profiles"],
next: StudioGatewaySettingsPatch["profiles"],
): StudioGatewaySettingsPatch["profiles"] => {
if (next === undefined) return current;
if (next === null) return null;
if (!current || current === null) return { ...next };
const merged: NonNullable<StudioGatewaySettingsPatch["profiles"]> = {
...current,
};
for (const [adapterType, profilePatch] of Object.entries(next)) {
if (profilePatch === null) {
merged[adapterType as keyof typeof merged] = null;
continue;
}
const existing = merged[adapterType as keyof typeof merged];
merged[adapterType as keyof typeof merged] =
existing && existing !== null
? { ...existing, ...profilePatch }
: { ...profilePatch };
}
return merged;
};
const mergeGatewayConnectionPatch = (
current: StudioGatewaySettingsPatch["lastKnownGood"],
next: StudioGatewaySettingsPatch["lastKnownGood"],
): StudioGatewaySettingsPatch["lastKnownGood"] => {
if (next === undefined) return current;
if (next === null) return null;
if (!current || current === null) return { ...next };
return { ...current, ...next };
};
const mergeGatewayPatch = (
current: StudioSettingsPatch["gateway"],
next: StudioSettingsPatch["gateway"],
): StudioSettingsPatch["gateway"] => {
if (next === undefined) return current;
if (next === null) return null;
if (!current || current === null) {
return {
...next,
...(next.profiles !== undefined
? { profiles: mergeGatewayProfilesPatch(undefined, next.profiles) }
: {}),
...(next.lastKnownGood !== undefined
? {
lastKnownGood: mergeGatewayConnectionPatch(
undefined,
next.lastKnownGood,
),
}
: {}),
};
}
const profiles = mergeGatewayProfilesPatch(current.profiles, next.profiles);
const lastKnownGood = mergeGatewayConnectionPatch(
current.lastKnownGood,
next.lastKnownGood,
);
return {
...current,
...next,
...(profiles !== undefined ? { profiles } : {}),
...(lastKnownGood !== undefined ? { lastKnownGood } : {}),
};
};
const mergeStudioPatch = (
current: StudioSettingsPatch | null,
next: StudioSettingsPatch
): StudioSettingsPatch => {
if (!current) {
return {
...(next.gateway !== undefined ? { gateway: next.gateway } : {}),
...(next.gateway !== undefined ? { gateway: mergeGatewayPatch(undefined, next.gateway) } : {}),
...(next.activeFloorId !== undefined ? { activeFloorId: next.activeFloorId } : {}),
...(next.focused ? { focused: { ...next.focused } } : {}),
...(next.avatars ? { avatars: { ...next.avatars } } : {}),
@@ -297,11 +368,12 @@ const mergeStudioPatch = (
const standup = mergeStandupPatch(current.standup, next.standup);
const taskBoard = mergeTaskBoardPatch(current.taskBoard, next.taskBoard);
const officeFloors = mergeOfficeFloorsPatch(current.officeFloors, next.officeFloors);
const gateway = mergeGatewayPatch(current.gateway, next.gateway);
return {
...(next.gateway !== undefined
? { gateway: next.gateway }
: current.gateway !== undefined
? { gateway: current.gateway }
? { gateway }
: gateway !== undefined
? { gateway }
: {}),
...(next.activeFloorId !== undefined
? { activeFloorId: next.activeFloorId }
@@ -50,6 +50,117 @@ describe("StudioSettingsCoordinator", () => {
coordinator.dispose();
});
it("merges queued gateway patches field-by-field before flushing", async () => {
const fetchSettings = vi.fn(async () => createResponse());
const updateSettings = vi.fn(async () => createResponse());
const coordinator = new StudioSettingsCoordinator({ fetchSettings, updateSettings }, 300);
coordinator.schedulePatch(
{
gateway: {
lastKnownGood: {
url: "ws://localhost:18789",
token: undefined,
adapterType: "openclaw",
},
},
},
300,
);
coordinator.schedulePatch(
{
gateway: {
url: "http://localhost:7770",
adapterType: "local",
profiles: {
local: {
url: "http://localhost:7770",
token: undefined,
},
},
},
},
300,
);
await vi.advanceTimersByTimeAsync(300);
expect(updateSettings).toHaveBeenCalledTimes(1);
expect(updateSettings).toHaveBeenCalledWith({
gateway: {
url: "http://localhost:7770",
adapterType: "local",
profiles: {
local: {
url: "http://localhost:7770",
token: undefined,
},
},
lastKnownGood: {
url: "ws://localhost:18789",
token: undefined,
adapterType: "openclaw",
},
},
});
coordinator.dispose();
});
it("merges queued gateway profile and last-known-good subpatches", async () => {
const fetchSettings = vi.fn(async () => createResponse());
const updateSettings = vi.fn(async () => createResponse());
const coordinator = new StudioSettingsCoordinator({ fetchSettings, updateSettings }, 300);
coordinator.schedulePatch({
gateway: {
profiles: {
hermes: {
url: "ws://localhost:18888",
token: undefined,
},
},
lastKnownGood: {
url: "ws://localhost:18789",
token: "stored-token",
},
},
});
coordinator.schedulePatch({
gateway: {
profiles: {
hermes: {
token: "new-token",
},
},
lastKnownGood: {
adapterType: "openclaw",
},
},
});
await vi.advanceTimersByTimeAsync(300);
expect(updateSettings).toHaveBeenCalledTimes(1);
expect(updateSettings).toHaveBeenCalledWith({
gateway: {
profiles: {
hermes: {
url: "ws://localhost:18888",
token: "new-token",
},
},
lastKnownGood: {
url: "ws://localhost:18789",
token: "stored-token",
adapterType: "openclaw",
},
},
});
coordinator.dispose();
});
it("flushPending persists queued patch immediately", async () => {
const fetchSettings = vi.fn(async () => createResponse());
const updateSettings = vi.fn(async () => createResponse());