Files
trade-message-center/docs/onetalk-message-content-formats.md
T

28 KiB
Raw Blame History

OneTalk 消息内容原始格式与媒体同步能力调查报告

调查日期:2026-09-02

适用页面:https://onetalk.alibaba.com/message/weblitePWA.htm

调试环境:Chromium 154.0.8012.0CDP 127.0.0.1:9222Trade Message Center 0.8.6

调查方式:通过 Chromium CDP 观察真实 OneTalk WebSocket 响应、调用页面只读历史 SDK,并对照扩展 IndexedDB、共享协议和 Bright 存储代码。调查过程中没有发送消息、没有调用会改变已读状态的 API,也没有记录正文、账号、token、完整 URL 或 URL 查询参数值。

v6 同步说明(2026-09-11):下列 raw 样本和当时能力结论保留为调查证据;当前规范性图片合同为无尺寸的 fileIdextensionsizeBytesisOriginalmd5previewUrlurlScope(另有 versionkind)。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 contentBright 不保存 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 启动方式

/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 确认:

Browser: Chrome/154.0.8012.0
Protocol-Version: 1.3

2.2 只读历史方法

调查只调用了页面现有的只读方法:

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 的方法:

fetchMessages
updateMessageToRead

2.3 本次运行态证据

本次调查完成时,扩展 IndexedDB 快照为:

数据库:trade-message-center
版本:4

onetalk_messages135
  contentType=1125
  contentType=101/custom.type=71
  contentType=101/custom.type=100109

onetalk_sync_candidates135
  confirmed135
  anomaly0

onetalk_sync_checkpoints2
  phase=uploading2
  syncResult=succeeded2

注意:

  • custom.type=10010 同时包含文件和非文件业务卡片,不能把 9 条全部解释为附件。
  • confirmed 表示插件已经收到 Bright 的 acceptedduplicate ACK,不表示 Mind UI 已经能渲染媒体。
  • checkpoint 仍为 uploading,说明同步完成/锚点最终收敛是另一个独立问题;它不改变本报告对消息内容格式的判断。

3. OneTalk 历史 WebSocket 原始响应

3.1 顶层 envelope

真实 WebSocket 历史响应结构如下。所有业务值都已省略:

{
    "headers": {
        "<protocol-header>": "<redacted>"
    },
    "code": 200,
    "body": {
        "degradeFailover": "<value>",
        "hasMore": 1,
        "nextCursor": "<cursor>",
        "userMessageModels": []
    }
}

本次网络原帧中的分页字段为:

body.hasMore0 | 1
body.nextCursornumber | null

3.2 单条历史消息包装

{
    "message": {
        "cid": "<raw-conversation-id>",
        "messageId": "<raw-message-id>",
        "createAt": 0,
        "content": {},
        "displayStyle": "<value>",
        "extension": "<value>",
        "msgReadStatusSetting": "<value>",
        "receiverCount": 0,
        "receivers": [],
        "redPointPolicy": "<value>",
        "searchableContent": "<value>",
        "sender": {
            "uid": "<participant-id>"
        },
        "unreadCount": 0
    },
    "msgStatus": 1,
    "readStatus": 2,
    "recallFeature": "<value>",
    "userExtension": "<value>"
}

当前页面观察器正是从这一层读取:

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 归一化后的扁平结果:

{
    "hasMore": true,
    "list": [],
    "nextCursor": 0
}

这里的字段类型与网络原帧不同:

SDK hasMoreboolean
WebSocket hasMore0 | 1

调查时 SDK list[] 条目确认包含:

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

{
    "contentType": 1,
    "text": {
        "content": "<message-text-redacted>",
        "extension": {
            "basicMessageInfo": "<serialized-json-string>",
            "messageDisplayInfo": "<serialized-json-string>",
            "messageEventInfo": "<serialized-json-string>"
        }
    }
}

5.2 SDK 归一化结果

msgType101
type1
subType1
viewType0
contentstring
originalData keystext

5.3 当前可以安全保留的字段

