dsh-botplugin
把 Telegram / 微信变成 DSH 的聊天入口的插件。三大功能:Telegram 入口、微信入口、handoff 记忆接续,外加语音转文字。
> 🔰 第一次接触、想知道"这东西到底能干什么" → 先看 功能说明.txt(大白话版,不用懂技术)。
> 装的时候怎么填、怎么启动 → 看 使用说明.txt。下面这份是给要自己动手的人的完整文档。
环境要求
- Node ≥ 18
- 机器上装好 DSH(有
dsh命令;没装的话先跑下面第 0 步) - 一个 Telegram bot token(在 Telegram 里找 @BotFather 发送
/newbot新建,不要和其他 bot 共用)
> 安装分成两件事,一个脚本管一头,互不越界:
> setupdsh = 升级(Git、Node、DSH 本体,外加已经装过机器人的工作区里的插件:装 + 升级);
> setupbot = 建/选工作区、首次装插件、绑 TG / 微信、生成工作区里那 5 个文件(英文名,双击或终端里跑都行)。
> 机器上没有 dsh 时 setupbot 会自己把 setupdsh 跑一遍,所以顺序不用记。
安装方式一:在线安装(两条命令,推荐)
- 先装/升级本体(Git、Node、DSH 一起办,可重复跑):
curl -fsSL https://raw.githubusercontent.com/aitcmhk-web/DSH-bot/main/setupdsh.sh | sh
- 再装机器人 —— 把下面这条整段贴进终端,回车:
curl -fsSL https://raw.githubusercontent.com/aitcmhk-web/DSH-bot/main/setupbot.sh | sh
它自己列出/新建工作区、自己把插件装好,然后只问你三件事:
工作区叫什么(直接回车就叫 mybot)、Telegram token(@BotFather 那串)、微信怎么绑(可选)。
每样都是「输个编号」或「粘一下回车」,不用你编辑任何文件。
- 跑完它会把两个词装进 PATH —— 以后升级(本体 + 插件)敲
setupdsh;
重新绑 TG、重新绑微信敲setupbot:
setupdsh
setupbot
- 启动:照着它最后打出来的那行,或者跑
~/DSH/<工作区名>/start.command
(同目录里另有stop.command、restart.command、install-autostart.command、uninstall-autostart.command)。
> 那五个文件名字一律英文:有桌面双击,没桌面(云主机 / 只有终端)就
> cd ~/DSH/<工作区名> && ./start.command。中文文件名在纯终端里得切输入法,太别扭。
开发者:本地安装(用你手上这份源码)
已经有一份源码(比如你自己 clone 下来的、或者要改着调试的),就用 file: 指向那个目录:
dsh plugin --profile mybot add file:/绝对路径/dsh-botplugin
例如源码在 ~/DSH/BOT/botplugin:
dsh plugin --profile mybot add file:$HOME/DSH/BOT/botplugin
> ⚠️ file: 后面不能写 ~ —— 它不会展开波浪号,会当成字面路径找不到。用 $HOME/ 或完整绝对路径。
> 用 file: 装出来的是软链 —— 改完源码不用重装,重启 profile 就生效。
> 调试的时候用这个;平时用上面的 git 方式。两种选一种,别都装
> (会装出两份同名插件,加载哪份不好说)。
>
> ⚠️ 路径必须是绝对路径,且指向含 package.json 的那一层(也就是仓库根),
> 不要指到 src/ 或上一层。
安装方式二:下载安装包(不想敲命令的)
- 下载安装包:dsh-mybot-安装包.zip(在本页文件列表里,点它再点 Download / Download raw)。
- 解压,双击里面的
安装-mybot.command。 - 接下来和在线方式完全一样:问你工作区名字(直接回车就叫
mybot)→ 粘 token → 微信要不要绑。
窗口最后会停住等你按回车,结果不会一闪而过。 - 以后每次使用:跑
~/DSH/<工作区名>/start.command(stop.command/restart.command/install-autostart.command/uninstall-autostart.command是同一个目录里的另外四个文件)。
想重新绑 TG / 微信:把安装-mybot.command再双击一次(或在终端敲setupbot)。
想升级(Git / Node / DSH 本体 / 已经装过机器人的工作区里的插件):在终端敲setupdsh(安装包里也带了这个脚本)。
> 两种方式装出来的是同一个插件,跑的是同一个 setupbot.sh,选一种就行。
> 安装包方式适合完全不想碰命令行的人;在线方式第一次要粘两条命令,之后也只剩 setupdsh / setupbot 两个词。
怎么确认装对了
脚本跑到最后会打「✅ 复核通过:配置已经生效」—— 看到这句就是装对了,它紧接着会给你启动命令。
没看到这句、或者它报「插件没装上」= 没装成,插件不会生效(而且不报错,只会静默什么都没发生)。
配置项速查
| 配置 | 说明 |
|---|---|
telegramToken / telegramApiRoot |
TG 必填;apiRoot 指向代理时改 |
telegramAllowedUsers |
允许的 TG 用户 ID;空 = 首个发消息者认领为主人 |
weixinToken / weixinApiRoot / weixinAccountFile |
微信入口(通常用扫码登录,不用填 token) |
routes + defaultRouteKey |
手工指定模型档位;留空 = 自动跟随 web 端「设置 → 模型」 |
cwd |
模型的工作目录 |
memoryDir / memoryScript |
handoff 记忆接续:默认开启(<工作目录>/memory + 插件自带的记账程序),不用配;要关闭把 memoryDir 设成空串 |
restartCommand |
重启用的启动器 .command 路径;不填默认找工作目录里的 start.command(兼容老的 启动-<工作区名>.command / 启动-mybot.command) |
turnTimeoutMs |
单轮超时,默认 90 分钟(2026-10-20 由 30 抬上来:满 30 分钟就判超时并把这一轮答案丢掉) |
asrBackend / asrWhisperBin / asrPythonBin |
语音转文字。默认 sensevoice(阿里 FunASR,中文准),不填路径就自动找 /opt/homebrew/bin/python3.11;本机没装时,安装收尾和发语音都会回一条可粘贴的安装命令(⛔ 不自动装)。whisper 中文差,只有显式填 asrBackend: whisper 才用 |
llmBudget |
模型流量闸:滚动 60 秒内请求数与 token 数封顶(默认 50 次 / 90 万 token),超了排队等,别撞供应商的 TPM 限额。字段:enabled(默认 true,关掉=不限流)/ tpm / rpm / 输出预留(每次先占多少输出 token,默认 8000)/ 最长等待毫秒(默认 10 分钟,等满就放行并打告警) |
模型流量闸(每分钟限额,2026-10-19 加)
一回合里跑工具、开子代理都会反复发模型请求,一分钟里的累计很容易顶到供应商的
TPM/RPM 限额(撞上就是 429)。插件在宿主 llm 的 llm/stream 上挂了一道闸
(src/llm-budget.js):滚动 60 秒窗口内 请求数 ≤ rpm、token 合计 ≤ tpm,
快满时让下一次调用排队等到窗口腾位,⛔ 不丢请求、⛔ 不改流里的内容。
- 记账口径:调前按请求体的可读字符量预估(中文 1 token/字、其余 1/4,另加输出预留),
流里收到宿主usage块后换成实际用量(替换,不叠加);失败/中断的那次仍按预估留在账上(保守)。 - fail-open:单次最多等
最长等待毫秒(默认 10 分钟),等满仍无额度就放行一次并打告警——
宁可偶尔超一次,也不让整台 bot 卡死。 - 子代理与工具循环一样算:它们都走同一条 waterfall,所以一处就管住全机流量。
- 默认口径:
rpm50 次 /tpm90 万 token(2026-10-19 定的最终值)。供应商(百度千帆)的
TPM 限额是 100 万,90 万是留一成余量。一条请求就带十几万 token(整段历史重发),所以一分钟
大约放六七发 —— 这是硬约束,要更快只能少发请求,⛔ 不能靠抬闸。
⛔ 历史上曾抬到 300 万(当时误判「供应商限的是请求密度、不是 token 量」),拿到实数后已收回。 - 关掉:配置里
llmBudget.enabled: false(出问题时可一键停,不影响启动)。
会话压缩口径(随版本下发,2026-10-19 加)
插件作为 profile 层装上时,会顺手把会话压缩的口径打进 profile:本包的
cordis.patch.yml(package.json 的 dsh.bundle.patch)多打一行 compaction-basic。
宿主代码默认 thresholdRatio 0.8 —— 在百万窗口上等于 80 万 token 才压缩,
正常对话根本触发不到,历史只能近乎全量重发,越聊越慢。
本包一律下发比例制(两个数都随窗口缩放,任何窗口都自洽):
| 键 | 值 | 作用 |
|---|---|---|
thresholdRatio |
0.15 |
窗口的 15% 触发压缩(百万窗口 = 15 万 token) |
retainRatio |
0.08 |
压缩后按窗口留约 8%(百万窗口 ≈ 5 万 token) |
headroomTokens |
8192 |
预留空间。出厂默认 65536 比小窗口本身还大,会把压力预算算成负数 |
⛔ 不要再用 retainTokens(绝对保留量):它是绝对值、不随窗口缩放,宿主会校验
「保留量必须小于触发线」,小窗口模型(如 6.5 万窗口)配上几万的绝对值会被判非法并抛
TargetPressureConfigError —— 压缩整个失效、历史无限增长,直到上下文溢出。
补丁顺序是「出厂 → 本插件 → 用户 profile 的 cordis.patch.yml」,最后一个写的人赢,
所以用户 profile 想改仍盖得住本包,这里只是把合理默认变成可分发的东西。
远程审批(手机点按钮,不用守着电脑)
DSH 的审批服务用 waterfall 广播 approval/request,插件把它桥接到 Telegram——
推一张和 web 端审批面板同口径的卡片(等待审批 / 已允许 / 已拒绝 / 已取消 / 已超时
+ 命令、工作目录、原因),下面挂两个内联按钮【✅ 允许一次】【❌ 拒绝】。
点完卡片收成终态并去掉按钮。
> 桥是只读旁路:卡片渲染失败、编辑失败、按钮超时,统统只记日志,
> 绝不影响审批本身的结果(该通过的照常通过)。
> 行动端入口在 src/approval-bridge.js,默认超时 10 分钟。
多端互通(TG ⇄ 微信,同一个大脑)
src/hub.js 是所有端点的唯一交汇点,纯逻辑、零依赖。两条规则,都是「同步到所有」:
| 方向 | 行为 |
|---|---|
| 端点到 hub | 同步给 ① DSH ② 其他所有端点 |
| DSH 到 hub | 同步发到所有端点 |
新增端点 = 加一个数组项(O(n)),不是两两镜像(O(n²))。
微信消息并入主人的 TG 会话,所以两边聊的是同一段上下文,
在微信里 /new、/model 操作的是同一个会话。
> ⛔ 这里刻意不做去重/合并:TG 和微信各进来一条就是两次真实输入,进来几条算几条。
指令
/whoami /status /model /new(新会话) /restart(重启进程,先留档) /help
> /restart 与老 bot 同路:先把进展写进 handoff,再由进程外的接力脚本延迟几秒杀掉宿主并重新拉起(有 start.command(兼容老的 启动-<工作区名>.command / 启动-mybot.command)就重开终端窗口;没有就按原始命令行后台拉起)。两者都没有时退化为只重开会话。重启完成后发条消息,新会话自动带上刚才的记忆。
记忆(流水账 + handoff)
默认开启,什么都不用配:
- 流水账:每轮对话原文追加到
<工作目录>/memory/conversation-cache/raw/ledger/,按月一个文件,只增不删。 - handoff:
/new、切换模型(本次会话满 20 条才写,调试期反复切不会覆盖)、/restart(无条件)和启动补写时,把最近 20 条进展落盘到<工作目录>/memory/handoff/handoff.md。 - 冷启动注入:新会话的第一条消息会自动带上最近一次 handoff,模型直接接着上段干,不用你复述上文。
- 记账程序随插件自带(
vendor/conversation-cache/),不依赖机器上是否装过 DSH 的其它项目。 - 想换位置或关闭:配置里写
memoryDir(自定路径;空串 = 关闭)、memoryScript(自定程序路径)。
微信入口(扫码绑定)
- 运行(本目录下):
node weixin-login.mjs --out 你的账号文件路径
- 手机微信扫终端二维码(约 2 分钟过期,自动刷新;备用链接发到手机微信里点开)。
- 凭据写入账号文件(0600)→ 在配置里加
weixinAccountFile: 那个路径→ 重启即启用。 - 扫码的这个微信就是主人,只有它能和 bot 对话。
⚠️ 三条硬注意事项
- 一个 token 只能一个进程。两个进程抢同一个 token,先启动的会 409 冲突。
- Telegram 游标在内存里:重启后从当前时刻开始拉消息,不补发离线期间的旧消息。
- 409 冲突时插件只停自己的轮询并打日志,不会拖垮宿主;原因要去启动日志里看(通常是同一个 token 另有进程在用)。
已知限制
- 微信扫码工具用 macOS 原生 CoreImage 渲染二维码(零依赖),Windows/Linux 机器上会退化为只打印链接。
- 回答由节点(hub)广播到所有端点(2026-10-19 定):大模型 → 节点 → 广播,含来源通道本身,出口只有一个。
进度占位消息只报进度,⛔ 收尾不在它上面写「✅ 已回复」(2026-10-20 起:回答进窗口后把气泡删掉),⛔ 不再承载最终回答 —— 同一条消息上出现两个写者
(进度编辑 / 收尾回答)时,迟到的进度编辑会把回答盖回去,而且日志里看不出任何异常(2026-10-19 线上丢件的成因)。 - Telegram 限流(429)不丢答案:发消息这一层(
sendRich)照 Telegram 给的retry_after排队重发
(单次最多等 90s、一轮最多 3 分钟,等待总预算可用DSH_BOTPLUGIN_RATE_LIMIT_BUDGET_MS调整,默认 180000);
回答广播到 TG 失败时,整篇回答落盘到DSH_BOT_DIR/未送达/(可用DSH_BOTPLUGIN_UNDELIVERED_DIR指定别的目录),
同时把占位消息改成「❌ 回答未送达…已存到 <路径>」——旧版遇限流会留下永远的「⏳ 正在处理…」并丢掉答案。 - 进度消息只显示「正在做什么」(2026-10-18 口径,同日修订):正文里带
[进度] …标记的行,加工具的中文动作标签,
标签后面接具体在干什么——如「🏃 跑命令:grep -rn notes src/index.js」「🛠 改文件:服务.mjs」
(只留文件名,不带目录)。模型思考(reasoning 块)一律不上屏;口令类字面量自动打码
(PGPASSWORD=***、Bearer ***)。抠不出细节的工具只留标签、不编占位词;认不出的工具原样给个🔧 名字。
工具标签可用DSH_BOTPLUGIN_PROGRESS_TOOLS=0关掉,只留进度行。 - 进度占位消息的版式(2026-10-19 定):顶行 = 我最新说的那句话——它顶掉原来「⏳ 正在处理…」那几个字,
我还没开口时才显示那句占位语;⛔ 不显示已耗时。底下滚动的是操作(工具标签、📌 进度行,最近 3 条),
与顶行互不影响。只在我出新话(或新操作)时编辑一次,⛔ 不做定时刷新(旧版每 2s/4s 刷一次,
秒数一变就编辑,内容其实没变,白撞限流)。吐字速度小尾巴跟回答走,气泡上不重复显示。
上面顶行出全文、底下三条操作行照旧掐字(2026-10-25 先定「你的输出不要截断 120 字、出全文」,
同日再厘清「底下那三条操作提示不变」):顶行那句走顶行_MAX = 0=不掐(truncate(text, max)
见max ≤ 0即原样返回);📌 进度行与工具提示仍各自掐到 120/90/60/80/40 字
(操作行_MAX = 120+摘要()里那几个数)——两个常量各管一路:同日早些时候那次「一律出全文」
把操作行也放开了、版式被撑散,故拆开;要收更紧就改操作行_MAX与那几个数。
收尾不写那一行「✅ 已回复」(2026-10-20 起):回答进窗口后把气泡删掉;没送达才保留气泡并改标「未送达」。
收尾前先等「已经在飞」的那次进度编辑落地,⛔ 不让它回头盖掉进度文本。
等待上限 90 分钟(2026-10-20 起;进度哨兵已删)
- 为什么抬:原来是 30 分钟。
turnTimeoutMs一到就判「等待回复超时」,这一轮的答案当场丢掉,
主人只会看到一句❌ 等待回复超时(30 分钟)—— 白干一轮。老板 2026-10-20 定:抬到 90 分钟。 - 等久一点 ≠ 让你多等:它只是「最多等多久才放弃」的上限;活干几分钟就是几分钟。
- 配置:
turnTimeoutMs(毫秒,默认90 * 60 * 1000)。超时那句话跟着实际值现算
(runtime.js:等待回复超时(${Math.round(timeoutMs / 60000)} 分钟)),⛔ 不再写死「30 分钟」。 - 回退:改回
30 * 60 * 1000即回到旧行为。 - ⛔ 进度哨兵已删除(2026-10-20 老板定「哨兵不起作用,删除哨兵」):旧版在进程外放一个
哨兵.mjs,
每 20 分钟用主 bot 令牌代报一句「还在干活」。老板判定它不起作用,改走「抬等待上限」这一条路 ——
模块src/progress-sentinel.js、四项配置(sentinelEnabled/sentinelScript/sentinelStateFile/
sentinelIntervalMinutes)、自测件test-progress-sentinel.mjs,以及项目根的哨兵.mjs/哨兵状态.json
全部删除。回退办法:装回v1.1.2。
发版规矩(2026-10-19 定)
- 版本号末段最多到 99:第 100 版起直接进位 ——
1.0.99→1.1.1,⛔ 不再出现1.0.100这种末段三位数。 - 升版本号必须同一个 commit 里一起改三处:
package.json的version、package-lock.json(两处)、setupdsh.sh的BOTPLUGIN_SPEC默认 tag。本仓 pre-commit 闸(.githooks/发版对账.sh)会拦「只改一边」与「末段 > 99」。 - 发完把提交与
git tag v<版本>一起推到origin;装到本机用npm install github:aitcmhk-web/DSH-bot#v<版本>(⛔ 别用裸npm install:旧锁文件会把版本带回上一个 tag)。
许可
MIT License(全文见 LICENSE):可以自由使用、修改、再发布,保留版权声明即可;软件不提供任何担保。