DSH WorkBuddy Connect
corrinehu/dsh-workbuddy-connect将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness,在 DSH 对话窗口里零配置使用。
获取清华 MadModel 的 API token,按每 20 分钟在无窗口浏览器里用已保存的 SSO 会话续期,写入 DSH 凭据 MADMODEL_API_KEY 并创建或复用 madmodel 模型 Provider;手动点一次 Get 就通过校内登录窗口捕获该会话。
DSH / DeepSeek Harness 的 Cordis 插件:一键获取清华大学 MadModel 平台
(madmodel.cs.tsinghua.edu.cn)的最新 API token,自动写入 DSH 模型配置,
并支持到时自动续期。当前版本 0.3.24;更新内容见 CHANGELOG,
使用边界见 已知限制。
MADMODEL_API_KEY 并自动创建/复用模型 Provider
llm-pi-ai.providers.madmodel。手动点 Get 不会被"现有令牌还有效"短路:
自动路径都刷不出新令牌时它会直接打开登录窗口,并且这一次点击就够了 ——
登录完成后插件自己捕获、校验、写凭据、同步 Provider,无需再点第二次;
等待期间控件会写着「等待登录」。hh:mm(超过 99 小时停在 99:xx)。站点的免登录签发端点 /model-api/auth-login/check 已不再允许无凭证签发
(返回 10001 ticket已过期或无效,请重新登录);保存的 SSO 会话过期后,
/model-api/auth-login 会返回 10003 抱歉哦,您无此权限。插件因此按下面的顺序续期:
10001);localStorage,新拿到的 SSO cookie 也能直接换成令牌;
这条链需要浏览器算设备指纹时,插件用自己启动的那台无窗口浏览器补最后一步:
先把保存的会话按域/路径写进它,再让它访问登录入口 —— 会话不先交进去,那台浏览器
读到的永远只是登录页(这条以前是缺的,无窗口续期因此一直走不通);exp —— 同一张令牌回来时(会话没变,站点就返回原来那串)
不算刷新成功:状态显示「刷新未生效:到期时间未前进」,圆点转橙,而不是继续显示 ok。登录状态是怎么留住的:插件在登录窗口还开着时读取 cookie 与页面令牌,
将会话材料保存到本地,令牌写入 DSH 凭据服务。会话 cookie 通常不会由浏览器常规持久化;
插件恢复保存的 cookie 后能否继续使用,仍由服务端的过期、撤销与设备校验决定。
Cookie 按全量保存,请求时按目标 URL 逐跳筛选(域 + 路径 + secure + 未过期),
跨 id. / oauth. / auth*.tsinghua.edu.cn 与 madmodel 都会带上对应的一份。
由此:登录窗口用完就会自动关闭(成功捕获之后,或超时/你手动关掉/插件卸载时),
不需要你留着它 —— 但这也意味着续期靠的是那份保存下来的会话材料:
它过期之后,下一次就得再登录一次(点一次 Get 即可)。
partial
(「Token 已更新,但模型配置未同步:…」),而不是假装整体成功;id.tsinghua.edu.cn,带 ?/authLogin 回调,登录成功会跳回 madmodel)。默认挂在左侧边栏底部:「设置」按钮上方;若设置上方已有其他插件的按钮 (如 Remote),则置于最顶部按钮的上方。Get/Auto 在左、计时器在右, 与 DSH 主题一致。连「设置」按钮都找不到时暂不显示,并持续重试。
作为 DSH profile bundle(正式 npm 插件)安装,随 profile 声明同步:
dsh plugin --profile web add thu-tok-auto
然后重启 DSH Web(或对应 profile)使插件加载。也可手动在
~/.dsh/profiles/web/package.json 的 dependencies 与 dsh.profile.bundles
中加入 thu-tok-auto 后重新安装。
更新:
dsh plugin --profile web up thu-tok-auto
免登录签发在 2026-09-29 被站点关掉了(无凭据调用返回 10001),所以现在必须先登录过一次:
手上有仍可用的令牌就直接复用;自动路径都刷不出新令牌时,手动点 Get 会打开清华统一认证登录页,
登录完成后自动捕获并继续。后台 Auto 永不弹窗:无人看管时它只会如实显示「需要登录」,
不会在旁边自己弹出窗口。拿到同一张令牌时不会显示成功,而是如实标成「刷新未生效」。
| 层级 | 方式 | 说明 |
|---|---|---|
| 1 | GET /model-api/auth-login/check(用现有凭据) | 用已在凭据里的令牌直接换一枚新令牌 |
| 2 | SSO 重放 | 用保存的 cookie jar 走一遍统一认证链取 ticket;登录窗口捕获时也用它兜底(页面还没写 localStorage,cookie 已经能用了) |
| 3 | 复用已保存 token | 仅当旧 token 仍被 /model-api/auth-login 校验通过 |
| 4 | 打开登录窗口 | 只在你手动点 Get 时;一次点击即完成全部后续(捕获→校验→写→同步→关窗)。后台 Auto 永不弹窗 |
| 5 | 比 exp 判成败 | 拿回同一张令牌(会话未变)不算刷新成功,状态标成「刷新未生效」而不是 ok |
成功签发的 token 会同时写入:
credentials.set('MADMODEL_API_KEY', token)apiKeyEnv: MADMODEL_API_KEY,或 baseURL 指向 madmodel
(含本地转发 http://127.0.0.1:…/v1 的情形)找到目标 Provider;找不到则自动创建
llm-pi-ai.providers.madmodel(api: openai-completions)。写入走 DSH 的
settings.describe() + settings.update(),只合并目标的 apiKeyEnv 与 models,
不会把其他 Provider 复制成用户层覆盖,也不会抹掉同命名空间下的其他 Provider;
madmodel 这个键已被别的配置占用时宁可不写并报错,也不覆盖它。站点的模型会改名、下架、换档位(2026-09 就把 DeepSeek-V4-Flash-0731 换成了
DeepSeek-V4.1-Flash,旧名字现在请求直接返回"模型不存在")。插件不写死这份名单:
/v1/models 会落回 SPA),转成 provider 的 models
写进去;列表与现有配置一致时不写盘。结果缓存 6 小时,避免每次重复下载几 MB 的包。EXCLUDED_MODEL_IDS):DeepSeek-V4-Flash-Vision-Exp(V4.1-Flash
已覆盖图片输入)与 DeepSeek-R1-W8A8。contextWindow 统一写 1000000,取两个实测上限中较小的那个(2026-10-01 复测):
模型侧 1,048,576 prompt tokens 通过、再往上被拒(返回「服务器繁忙」);网关侧
client_max_body_size 8m,8,388,608 字节通过、8,388,609 被 413(折合英文约 134 万
token)——所以现在模型才是瓶颈。留约 4.8 万 token 给系统提示与工具定义。
(旧的 150000 来自 2026-10 之前那个 1 MiB 网关上限,已被站点抬高。)exp − iat == 6h,历史采样观察,并非站点保证)。
面板按本地时钟计算剩余寿命;若签发服务时钟比本地快约 9.6 分钟,显示值会
高估服务端的剩余寿命约 10 分钟。请保持系统时钟同步,并以服务端校验为准。exp(本地解码,不发请求),不再用「最近一次获取
时间 + 6h」估算:后者在「读回浏览器里那枚旧令牌」时会多报寿命——2026-10-01 实测到
令牌 14:06 签发、16:30 读回,面板按 16:30 + 6h 报,多出约 2.4 小时。/model-api/auth-login),结果记进状态;服务端
明确拒绝(401/403,或 200 且 success:false)时面板按「已失效」显示,而不是继续显示 ok。
核对问不出结果时(连不上、答非所问)不写判定:状态是 partial
(「未能向服务端校验:判定未知」),而不是把网络问题写成"服务端不接受该令牌"。now − 最近一次获取时间。01:00/00:20 也是宿主报上来的、不是界面猜的。--:--;超过 99 小时停在 99 小时不再增长
(这个字段是固定宽度的时钟,三位数会把这一行撑出侧栏)。~/.dsh/.credentials.yaml 里 MADMODEL_API_KEY 的值是 YAML 双引号折行标量:
行尾的 \ 是续行转义符,不属于值本身(真值 275 字符;按行拼起来会得到 278 字符并
带上反斜杠,服务端一律回 10003)。插件走 DSH 的凭据服务(credentials.resolve),
这一点是对的;任何脚本或文档示例要自己取这个值,请用 YAML 解析器,不要手动拼行或过滤字符。
2026-10-01 有会话因为这一步出错,把"服务端拒绝令牌"写进了文档,
事后更正。
/thu-tok-auto/api/* 每 10 秒轮询更新,
计时器本身在页面本地每秒走字。…/devtools/browser/<uuid>),用完(或超时、或你手动
关掉、或插件卸载)按这台实例的身份请求关闭,关闭后还要复探确认。
若 CDP 无法确认关闭,Windows 上可以用进程表核验启动记录:专属 profile、
可执行文件、记录的 pid 与启动时间,以及调试端口等证据必须匹配,才允许
taskkill /PID … /T /F 清理核验过的自有进程树;不会按 Chrome/Edge 进程名批量结束。
身份明确不匹配、证据不足或进程表不可读时拒绝回退,并如实报告未确认关闭。
可用 node lib/stop-owned.js --dry-run 先查看记录,再用
node lib/stop-owned.js 尝试清理自有残留。%USERPROFILE%\.dsh\madmodel\state.json
(cookies、ssoCookies、lastGetAt、auto;不再保存 token)。旧版状态中的 token
会迁移到 DSH 凭据存储,并从状态文件移除。%USERPROFILE%\.dsh\madmodel\profile\;从本机 CDP 端口
9333–9343 中选择未占用端口,只连接 URL 主机名严格匹配清华域的页面。MADMODEL_API_KEY,不会通过 HTTP 发给浏览器,
也不会出现在任何子进程命令行中。0.2.0-rc.2 上逐项核对 profile bundle(dsh.bundle.patch)、settings
(describe() / update();本插件不使用也不存在 settings.get())、
credentials(resolve / set)、timer、webServer(webserver/index-inject
注入表)、connection(requestRejection 判拒)与 llm-pi-ai
(providers 是 volatile 字段,写入即时热更新、无需重启)。npm run check:检查 Host/核心/UI 语法与补丁声明,并断言若干 DSH 契约
(例如任何文件都不得出现 settings.get(、站点包请求必须放大响应上限)。npm test:213 项行为回归(token 生命周期、Provider 写入与不覆盖、
Auto 退避与"后台永不弹窗"边界、登录捕获与一次点击全链、
一次点击后 Auto 只用存下来的会话换新 token 且 exp 必须前进、
登录成功那一次捕获必须自己留下运行记录(捕获臂在 runGetTok 返回后才结束,
它得在每种出口都记账;窗口已经交出令牌的那一次不再额外花掉会话去问将来——
没有谁能担保那次兑换的副作用)、
只关自己那个窗口(实例身份 + 关闭后复探确认)、
三态校验(连不上≠被拒绝)、断网≠令牌失效、同一张令牌不算续期、
未被校验或被拒的候选不覆盖还能用的凭据、
UI 文本层与注入清理、Host 请求校验边界、
版式按容器宽度分层(容器 34px ⇒ 收起态;容器 240px 而浏览器窗口只有 54px
仍保持整行——判据是父容器,不是 viewport)。
其中线路回归走的是生产路由:用面板真正发出的请求形状
(POST + application/json + {})打进 Host 注册的那条 get-tok,
断言这次运行以无参数启动(请求体不能把点击变成后台运行)、
并在续期通道下线时确实开出登录窗口;另有一条断言「上一次点击」的记录
不会被随后的 Auto tick 覆盖。发布前 prepack 自动执行。test/preview/:隔离预览工装(假状态、无真实凭据、不出网)。可离线渲染
九种状态的浅色/深色 × 宽(280/260/200)/收起(54)版式并量测几何、导出截图,
用于评审界面改动;仅开发使用,不随 npm 包发布。lib/core.js —— 核心逻辑(依赖注入,可独立单测):获取阶梯、凭据/配置写入、
状态迁移、Auto 判据、登录捕获编排;lib/index.js —— Cordis 插件入口:装配真实环境(Node fetch/WebSocket/fs、
DSH settings/credentials/timer/webServer/connection),提供
/thu-tok-auto/api/* 与 UI 注入;lib/ui.js —— 浏览器端全局脚本 UI。0.2.0 及更早版本为「DSH 会话内动态 Cordis 插件」形态(lib/host.js /
lib/client.js);0.2.1 起改为可直接安装的 profile bundle。行为与状态文件
路径保持一致,切换无需迁移。
MIT