dsh-im-ops/docs/方案/Issue-62-九渠道会话历史预览方案.md
xmanrui 8c6c31a1b6 feat: add bounded history previews across nine channels
Add shared /history parsing, read-only session access, message filtering, and bounded replies. Default to three messages, cap at five, and exclude saved history commands.

Include channel integration tests, translations, command help, and the Issue #62 implementation plan.
2026-08-28 03:01:53 +08:00

201 lines
14 KiB
Markdown
Raw 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.

# Issue #62:九渠道会话历史预览方案
日期:2026-08-28。代码基线:v3.0.8 / `bf0e157`。状态:已实施。
需求来源:[Issue #62](https://github.com/xmanrui/dsh-im/issues/62)。本方案仅描述实现,不代表功能已经上线。
## 1. 最终命令约定
新增一个共享命令:`/history [数量]`。
| 输入 | 行为 |
| --- | --- |
| `/history` | 返回最近最多 3 条历史消息 |
| `/history 1` | 返回最近最多 1 条 |
| `/history 4` | 返回最近最多 4 条 |
| `/history 5` | 返回最近最多 5 条 |
| `/history 6`、`/history 100` | 均按 5 条处理,不报超限错误 |
| `/history 0`、负数、小数、非数字、多个参数 | 返回用法,不访问模型 |
这里的默认值是 **3 条,不是 3 天**,不提供时间范围查询。
“一条历史消息”指一条用户消息,或一条助手最终回复;不是一轮对话,也不是一个工具事件。例如两次完整问答共有 4 条。数量不足时按实际数量返回,不补齐、不生成摘要。
先取最新 N 条,再按从旧到新的顺序展示。奇数条可能从助手回复开始,这是按消息计数的正常结果,不额外补入第 N+1 条。
## 2. 范围与体验
九渠道统一提供文字命令和文字预览,不新增卡片、按钮、设置项、数据库或历史缓存,不为 QQ、企业微信添加专门的发送逻辑。
本期沿用前一轮建议的私聊范围:微信、飞书、钉钉、企业微信、QQ、Slack、Telegram、Discord、WhatsApp 的私聊均支持;群聊、频道和群内线程只回复“请在与机器人的私聊中使用 /history”。它们不读取历史,也不自动转发到另一个私聊。渠道现有的入站路由行为保持不变。
只查看当前聊天已绑定的 Harness Session,不接受 Session ID 参数。切换成功后只增加一句“发送 /history 查看最近对话”,不自动发送历史。AI Office Connector 不属于本次九个聊天渠道。
预览示意:
```text
会话历史|会话 abc123|最近 3 条
1. 助手
上一次已经完成登录接口,下一步是补充测试。
2. 用户
先补充登录失败场景的测试。
3. 助手
已补充三个失败场景,测试全部通过。
以上为历史记录,不是本次新回复。
```
这是历史文字摘录,不是让模型重新回答或概括。
## 3. 历史读取与筛选
### 3.1 读取流程
1. 渠道先执行现有访问策略、去重和命令识别。
2. 共享命令解析数量:省略为 3,正整数超过 5 时取 5。
3. 确认是私聊、没有图片或文件,并读取当前聊天绑定的 Session ID;未绑定直接提示,不创建会话。
4. 通过共享 HarnessClient 调用现有 `session.history`。本机沿用 v3.0.8 的进程内 API,显式配置远程 Host 时沿用 HTTP,不另建访问链路。
5. 筛选可显示消息,选取最新 N 条,按原顺序排版。
6. 读取结束后再次检查当前绑定和工作区作用域;读取期间切换了会话、工作区或机器人已失效,则丢弃结果并提示重试。
7. 返回现有的 `{ handled, message, messages }` 命令结果,由各桥接器现有发送函数投递到本次请求的原聊天。
发送一旦开始,整份回复始终使用同一份已读取快照和原回复目标,不在分段之间改读新会话。标题或短 ID 用于标明这份历史属于哪个会话。
### 3.2 什么可以显示
| 内容 | 处理 |
| --- | --- |
| 原始用户消息 | 只接受能确认是人类输入的消息来源,提取正文文字 |
| 用户历史中的 `/history` 命令记录 | 排除被 `isHistoryCommand` 识别的命令(含参数和首尾空白),不显示、不计数;普通正文或助手回复中提到 `/history` 的内容保留,不推断关联 Turn |
| 助手最终回复 | 每个成功结束的 Turn 只取最后一条定稿助手消息中的文字 |
| 当前运行中的用户输入 | 已进入历史时可以显示;未提交的排队输入不读取 |
| 正在生成的助手回复 | 不显示 token 分片或过程消息,可附“当前任务仍在进行中” |
| 图片、文件等非文字内容 | 不下载、不重发;有可靠结构化信息时显示类型占位说明 |
| 工具调用、工具结果、推理内容、系统指令、注入上下文、审批内容 | 不展示 |
| 压缩产生的替换副本 | 不当成新对话,不重复计数;保留原始人类对话的含义 |
| 无可显示文字的助手最终消息 | 显示“本条没有可预览的文字”,不拿更早的过程消息冒充最终回复 |
实现时不能简单地过滤 `user/message`、`assistant/message` 后取最后 N 个事件:
- `user/message` 也可能是内部注入内容,必须检查 `data.source.kind === 'user'`。
- 当前协议中,原始对话使用 `surfaceOp: 'append'`;`replace` 是模型侧替换内容,不重复展示。缺少关键字段时不猜测为普通对话。
- 助手消息需要结合 `turn/end` 确认最终状态;忽略 `assistant/chunk`、中断片段以及中间工具步骤的助手消息。
- 用户正文在 `event.data.content`,助手正文在 `event.data.message.content`,两者不能用同一个未经区分的字段路径读取。
这些规则只限制显示哪些事件、哪些内容块,不承诺自动识别并清除用户正文里原本就包含的密码、路径等敏感文字。权限仍依赖机器人已有的可信访问范围,不新增一套管理员或 Session 所有者体系。
### 3.3 有限读取即可,不做历史浏览器
首次读取 `maxMessages: 50`;若筛选后不足 N 条且响应 `hasMore` 为真,使用 `beforeSeq` 向前补读,最多总共 3 次。整次读取设置 10 秒截止时间,不订阅实时事件。
`maxMessages` 是 Harness 的分页计数,不是本命令的返回条数。每页按 `seq` 校验、排序和去重,向前游标必须严格推进。固定首次尾页的快照边界,不在补读期间追赶新消息。
达到上限仍不足时,返回已确认的记录并说明不足;没有可用记录则给出对应提示。字段不合法、游标不推进或记录冲突时返回安全错误,不把原始响应作为文字发出去。
## 4. 防刷屏统一规则
防刷屏只在共享命令层处理,不按渠道另写策略。
- 最多选择 5 条历史消息。
- 每条正文最多展示 500 个字符,超出注明“已截断”;切分不得拆开 Unicode 代理对。
- 合并成一份带角色标签的文字回复,总长度控制在 3,000 个字符以内,标题、标签和截断提示都计入预算。
- 复用现有 `splitWorkspaceCommandMessage()` 按 1,800 字符分段;最多发送 3 段,超出先缩短正文并标注截断。
- 逻辑历史消息数与 IM 分段数分开计算:`/history 5` 最多包含 5 条历史记录,不是固定发送 5 个气泡。
- 命令结果一次生成完再发送,不流式推送历史,不引导用户发送“继续”,不自动翻页。
上述 500 / 3,000 / 3 段是建议的统一显示预算,可在共享常量中调整;默认 3 条、上限 5 条则按本次需求固定。
## 5. 九渠道接入
| 渠道 | 接入位置 | 回复方式 |
| --- | --- | --- |
| 微信 | `src/channels/weixin/weixin-bridge.mjs` | 现有命令回复流程 |
| 飞书 | `src/channels/feishu/bridge.mjs` | 现有命令文字回复流程,不新增卡片 |
| 钉钉 | `src/channels/dingtalk/dingtalk-bridge.mjs` | 现有命令回复流程 |
| 企业微信 | `src/channels/wecom/wecom-bridge.mjs` | 现有命令回复流程,不单独处理 |
| QQ | `src/channels/qq/qq-bridge.mjs` | 现有命令回复流程,不单独处理 |
| Slack | `src/channels/shared/text-harness-bridge.mjs` | 继承共享桥接器,复用现有发送函数 |
| Telegram | `src/channels/shared/text-harness-bridge.mjs` | 同上 |
| Discord | `src/channels/shared/text-harness-bridge.mjs` | 同上 |
| WhatsApp | `src/channels/shared/text-harness-bridge.mjs` | 同上 |
因此实际只需要接入六处桥接入口,不复制九份查询或格式化代码。各渠道现有回复目标、鉴权、入站去重、发送失败处理和机器人回声过滤都保留。
不修改 QQ Markdown 回复机制、企业微信即时回复机制、Discord Thread 路由、Telegram 富消息机制或任何平台 SDK。这里承诺的是复用现有行为,不额外承诺本次解决底层发送链路已有的重试或重复投递问题。
## 6. 状态和异常
| 情况 | 结果 |
| --- | --- |
| 没有绑定 Session | 提示先开始或绑定会话,不自动创建 |
| Session 为空 | “当前会话暂无可预览的历史消息” |
| 请求 N 条但只有更少 | 返回实际数量 |
| 最近记录包含失败或中断的 Turn | 用户输入仍可显示,不把未完成的助手片段当成最终回复 |
| 任务正在运行 | 可查历史,不停止、不等待任务结束 |
| 正在等待提问或审批 | 命令不被当作交互答案,不提交或取消原交互 |
| 批量输入正在收集 | 沿用现有规则,提示先 `/send` 或 `/cancel`;不收录、不查询 |
| `/history` 带图片或文件 | 提示仅支持文字命令,不转给模型 |
| 数量格式错误 | 返回用法;大于 5 的合法正整数不是错误 |
| Session 不存在 | 提示重新绑定,不静默新建或切换 |
| 读取期间发生切换 | 丢弃旧结果,提示重新执行 |
| Harness 不支持、超时或返回异常 | 返回简短安全提示,不泄露原始异常和历史数据 |
| 平台发送失败 | 走该渠道现有错误路径,不新增历史专用重试 |
`/history` 应接到现有快速命令分支:在批量输入规则之后、待回答问题和审批识别之前处理。无效参数同样在本地消费,不能落入普通消息流程。新命令的识别不能因为携带附件而绕过校验、转发给模型。
## 7. 最小改动清单
1. 新增 `src/channels/shared/history-command.mjs`:命令解析、数量上限、历史筛选、输出预算、提示文案;返回现有命令结果结构。
2. 修改 `src/channels/shared/harness-client.mjs`:增加 `readSessionHistory(sessionId, options)`,复用已有 RPC 和错误类型。
3. 修改 `src/channels/shared/bot-workspace-store.mjs`:在 `workspaceSession()` 句柄增加历史读取方法,复用工作区代际校验;命令另检查当前聊天绑定是否仍相同。
4. 六处桥接器接入共享命令,并传递私聊类型及文字/附件信息。
5. 更新九渠道帮助文案、共享 `/session` 成功提示及飞书自己的绑定成功提示。只增加命令提示,不重放历史或新增按钮。
6. 更新现有中英文文案、`README.md`、`README.en.md` 和 `CHANGELOG.md`。
7. 增加共享测试及九渠道接入测试。不新增持久化状态、依赖包或配置迁移。
读取链路不得调用 `session.create`、`session.prompt`、`session.cancel`、`executeCommand` 或会话恢复/接管方法;`session.history` 本身能够读取未激活会话,不需要先创建 Agent。
## 8. 验收与实施顺序
先完成共享命令和历史读取测试,再一次性接入六处入口覆盖九渠道,最后补帮助文案并执行现有 `npm run check`。
共享验收:
- 默认 3 条;参数 1、3、5 正确;6、100 和很大的正整数均按 5 条;0、负数、小数及多个参数返回用法。
- 最新 N 条、旧到新显示、数量不足及空会话正确;不能把“一轮”误当“一条”。
- 用户历史中的 `/history` 命令记录在计数和截取前排除,数量不足时沿用有限分页补取;普通正文和助手回复中的提及不删。
- 多工具步骤、内部注入、推理内容、压缩替换、运行中回复、失败/中断及重复事件的筛选正确。
- 单条超长、中文、emoji、代码文字均遵守预算,最多 5 条记录和 3 段回复。
- 补读有上限且游标推进;API 不支持、超时、坏数据和读取期间切换均安全退出。
- 进程内 API 与显式 HTTP 模式都覆盖;查询全程不触发模型、会话创建、恢复或交互响应。
九渠道都执行同一组接入验收,不以“继承共享类”代替覆盖:
- 私聊发送 `/history` 和 `/history 10`,分别得到最多 3 条和 5 条,且回到原聊天。
- 未授权来源按原策略拒绝;已接收的群聊命令不读历史,提示去私聊。
- 正在运行、待回答/审批、批量输入三种状态都符合上表。
- 重复平台事件不重复处理;发送失败按原渠道处理;历史回声不触发新模型请求。
- `/help`、绑定成功提示和英文提示包含正确用法。
发布前使用可用的真实机器人做九渠道私聊冒烟。未完成的实机项明确记录,不能将 mock 测试通过写成九渠道已经实测通过。发布回退可撤回命令入口和帮助提示,无需回滚会话数据。
## 9. 核对依据
- 本仓库 `workspace-command.mjs` 的命令结果与 1,800 字符分段器,以及六处桥接器现有的 `result.messages` 发送循环。
- 本仓库 `harness-client.mjs`、`bot-workspace-store.mjs` 和 v3.0.8 的 `harness-connection.mjs`。
- [Harness 历史 API 契约](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/host/apiproxy/src/api/sessions.ts):`session.history`、反向分页及只读行为。
- [Harness 事件定义](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts):用户来源、Turn 状态与定稿助手消息。
- [Harness 原始对话与替换内容的区别](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/surface.ts):人类对话预览不能照搬压缩后的模型上下文。
上游仍在演进,实施时以实际支持的 Host 协议和测试样本为准,不额外假设新的 RPC 或字段。
## 10. 验收记录
2026-08-28:
- 用户确认:九个渠道实测均 OK,并要求停止进一步客户端测试。此项为用户确认,不代表我们独立完成了全部原生客户端实测。
- 自动验证:`npm run check` 的构建、1,554 条全量测试及包产物校验通过;随后新增的 Slack 前导空格联合回归通过,`history-bridge` 测试文件 112/112 通过。
- 追加优化:在计数前排除 `/history` 命令记录,普通正文中的命令引用不受影响;`history-command` 34/34、重新构建及包产物校验通过。重启加载后,仅向飞书机器人“今天是牢梁”发送一次 `/history`,返回 3 条正常历史消息,不包含查询命令;本轮未复测其他渠道。