Chlyz621/dsh-unstuck ↗★ 0

dsh-unstuck

自动恢复卡顿或异常中断的会话 适合希望自动检测并解决会话卡死、异常中断等问题的用户。

套件
dsh-unstuck
相容性
待驗證
Cordis 依賴範圍
^4.0.1
版本
0.2.1
授權
MIT
最近更新
2026年10月20日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Chlyz621/dsh-unstuck

dsh-unstuck

给 DeepSeek Harness 用的监督员插件。dsh 跑着跑着自己断了、卡住了、闷头不动了,它负责发现并把你解开(unstuck),不需要你手动发消息。

English — A watchdog plugin for DeepSeek Harness. When a run stops on its own and waits for you to say something, this continues it.

It handles three cases, and nothing else:

  • Abnormal turn end (error, aborted not caused by you, max-tokens, interrupted) — sends one "continue" after a short delay.
  • Swallowed turns — the turn is logged as completed even though the model meant to call a tool and an OpenAI-compatible relay emitted the call markup as plain text instead, leaving the agent silently idle. Detected structurally (the reasoning block and the text block are byte-identical, or the text is empty while reasoning is not, or the tail is a broken call tag) and continued with a pointed message asking for the tool call again.
  • Stalled requests — a model request was issued and then no stream frame arrived for stallMs. The turn is cancelled (kind: 'hook', inbox kept) and continued.

Waiting on an approval or on a question is logged and notified, never answered on your behalf. A stop you pressed yourself, a turn that really finished, and non-retryable errors (auth, quota, context overflow) are all left alone. Detection is pure structure over the session log — no judge model, no network calls.

Install: dsh plugin --profile add github:Chlyz621/dsh-unstuck (or run install.ps1 from a local clone). Config: /unstuck/config.json. Log: /unstuck/unstuck.jsonl. The detailed documentation below is in Chinese; PLUGIN-NOTES.md records why each decision was made.


它管三件事

现象监督员怎么做
一轮对话异常结束(上游报错、输出被截断、进程崩溃留下的残局、非你按的中断)等一小会儿,补发一条「继续」
「假完成」:日志写着这轮正常结束,其实工具调用被吞了、活没干完(最常见,见下节)补一条带指向性的「继续」,让它重新发工具调用
模型请求发出去了,但很久没有任何流片段(假死:TCP 断了但不报错)掐掉当前这一轮,再补「继续」
卡在等你审批 / 等你回答问题只记录 + 通知,绝不替你回答

不插手的情况:你按了停止、你这轮真的干完了、你正在输入(inbox 里有你的消息)、连续中断超过预算、认证失败这类再试也没用的错误。

「异常结束」具体指这几种 turn/end 原因:error(上游报错)、aborted(不是你按的停止)、max-tokens(输出被 token 上限截断)、interrupted(进程中断留下的残局)。


断连根因:中转站把工具调用吞成了正文

这不是猜的,是从真实会话日志里挖出来的。同一会话三轮,每一轮都是这么断的:

turn末 step耗时块结构reasoning 与 texttool-call正文尾部
12110.6sreasoning+text逐字节相同无…
21384.5sreasoning+text逐字节相同无…temp dir.
31018.4sreasoning+text逐字节相同无Hmm, the(断在词中间)

同一会话 172 条 assistant 消息里,「reasoning 和 text 逐字节相同且没有 tool-call」只出现这 3 次 —— 正是被吞的 3 次,其余 169 条零误报。

机理:模型本来要发工具调用,中转站(api: openai-completions 兼容层)没能把 DeepSeek 原生的 ||DSML|| 工具调用标记转成 OpenAI 的 tool_calls,那段标记被当普通正文吐了出来。于是 assistant 消息里只剩 reasoning + text、没有 tool-call;agent 循环认为「模型没要求调工具 = 这轮说完了」,turn/end 记成 completed,agent 静默 idle。

你看到的现象就是「跑着跑着自己断了,得手动发一句才继续」。耗时正常(4.5–18.4s,不是超时)、长度不一(2868–8892 字符,不是 token 上限),所以既不是超时也不是截断,是协议转换层偶发失败。

dsh 自带的「继续」按钮也救不了它 —— 那个按钮把 completed 当「已收工」,直接拒绝续跑。本插件的 continueOnSwallowed 就是专门补这个洞。

想根治的话(可选)

根因在中转站那一层,插件只能兜底。能减轻的办法:

  • 换模型:glm-5.3 / kimi-k3-1 / minimax-m3 这些模型原生就是 OpenAI 风格的工具调用,不走 DSML,理论上不会踩这个坑。改 profile 的 agent-default-model 即可。
  • 降 reasoningEffort:max 会让 reasoning 特别长,长流更容易在中转站出错。降到 high 试试。
  • 换官方端点:DeepSeek 官方 API 不会有这层转换。

三个必须知道的默认行为

  1. 只补「继续」两个字。 它不猜你想干什么,也不改你的需求。续跑消息的来源标记是 dsh-unstuck,所以它不会把自己发的消息当成你在说话。
  2. 有预算。 同一个会话连续中断最多自动续跑 continueMax(默认 3)次,用完就停手并在日志里记一条 continue-budget-exhausted。你只要手动发一条消息,预算立刻清零。
  3. 有退避。 第 1/2/3 次续跑前分别多等 1.5s / 5s / 15s,避免把上游打爆、也避免烧钱。

