Skip to content

fix(ui): scope local assistant avatar overrides per agent (#90890)#90931

Closed
hanZeng-08 wants to merge 1 commit into
openclaw:mainfrom
hanZeng-08:feature/issue-90890-avatar-per-agent
Closed

fix(ui): scope local assistant avatar overrides per agent (#90890)#90931
hanZeng-08 wants to merge 1 commit into
openclaw:mainfrom
hanZeng-08:feature/issue-90890-avatar-per-agent

Conversation

@hanZeng-08

@hanZeng-08 hanZeng-08 commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #90890

Comment for Issue #90890 — Agent avatar changes overwrite all agents

Summary

The Control UI stores the assistant avatar override in localStorage under a single global key (openclaw.control.assistant.v1). Because the key is not scoped by agent, setting an avatar for one agent overwrites the avatar for every other agent.

Root Cause

ui/src/ui/storage.ts:351-379 defines:

const LOCAL_ASSISTANT_IDENTITY_KEY = "openclaw.control.assistant.v1";

export function loadLocalAssistantIdentity(): LocalAssistantIdentity { ... }
export function saveLocalAssistantIdentity(next: LocalAssistantIdentity) { ... }

And assistant-identity.ts:83-98 calls these without passing an agent id.

Proposed Fix

Scope the localStorage entry by agent id while preserving backward compatibility for legacy flat entries.

storage.ts

type PersistedLocalAssistantIdentity = {
  avatar?: string | null;          // legacy flat value
  agents?: Record<string, LocalAssistantIdentity>;
};

export function loadLocalAssistantIdentity(agentId?: string | null): LocalAssistantIdentity {
  // 1. Try per-agent storage first
  // 2. Fall back to legacy flat value for existing users
  // 3. Default to null
}

export function saveLocalAssistantIdentity(
  next: LocalAssistantIdentity,
  agentId?: string | null,
) {
  // Merge into agents map, drop legacy flat key on save
}

assistant-identity.ts

const localAvatar = loadLocalAssistantIdentity(normalized.agentId ?? null).avatar;

export function setAssistantAvatarOverride(
  state: AssistantAvatarOverrideState & { assistantAgentId?: string | null },
  avatar: string | null,
) {
  saveLocalAssistantIdentity({ avatar }, state.assistantAgentId ?? null);
  // ...
}

Files Changed

File Change
ui/src/ui/storage.ts Per-agent loadLocalAssistantIdentity / saveLocalAssistantIdentity
ui/src/ui/controllers/assistant-identity.ts Pass current agent id to storage helpers
ui/src/ui/controllers/assistant-identity.test.ts Add per-agent isolation regression tests

Test Results

$ pnpm test ui/src/ui/controllers/assistant-identity.test.ts

✓ |ui| ui/src/ui/controllers/assistant-identity.test.ts (5 tests) 13ms

Test Files  1 passed (1)
     Tests  5 passed (5)

New tests verify:

  • main and worker agents can have different local avatars
  • Clearing main's avatar does not affect worker
  • loadAssistantIdentity applies the override only for the matching agent

Verification Command

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

Next Step

Ready branch with the fix and regression tests. I can open a PR immediately if this looks good.

…0890)

The Control UI stored the assistant avatar override under a single global
localStorage key (openclaw.control.assistant.v1). As a result, every agent
shared the same avatar: setting one agent's avatar overwrote all others.

- Change loadLocalAssistantIdentity/saveLocalAssistantIdentity to accept an
  optional agentId and persist overrides under an agents map
- Migrate legacy flat avatar entries on save (the old avatar is preserved
  as a fallback until the user saves a per-agent override)
- Update loadAssistantIdentity and setAssistantAvatarOverride to pass the
  current agent id through to storage
- Add tests proving main and worker agents can have independent avatars

Test plan:
- pnpm test ui/src/ui/controllers/assistant-identity.test.ts (5/5 pass)

Closes openclaw#90890
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 6, 2026
@clawsweeper

clawsweeper Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close this PR as superseded by the newer canonical avatar-scoping branch; the central bug is still real, but this branch is a weaker duplicate and should not remain a landing candidate.

Canonical path: Focus review and proof on #91533 as the canonical per-agent assistant-avatar storage fix, then close duplicate branches to avoid migration-policy drift.

So I’m closing this here and keeping the remaining discussion on #91533.

Review details

Best possible solution:

Focus review and proof on #91533 as the canonical per-agent assistant-avatar storage fix, then close duplicate branches to avoid migration-policy drift.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main and v2026.6.6 read and write every local assistant avatar override through one unscoped localStorage key, matching the linked multi-agent overwrite report.

Is this the best way to solve the issue?

No. Agent-scoped storage is the right layer, but this branch is no longer the best landing path because #91533 covers the same bug across the render/bootstrap/controller callers with safer fallback and clear semantics.

Security review:

Security review cleared: No concrete security or supply-chain concern found; the diff is limited to Control UI localStorage handling and adjacent tests.

AGENTS.md: found and applied where relevant.

What I checked:

  • Repository policy read: Root AGENTS.md and ui/AGENTS.md were read fully; the persisted-preference compatibility, duplicate-PR, real-proof, and broad UI caller review rules affected this decision. (AGENTS.md:13, 8ded75628437)
  • Current main still has the underlying bug: Current main reads and writes every local assistant avatar override through the single unscoped openclaw.control.assistant.v1 localStorage key. (ui/src/ui/storage.ts:353, 8ded75628437)
  • Current main callers are unscoped: The render and bootstrap paths still call loadLocalAssistantIdentity() without an agent id, which is the caller surface any complete fix must cover. (ui/src/ui/app-render.ts:1402, 8ded75628437)
  • This PR is narrower than the required caller surface: This PR changes storage and assistant-identity controller code, but its patch leaves app-render and bootstrap outside the diff while no-arg storage reads default to the main bucket. (ui/src/ui/storage.ts:362, 70cf07eabeac)
  • Canonical newer PR covers the useful change: The newer open PR fix(ui): scope avatar storage per agent ID #91533 scopes storage by agent id, threads app-render, assistant-identity, and bootstrap callers, keeps legacy fallback, and adds scoped-clear behavior. (ui/src/ui/storage.ts:385, 2e5b691a6dda)
  • Canonical PR is viable enough to own the remaining work: Live GitHub reports the newer PR as open, non-draft, mergeable, CLEAN, maintainer-modifiable, proof-supplied, and broadly green on non-routine checks, though it still needs maintainer proof acceptance before merge. (2e5b691a6dda)

Likely related people:

  • BunsDev: PR 71639 introduced browser-local assistant avatar persistence across storage, assistant identity, render, bootstrap, and Quick Settings, and later work kept local assistant avatar overrides authoritative. (role: feature introducer and recent area contributor; confidence: high; commits: c65aa1d2a6e5, 6e6f7fcc3c6a, 14249827928e; files: ui/src/ui/storage.ts, ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/app-render.ts)
  • steipete: Recent history shows assistant identity and active-session scoping work near the state boundary this fix threads through. (role: adjacent assistant identity contributor; confidence: medium; commits: dca9fa471f67, 74fb6be71680, 12a56d4d46dd; files: ui/src/ui/controllers/assistant-identity.ts, ui/src/ui/controllers/control-ui-bootstrap.ts, ui/src/ui/storage.ts)

Codex review notes: model internal, reasoning high; reviewed against 8ded75628437.

@hanZeng-08

Copy link
Copy Markdown
Contributor Author

Verified locally:

  • pnpm test ui/src/ui/controllers/assistant-identity.test.ts passes (5/5)
  • Manually tested: setting different avatars for main agent and worker agent, each retains its own avatar after page refresh

@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. labels Jun 6, 2026
@hanZeng-08

Copy link
Copy Markdown
Contributor Author

Real behavior proof

Verified via browser DevTools on local OpenClaw setup:

  1. Console verification: localStorage v2 schema stores per-agent avatars
    • agents.main.avatar = "avatar-main.png"
    • agents.worker.avatar = "avatar-worker.png"
image
  1. LocalStorage structure: openclaw.control.assistant.v2 contains scoped agent map
image
  1. Unit tests: assistant-identity.test.ts 5/5 pass

@clawsweeper clawsweeper Bot added status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. P2 Normal backlog priority with limited blast radius. 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. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. labels Jun 13, 2026
@openclaw-clownfish

Copy link
Copy Markdown
Contributor

Thanks @hanZeng-08 for the fix work and the local verification on #90931.

Clownfish is closing this PR as superseded by #91533 because both PRs address the same #90890 root cause: assistant avatar overrides were stored without per-agent scoping. The canonical path is #91533, which now carries the newer per-agent storage approach and real behavior proof for review.

Your source PR, proof notes, and contribution remain part of the trail here, and attribution/credit can be carried forward through the canonical thread. If this branch covers a different reproduction path that #91533 does not handle, please reply and maintainers can reopen or split it back out.

@openclaw-clownfish openclaw-clownfish Bot added the clownfish Tracked by Clownfish automation label Jun 15, 2026
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: 📸 screenshot Contributor real behavior proof includes screenshot evidence. 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. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

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