{
    "kind": "text",
    "text": "<message-text>"
}

不应跨页面边界发送:

text.extension
basicMessageInfo
messageDisplayInfo
messageEventInfo

真实 basicMessageInfo 字符串解析后包含 chatToken 键,因此不能把这段字符串当作普通文本保存。

6. 图片原始格式

6.1 WebSocket 原始 content

{
    "contentType": 101,
    "custom": {
        "type": 7,
        "data": "<base64-json>",
        "degrade": "",
        "summary": "",
        "title": ""
    }
}

本次图片样本:

custom.data 字符长度:532
Base64 解码后 UTF-8 长度:397
Base64 解码结果:JSON object

6.2 Base64 解码后的 JSON

{
    "fileId": "<file-id>",
    "height": 1188,
    "isOriginal": 1,
    "md5": "<md5>",
    "size": 370256,
    "suffix": "jpeg",
    "url": "<https-action-url>",
    "width": 1192
}

这是 OneTalk raw payload 证据,不是 normalized contract。raw width / height 可保留在此样本中,但 v6 MAIN decoder 忽略它们,绝不将其传过页面桥或写入 canonical content。

6.3 SDK 归一化结果

msgType102
type1
subType60
viewType0

SDK originalData 的字段与 Base64 解码结果一致:

fileId
height
isOriginal
md5
size
suffix
url
width

6.4 可实现的规范化图片合同

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。
  • fileIdsuffixurl 必须为非空字符串。
  • size 必须为有限非负整数,并设置合理上限;raw width / height 被 MAIN decoder 忽略,不参与校验或输出。
  • isOriginal0/1 显式转换为 boolean。
  • url 只接受绝对 HTTPS URL,并在确认真实主机后映射为 previewUrlurlScope 固定为 onetalk_session
  • 不根据 suffix 猜造 OneTalk 未提供的 MIME;UI 可以使用安全扩展名映射做展示提示。

7. 文件附件原始格式

7.1 WebSocket 原始 content

{
    "contentType": 101,
    "custom": {
        "type": 10010,
        "data": "<base64-json>",
        "degrade": "",
        "summary": "",
        "title": ""
    }
}

本次 PDF 样本:

custom.data 字符长度:912
Base64 解码后 UTF-8 长度:680
Base64 解码结果:JSON object

7.2 Base64 解码后的 JSON

{
    "cardType": 12,
    "params": {
        "ctime": "<epoch-string>",
        "downloadUrl": "",
        "extensionType": "pdf",
        "id": "<file-id>",
        "md5": "<md5>",
        "name": "<redacted>.pdf",
        "parentId": "<parent-id>",
        "size": "<decimal-string>",
        "thumbnailUrl": "<https-action-url>",
        "type": "<value>",
        "url": "<https-action-url>",
        "version": "<value>"
    }
}

重要事实:

  • 文件判定不能只看 custom.type=10010
  • 真实附件还需要 cardType=12
  • params.size 是 decimal string,不是 number。
  • params.downloadUrl 在本次样本中为空。
  • 实际可用候选位于 params.urlthumbnailUrl 是另一个操作地址。
  • URL 路径表现为 .htm 操作入口,并带有 appkeyfileActionidparentIdscenesecOperateAliId 等查询键。报告没有记录查询值。

7.3 SDK 归一化结果

msgType10010
type1
subType61
viewType0
originalData.cardType12
originalData.params.extensionTypepdf

7.4 可实现的规范化文件合同

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.idparams.nameparams.extensionTypeparams.sizeparams.url 必须通过边界校验。
  • params.size 只接受十进制正整数字符串,并转换为安全整数。
  • name 后缀和 extensionType 不一致时 fail closed 或降级为 unsupported,不静默选择其中一个。
  • url、非空的 downloadUrlthumbnailUrl 只接受绝对 HTTPS URL 和固定 host allowlist。
  • 文件名用于展示时必须由 UI 作为文本渲染,不得作为 HTML。

