Files
trade-message-center/docs/onetalk-quote-reply-mind-integration.md
YBF 784bbec2a0 feat: 支持 OneTalk 文本引用回复协议 (#66)
* feat: add OneTalk quote reply support

* chore(task): archive 09-17-onetalk-quote-reply-protocol

* chore: record journal

* docs: format OneTalk DOM card collection plan

* chore: release 0.8.29

* chore: release 0.8.30
2026-09-17 11:56:35 +08:00

137 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OneTalk 引用回复:Mind 对接说明
> 适用范围:Mind 已接入 Bright OneTalk WebSocket 的文本发送链路。本说明只增加引用目标字段,不改变连接、授权、普通发送或发送结果协议。
## 1. 能力与边界
Mind 在 OneTalk 时间线中选择一条**文本消息**后,可以发送一条引用该消息的新文本。Mind 只向 Bright 提交被引用消息的 OneTalk `messageId`;Bright 和插件负责在同一账号、同一会话内解析 OneTalk SDK 所需的原始引用对象。
Mind 不得发送、保存或展示下列数据:
- OneTalk SDK `referMessage` / `originalData`
- `.PNM` 后缀的 SDK 传输别名;
- 根据正文、时间、发送人或 UI 序号推导出的 ID;
- Mind 数据库 UUID(它不是 OneTalk `messageId`)。
第一版只支持“引用文本后发送文本”。图片和文件仍走既有普通发送,不能附带引用字段。
## 2. 引用目标的来源
从 Bright 返回的 OneTalk 时间线消息中读取该条消息的 `id` / `messageId`,将其原样作为 `replyToMessageId`
```ts
type ReplyTarget = {
messageId: string; // Bright OneTalk messageId,不是 Mind UUID
from: string;
text: string;
};
```
仅当时间线行满足以下条件时显示“回复”入口:
- 消息正文是非空文本;
- `messageId` 为非空字符串;
- 消息属于当前 OneTalk 会话,且不是 system、optimistic 或本地临时消息。
`messageId` 不做 trim、拼接或格式转换;例如 `123456.PNM` 不是合法的 `replyToMessageId`
## 3. 发送帧增量
沿用既有 `send.request` WebSocket 帧。在当前共享协议版本 **9** 中,引用发送的 payload 必须恰好为以下形状:
```json
{
"conversationId": "c123",
"content": {
"kind": "text",
"text": "这条消息是在回复前文"
},
"replyToMessageId": "123456"
}
```
完整帧示例(`scope``requestId``sendRequestId` 继续使用当前 Mind 的既有生成与关联逻辑):
```json
{
"protocolVersion": 9,
"connectionType": "mind_page",
"type": "send.request",
"requestId": "mind-frame-001",
"sendRequestId": "mind-send-001",
"scope": {
"workspaceId": "workspace-1",
"mindUserId": "mind-user-1",
"channelAccountId": "286995452"
},
"payload": {
"conversationId": "c123",
"content": {
"kind": "text",
"text": "这条消息是在回复前文"
},
"replyToMessageId": "123456"
}
}
```
普通发送保持原样,**省略** `replyToMessageId`
```json
{
"conversationId": "c123",
"content": { "kind": "text", "text": "普通文本" }
}
```
不要把 `replyToMessageId` 放到 `content``ext` 或其他扩展对象中;协议采用严格字段校验,未知字段或错误层级会被拒绝。
## 4. Mind 侧校验
发起引用发送前,Mind 必须确认:
- `conversationId` 是当前会话的 OneTalk `conversationId`
- `replyToMessageId` 非空、没有首尾空白,且不是纯数字加 `.PNM`
- `content.kind === "text"``content.text` 非空;
- 当前 WebSocket scope 中的 `channelAccountId` 与时间线消息所属账号一致。
不要在客户端把无效引用降级为普通文本发送。若本地无法确定目标 ID,应禁用发送并提示用户重新选择消息。
## 5. Composer 交互
点击“回复”后,Mind 只保留临时 UI 状态 `{ messageId, from, text }`:composer 中显示发送者和截断文本摘要,并提供“取消引用”。
以下场景必须清除该临时状态:
- 切换 OneTalk 会话;
- 收到 `confirmed_sent`
- 收到 `rejected_before_send`
收到 `delivery_unknown` 时,不能自动重试或伪造发送成功;应保留既有“结果未知”的可见状态,由用户决定后续操作。
第一版不要求 Mind 解析或重新绘制远端消息里的引用卡片。引用内容由 OneTalk 页面渲染,Mind 时间线仍按 Bright 已返回的规范化消息展示。
## 6. 结果处理
`send.result` 与普通发送使用同一关联键 `sendRequestId`,结果语义不变:
| status | Mind 行为 |
| ---------------------- | --------------------------------------------------- |
| `confirmed_sent` | 将返回的已发送消息并入时间线,清除引用临时状态。 |
| `rejected_before_send` | 显示 `reason`,清除引用临时状态;不得改发普通文本。 |
| `delivery_unknown` | 显示结果未知;不得自动重试、不得假定消息已发送。 |
常见拒绝原因包括 `invalid_request`(引用 ID 不合法、引用目标不在当前会话或不支持的内容类型)、`waiting_for_page``page_not_found``page_identity_mismatch``send_not_supported`。Mind 应直接展示已有的发送错误状态,不自行转换错误码。
## 7. 联调验收
1. 加载 OneTalk 会话与文本消息,确认时间线行使用 Bright 返回的真实 `messageId`
2. 选择一条文本消息,发送引用文本,检查发送帧只有顶层 `payload.replyToMessageId`
3. 确认 `send.result` 与同一个 `sendRequestId` 关联,并仅在 `confirmed_sent` 时显示发送成功。
4. 验证切换会话、取消引用与 `rejected_before_send` 都会清除 composer 的引用状态。
5. 验证普通文本、图片和文件发送均不携带 `replyToMessageId`,原有行为不变。
## 8. 兼容性
Mind、Bright 与 Chrome 插件必须使用兼容的 OneTalk 协议版本部署。当前源码的协议版本为 9;上线前以实际运行中的共享 contract 为准,不要让旧客户端向新协议帧附加未知字段。