# Chat 会话产物版本语义设计 状态:设计审计稿,待评审;不代表已经实现。 更新时间:2026-09-01。 产品裁决来源:会话栏参考 Manus 的产物版本语义。 ## 1. 结论摘要 推荐把“工作区当前文件”和“某条 Chat 消息当时看到的结果”拆成两个不同的身份: - `workspace artifact` 是可变的 latest 指针。它回答“Design Files 里现在是什么”,允许覆盖、重命名和恢复版本。 - `chat artifact snapshot` 是不可变的消息证据。它回答“这一轮当时产出了什么”,一旦消息落库便不跟随工作区文件变化。 两类产物采用不同打开规则: | 产物类型 | 会话卡封面 | 点击后右侧预览 | 后续同名覆盖的影响 | | --- | --- | --- | --- | | HTML / prototype / slide / document | 该轮生成的静态“假预览图”;截取首屏 viewport,不渲染活动 iframe | 打开 `workspaceArtifactId` 当前指向的 latest 文件 | 卡面不变;右侧看到 latest | | image | 该轮图片的 immutable snapshot 真图 | 打开该 snapshot,而不是工作区同名文件 | 历史卡与右侧历史预览均不变;Design Files 仍展示 latest | 当前实现不满足图片历史语义。`ProjectFile` 只保存路径、大小、mtime、kind/mime,没有内容 digest、version id 或 snapshot id(`packages/contracts/src/api/files.ts:32-45`);`ChatMessage.producedFiles` 直接持久化这份可变路径元数据(`packages/contracts/src/api/chat.ts:1006-1112`),卡片又用项目当前文件 URL 渲染和打开(`apps/web/src/components/FileOpsSummary.tsx:497-570`)。因此同名图片被覆盖后,历史卡会读到新字节。 HTML 已有“只保存 HTML 文本”的版本系统,但它没有绑定到普通 Chat 消息,也没有版本化本地依赖图,不能直接当作会话快照系统。现有版本内容文件固定为 `.html`(`apps/daemon/src/project-file-versions.ts:188-193, 491-560`),运行结束只对 touched HTML 建版本(`apps/daemon/src/run-html-version-snapshots.ts:82-132`),而 `ChatMessage` 没有对应 version id 字段。 ## 2. 当前实现审计 ### 2.1 Web 卡片实际上只拿到路径 `FileOpsSummary` 把 `FileOpEntry` 投影成: ```ts interface ArtifactCardItem { name: string; kind: 'html' | 'image' | 'video' | 'doc'; pending?: boolean; } ``` 证据是 `apps/web/src/components/FileOpsSummary.tsx:127-175, 399-410`。这里没有 `mtime`、digest、run/task id、HTML version id 或 snapshot id。随后: - HTML 卡把 `projectFileUrl(projectId, item.name)` 塞进活动 iframe(`FileOpsSummary.tsx:497-537`)。这不是静态假预览,动画与脚本仍可能运行。 - 图片卡把同一个当前文件 URL 塞进 ``(`FileOpsSummary.tsx:545-559`)。 - 点击卡片只回传 `item.name`(`FileOpsSummary.tsx:565-572`),因此右侧也只能按当前路径打开。 - `projectFileUrl` 本身只是当前项目 raw URL,没有历史版本参数(`apps/web/src/providers/registry.ts:2498-2503`)。 `HtmlProjectCoverFrame` 同样是活动 iframe:验证 URL 后直接 `