Skip to content

fix(ui): scope local assistant avatar override to agent ID (fixes #90890)#91447

Closed
zenglingbiao wants to merge 2 commits into
openclaw:mainfrom
zenglingbiao:fix/issue-90890-avatar-agent-scope
Closed

fix(ui): scope local assistant avatar override to agent ID (fixes #90890)#91447
zenglingbiao wants to merge 2 commits into
openclaw:mainfrom
zenglingbiao:fix/issue-90890-avatar-agent-scope

Conversation

@zenglingbiao

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Local assistant avatar overrides are stored under a single global localStorage key (openclaw.control.assistant.v1), so setting an avatar for one agent changes it for all agents.
  • Root Cause: loadLocalAssistantIdentity and saveLocalAssistantIdentity in ui/src/ui/storage.ts use a hardcoded key without scoping to the current agent ID. Callers in app-render.ts, assistant-identity.ts, and control-ui-bootstrap.ts never pass an agentId.
  • Fix: Add an optional agentId parameter to the load/save functions. When an agentId is provided, the storage key becomes openclaw.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. Thread state.assistantAgentId through all four call sites.
  • What changed:
    • ui/src/ui/storage.ts — scoped avatar key per agent, with legacy migration fallback (+24 −6)
    • ui/src/ui/controllers/assistant-identity.ts — thread state.assistantAgentId into load/save (+2 −2)
    • ui/src/ui/app-render.ts — pass state.assistantAgentId to load (+1 −1)
    • ui/src/ui/controllers/control-ui-bootstrap.ts — pass state.assistantAgentId to load (+1 −1)
  • What did NOT change (scope boundary):
    • Gateway-side assistant identity resolution is unchanged
    • Remote (configured) avatar handling is unchanged
    • User avatar override logic is unchanged
    • No new config options or API surfaces were added

Reproduction

  1. Open Control UI, select agent main → Settings → set a local assistant avatar image
  2. Switch to a different agent (e.g. stockclaw)
  3. Observe that stockclaw shows the same avatar that was set for main — the override is not scoped
  4. Before this PR: setting a different avatar for stockclaw overwrites main's avatar because both share the same localStorage key
  5. After this PR: each agent stores its local avatar override under a scoped key, so main and stockclaw retain independent avatars; existing unscoped avatars are migrated on first access

Real 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 localStorage key with a one-time migration path for existing unscoped values.

Real environment tested: Linux, Node 22 — Node test runner with fake timers and localStorage simulation against loadLocalAssistantIdentity, saveLocalAssistantIdentity, applyLocalAssistantAvatarOverride, loadAssistantIdentity, and setAssistantAvatarOverride

Exact 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.ts

Evidence after fix:

 RUN  v4.1.7 /home/0668001395/openclawProject1

 Test Files  4 passed (4)
      Tests  48 passed (48)
   Start at  22:30:22
   Duration  3.15s (transform 611ms, setup 104ms, import 1.07s, tests 475ms, environment 1.09s)

[test] passed 1 shard in 3.66s

Observed result after fix: loadLocalAssistantIdentity(agentId) resolves scoped keys correctly: when agentId is "main", the function reads from openclaw.control.assistant.v1:main; when agentId is "stockclaw", it reads from openclaw.control.assistant.v1:stockclaw. On first access with an agentId where no scoped entry exists, the function falls back to the legacy unscoped key openclaw.control.assistant.v1 and 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 localStorage migration 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.ts confirms that loadLocalAssistantIdentity() without an agentId still reads the unscoped key, preserving backward compatibility. On origin/main, calling loadLocalAssistantIdentity() always reads the same unscoped key regardless of which agent is selected. After this patch, loadLocalAssistantIdentity("main") and loadLocalAssistantIdentity("stockclaw") read from independent scoped keys, proving the fix.

Risk / Mitigation

  • Risk: Existing users who have set a local avatar will see it "disappear" for agents where no scoped entry exists yet.
    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.
  • Risk: Agent ID normalisation differences could cause key mismatches.
    Mitigation: The agentId value is passed directly from state.assistantAgentId, which is already normalised by the Gateway/identity resolution path; resolveLocalAssistantIdentityKey applies a trim() guard.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • app: web-ui
  • Control UI — assistant avatar local storage

Regression Test Plan

  • node scripts/run-vitest.mjs ui/src/ui/storage.node.test.ts
  • node scripts/run-vitest.mjs ui/src/ui/controllers/assistant-identity.test.ts
  • node scripts/run-vitest.mjs ui/src/ui/app-render.assistant-avatar.test.ts
  • node scripts/run-vitest.mjs ui/src/ui/controllers/control-ui-bootstrap.test.ts

Review 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.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 8, 2026
@clawsweeper

clawsweeper Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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 details

Best 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:

  • Repository policy applied: Root AGENTS.md and ui/AGENTS.md were read fully; root persisted-state, duplicate-PR, proof, and PR review guidance affected the decision, while the UI scoped guide only contained i18n guidance not relevant to this diff. (AGENTS.md:1, 66079161d72a)
  • Current main still has the bug: Current main loads and saves assistant avatar overrides through one unscoped LOCAL_ASSISTANT_IDENTITY_KEY, so the linked issue is not implemented on main. (ui/src/ui/storage.ts:353, 66079161d72a)
  • Current callers omit agent ID: Current main sets state.assistantAgentId from gateway identity, then calls loadLocalAssistantIdentity() without passing that agent id. (ui/src/ui/controllers/assistant-identity.ts:69, 66079161d72a)
  • Latest release still has unscoped storage: The latest release tag v2026.6.6 also reads and writes the single unscoped assistant avatar key, so the remaining work belongs in a canonical fix PR rather than being already shipped. (ui/src/ui/storage.ts:353, 8c802aa68351)
  • This PR has a weaker migration shape: The branch migrates a legacy global avatar by writing it to the first scoped key read and then removing the legacy key, which can mis-associate existing browser state with the first agent opened after upgrade. (ui/src/ui/storage.ts:379, 7e134f58c5fd)
  • Newer canonical candidate covers the useful change: fix(ui): scope avatar storage per agent ID #91533 is open, cleanly mergeable, covers the same per-agent storage/caller changes, keeps legacy fallback, and adds explicit scoped-null behavior after clear. (ui/src/ui/storage.ts:383, 2e5b691a6dda)

Likely related people:

  • BunsDev: GitHub path history shows BunsDev authored the browser-local assistant avatar persistence feature and a later fix keeping local assistant avatar overrides authoritative. (role: feature introducer and recent area contributor; confidence: high; commits: c65aa1d2a6e5, 14249827928e; files: ui/src/ui/storage.ts, ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/app-render.ts)
  • steipete: GitHub path history shows recent assistant identity and bootstrap/session identity work near the state boundary this fix threads through. (role: adjacent identity/bootstrap contributor; confidence: medium; commits: dca9fa471f67, 74fb6be71680; files: ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/controllers/control-ui-bootstrap.ts)

Codex review notes: model internal, reasoning high; reviewed against 66079161d72a.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 8, 2026
@zenglingbiao

Copy link
Copy Markdown
Contributor Author

Addressed ClawSweeper P1 feedback:

  1. Fixed type contract: Added assistantAgentId to AssistantAvatarOverrideState type
  2. Added regression tests: 5 new tests covering scoped storage, legacy migration, backward compatibility, null clearing, and empty-value handling — all passing via node scripts/run-vitest.mjs
  3. Real browser proof: Not available in this environment (no browser/display); the structured test evidence covers the scoping and migration logic paths

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 8, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 15, 2026
@openclaw-clownfish

Copy link
Copy Markdown
Contributor

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.

@openclaw-clownfish openclaw-clownfish Bot added the clownfish Tracked by Clownfish automation label Jun 15, 2026
@zenglingbiao zenglingbiao deleted the fix/issue-90890-avatar-agent-scope branch June 22, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui clownfish Tracked by Clownfish automation merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: You cannot give an avatar to a specific agent in settings

1 participant