dsh-web-ui/packages/dsh-chat-recovery

5.1kStar311Fork49Issue5Watching

为 dsh web GUI 加对话恢复能力:编辑最近一条用户消息重新生成、按可恢复错误自动重试失败轮次(最多 5 次指数退避),所有路径走 fork,原会话不被改动。

语言
TypeScript
License
Apache-2.0
分支
dev
deepseek-harnessdshdsh-pluginweb-ui

安装

$ dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-chat-recovery

在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程

一句话定位

为 dsh web GUI 加对话恢复能力:编辑最近一条已结束的用户消息让它从原位置之前重新生成;对失败轮次自动按可恢复错误最多 5 次指数退避重试(或给出手动重试按钮)。两条路径都走 fork,原会话始终不被改动。

核心能力

  • 在最近一条已结束用户消息的尾部出现「编辑」按钮:点击进入行内编辑器(预填原文本),保存即从该消息之前的历史前缀切分支、打开子分支并重新发送编辑后文本;原会话完整保留
  • 自动重试失败轮次:仅对可恢复的模型/API 错误(超时、网络、5xx、429 限流、空响应等)且轮次不含工具/命令时启动,最多额外 5 次,节奏 1s/2s/4s/8s/16s
  • 手动重试入口:不可恢复错误、含工具/命令的轮次、用户主动停止、输出 token 上限一律只显示「重试」按钮,每次点击重放一次,由用户自行决定是否接受工具副作用
  • composer 上方 dock 状态行:显示当前等待次数/进行中、剩余退避秒数、最终失败原因,附「立即重试」(跳过剩余等待)与「取消重试」按钮
  • 与 host 自身 llm/retry 链协同:当 host 已对同轮次发起 model-retry 时,本插件主动让位,避免双轨重试

