Skip to content

Commit 7e134f5

Browse files
committed
fix: add agentId to type contract and regression tests (fixes #90890)
1 parent a9f500b commit 7e134f5

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

ui/src/ui/controllers/assistant-identity.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type AssistantAvatarOverrideState = {
2020
assistantAvatarSource?: string | null;
2121
assistantAvatarStatus?: "none" | "local" | "remote" | "data" | null;
2222
assistantAvatarReason?: string | null;
23+
assistantAgentId?: string | null;
2324
};
2425

2526
const assistantIdentityRequestVersions = new WeakMap<object, number>();

ui/src/ui/storage.node.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33
import { createImportedCustomThemeFixture } from "../test-helpers/custom-theme.ts";
44
import { createStorageMock } from "../test-helpers/storage.ts";
55
import {
6+
loadLocalAssistantIdentity,
67
loadLocalUserIdentity,
78
loadSettings,
9+
saveLocalAssistantIdentity,
810
saveLocalUserIdentity,
911
saveSettings,
1012
} from "./storage.ts";
@@ -680,3 +682,70 @@ describe("loadSettings default gateway URL derivation", () => {
680682
expect(localStorage.getItem("openclaw.control.user.v1")).toBeNull();
681683
});
682684
});
685+
686+
describe("loadLocalAssistantIdentity / saveLocalAssistantIdentity", () => {
687+
beforeEach(() => {
688+
vi.stubGlobal("localStorage", createStorageMock());
689+
localStorage.clear();
690+
});
691+
692+
afterEach(() => {
693+
vi.restoreAllMocks();
694+
});
695+
696+
it("scopes avatar override to agent when agentId is provided", () => {
697+
saveLocalAssistantIdentity({ avatar: "data:img/main.png" }, "main");
698+
saveLocalAssistantIdentity({ avatar: "data:img/stockclaw.png" }, "stockclaw");
699+
700+
expect(loadLocalAssistantIdentity("main").avatar).toBe("data:img/main.png");
701+
expect(loadLocalAssistantIdentity("stockclaw").avatar).toBe("data:img/stockclaw.png");
702+
});
703+
704+
it("uses unscoped key when agentId is not provided (backward compat)", () => {
705+
saveLocalAssistantIdentity({ avatar: "data:img/legacy.png" });
706+
707+
expect(loadLocalAssistantIdentity().avatar).toBe("data:img/legacy.png");
708+
expect(loadLocalAssistantIdentity(null).avatar).toBe("data:img/legacy.png");
709+
expect(localStorage.getItem("openclaw.control.assistant.v1")).toBe(
710+
'{"avatar":"data:img/legacy.png"}',
711+
);
712+
});
713+
714+
it("migrates legacy unscoped value to scoped key on first access", () => {
715+
// Simulate a user who set an avatar before this fix
716+
localStorage.setItem(
717+
"openclaw.control.assistant.v1",
718+
JSON.stringify({ avatar: "data:img/legacy.png" }),
719+
);
720+
721+
// First access with agentId should read legacy and migrate
722+
const result = loadLocalAssistantIdentity("main");
723+
expect(result.avatar).toBe("data:img/legacy.png");
724+
725+
// Legacy key should be removed after migration
726+
expect(localStorage.getItem("openclaw.control.assistant.v1")).toBeNull();
727+
728+
// Scoped key should now have the migrated value
729+
expect(JSON.parse(localStorage.getItem("openclaw.control.assistant.v1:main") ?? "{}")).toEqual({
730+
avatar: "data:img/legacy.png",
731+
});
732+
});
733+
734+
it("clears scoped avatar when setting null", () => {
735+
saveLocalAssistantIdentity({ avatar: "data:img/main.png" }, "main");
736+
expect(loadLocalAssistantIdentity("main").avatar).toBe("data:img/main.png");
737+
738+
saveLocalAssistantIdentity({ avatar: null }, "main");
739+
expect(loadLocalAssistantIdentity("main").avatar).toBeNull();
740+
expect(localStorage.getItem("openclaw.control.assistant.v1:main")).toBeNull();
741+
});
742+
743+
it("does not migrate empty legacy value", () => {
744+
localStorage.setItem("openclaw.control.assistant.v1", JSON.stringify({ avatar: null }));
745+
746+
const result = loadLocalAssistantIdentity("main");
747+
expect(result.avatar).toBeNull();
748+
// Legacy key should still be there since migration only happens for non-null values
749+
expect(localStorage.getItem("openclaw.control.assistant.v1")).toBe('{"avatar":null}');
750+
});
751+
});

0 commit comments

Comments
 (0)