deepseek-harness/.agents/notes/implemented/feature/2026-08-24-trajectory-image-attachments.zh.md
creatixchu d420292400 fix(web): 处理评审发现的图片记录边界情况
- 图片错误结果在 Result 页签保留错误名称与代码
- 空文本块加图片的记录按纯图片标注,不再空行
- sourceBlock 的持久化图片守卫检查 attachmentId 字段
- 移除 ui-chat 对 util/crypto 的过期 tsconfig 引用
- 同步 slots.md 层级图与 2026-08-20 所有权 Note
2026-08-24 18:58:25 +08:00

4.5 KiB
Raw Blame History

Agent Note: Trajectory 持久化图片附件

Status: implemented

English | 中文

Problem

Trajectory 不展示会话图片。持久化的 { type: 'image', attachment: ImageAttachmentRef } 块在详情面板里渲染成格式化 JSON,纯图片的用户消息在记录表中是一个空行。Trajectory 唯一认识的图片路径是对内联 wire 字段(url、image_url、base64 data)的 imageSrc 嗅探,而生产事件从不携带这些字段:每个生产方都在事件追加前提交持久化的 ImageAttachmentRef。用户无法从执行记录确认模型看到了哪张图(issue #2986),而 Chat 已经能展示同样的附件。

Decision

  • ui-conversation 拥有按会话的持久化图片 URL 缓存。HistoricalImageCache 从 ui-chat 移入 packages/client/ui-conversation/src/client/conversation/historical-images.ts,以 ctx.uiConversation.imageUrl(sessionId, attachment) 提供。Chat 与 Trajectory 通过同一实例解析,因此一个会话附件只产生一次 session.attachment 读取和一个浏览器 URL,并随 Session binding 释放而撤销。这部分取代了 client Session/Conversation 所有权中记录的 ui-chat 缓存归属。
  • 画廊 owner 契约(MessageImagesOwnerProps、RenderMessageImages)移入 ui-conversation 客户端契约。ui-chat 的 conversation.message.images SlotMap 行沿用共享 owner 类型;ui-trajectory 以同一 owner 类型声明自己的子槽位 conversation.trajectory.images;ui-attachment 把同一个 MessageImages 画廊组件注册进两个键,因此加载、重试与灯箱行为在两个视图中完全一致。
  • TrajectorySourceBlock 以 attachment?: ImageAttachmentRef 取代 imageSrc/imageAlt。内联来源嗅探(sourceImage、safeImageSource)与 Trajectory 本地的 PanelImage 渲染器一并删除:没有生产方向会话日志写入内联图片字节或 URL,这些路径是死代码,且 issue 明确排除上传来源的临时路径。
  • 内容含图片但没有文本的记录,其记录表行以 locale 持有的 layout.imageOnly 计数标注;只含图片的工具结果的摘要也使用同一标签,而不是 JSON 转储。
  • 存储与 BFF 均不改动:session.attachment 已按会话日志引用授权(缺失、损坏与未被引用的附件显式失败并进入画廊的重试态),sha256 内容寻址已保证每张图片只存一份。

Alternatives considered

保留 Trajectory 自己的 <img> 渲染并喂给它解析好的 URL。 这会重复 ui-attachment 已拥有的加载占位、重试控件和灯箱,并与基于 slot 的附件所有权相抵触,该决定已拒绝跨插件直接 import 组件。

把 conversation.message.images 的声明上提到共享父级,让两个视图渲染同一个键。 renderSlot 的类型限定在声明入口自己的 children 表内,同级的 conversation.view 入口无法渲染另一个入口的子键;slot registry 也拒绝对同一键的第二次声明。共享 owner 类型的第二个键是受支持的组合方式,且允许主题独立替换任一画廊。

在持久化路径之外保留内联 imageSrc 嗅探。 所有生产方(宿主 prompt admission、read_image、MCP 投影、ACP 入口)都在事件追加前提交持久化引用,嗅探不会命中任何东西;保留它等于保留验收标准明确排除的非持久化渲染路径。

Trajectory 自有的图片缓存。 每个视图一份缓存会对同一会话附件发出重复的 session.attachment RPC 和重复的 blob URL,违背"Chat 与 Trajectory 引用同一会话附件"的要求,且没有任何收益。

Consequences

  • 两个视图共用一个画廊实现,图片行为(尺寸、重试、灯箱、文案)不会在 Chat 与 Trajectory 之间漂移,且无论多少个视图展示,一个会话附件只读取一次。
  • TrajectoryTable 需要把必填的 renderImages prop 逐层传入详情组件;ui-trajectory 新增对 dsh-attachment 的仅类型依赖,ui-attachment 为新的 SlotMap 行新增对 ui-trajectory 的仅类型依赖。
  • keyless 组装快照 apps/web/tests/trajectory-image-display.snapshot.ts 直接钉住共享缓存这一事实:详情面板中的图片 URL 与 Chat 画廊对同一 fixture 附件的 URL 字符串相同。