dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
把本机已登录的豆包工作(DoubaoWork)客户端接入 DeepSeek Harness:通过客户端 DevTools 端点复用登录态调用 /chat/completion,并在输入框显示 5 小时 / 7 天额度。
把本机已经登录的豆包工作(DoubaoWork)客户端变成 DeepSeek Harness 里的一个模型 provider:
不用另外配账号、不用 API Key,直接借客户端自己的登录态调它自己的 /chat/completion。
装好并重启 DSH 后,模型选择器里会多出一个 「豆包工作」 分组,含三个模型:
| 模型 id | 显示名 | 说明 |
|---|---|---|
doubao | 豆包 2.1(快速) | 不思考,最快 |
doubao-think | 豆包 2.1(思考) | 默认一档思考,可由 DSH 的 reasoningEffort 调整 |
doubao-expert | 豆包 2.1(专家) | 默认最高档思考,最强 |
思考档位:三个模型都会把 DSH 传来的 reasoning_effort 映射到豆包的
need_deep_think(off→0、minimal/low→1、medium→2、high/xhigh/max→3);
不传时按模型 id 取默认值(doubao→0、doubao-think→1、doubao-expert→3)。
模型 id 里的档位只是默认值,实际请求以 DSH 的选择为准。
工具调用:豆包的客户端协议没有 OpenAI 的 tools 字段,所以插件用提示词模拟——
把工具目录注入系统指令,要求模型输出 <tool_call>{"name":…,"arguments":{…}}</tool_call>,
小门再把这段标记解析成标准 tool_calls(流式按 chunk 增量、跨 chunk 拼接)
并给出 finish_reason: "tool_calls"。这是尽力兼容,不是原生 function calling:
模型不一定每次都老实输出标记,复杂 schema 的遵循度不如真原生协议。
DSH 判断一个插件「存在」有三个彼此独立的机制,本地开发的插件三样都要做,缺一样就表现为 「我明明写了插件,DSH 里却没有」:
| 步骤 | 做什么 | 缺了会怎样 |
|---|---|---|
① 插件目录里的 node_modules 软链 | 让插件能 import 宿主包(@deepseek-ai/dsh-llm 等) | 启动时报 ERR_MODULE_NOT_FOUND |
② profile 的 node_modules junction | 让宿主能 resolve 到这个包 | 包根本找不到 |
③ dsh.profile.bundles 登记 | 启动时才会组装它的 cordis.patch.yml | 能 resolve,但 apply() 永远不被调用 |
④ dependencies 里写 link: | 只影响市场「已安装」列表是否显示 | 插件能跑,但 UI 里查无此插件 |
install.mjs 就是把 ①–④ 一次做完的脚本。手抄任何一步都会重现「装不上」的现象。
node install.mjs
脚本做的事:建插件内 4 个宿主包软链 → 在 <profile>/node_modules 建 junction →
把包名加进 dsh.profile.bundles → 在 dependencies 写 link:<本目录> → 备份一份
package.json.<插件名>.bak。
装到哪个 profile:默认挑「装了 dsh-connect-workbuddy/dsh-connect-trae 的那个,
否则名字以 desktop 结尾的,否则第一个」。要装到别的 profile(例如网页端用的 web)
必须显式指定:
node install.mjs --profile web
0.1.1 修正:此前
resolveProfile()只按目录名猜,永远选 desktop,导致 直接跑node install.mjs装不进网页端 profile——网页端因此表现为插件完全不存在 (/plugins/dsh-doubao-work-connect/status直接 404)。现在支持--profile。
装完重启 DSH(Electron 应用,不是这个 CLI)。
本机实测:profile 的
package.json变更后宿主会热加载——不重启,22:25:08 时127.0.0.1:30812小门就自己起来了(宿主进程 21:16:28 启动,未重启)。 但热加载不是契约,重启一次最稳。
回滚:
node install.mjs --remove
Windows 上的豆包工作桌面客户端(DoubaoWork.exe),且已登录。
客户端需要带调试口启动:DoubaoWork.exe --remote-debugging-port=30813。
Chromium 只在启动时接受这个参数,所以插件发现「客户端在跑但调试口不通」时不会替你重启客户端
(autoRestartDoubao 默认 false,见下),而是报错并在错误信息里提示你:自己关掉豆包工作,
再用 D:\Program Files\DoubaoWork\app\DoubaoWork.exe --remote-debugging-port=30813 启动它
(注意根目录的 DoubaoWork.exe 只是启动器,给它传参不会传给主程序)。
破坏性默认值变更(0.1.3):
autoRestartDoubao的默认值由true改为false。 旧默认值意味着插件会主动taskkill /F掉你正在用的豆包客户端,打断你手上的对话; 现在默认不动你的进程。要恢复旧行为需显式设autoRestartDoubao: true, 那会强杀你正在使用的客户端。
网络能直连 www.doubao.com。
127.0.0.1:30813,用 Storage.getCookies 拿登录 Cookie,
在页面里求值取 localStorage 的 device_id / web_id / s_v_web_id。
磁盘上的 Cookie 库被客户端独占锁定,这条路走不通。window.bdms.frontierSign({ url }),
拿回 X-Bogus 值,作为 query 参数拼到 /chat/completion?_signature… 上。
不重写反爬算法,客户端升级也不用改插件。www.doubao.com/chat/completion 读 SSE。必须丢弃 FULL_MSG_NOTIFY
回声帧(那是自己发出的消息),否则答案开头会重复用户原话。127.0.0.1:30812 起一个 OpenAI 兼容小门(只认真 Bearer,
只认 loopback 的 Host/Origin),再用 ctx.llm.registerAdapter 把 doubao-work
这个 provider 挂到 ctx.llm 上。DSH 的模型目录是现算的,注册完立刻可见。/alice/commerce/sale/subscription/quota/summary/,把 5 小时 / 7 天两个滚动窗口
折算成百分比。宿主侧经 /plugins/dsh-doubao-work-connect/quota 暴露给浏览器半,
由输入框左侧的小胶囊显示。端口:调试口 30813 沿用上游参考仓库的默认值;小门 30812 是本插件自选(避开 30813)。
两者都可配置,小门被占用时会自动退回随机端口。
多开注意:桌面端和网页端 DSH 若同时运行、都装了本插件,它们会抢同一个调试口
30813、并争着用taskkill重启同一个豆包客户端。小门端口冲突会自动退回随机 端口,但调试口没有隔离机制。要么只在一个 profile 里启用,要么给其中一个显式配置 不同的debugPort——不过豆包客户端本身只会开一个调试口,所以实际上只能二选一。
都可选,改 profile 里的插件配置即可:
| 键 | 默认 | 说明 |
|---|---|---|
shimPort | 30812 | 小门监听端口 |
debugPort | 30813 | 客户端调试口 |
doubaoExePath | 自动探测 | DoubaoWork.exe 路径 |
autoRestartDoubao | false | 调试口不通时自动 taskkill /F 重启客户端一次。默认关(0.1.3 起);打开会打断你正在用的客户端 |
reasoningBlockTypes | [10040] | block_type 归类为「思考」的白名单。默认 [10040] —— 客户端自己枚举里的 BLOCK_THINKING(「思考过程」,证据见 docs/protocol-notes.md §7.2)。写 [] 可退回「全部当正文」的旧行为 |
autoDeleteConversation | true | 每轮答完删掉豆包侧临时会话(即用即焚) |
输入框左侧会有一个「豆包额度」胶囊,鼠标悬停展开明细:套餐名、每个窗口的
已用百分比与 已用/总量、以及窗口重置时间。点一下立即刷新。
modelSelection 投影判定,换成别的 provider 胶囊就不渲染,也不会有轮询。
宿主太旧、不提供该投影时退回「一直显示」。对应的宿主路由(只接受来自本机回环地址的请求,供浏览器半读取额度):
GET /plugins/dsh-doubao-work-connect/quota
→ 200 {state:"ok", clientRunning:true, plan, personal:{windows:[…]}, …}
→ 200 {state:"ok", clientRunning:false, …} 客户端关着,数字来自登录缓存
→ 200 {state:"app-not-running"} 客户端没开且本机没有登录缓存
→ 202 {state:"loading"} 首次解析还没完成,稍后重试
说明(0.1.1 修正):文档此前写「需要 GUI 会话的 bearer」,实际实现只校验 Host/Origin 是否回环,不要求 Authorization。实测无凭据也能拿到 200。 这类本机路由无法区分「浏览器半」与「本机其他进程」,所以请把它当作 本机可见的只读接口,不要假设它有鉴权。
豆包工作客户端关着时,插件照样能查额度、也能正常对话:
<DSH_HOME>/dsh-doubao-work-connect/auth-cache.json;X-Bogus——签名只能由活着的页面算,而客户端自己的请求本来也不带它。
实测(2026-10-21,客户端已退出):额度正常返回,对话「7 乘 6」返回 "42"。需要知道的三件事:
auth-cache.json 是凭据,不是配置。读到它等于能借用该账号的豆包额度,
和客户端自己把 Cookie 存在 User Data 里性质相同。别外传、别提交进仓库;
删掉它即可让离线模式失效(下次开客户端会重新生成)。710012001 时插件会同时清掉内存与落盘凭据,
这时需要打开客户端重新登录一次。--remote-debugging-port 启动):因为
autoRestartDoubao 默认关闭,插件不会杀它;此时会走缓存而不是现场读,
所以拿到的是上次的登录态。要刷新就自己带参数重启客户端一次。说明(0.1.1 修正):文档此前写「需要 GUI 会话的 bearer」,实际实现只校验 Host/Origin 是否回环,不要求 Authorization。实测无凭据也能拿到 200。 这类本机路由无法区分「浏览器半」与「本机其他进程」,所以请把它当作 本机可见的只读接口,不要假设它有鉴权。
两套脚本,都不改任何配置:
node test/smoke.mjs # 离线:假 ctx 走一遍 apply(),验注册/模型目录/小门鉴权。不需要豆包、不需要网络。
node test/live.mjs # 真机:小门可达 → 模型列表 → 非流式一轮 → 流式一轮。需要豆包已登录 + DSH 已加载本插件。
live.mjs 可带参:--port 30812、--model doubao-think。
也可以直接打小门(PowerShell 里 curl 是 Invoke-WebRequest 的别名,要写 curl.exe)。
令牌是每次启动随机生成的,不再是源码里的固定字符串;它在 DSH 启动插件时写到
<DSH_HOME>/dsh-doubao-work-connect/shim-token.txt,即本机的
~/.dsh/dsh-doubao-work-connect/shim-token.txt(插件卸载时删除)。
关于权限(0.1.3 更正):此前文档写这个文件是「0600」。实际上 Windows 上 Node 的
mode对文件权限是 no-op,写盘时传的权限位不生效;文件的实际权限跟随用户目录(~/.dsh)的 ACL。 早期 0.1.1 条目里「(0600,卸载即删)」的写法沿用了这个误述。这不影响它是机密这件事—— 读到它等于能借用你的豆包额度,别往外传、别提交进仓库。
$token = Get-Content "$env:USERPROFILE\.dsh\dsh-doubao-work-connect\shim-token.txt"
curl.exe -H "Authorization: Bearer $token" http://127.0.0.1:30812/v1/models
不带 Authorization 会得到 401,这是设计:小门只服务宿主进程,不是公开端口。
0.1.1 修正:此前小门令牌是写死在源码里的常量
doubao-connect-local,等于没有 密钥——任何本机进程读到这个公开仓库就能借用你的豆包额度。现在改为每进程随机。
插件还注册了两条 host 路由,可以用它们确认插件真的被宿主加载了:
GET /plugins/dsh-doubao-work-connect/status → {state, debugPort, userId, deviceId, webId, cookieCount}
GET /plugins/dsh-doubao-work-connect/quota → 见上节
在本机 DSH 0.2.1-rc.2 上实测这两条都是 200(宿主会转发插件的 host 路由;早期笔记里
「返回 404」的结论是在旧端口上测的,已作废)。status 返回 state:"signed-in" 即说明
CDP 连通、Cookie 与签名都拿到了。
/plugins/.../status 能回答一半问题——它 404 只说明「路由没挂上」,但不区分「插件没加载」
和「宿主没转发」。0.1.3 起多了一个更直接的判据:apply() 注册成功后会在
<DSH_HOME>/dsh-doubao-work-connect/host-heartbeat.json 写一份心跳,
字段为 version / package / pluginVersion / pid / startedAt / updatedAt / shimPort / debugPort,
每 30 秒刷新一次 updatedAt(卸载时只在 pid 属于自己时才删除):
Get-Content "$env:USERPROFILE\.dsh\dsh-doubao-work-connect\host-heartbeat.json"
看到 updatedAt 时间在往前走,就说明宿主确实加载并 apply 了本插件;
文件不存在或 updatedAt 停住,则插件没起来、或者进程已经死了。
注意:桌面端与网页端 DSH 会共用这一个文件(谁后写谁赢), 所以它一次只能反映最后写的那一端,不能证明"只有一端在跑"。 文件里的
shimPort是那一端的实际监听端口;想知道两端各在什么端口上跑, 更可靠的办法是看数据目录里的shim-token-<端口>.txt——每个活着的小门都会留一份自己的 (见「双端同时启用」一节)。
upstream.js 的 FALLBACK_MODELS),豆包出新模型要手动加。block_type → 思考/正文的映射还没被真机帧印证(0.1.4)。分类口径本身已不再是猜测:
它是从豆包工作客户端自己安装的 bundle里读到的(枚举 BLOCK_THINKING:10040、
元数据表把 10040 | thinking_block.scene:* 标注为「思考过程」、思考块被改写成
custom_type:thread_thinking_node),因此默认值定为 [10040],并据此修掉了两个载体问题
(思考文本在 content.thinking_block.text、增量文本走 CHUNK_DELTA 且归属上一帧声明的块)。
证据链见 docs/protocol-notes.md §7.2。
仍然缺的是端到端印证:取证时该账号对 /chat/completion 一律返回 710022004
(连豆包客户端自己发出的请求也一样被拒),拿不到一条带正文/思考的真实帧。
如果事后实测发现口径不对,把 reasoningBlockTypes 配成 [] 即可一键退回「全部当正文」。messages 决定,插件侧不保存任何会话状态;
送上游前会按 HISTORY_MAX_CHARS(48000 字符)从最老的消息开始裁剪。max_tokens 由插件侧按字符预算近似执行(不是上游硬截断),finish_reason
会相应给出 "length";temperature 会透传,但豆包是否采纳取决于其服务端。本项目仅供个人学习与研究使用,只驱动使用者本人在本机已登录的豆包账号。 因使用本项目产生的任何后果由使用者自行承担。本项目与豆包、字节跳动、 DeepSeek 均无关联,未获其授权或认可。
本项目的代码骨架(upstream.js 与 credentials.js 的 CDP / 签名 / SSE 部分)
来自 marsma-101/dsh-doubao-connect(MIT),
其中 upstream.js 与本机所取的上游版本逐字节一致;credentials.js、index.js、
cordis.patch.yml 做了针对豆包工作(DoubaoWork.exe、doubaowork-chat 页面、
provider id doubao-work)的改造。因此 LICENSE 里保留的是上游的版权行
Copyright (c) 2026 marsma-101——这是 MIT 的要求,不是笔误。
结论:不需要二选一,两端的端口隔离是自动的。 下面全部是 2026-10-20 的双端实测值
(桌面端 DSH 与 dsh web 同时运行、同一个 ~/.dsh、同一个豆包客户端):
| 项 | 桌面端 DSH | 网页端 DSH | 是否冲突 |
|---|---|---|---|
| host Web 端口 | 2646(动态分配,本次实例还有 2647/2648) | 8080 | 否 |
| 小门监听端口 | 30812 | 7112 | 否——30812 被桌面端先占,网页端 EADDRINUSE 后自动回落随机端口 |
| 小门令牌文件 | shim-token-30812.txt | shim-token-7112.txt | 否——按端口分开 |
| 豆包调试口 | 30813 | 30813 | 否——两端都只做只读 CDP(读 Cookie / 调签名),不独占该端口 |
/plugins/.../status | 200 | 200 | — |
跨端鉴权实测:拿桌面端的令牌敲 127.0.0.1:7112/healthz 得 401,反过来同样是 401;
各自用自己那份令牌都是 200。也就是说两端的小门是彼此独立的,不会互相借用额度。
几点必须知道的细节:
shim-token.txt、shim-token.pid、host-heartbeat.json
在两端之间是同一份。所以 0.1.3 起每个小门额外写一份 shim-token-<实际端口>.txt,
心跳里的 shimPort 记的也是实际监听端口(不是配置里的 30812)——
否则双端同时启用时,配置值会让人误以为"只有一端在跑"。test/live.mjs 请带 --port:node test/live.mjs --port 7112。脚本会优先读
shim-token-7112.txt;不加 --port 时默认 30812、拿桌面端的令牌去敲,正好敲对。autoRestartDoubao 默认已改为 false:豆包在跑但没开调试口时,插件不会再
taskkill /F 掉它,而是报错提示你自己带 --remote-debugging-port=30813 重启。
双端同时启用时这一点尤其重要——否则杀掉客户端会同时打断两端。