PDF 已有真实样本。TXT 是否沿用完全相同的 cardType=12 结构尚无真实证据;实现可以设计为通用文件合同,但 TXT 的验收必须等待真实样本。

8. custom.type=10010 不等于附件

本次还抓到以下 WebSocket 原始类型:

contentType=101
custom.type=10010
custom.data=<base64-json>

但 Base64 解码结果为:

{
    "cardType": 2000,
    "params": {
        "country": "<value>",
        "ctime": "<value>",
        "ids": "<value>",
        "type": "<value>"
    }
}

SDK 将其归一化为:

msgType=10010
subType=2000

这是一类业务卡片,不是附件。若代码只写:

if (content.custom.type === 10010) return file;

就会错误地把商品、国家或其它业务卡片当成文件。

正确判定必须至少包含:

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

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 变成 imagefile

9.2 Service Worker 与 IndexedDB

pageMessageToObserved() 会复制页面观察字段。IndexedDB 只检查:

content 是合法 JSON value
contentType 是有限整数
text 是 string | null | undefined

所以图片和文件不会因为 text=null 被拒绝。它们以 opaque JSON 进入:

onetalk_messages
onetalk_sync_candidates

本次运行态的 confirmed 计数证明图片/附件 raw 记录确实通过了现有 ACK 链路。

9.3 共享协议与 Bright

调查时的 OneTalkMessage 定义:

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 数据库当前保存:

content_type integer
text text nullable
content jsonb

因此调查时的 Bright 能保存媒体 raw JSON,但不能告诉 Mind

这是图片还是文件
哪个 URL 用于预览
哪个 URL 用于下载
文件名和大小是什么
未知 card 应如何展示

9.4 当前展示边界

仓库内的 Bright harness 只执行:

JSON.stringify({ content: message.content }, null, 2);

它没有:

<img>
附件链接
文件名
文件大小
类型图标
unsupported fallback

真实 Mind UI 如果只读取 message.text,媒体消息自然不可见。

10. 调查时发现的安全风险

10.1 字符串内部的敏感字段绕过清洗

Bright 当前的敏感键规则能删除普通对象中的:

token
cookie
csrf
authorization
secret
password
credential
sid
app-key

但清洗器遇到字符串会立即原样返回:

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 后再尽量清洗”。应改为:

OneTalk raw message
  → MAIN world 严格解码
  → 显式构造白名单 OneTalkMessageContent
  → 页面桥
  → Service Worker exact decoder
  → Bright exact decoder
  → JSONB / message.created / HTTP history

禁止跨边界的字段包括:

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 内容边界

当前规范性数据流是:

OneTalk raw message
  → MAIN decoderraw 仅在此处出现)
  → 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 成为消息展示内容的唯一事实源:

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,但它必须始终从:

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. 保留事实身份

媒体规范化不得改变现有幂等键:

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 约束:

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. 安全清除

任何通过网络、数据库、日志或诊断的消息都必须证明不包含:

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 imagefileIdextensionsizeBytesisOriginalmd5previewUrlurlScope 通过 canonical contractraw 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、数据库输入和诊断事件,断言不包含 chatTokenbasicMessageInfocustom.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 与基础字段类型
共享合同 apps/onetalk-contract/src/model.ts content 为任意 JSON value
共享 decoder apps/onetalk-contract/src/decoder.ts 不理解 text/image/file 语义
Bright 清洗 apps/server/src/onetalk/service.ts 按键过滤对象,字符串原样通过
Bright 持久化 apps/server/src/database/schema/onetalk.ts content jsonbtext 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 的单一白名单边界:

opaque raw content
  → 严格、安全、可测试的 text/image/file/unsupported 合同
  → Bright 持久化与事件只使用 normalized content
  → Mind read model 只接收 normalized content

raw 字符串中嵌套凭证进入 Bright 是本报告记录的历史风险;当前 v6 边界以不让 raw payload 离开 MAIN decoder 的方式关闭它。真实 v6 Chromium 运行态验收仍未执行。