OpenClaw Dashboard 更新头像踩坑记:从 broken data URL 到本地文件 Avatar
今天踩了一个很典型、也很适合写下来复盘的小坑:OpenClaw control-ui / dashboard 更新头像时,页面出现 broken image,最终发现问题不在"图片内容",而在"头像值的表达方式"和"文件可达性"。
这篇文章把整个过程整理一下,给后面也想自定义 agent avatar 的人一个可复用的思路。
现象:头像显示异常,页面出现 broken image
最开始的症状很直接:
- control-ui 里头像不正常
- 页面尝试加载一个坏掉的超长图片值
- 刷新后依然会出现 broken image
从现象看,第一反应很容易怀疑:
- 图片本身坏了
- UI 渲染有 bug
- 缓存没刷新
但这次真正的问题更偏向配置层和资源可达性。
第一轮判断:超长 data URL 不是一个稳定的 avatar 方案
当时的头像值本质上是一个很长的 data URL 。理论上,很多前端组件都支持 data URI;但在实际系统里,尤其是经过配置、序列化、UI 渲染、状态同步之后,超长 data URL 很容易变成脆弱点。
它可能带来的问题包括:
- 配置可读性极差
- JSON / UI 状态同步时不够稳
- 某些组件或中间层对超长字符串处理不友好
- 页面缓存后更难判断到底是"旧值没清掉"还是"新值没生效"
所以当时先做了一个保守处理:先把 Avatar 移除,让 UI 回退到默认/空状态,确认 broken image 是否消失。
这个思路很重要,因为它能先把问题拆成两层:
- 是 UI 整体坏了?
- 还是只是当前 avatar 值有问题?
移除后,破图消失,说明方向是对的:根因确实在 avatar 值,而不是 dashboard 主体渲染逻辑。
第二个坑:聊天里能看见图片,不等于拿得到图片原始文件
接下来本来想把聊天里发过来的龙虾图直接拿来当头像。
结果这里遇到一个很容易误判的问题:
模型"看得见"一张图片,不代表 agent/tooling 层就拿到了这张图的原始 bytes、下载 URL,或者本地文件路径。
这次场景里,图片来自当前聊天界面。对于模型来说,它是"可见"的;但对执行层来说,并没有天然暴露出以下任一能力:
- 本地临时文件路径
- 可下载 URL
- 附件 token / file key
- 原始上传文件句柄
也就是说,这里存在两个不同层次:
1. Perception layer
Agent 能"看见图长什么样"。
2. File access layer
Agent 能不能真正拿到原始文件并落盘。
这两个层次不是一回事。
像 Feishu 这类 attachment-aware 的通道,插件通常能拿到更结构化的附件信息;但当前 webchat 场景更接近一种 vision-only 能力:看得到,不代表拿得到。
这也是为什么当时不能直接声称"我已经拿到这张图的原始文件,可以直接写入配置"。
为什么这点很关键
如果把"看到图"误当成"拿到了原图",后面会出现两类问题:
- 技术上不准确:实际上并没有原始 bytes
- 结果上不稳定:你以为写进去的是原图,最后可能只是某个渲染态、缓存态,甚至根本没有有效资源可供 dashboard 读取
所以更稳的做法是承认约束:
- 如果只有 inline chat image,而没有原始附件句柄,不能假装自己拿到了原文件
- 如果一定要从聊天界面取图,最多只能走 browser screenshot fallback,而且产物应该被描述为"截图派生 PNG",不是"原始上传图的等价副本"
这个边界,技术上要说清楚。
最终方案:直接使用本地文件路径
最后真正稳定生效的方案很简单:不用 data URL,不赌聊天附件能力,直接给一个本地可访问的 PNG 文件。
实际使用的文件路径是:
text
/home/water/.openclaw/workspace/waterlobster.png
然后在工作区根目录的 IDENTITY.md 里加入 Avatar 字段:
md
- **Avatar:** waterlobster.png
这里有一个关键点:
Avatar path 会相对 workspace root 解析。
也就是说,在 IDENTITY.md 里写 waterlobster.png,实际会解析到 workspace 根目录下对应文件,而不是任意系统路径。
这比塞一个巨大 data URI 稳定得多,也更容易维护。
让 control-ui 真正同步:别只改 IDENTITY.md,还要 set-identity
只改 IDENTITY.md 还不够,后面还执行了这条命令,把 identity 同步写回 agent 配置:
bash
openclaw agents set-identity --workspace /home/water/.openclaw/workspace --from-identity --json
执行后,main agent 的 identity 被同步成类似这样:
json
{
"name": "ken-kit",
"emoji": "🤖",
"theme": "AI robot assistant",
"avatar": "waterlobster.png"
}
这一步的意义是:
- 让配置层和 workspace identity 文件保持一致
- 让 control-ui 读取到明确的 avatar 值
- 避免"文件改了,但运行态/配置态还没同步"的情况
最后一步:刷新页面,必要时强刷
配置改好之后,control-ui 侧还可能受到浏览器缓存影响。
所以最后的收尾动作也很朴素:
- 普通刷新
- 如果还显示旧图,
Ctrl + Shift + R强刷
在这次处理里,刷新之后头像正常显示,说明整条链路已经打通:
- 本地文件可访问
IDENTITY.md中 Avatar 可解析set-identity已同步- control-ui 渲染正常
这次复盘后的结论
1. 给 OpenClaw dashboard / control-ui 配头像,优先用本地文件路径
推荐顺序:
- workspace 内的 PNG/JPG 文件
- 可稳定访问的 URL
- data URI(除非非常小且你明确知道整条链路都能稳定支持)
如果追求稳定性,本地文件路径通常是最省心的。
2. "看见图片" 不等于 "拿到原图"
对 agent 系统来说,这个区别非常重要。
很多时候模型视觉能力和工具访问能力是分离的:
- Vision 可以识别图像内容
- Tooling 不一定能拿到原始附件文件
如果系统没有暴露 file handle / URL / 本地路径,就不要假设自己已经拥有原图。
3. Browser screenshot fallback 可以作为兜底,但要正确命名
如果未来真的需要从聊天 UI 里"取图",而平台又不给原始附件能力,可以考虑:
- 用 browser automation 打开实际聊天界面
- 对图片做 screenshot / crop
- 落盘为 PNG
但这个产物应明确描述为:
- rendered screenshot-derived PNG
而不是:
- "我拿到了原始上传文件"
这不仅是技术诚实问题,也会影响后续处理预期。
可复用的最小操作步骤
如果你也想稳定给 OpenClaw 的 agent 设置头像,可以直接照这个最小流程:
Step 1:把头像文件放进 workspace
例如:
text
/home/water/.openclaw/workspace/waterlobster.png
Step 2:修改 IDENTITY.md
md
- **Avatar:** waterlobster.png
Step 3:同步到 agent identity
bash
openclaw agents set-identity --workspace /home/water/.openclaw/workspace --from-identity --json
Step 4:刷新 control-ui
如果没立即变化,就强刷:
text
Ctrl + Shift + R
结语
这次问题不大,但很典型:前端表现看起来像图片坏了,真正的根因却是"资源表达方式"和"文件可达性"不对。
很多 agent / dashboard / automation 系统的问题,最后都不是卡在"功能有没有",而是卡在:
- 配置值是否适合长期维护
- 运行态能不能真实拿到资源
- UI 读到的是不是最终一致状态
如果只看表面,很容易在"换图、刷新、再试一次"里打转;但一旦把问题拆成 data representation / resource access / config sync / UI cache 四层,处理就会清晰很多。
如果你也在折腾 OpenClaw 的 control-ui、自定义 identity,或者别的 agent dashboard,这个思路大概率也能复用。