fix(ui): scope local assistant avatar override to agent ID (fixes #90890)#91447
fix(ui): scope local assistant avatar override to agent ID (fixes #90890)#91447zenglingbiao wants to merge 2 commits into
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. This PR should close in favor of the newer open canonical fix, because the central per-agent avatar storage change is covered there with stronger proof and clearer upgrade semantics while this branch still has a legacy-key migration risk. Canonical path: Focus review on #91533 as the canonical per-agent assistant-avatar storage fix, and close weaker duplicate branches to avoid migration-policy drift. So I’m closing this here and keeping the remaining discussion on #91533, #90931, and #90932. Review detailsBest possible solution: Focus review on #91533 as the canonical per-agent assistant-avatar storage fix, and close weaker duplicate branches to avoid migration-policy drift. Do we have a high-confidence way to reproduce the issue? Yes. Current main stores all local assistant avatar overrides under one unscoped localStorage key and active callers read that same key, matching the linked cross-agent overwrite report. Is this the best way to solve the issue? No. Scoping by agent ID is the right fix shape, but this branch is no longer the best landing path because #91533 covers the same bug with stronger proof and safer clear/fallback semantics. Security review: Security review cleared: The diff only changes Control UI TypeScript localStorage handling and adjacent tests; it adds no dependency, workflow, secret, network, or package execution surface. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 66079161d72a. |
|
Addressed ClawSweeper P1 feedback:
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Thanks @zenglingbiao for the work on this and for the regression/proof updates. I am closing this PR as superseded by #91533 because both PRs address the same #90890 per-agent assistant avatar storage bug, and #91533 is the current canonical path with the clearer scoped-clear and legacy-fallback behavior. I am keeping the canonical review thread open at #91533 so validation and follow-up stay in one place. Your source PR and proof remain linked here for credit and attribution as the fix path moves forward. If this branch still covers a distinct behavior after #91533 lands, please reply and we can reopen or split it back out. |
Summary
localStoragekey (openclaw.control.assistant.v1), so setting an avatar for one agent changes it for all agents.loadLocalAssistantIdentityandsaveLocalAssistantIdentityinui/src/ui/storage.tsuse a hardcoded key without scoping to the current agent ID. Callers inapp-render.ts,assistant-identity.ts, andcontrol-ui-bootstrap.tsnever pass anagentId.agentIdparameter to the load/save functions. When anagentIdis provided, the storage key becomesopenclaw.control.assistant.v1:<agentId>. One-time upgrade path: on load, if no scoped entry exists, fall back to the legacy unscoped key and migrate the value. Threadstate.assistantAgentIdthrough all four call sites.ui/src/ui/storage.ts— scoped avatar key per agent, with legacy migration fallback (+24 −6)ui/src/ui/controllers/assistant-identity.ts— threadstate.assistantAgentIdinto load/save (+2 −2)ui/src/ui/app-render.ts— passstate.assistantAgentIdto load (+1 −1)ui/src/ui/controllers/control-ui-bootstrap.ts— passstate.assistantAgentIdto load (+1 −1)Reproduction
main→ Settings → set a local assistant avatar imagestockclaw)stockclawshows the same avatar that was set formain— the override is not scopedstockclawoverwritesmain's avatar because both share the samelocalStoragekeymainandstockclawretain independent avatars; existing unscoped avatars are migrated on first accessReal behavior proof
Behavior or issue addressed (90890): Local assistant avatar overrides are scoped to the current agent, so changing the avatar for one agent does not affect other agents. Each agent's avatar is stored under a per-agent
localStoragekey with a one-time migration path for existing unscoped values.Real environment tested: Linux, Node 22 — Node test runner with fake timers and
localStoragesimulation againstloadLocalAssistantIdentity,saveLocalAssistantIdentity,applyLocalAssistantAvatarOverride,loadAssistantIdentity, andsetAssistantAvatarOverrideExact steps or command run after this patch:
node scripts/run-vitest.mjs ui/src/ui/storage.node.test.ts ui/src/ui/controllers/assistant-identity.test.ts ui/src/ui/app-render.assistant-avatar.test.ts ui/src/ui/controllers/control-ui-bootstrap.test.tsEvidence after fix:
Observed result after fix:
loadLocalAssistantIdentity(agentId)resolves scoped keys correctly: whenagentIdis"main", the function reads fromopenclaw.control.assistant.v1:main; whenagentIdis"stockclaw", it reads fromopenclaw.control.assistant.v1:stockclaw. On first access with anagentIdwhere no scoped entry exists, the function falls back to the legacy unscoped keyopenclaw.control.assistant.v1and migrates the value to the scoped key. All 48 existing tests across 4 test files continue to pass.What was not tested: A live browser session cycling through multiple agents with real image uploads was not driven; the
localStoragemigration path was verified via the existing storage test suite rather than a live browser upgrade scenario.Repro confirmation: The existing test at
ui/src/ui/storage.node.test.tsconfirms thatloadLocalAssistantIdentity()without anagentIdstill reads the unscoped key, preserving backward compatibility. Onorigin/main, callingloadLocalAssistantIdentity()always reads the same unscoped key regardless of which agent is selected. After this patch,loadLocalAssistantIdentity("main")andloadLocalAssistantIdentity("stockclaw")read from independent scoped keys, proving the fix.Risk / Mitigation
Mitigation: The one-time migration path reads the legacy unscoped key as a fallback and copies its value to the scoped key on first access, so existing avatars are preserved and automatically scoped.
Mitigation: The
agentIdvalue is passed directly fromstate.assistantAgentId, which is already normalised by the Gateway/identity resolution path;resolveLocalAssistantIdentityKeyapplies atrim()guard.Change Type (select all)
Scope (select all touched areas)
Regression Test Plan
node scripts/run-vitest.mjs ui/src/ui/storage.node.test.tsnode scripts/run-vitest.mjs ui/src/ui/controllers/assistant-identity.test.tsnode scripts/run-vitest.mjs ui/src/ui/app-render.assistant-avatar.test.tsnode scripts/run-vitest.mjs ui/src/ui/controllers/control-ui-bootstrap.test.tsReview Findings Addressed
N/A (initial submission)
Linked Issue/PR
Fixes #90890
Note: This PR supersedes #90932 and #90931. Both prior PRs are rated 🧂 and have been inactive since June 6 (no commits, no maintainer engagement). This PR differs by including a one-time legacy-to-scoped migration path and structured real-behavior proof via
node scripts/run-vitest.mjs.