技术实现

  • 语言: TypeScript(host 半区)+ TypeScript + React 18 + CSS Modules(client 半区)
  • 关键依赖: react ^18.2.0(peer,宿主运行时注入)、@deepseek-ai/dsh-client-ui-conversation(提供 SlotMap 与 turnTail/input.dock 槽位)、@deepseek-ai/dsh-client-ui-slots(slot 注册与翻译运行时)、@deepseek-ai/dsh-client-runtime(sessions/workspaces/snapshot 服务)
  • 架构模式: 双半区 cordis bundle。src/index.ts 是 host 半区(apply 为 no-op,纯注册用占位让 bundle 可见);src/client/index.ts 是 browser 半区(apply 时先做 globalThis 装载守护,再注册 locale 与两个 slot:conversation.chat.turnTail 放编辑+手动重试按钮、conversation.input.dock 放监督器状态行)。跨半区共享的纯逻辑放在 src/core/transcript / retry-policy / retry-supervisor),框架无关便于单测
  • 入口文件: packages/dsh-chat-recovery/src/index.ts(host 入口,apply 为 no-op)、packages/dsh-chat-recovery/src/client/index.ts(client 入口,apply 注册槽位与监督器);bundle 声明在 packages/dsh-chat-recovery/cordis.patch.yml:5-7(插入 id ui-chat-recovery),浏览器依赖在 packages/dsh-chat-recovery/package.json:24-37(注入 4 个官方 @deepseek-ai/dsh-client-* 模块 + platform: web

适用场景

  • 发完消息后发现措辞不对/想换个问法的普通用户:点尾部「编辑」改一行直接重生成,比手动复制粘贴再发一遍省事;
  • 跑大批量 agent 任务、偶尔遇到网络抖动/限流的用户:模型/API 抽风时不必自己守在屏幕前,1s/2s/4s/8s/16s 自动重试 5 次,工具调用这种"重放有副作用"的轮次则只给手动按钮,避免偷偷再调一次工具;
  • 想在原会话基础上尝试不同提问方向、又不想污染主线历史的用户:编辑/重试都从原位置之前切子分支,主线永远是干净的,事后可以对比多个分支版本。

前置依赖与兼容性

依赖最低版本说明
DSH 宿主0.1.0-rc.6+ 推荐package.json 未在 dsh.engines 声明;devDependencies 统一锁到 @deepseek-ai/dsh-* ^0.1.0-rc.8
Node.js^22.19.0>=24.0.0package.json engines.node
平台跨平台host 半区 apply 为 no-op;client 半区跑在浏览器,无 OS 限制
原生模块全 TypeScript / React,不引入 native 绑定
React^18.2.0peerDependency,由宿主注入

安装方式

dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-chat-recovery

配置项

本插件无需用户配置(无 dsh settings 卡、无环境变量、无 Schema 声明)。

源码内仅 src/core/retry-policy.ts 暴露两个运行时常量,调整需改源码后重新构建:

内部常量位置默认值作用
MAX_EXTRA_RETRIEScore/retry-policy.ts:185自动重试在原失败之外的额外尝试上限
BACKOFF_DELAYS_MScore/retry-policy.ts:21[1_000, 2_000, 4_000, 8_000, 16_000] ms第 1..5 次自动重试前的退避延迟

常见问题

Q: 安装之后按钮没出现怎么办?

A: 三步排查。第一,确认 dsh web 重启过,bundle 激活是启动时一次性做的,刷新浏览器不足以触发;第二,看一眼浏览器控制台是否有 chat-recovery 相关的报错;第三,编辑按钮只在"最近一条已结束用户消息"的尾部出现——会话正在跑、或最近一条消息含附件/图片,会让编辑按钮不渲染。turnTail 槽位的选择器只能拿到 turn/seq/openFile 三个字段,无法读会话快照,所以入口会在每个已完成轮次上都尝试挂载,由组件内部用快照决定是否显示。证据:packages/dsh-chat-recovery/src/client/TurnActionsView.tsx:11-15、56-60packages/dsh-chat-recovery/README.zh.md:64-66

Q: 编辑了首轮消息,结果会怎样?

A: 首轮之前没有可切的 turn/end 前缀,分叉退化为在同一工作区下新建一个空白会话,并把编辑后的文本发过去——原会话不受任何影响。如果当时没有匹配的工作区,会先创建再 connect。代码:packages/dsh-chat-recovery/src/client/wiring.ts:20-31(connectBlank 工作区回退)+ packages/dsh-chat-recovery/src/client/wiring.ts:82-94(forkAtSeq 为 null 时走空白分支)。

Q: 自动重试和手动重试怎么触发?

A: 自动重试仅在满足四个条件时启动:错误码/消息命中可恢复正则(timeout/network/5xx/429/rate-limit/empty/no-response 等)、轮次不含 tool-result/command 节点、不是用户主动停止、不是输出 token 上限;并且 host 端没有同轮次 model-retry 在调度。一旦命中,监督器在 dock 上显示「自动重试 1/5,约 1s 后」并按指数退避自动发起 fork+prompt。其它任何失败——包括不可恢复错误(401/403/402/422/quota/auth/key/permission/invalid/cancel 等)、含工具/命令的轮次、用户停止、max-tokens——都不会自动重试,只在尾部放一个「重试」按钮,每次点击重放一次。判定:packages/dsh-chat-recovery/src/core/retry-policy.ts:114-122、154-164、172-182

Q: 自动重试正在跑的时候我想取消,怎么做?

A: dock 上的「取消重试」按钮随时可点,点了之后不再发起后续尝试;同一会话若再次出现新失败才会重新进入自动流程。auto 等待阶段还有「立即重试」按钮,跳过剩余退避直接发起下一次。取消后界面停在失败轮次,不会被强制跳到子分支。代码:packages/dsh-chat-recovery/src/client/RetryDock.tsx:36-58packages/dsh-chat-recovery/src/core/retry-supervisor.ts:215-219、222-226

Q: 5 次自动重试用完了会怎样?

A: 监督器进入 exhausted 状态("已重试 5 次仍失败:原因"),停在原失败轮次上,dock 给出「手动重试」按钮;点一次只会跑一次 fork+prompt(手动模式不走指数退避,也不自动重试)。同时若有自动重试期间的子分支,会自动 open 回原会话,避免用户停留在一个无意义的中间分支上。代码:packages/dsh-chat-recovery/src/core/retry-supervisor.ts:160-167、305-313packages/dsh-chat-recovery/src/client/RetryDock.tsx:75-90

Q: 会不会在原会话里堆出重复消息?

A: 不会。每次重试都从失败轮次"之前"的 turn/end 前缀切一个全新子分支(首轮失败退化为同工作区空白会话),仅在该子分支里把原始文本重发一次。原会话保持不变,失败轮次的流式片段也不会进入下一次模型请求。同时如果用户在重试子分支里又自己发了新消息,监督器会立即让位(cancel),避免覆盖用户意图。代码:packages/dsh-chat-recovery/src/core/retry-supervisor.ts:263-303packages/dsh-chat-recovery/src/core/retry-supervisor.ts:146-149

Q: 关闭浏览器标签页会取消自动重试吗?

A: 会。自动重试依赖浏览器侧的 setTimeout 调度(与任务看板的 cron 同模型),关闭/刷新标签页即丢失本次监督;下次重新打开会话只能看到手动重试按钮,不会自动继续尝试。原会话和失败轮次的历史由 host 持久化保留,不会丢失。说明:packages/dsh-chat-recovery/README.zh.md:66-68

Q: 含图片/附件的消息能编辑吗?

A: 不能。判定逻辑要求 user 消息的所有内容块都是文本块;只要有一条非文本块(图片/附件/文件等),就返回 null 不给编辑。这是出于"无法安全复制进重新提交的提示词"的考量——图片二进制不能简单重发。代码:packages/dsh-chat-recovery/src/core/transcript.ts:44-52、61-94

Q: 主机自身已经在重试,本插件会重复发起吗?

A: 不会。turn 上若已有 model-retry 节点且处于 scheduled/started,verdictFor 直接返回 {action:'none'} 让本插件静默;只有 host 让出之后,本插件才会接管后续判断。代码:packages/dsh-chat-recovery/src/core/transcript.ts:168-172packages/dsh-chat-recovery/src/core/retry-policy.ts:176

Q: 卸载插件会不会留下残留?

A: 不会。本插件不在 host 上写任何持久文件,编辑/重试都通过 fork 创建新会话,原会话不变,失败历史由 host 自己保留。卸载 dsh plugin --profile web remove @linxin666/dsh-chat-recovery 后无插件自留状态。说明:packages/dsh-chat-recovery/README.zh.md:29-39

上手难度

入门 — 一行命令安装,重启 dsh web 即可见尾部「编辑」按钮与 dock 状态行;无需任何配置,原会话始终不受影响。

已知问题与限制

  • 客户端 turnTail chain 槽位的选择器拿不到会话快照,只能匹配每个已结束轮次;按钮是否显示由组件内部用快照过滤;选择器本身的覆盖范围无法缩小(packages/dsh-chat-recovery/src/client/TurnActionsView.tsx:11-15
  • 仅纯文本用户消息可编辑;含图片/附件的消息一律不显示编辑按钮,避免把二进制块复制进重发的提示词(packages/dsh-chat-recovery/src/core/transcript.ts:44-52
  • 自动重试依赖浏览器侧的 setTimeout 调度,关闭/刷新标签页即取消监督;下次打开只会看到手动重试按钮,不会自动继续(packages/dsh-chat-recovery/src/client/index.ts:80-82packages/dsh-chat-recovery/README.zh.md:66-68
  • 任何含 tool-result/command 节点的轮次只走手动重试,不自动重试;工具副作用只在用户显式点击时重放(packages/dsh-chat-recovery/src/core/retry-policy.ts:154-164
  • max-tokens 与用户主动停止的 interrupted 一律只走手动,不自动重试(packages/dsh-chat-recovery/src/core/retry-policy.ts:158-162
  • 自动重试到 MAX_EXTRA_RETRIES=5 后停在 failed/exhausted 状态;不再自动尝试,需要用户手动重试或编辑消息(packages/dsh-chat-recovery/src/core/retry-supervisor.ts:160-167
  • 编辑首轮消息退化为同工作区空白会话;fork 路径不可用时不再回退到原会话内修改(packages/dsh-chat-recovery/src/client/wiring.ts:82-94
  • 客户端 apply 用 globalThis 上的 __dshChatRecoveryApplied 做单实例守护;hot-reload 时由 fiber cleanup 释放,但页面级 reload 之前只能有一个生效工厂(packages/dsh-chat-recovery/src/client/apply-guard.ts:21-30