安装

两种装法。只想装上用走第一种;要改代码或者离线装走第二种。

一、从 GitHub 装(推荐)

dsh plugin --profile tauri add github:Chlyz621/dsh-unstuck

dsh 会把仓库取到 profile 的 node_modules 下并登记进 dsh.profile.bundles。装完完全退出 DeepSeek Harness(含托盘图标)再打开。

二、从本地目录装(改代码时用)

cd 
pwsh -File .\install.ps1

装完完全退出 DeepSeek Harness(含托盘图标)再打开。

脚本做三件事,每一步都会先备份:

  1. 把插件复制到 \plugins\dsh-unstuck
  2. 在 \profiles\tauri\package.json 里登记依赖并加进 dsh.profile.bundles
  3. 在 profile 的 node_modules 下建一个目录联接,让 dsh 能 require 到它(不联网、不跑 pnpm)

参数:

pwsh -File .\install.ps1 -Profile tauri -DshHome C:\Users\me\.dsh -Force

卸载:

pwsh -File .\uninstall.ps1          # 只从 profile 摘掉,插件文件留着
pwsh -File .\uninstall.ps1 -Purge  # 连插件文件一起删

怎么确认它活着

Get-Content "$env:USERPROFILE\.dsh\unstuck\unstuck.jsonl" -Tail 20

开机应该有一行 {"event":"boot",...},里面能看到当前生效的配置和挂上了几个监听器。之后每次动手都有记录:

event含义
boot / dispose装上了 / 卸载了
continue-sent补了一条「继续」(带 attempt、kind、code)
turn-swallowed发现「假完成」(带 why:dsml-leak / echo-reasoning / no-output)
continue-skipped判断过但没补(带 why:user-abort / policy / stall-already-handled)
continue-deferred本来要补,但你在输入或已经跑起来了(user-took-over / inbox-busy)
continue-budget-exhausted预算用完,需要人看一眼
stall-detected判定假死(带 silenceMs)
clock-jump机器睡眠/事件循环被卡,这一轮巡检只搬时间锚点、不判定(带 gapMs)
question-asked / approval-asked记录了你被卡住
question-blocked / approval-blocked卡太久,提醒你
request-retry重试了一次模型请求(默认关闭)

debug 级的记录(比如每轮一条 running)默认不落盘,只有 verbose: true 才写,免得把上面这些有用的记录淹掉。


调参

默认值够用。想改就把示例配置复制过去,改完重启 dsh:

Copy-Item .\unstuck.config.example.json "$env:USERPROFILE\.dsh\unstuck\config.json"

优先级:内置默认 node --test "test/*.test.js"


- `lib/core.js` 纯逻辑(判原因、算退避、造消息、读 session 事件),无副作用,好测
- `lib/config.js` 默认值 + 归一化 + 用户配置文件
- `lib/index.js` cordis 装配层,只管接事件和发消息
- `test/core.test.js` 逻辑单测;`test/plugin.test.js` 用假 ctx / 假 agent 跑装配层

改完 `lib/` 想让它生效,有两条路(实测过,别信「改 profile patch 就能热重载」这种说法,那个不成立):

1. **改已安装目录里的文件** —— dsh 在监听 `~/.dsh/plugins/` 下的插件目录,文件一变,1~2 秒内自己 `dispose` + `boot`,不用重启。所以先同步:
   ```powershell
   Copy-Item .\lib\*.js $env:DSH_HOME\plugins\dsh-unstuck\lib\ -Force

然后看 ~/.dsh/unstuck/unstuck.jsonl 尾部有没有新的 boot 行。 2. 重跑 install.ps1 或直接重启桌面端 —— 最稳,任何情况下都算数。

注意:只往 profile 的 cordis.patch.yml 里加注释不会触发重载(实测等 30 秒无反应)。想改配置,写进 \unstuck\config.json 再重启。


关于名字和市场里的描述

为什么叫 unstuck:它的三件事——异常结束、假完成、请求假死——本质都是「卡住了」,本插件做的就是把卡住解开。这个名字在 npm 和插件市场 catalog 上都是空闲的;而 dsh-supervisor 那个名字已经被人发布后撤回(registry 里只剩墓碑,0 版本),发不了,也进不了 catalog。

为什么插件市场里一开始没有描述:市场只显示 catalog 里已发布插件的描述,不读本地 package.json。本地 file: 安装的插件没有 catalog 条目,所以只有包名、没有描述。目前的解决办法是把一条说明写进市场备注(.dsh-market\state.json 的 notes),市场「已安装」那一行会显示它;你可以在行尾「···」里改成自己的话。

想让它有真正的「作者描述」,只能发布到 npm 并被 catalog 收录——catalog 支持 description.en 与 description.zh-CN 两栏。发布前请确认 package.json 里的 files、dsh.bundle.patch、dshTarget 都对(现在是对的)。