# OneTalk 消息内容原始格式与媒体同步能力调查报告 > 调查日期:2026-09-02 > > 适用页面:`https://onetalk.alibaba.com/message/weblitePWA.htm` > > 调试环境:Chromium `154.0.8012.0`,CDP `127.0.0.1:9222`,Trade Message Center `0.8.6` > > 调查方式:通过 Chromium CDP 观察真实 OneTalk WebSocket 响应、调用页面只读历史 SDK,并对照扩展 IndexedDB、共享协议和 Bright 存储代码。调查过程中没有发送消息、没有调用会改变已读状态的 API,也没有记录正文、账号、token、完整 URL 或 URL 查询参数值。 > > v6 同步说明(2026-09-11):下列 raw 样本和当时能力结论保留为调查证据;当前规范性图片合同为无尺寸的 `fileId`、`extension`、`sizeBytes`、`isOriginal`、`md5`、`previewUrl`、`urlScope`(另有 `version`、`kind`)。MAIN decoder 忽略 raw `width` / `height`,它们不跨 normalized boundary;本报告不是 v6 live runtime 验收。 ## 1. 结论摘要 以下结论是本报告调查时(2026-09-02)的历史快照:当时系统并不是完全没有采集图片和附件,而是只实现了文本的**语义化解析**: - 文本能够从 `content.text.content` 提取为 `message.text`。 - 图片和附件能够作为原始 `contentType=101/custom` JSON 被观察、写入 IndexedDB,并通过 Bright ACK。 - 图片和附件当时没有规范化的 `kind`、文件名、扩展名、大小、缩略图或下载地址合同;当前 v6 image canonical contract 见第 6 节。 - 消费端如果只读取 `message.text`,就会表现为“文本存在,图片和附件不存在”。 这不是当前 v6 数据流。当前 raw OneTalk content 只在 MAIN decoder 内解码和收窄;页面桥、Service Worker、IndexedDB、Bright JSONB、HTTP read 和 Mind 下游只接收 normalized content,Bright 不保存 raw JSON。 真实样本已确认以下映射: | 业务类型 | WebSocket 原始类型 | Base64 解码后的判定 | OneTalk SDK 归一化类型 | | ------------ | ------------------------------------ | -------------------- | --------------------------- | | 文本 | `contentType=1` | `text.content` | `msgType=101, subType=1` | | 图片 | `contentType=101, custom.type=7` | 图片元数据对象 | `msgType=102, subType=60` | | 文件附件 | `contentType=101, custom.type=10010` | `cardType=12` | `msgType=10010, subType=61` | | 其它业务卡片 | `contentType=101, custom.type=10010` | 例如 `cardType=2000` | 不是附件 | 本次真实附件样本为 PDF;没有抓到 TXT 附件、实时图片 push 或实时文件 push,因此这些场景不能标记为已验证。 调查时还发现一个必须优先修复的安全问题:文本内容的 `text.extension.basicMessageInfo` 是一段序列化 JSON,真实样本中包含 `chatToken` 键。页面观察器会复制整个原始 `content`,而 Bright 的清洗器对字符串直接原样放行,因此嵌套在字符串或 Base64 中的敏感字段可能进入持久化。 ## 2. 调查范围与证据边界 ### 2.1 使用的 Chromium/CDP 启动方式 ```bash /Applications/Chromium.app/Contents/MacOS/Chromium \ --remote-debugging-address=127.0.0.1 \ --remote-debugging-port=9222 \ --user-data-dir="/Users/ybf/work/demo-playwright/.chromium-profile" ``` CDP 确认: ```text Browser: Chrome/154.0.8012.0 Protocol-Version: 1.3 ``` ### 2.2 只读历史方法 调查只调用了页面现有的只读方法: ```js const sdk = window.IcbuIM.IMBaaSSDK.default; const conversationService = sdk.getConversationServiceV2(); const messageService = sdk.getMessageService(); await conversationService.getConversationListByPagination({ cursor: 0, count: 20, }); await messageService.fetchMessagesWithoutUpdateToRead(options, conversation); ``` 没有调用以下会改变已读状态或页面 store 的方法: ```text fetchMessages updateMessageToRead ``` ### 2.3 本次运行态证据 本次调查完成时,扩展 IndexedDB 快照为: ```text 数据库:trade-message-center 版本:4 onetalk_messages:135 contentType=1:125 contentType=101/custom.type=7:1 contentType=101/custom.type=10010:9 onetalk_sync_candidates:135 confirmed:135 anomaly:0 onetalk_sync_checkpoints:2 phase=uploading:2 syncResult=succeeded:2 ``` 注意: - `custom.type=10010` 同时包含文件和非文件业务卡片,不能把 9 条全部解释为附件。 - `confirmed` 表示插件已经收到 Bright 的 `accepted` 或 `duplicate` ACK,不表示 Mind UI 已经能渲染媒体。 - checkpoint 仍为 `uploading`,说明同步完成/锚点最终收敛是另一个独立问题;它不改变本报告对消息内容格式的判断。 ## 3. OneTalk 历史 WebSocket 原始响应 ### 3.1 顶层 envelope 真实 WebSocket 历史响应结构如下。所有业务值都已省略: ```json { "headers": { "": "" }, "code": 200, "body": { "degradeFailover": "", "hasMore": 1, "nextCursor": "", "userMessageModels": [] } } ``` 本次网络原帧中的分页字段为: ```text body.hasMore:0 | 1 body.nextCursor:number | null ``` ### 3.2 单条历史消息包装 ```json { "message": { "cid": "", "messageId": "", "createAt": 0, "content": {}, "displayStyle": "", "extension": "", "msgReadStatusSetting": "", "receiverCount": 0, "receivers": [], "redPointPolicy": "", "searchableContent": "", "sender": { "uid": "" }, "unreadCount": 0 }, "msgStatus": 1, "readStatus": 2, "recallFeature": "", "userExtension": "" } ``` 当前页面观察器正是从这一层读取: ```text message.cid message.messageId message.createAt message.content message.sender.uid message.unreadCount item.msgStatus item.readStatus ``` ## 4. OneTalk SDK 返回结构 同一次请求在 SDK Promise 中返回的不是 WebSocket 原始 envelope,而是 SDK 归一化后的扁平结果: ```json { "hasMore": true, "list": [], "nextCursor": 0 } ``` 这里的字段类型与网络原帧不同: ```text SDK hasMore:boolean WebSocket hasMore:0 | 1 ``` 调查时 SDK `list[]` 条目确认包含: ```text autoReply contact contactRead content conversationCode extInfo localExt messageId messageType msgType opId originExt originalData owner receiver sendTime sender spamStatus status subType type unread uuid viewType ``` 这份 SDK 字段清单是历史 raw/boundary-only 证据,包含的 `originalData.width` / `originalData.height` 只可在 MAIN decoder 边界被忽略,不能成为 canonical 字段、存储字段或下游输入。 其中: - `content` 已经被 SDK 转成展示字符串。 - 媒体的结构化字段位于 `originalData`。 - 现有扩展历史观察链使用 WebSocket 原始消息作为事实来源,不应直接切换为 SDK 扁平消息并替换现有消息 ID。 - 更安全的做法是直接解码 WebSocket `custom.data`,从而保留当前 `channelAccountId + conversationId + messageId` 幂等边界。 ## 5. 文本原始格式 ### 5.1 WebSocket 原始 content ```json { "contentType": 1, "text": { "content": "", "extension": { "basicMessageInfo": "", "messageDisplayInfo": "", "messageEventInfo": "" } } } ``` ### 5.2 SDK 归一化结果 ```text msgType:101 type:1 subType:1 viewType:0 content:string originalData keys:text ``` ### 5.3 当前可以安全保留的字段 ```json { "kind": "text", "text": "" } ``` 不应跨页面边界发送: ```text text.extension basicMessageInfo messageDisplayInfo messageEventInfo ``` 真实 `basicMessageInfo` 字符串解析后包含 `chatToken` 键,因此不能把这段字符串当作普通文本保存。 ## 6. 图片原始格式 ### 6.1 WebSocket 原始 content ```json { "contentType": 101, "custom": { "type": 7, "data": "", "degrade": "", "summary": "", "title": "" } } ``` 本次图片样本: ```text custom.data 字符长度:532 Base64 解码后 UTF-8 长度:397 Base64 解码结果:JSON object ``` ### 6.2 Base64 解码后的 JSON ```json { "fileId": "", "height": 1188, "isOriginal": 1, "md5": "", "size": 370256, "suffix": "jpeg", "url": "", "width": 1192 } ``` 这是 OneTalk raw payload 证据,不是 normalized contract。raw `width` / `height` 可保留在此样本中,但 v6 MAIN decoder 忽略它们,绝不将其传过页面桥或写入 canonical content。 ### 6.3 SDK 归一化结果 ```text msgType:102 type:1 subType:60 viewType:0 ``` SDK `originalData` 的字段与 Base64 解码结果一致: ```text fileId height isOriginal md5 size suffix url width ``` ### 6.4 可实现的规范化图片合同 ```ts type OneTalkImageContent = { version: 1; kind: "image"; fileId: string; extension: string; sizeBytes: number; isOriginal: boolean; md5: string | null; previewUrl: string | null; urlScope: "onetalk_session"; }; ``` 规范化规则: - `custom.type` 必须为 `7`。 - `custom.data` 必须是有大小上限的合法 Base64。 - Base64 解码结果必须是 UTF-8 JSON object。 - `fileId`、`suffix`、`url` 必须为非空字符串。 - `size` 必须为有限非负整数,并设置合理上限;raw `width` / `height` 被 MAIN decoder 忽略,不参与校验或输出。 - `isOriginal` 的 `0/1` 显式转换为 boolean。 - `url` 只接受绝对 HTTPS URL,并在确认真实主机后映射为 `previewUrl`;`urlScope` 固定为 `onetalk_session`。 - 不根据 `suffix` 猜造 OneTalk 未提供的 MIME;UI 可以使用安全扩展名映射做展示提示。 ## 7. 文件附件原始格式 ### 7.1 WebSocket 原始 content ```json { "contentType": 101, "custom": { "type": 10010, "data": "", "degrade": "", "summary": "", "title": "" } } ``` 本次 PDF 样本: ```text custom.data 字符长度:912 Base64 解码后 UTF-8 长度:680 Base64 解码结果:JSON object ``` ### 7.2 Base64 解码后的 JSON ```json { "cardType": 12, "params": { "ctime": "", "downloadUrl": "", "extensionType": "pdf", "id": "", "md5": "", "name": ".pdf", "parentId": "", "size": "", "thumbnailUrl": "", "type": "", "url": "", "version": "" } } ``` 重要事实: - 文件判定不能只看 `custom.type=10010`。 - 真实附件还需要 `cardType=12`。 - `params.size` 是 decimal string,不是 number。 - `params.downloadUrl` 在本次样本中为空。 - 实际可用候选位于 `params.url`;`thumbnailUrl` 是另一个操作地址。 - URL 路径表现为 `.htm` 操作入口,并带有 `appkey`、`fileAction`、`id`、`parentId`、`scene`、`secOperateAliId` 等查询键。报告没有记录查询值。 ### 7.3 SDK 归一化结果 ```text msgType:10010 type:1 subType:61 viewType:0 originalData.cardType:12 originalData.params.extensionType:pdf ``` ### 7.4 可实现的规范化文件合同 ```ts type OneTalkFileContent = { kind: "file"; fileId: string; fileName: string; extension: string; sizeBytes: number; md5?: string; sourceUrl: string; thumbnailUrl?: string; }; ``` 规范化规则: - `contentType` 必须为 `101`。 - `custom.type` 必须为 `10010`。 - Base64 JSON 的 `cardType` 必须为 `12`。 - `params.id`、`params.name`、`params.extensionType`、`params.size`、`params.url` 必须通过边界校验。 - `params.size` 只接受十进制正整数字符串,并转换为安全整数。 - `name` 后缀和 `extensionType` 不一致时 fail closed 或降级为 unsupported,不静默选择其中一个。 - `url`、非空的 `downloadUrl`、`thumbnailUrl` 只接受绝对 HTTPS URL 和固定 host allowlist。 - 文件名用于展示时必须由 UI 作为文本渲染,不得作为 HTML。 PDF 已有真实样本。TXT 是否沿用完全相同的 `cardType=12` 结构尚无真实证据;实现可以设计为通用文件合同,但 TXT 的验收必须等待真实样本。 ## 8. `custom.type=10010` 不等于附件 本次还抓到以下 WebSocket 原始类型: ```text contentType=101 custom.type=10010 custom.data= ``` 但 Base64 解码结果为: ```json { "cardType": 2000, "params": { "country": "", "ctime": "", "ids": "", "type": "" } } ``` SDK 将其归一化为: ```text msgType=10010 subType=2000 ``` 这是一类业务卡片,不是附件。若代码只写: ```ts if (content.custom.type === 10010) return file; ``` 就会错误地把商品、国家或其它业务卡片当成文件。 正确判定必须至少包含: ```text contentType=101 custom.type=10010 decoded.cardType=12 decoded.params 通过文件 schema ``` 其它合法但未支持的 `cardType` 应返回受控的 `unsupported` 内容,不应丢弃整条消息,也不应保留完整 raw payload。 ## 9. 调查时的代码为什么表现为“只有文本” ### 9.1 页面观察器 当前 `apps/chrome-extension/src/onetalk/main-page/message-observer/model.ts`: ```ts const contentRecord = isRecord(message.content) ? message.content : null; if (contentRecord && isFiniteNumber(contentRecord.contentType)) { output.contentType = contentRecord.contentType; } if (contentRecord) output.content = contentRecord; const contentText = contentRecord?.text; if (isRecord(contentText) && typeof contentText.content === "string") { output.text = contentText.content; } else { output.text = null; } ``` 因此: - 文本:得到 `contentType=1`、完整 raw content 和 `text=正文`。 - 图片/文件:得到 `contentType=101`、完整 raw content 和 `text=null`。 - 当前没有任何代码把 Base64 `custom.data` 变成 `image` 或 `file`。 ### 9.2 Service Worker 与 IndexedDB `pageMessageToObserved()` 会复制页面观察字段。IndexedDB 只检查: ```text content 是合法 JSON value contentType 是有限整数 text 是 string | null | undefined ``` 所以图片和文件不会因为 `text=null` 被拒绝。它们以 opaque JSON 进入: ```text onetalk_messages onetalk_sync_candidates ``` 本次运行态的 `confirmed` 计数证明图片/附件 raw 记录确实通过了现有 ACK 链路。 ### 9.3 共享协议与 Bright 调查时的 `OneTalkMessage` 定义: ```ts type OneTalkMessage = { messageId: string; conversationId: string; senderId: string; direction: "sent" | "received"; sentAtMs: number; content: OneTalkJsonValue; contentType: number; text?: string | null; participantIds: string[]; readStatus: number; messageStatus: number; unreadCount: number; }; ``` `content` 只有“可序列化 JSON”约束,没有 `text/image/file` 语义。 Bright 数据库当前保存: ```text content_type integer text text nullable content jsonb ``` 因此调查时的 Bright 能保存媒体 raw JSON,但不能告诉 Mind: ```text 这是图片还是文件 哪个 URL 用于预览 哪个 URL 用于下载 文件名和大小是什么 未知 card 应如何展示 ``` ### 9.4 当前展示边界 仓库内的 Bright harness 只执行: ```js JSON.stringify({ content: message.content }, null, 2); ``` 它没有: ```text 附件链接 文件名 文件大小 类型图标 unsupported fallback ``` 真实 Mind UI 如果只读取 `message.text`,媒体消息自然不可见。 ## 10. 调查时发现的安全风险 ### 10.1 字符串内部的敏感字段绕过清洗 Bright 当前的敏感键规则能删除普通对象中的: ```text token cookie csrf authorization secret password credential sid app-key ``` 但清洗器遇到字符串会立即原样返回: ```ts if (typeof value === "string" || typeof value === "boolean") return value; ``` 以下两种载荷因此不会被递归检查: 1. `text.extension.basicMessageInfo` 中的序列化 JSON。 2. `custom.data` 中的 Base64 JSON。 真实 `basicMessageInfo` 解析后已确认含有 `chatToken` 键。调查时的实现把完整 raw content 交给 Bright,违反了数据库注释中“不得写入带认证信息的完整 envelope”的不变量。 ### 10.2 推荐的安全边界 媒体支持不能靠“服务端收到 raw 后再尽量清洗”。应改为: ```text OneTalk raw message → MAIN world 严格解码 → 显式构造白名单 OneTalkMessageContent → 页面桥 → Service Worker exact decoder → Bright exact decoder → JSONB / message.created / HTTP history ``` 禁止跨边界的字段包括: ```text raw content object text.extension basicMessageInfo custom.data chatToken Cookie Authorization 完整 OneTalk response/envelope 原始 SDK row ``` ### 10.3 URL 风险 图片和文件样本中的 URL 是带查询参数的 HTTPS 操作地址,而不是直接媒体 URL。实现前还必须验证: - 是否依赖 OneTalk 登录 Cookie。 - 是否包含短期授权或一次性参数。 - 是否可以从 Mind 页面所在 Origin 使用。 - 是否会发生 302 跳转,以及跳转地址的生命周期。 - 是否允许服务端代理;若允许,如何做授权和 SSRF 防护。 - URL 是否适合长期持久化,还是只能保存媒体 ID 并在需要时换取临时地址。 在上述行为没有真实验证前,只能称其为 `sourceUrl`,不能承诺“Mind 可直接下载”。 ## 11. 当前 v6 内容边界 当前规范性数据流是: ```text OneTalk raw message → MAIN decoder(raw 仅在此处出现) → exact normalized content → page bridge / Service Worker / IndexedDB → Bright canonical JSONB / message.created / HTTP history → Mind read model ``` 因此 Bright 不保存 raw payload、`custom.data` 或 SDK row;它只保存共享 guard 已接受的 normalized content。下列合同说明该边界的目标形状。 建议让 `content` 成为消息展示内容的唯一事实源: ```ts type OneTalkNormalizedContent = | { kind: "text"; text: string; } | { version: 1; kind: "image"; fileId: string; extension: string; sizeBytes: number; isOriginal: boolean; md5: string | null; previewUrl: string | null; urlScope: "onetalk_session"; } | { kind: "file"; fileId: string; fileName: string; extension: string; sizeBytes: number; md5?: string; sourceUrl: string; thumbnailUrl?: string; } | { kind: "unsupported"; sourceContentType: number; sourceCustomType?: number; cardType?: number; }; ``` 兼容期可以保留顶层 `message.text`,但它必须始终从: ```ts message.content.kind === "text" ? message.content.text : null; ``` 派生,不能成为第二个可独立写入的事实源。 现有 JSONB 列可以保存这个 union。只有在需要文件类型索引、全文搜索或独立媒体生命周期时,才需要新增数据库列或媒体表;第一阶段不应为了渲染图片/PDF创建第二套消息表。 ## 12. 可以做到什么 ### 12.1 调查时已经做到 - 观察文本、图片和 custom card 的原始 WebSocket content。 - 使用 raw `channelAccountId + conversationId + messageId` 保持消息幂等。 - 将非文本消息写入 IndexedDB。 - 将非文本消息作为 JSON observation 发送给 Bright。 - Bright 可以 ACK 图片/附件 raw observation。 - Bright JSONB 可以保存规范化后的 `text/image/file/unsupported` union,无需立即增加第二张消息表。 ### 12.2 基于现有证据可以立即实现 - 文本严格白名单提取,删除全部 extension。 - `custom.type=7` 图片 Base64 JSON 解码和字段校验。 - `custom.type=10010 + cardType=12` 通用文件解码。 - PDF 文件名、扩展名、大小、源 URL、缩略图元数据同步。 - 未支持 card 的安全 fallback,不丢失消息身份和时间线位置。 - Mind 侧按 `content.kind` 渲染文本、图片占位/预览和附件链接。 - 在共享协议、HTTP history 和 `message.created` 中使用同一 typed content。 - 添加敏感字段负向测试,证明 raw content、Base64 和序列化 JSON 不再跨边界。 ### 12.3 当前还不能承诺 - TXT 附件与 PDF 完全同构:没有真实 TXT 样本。 - 实时图片/文件 push 与历史响应完全同构:没有实时媒体 push 样本。 - `sourceUrl` 能在 Mind 页面直接打开:未验证 Cookie、CORS、跳转和有效期。 - 图片/附件可以永久下载:当前 URL 可能只是临时操作入口。 - 支持音频、视频、语音、压缩包或所有 OneTalk custom card:没有真实样本和枚举。 - checkpoint 已完整收敛:本次末态仍为 `uploading/succeeded`。 ## 13. 当前 v6 不变量 ### R1. 单一内容合同 扩展、Bright 和 Mind 必须共用一个 `OneTalkNormalizedContent` 判别联合。页面 raw payload、服务端 JSONB 和 UI 本地模型不得分别维护不同的媒体判定逻辑。 ### R2. 页面边界白名单解码 MAIN world 是 OneTalk raw content 的唯一解码所有者: - 文本只取 `text.content`。 - 图片只接受 `contentType=101/custom.type=7` 的有效 Base64 JSON。 - 文件只接受 `contentType=101/custom.type=10010/cardType=12`。 - 其它类型输出有限 `unsupported` 元数据。 - raw content 和 custom data 不得进入页面桥。 ### R3. 保留事实身份 媒体规范化不得改变现有幂等键: ```text channelAccountId + conversationId + raw messageId ``` 不得用 SDK 展示层 ID、时间戳、hash 或合成 ID 代替 raw message ID。 ### R4. URL 与文件元数据校验 - URL 只允许 HTTPS 和固定 OneTalk host allowlist。 - URL query/hash/userinfo 不进入日志或错误。 - 文件大小和时间字段必须有上下限;图片 raw `width` / `height` 由 MAIN decoder 忽略,不能成为 normalized 字段、匹配条件或展示数据。 - 文件扩展名规范化为小写有限字符集。 - 文件名按纯文本处理并限制长度。 - MD5 只能作为来源元数据,不能替代消息 ID 或安全签名。 ### R5. 历史与实时一致 历史 `body.userMessageModels[].message.content` 和实时 `lastMessage.message.content` 必须走同一个 decoder。只有在真实 live 媒体样本证明 envelope 不同后,才允许增加窄化适配层。 ### R6. 未支持类型行为 合法但未支持的 custom card: - 不伪装成文本或附件。 - 不保存 raw content。 - 保留消息事实并使用 `kind=unsupported`。 - 输出稳定的安全类型码或诊断,不输出 raw data。 非法 Base64、非法 JSON、字段类型错误、超限或非 HTTPS URL: - 进入明确 anomaly 或受控 unsupported 策略。 - 不得静默返回空文本。 - 不得阻塞同批其它有效消息。 ### R7. 跨层一致性 以下边界必须同时升级并由共享 decoder 约束: ```text MAIN observed message page bridge Service Worker storage/candidate message.observed Bright normalization PostgreSQL JSONB message.created HTTP history Mind UI model ``` 如果合同为不兼容变更,应提升 OneTalk 协议版本并让旧插件得到明确升级错误,不能在同一版本内让 `content` 同时表示 raw 与 normalized 两种含义。 ### R8. 安全清除 任何通过网络、数据库、日志或诊断的消息都必须证明不包含: ```text chatToken basicMessageInfo custom.data Cookie Authorization accountIdEncrypt aliIdEncrypt 原始 URL query 值 完整 raw payload ``` ## 14. 推荐验收标准 - [ ] 真实文本历史消息归一化为 `{ kind: "text", text }`,并且 content 不含 extension。 - [ ] 真实 JPEG 样本从 `custom.type=7` Base64 JSON 归一化为无尺寸 v6 image;`fileId`、`extension`、`sizeBytes`、`isOriginal`、`md5`、`previewUrl` 与 `urlScope` 通过 canonical contract,raw `width` / `height` 不跨 MAIN 边界。 - [ ] 真实 PDF 样本从 `custom.type=10010/cardType=12` 归一化为 file,文件名、扩展名、大小和 URL 元数据一致。 - [ ] `custom.type=10010/cardType=2000` 不会被识别成文件。 - [ ] 非法 Base64、非法 JSON、超限数据、非 HTTPS URL、缺字段和大小溢出均 fail closed,不影响同批其它消息。 - [ ] page bridge、IndexedDB、Bright DB、HTTP history 和 `message.created` 中均只存在 normalized content。 - [ ] 序列化最终 frame、数据库输入和诊断事件,断言不包含 `chatToken`、`basicMessageInfo`、`custom.data` 或加密身份字段。 - [ ] Mind UI 能渲染文本、图片状态和 PDF 附件;URL 不可用时显示明确失败,不显示空白消息。 - [ ] unknown custom card 显示“暂不支持的消息类型”,同时保留正确的消息时间线和幂等身份。 - [ ] 获得真实 TXT 样本后补充同构性测试;未获得前不宣称 TXT 已验证。 - [ ] 获得真实 live 图片和附件 push 后证明历史与实时共用同一 decoder。 - [ ] 验证媒体 URL 在有 Cookie、无 Cookie、跨 Origin、过期和跳转场景的行为,再决定保存 URL、保存 ID 或增加受控代理。 ## 15. 调查时的相关代码位置 | 层 | 文件 | 当前行为 | | -------------- | --------------------------------------------------------------------------------- | --------------------------------------- | | WebSocket 分流 | `apps/chrome-extension/src/onetalk/main-page/message-observer/index.ts` | 识别 history/new envelope | | 页面消息提取 | `apps/chrome-extension/src/onetalk/main-page/message-observer/model.ts` | 复制 raw content,仅提取 text | | 历史只读 SDK | `apps/chrome-extension/src/onetalk/main-page/current-conversation-history/sdk.ts` | 调用 `fetchMessagesWithoutUpdateToRead` | | 页面到 SW | `apps/chrome-extension/src/onetalk/service-worker/sync-engine/helpers.ts` | 原样复制观察字段 | | IndexedDB 校验 | `apps/chrome-extension/src/onetalk/service-worker/storage.ts` | 只检查 JSON 与基础字段类型 | | 共享合同 | `packages/onetalk-contract/src/model.ts` | `content` 为任意 JSON value | | 共享 decoder | `packages/onetalk-contract/src/decoder.ts` | 不理解 text/image/file 语义 | | Bright 清洗 | `apps/server/src/onetalk/service.ts` | 按键过滤对象,字符串原样通过 | | Bright 持久化 | `apps/server/src/database/schema/onetalk.ts` | `content jsonb`、`text nullable` | | 开发展示 | `apps/mind-test-harness/src/harness.ts` | 只展示 raw JSON | | 现有媒体覆盖 | `apps/chrome-extension/test/onetalk-websocket-tap.test.js` | 只有简化 `custom.type=10010` fixture | ## 16. 最终判断 历史调查证明 OneTalk raw payload 提供实现图片和文件同步所需的核心元数据。当前 v6 不再沿用该报告中 raw content 跨页面桥、IndexedDB、Bright 或 Mind 的历史路径;实现核心已经收敛为 MAIN decoder 的单一白名单边界: ```text opaque raw content → 严格、安全、可测试的 text/image/file/unsupported 合同 → Bright 持久化与事件只使用 normalized content → Mind read model 只接收 normalized content ``` raw 字符串中嵌套凭证进入 Bright 是本报告记录的历史风险;当前 v6 边界以不让 raw payload 离开 MAIN decoder 的方式关闭它。真实 v6 Chromium 运行态验收仍未执行。