dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
给 DeepSeek Harness 接的共生体(dsh-sym):实时会话花费(人民币,按厂商/模型与峰谷时段折算)、每轮费用、账户余额、峰谷时段标记,以及把任一回复作为上下文引用的 @ 按钮。名字取自 symbiote —— 附着在宿主上、持续长出能力,功能不限于计费。
给 DeepSeek Harness 的界面读数层。 实时花费、账户余额、峰谷时段、引用回复 —— 四件事直接叠在 DSH 原生界面上, 不新增面板,不打断工作流。
为什么叫 dsh-sym:sym 取自 symbiote(共生体) —— 就是毒液(Venom)的本体。
共生体的行为是附着在宿主身上、与宿主共生、把宿主的能力放大;这个名字记录的正是这个插件
和 DSH 的关系,也刻意不限定功能范围 —— 它会长出什么,不由名字预先决定。
| 图标 | 功能 | 出现位置 |
|---|---|---|
| 💵 | 会话花费 | 输入框状态栏末尾,两个人民币金额 |
| 👛 | 账户余额 | 输入框下方工具行,模型名左边 |
| 🕐 | 峰谷时段 | 品牌行 deepseek HARNESS 后面 |
@ | 引用回复 | 每条已完成回复的动作行 |
| ▮ | 快捷按钮条 | 侧栏与对话区之间的竖条,内容自己配 |
| ⚙ | 共生体设置 | 设置 → 插件 → 共生体设置 |
| ⑂ | Git 状态 | 会话头部标题行最前,显示当前分支与工作区改动,点分支名可切分支 |
这些地方互相独立:某一处读不到数据时它自己安静退场,不影响其余几处,也不影响 DSH 本身。
输入框下方状态栏的最后,多出两个人民币金额:
⏱ 8 轮 355 步 · 255 tok/s 🗄 125M tok · 缓存命中 99.8% 💵 ¥18.03 · ¥0.466 🔲 333M ⟳ 压缩 2 次 ◐ 61%
└ 本会话总计 └ 刻度选中那次任务 └ DSH 进程内存 └ 上下文压缩
--dsh-content-font-size-secondary)。计价覆盖 1046 个模型、42 家厂商,不只是 DeepSeek —— 详见计价规则。
在输入框下方工具行的右半段,模型选择器左边:
[+] [权限] [计划] [钱包] ¥123.45 [模型名 ▾] [活动] [发送]
ctx.remote.account,和「设置 → 账户」那张卡同一个官方接口、同一份凭据。$。位置是怎么落上去的:挂
conversation.input.right—— 它和模型选择器同在官方的standardControls容器里,官方渲染顺序就是「right 槽 → 模型选择器」,所以余额天然落在 模型名正左边,不需要任何 CSS 去推它。它不改造官方布局:没有
position: fixed、没有量 DOM、没有重试定时器。早先的版本把 自己注册在会话头部槽里、再用 fixed 把像素画到侧栏底部,还配了一条 CSS 把官方 footer 从竖排改成横排 —— 那条规则帮不上脱流的 fixed 元素,唯一的实际效果是把官方账户行推到 了右侧,已删除。
品牌行后面跟着一个状态标记,一眼就能看出现在贵还是便宜:
悬停显示规则原文。
刷新时机:定时器直接算到下一个边界,一天只在固定时刻触发 5 次
(北京时间 09:00、12:00、14:00、18:00、00:00),在边界后约半秒翻转。
这是单次定时器,不是轮询 —— 一天 5 次,比任何固定间隔轮询都省, 而且边界一到就变。窗口重新获得焦点、或从后台切回时还会立即重新对时: 浏览器会节流后台标签页的定时器,只靠定时器的话,从睡眠唤醒后标记可能还是旧的。
实现方式:品牌行是官方元素,没有加法插槽。本插件用 CSS 给它的
::after接了个标签,内容从根上的 CSS 变量--dsh-peak-label读,颜色由data-dsh-peak决定。 没有替换任何官方组件;卸载后属性、变量和样式一起消失。
回复里的文件链接(写成 [短名](/绝对/路径) 的 Markdown 链接)除点击打开外,
右键会弹出一个菜单:
session.openWorkspacePath 的 reveal 动作只在文件链接上接管右键,其他位置的系统菜单原样保留。菜单样式沿用官方
弹出层的变量(--dsw-specific-menu / --dsw-elevation-prominent)。
顺带一条使用约定:给模型看的路径写成反引号(只读),给人点的写成 Markdown 链接。裸写路径虽然 DSH 也会自动转成链接,但显示出来是一长串 URL,比链接难看。
每条已完成的回复,动作行里多一个 @(在 👍👎 之后)。点一下,输入框里只出现一个短标记:
@引用#9806056fac67
你接着写新任务、发送即可 —— 模型读到的是那条回复的完整原文,不是这个标记。
点完 @ 光标仍在输入框里(标记落在原来的插入点之后),不用再点一下输入框。
怎么做到的:标记里只有消息 id 的前 12 位,替换发生在发送时、宿主侧:
session/event,把每条已完成的回复按 message id 记进一张有界索引
(只留最近 400 条);agent/pre-step 钩子扫描即将发送的消息,找到
@引用#xxxxxxxxxxxx,从索引取出原文,替换成 Markdown 引用块(每行前缀 >);思路和 DSH 自己注入会话快照(dsh-session-reference)的方式一致。整个链路都在本地,不联网。
几个边界:
text 内容块按顺序拼接;推理内容和工具调用不含在内 ——
引用的是结论,不是过程(否则一次带界面操作的回复会拖进去几万字的快照)。✓;输入框不可用(拿不到插入点)显示 !,把光标放进输入框再点一次。为什么不用 DSH 原生的
@引用:DSH 的引用语法@[label](dsh-session:<id>)是会话级的, 最小粒度就是整个会话,没有「引用某一条消息」。所以要精确引用「中途那一次回复」, 只能用一个自定义标记,再由宿主在发送时展开。
侧栏与对话区之间有一条竖排的图形按钮。点一下,按钮里的内容就填进输入框, 而且光标留在输入框里,接着写就行:
+ 菜单里选中同一条命令完全一致:
有参数的命令(目标、计划)在草稿里落成蓝色命令 chip 并提示输入参数;没有参数的命令
(压缩上下文、权限)点一下直接执行。/技能名 。末尾那个空格是关键:客户端把「命令 + 空格」认作
"指令行、开始收参数",和你在 / 菜单里选中一条命令后的状态一致。三类在竖条上分组排布,组间有分割线,图标统一用中性的次级文字色 —— 不按类型着色: 一排小图标各染一色会显得吵,也把颜色从"承担语义"降格成装饰。分组靠位置 + 分割线 表达:顺序固定「官方命令 → 技能 → 提示词」,同一类永远挨在一起。
默认 7 个按钮:目标、计划、压缩上下文,加四个预设提示词(审查改动、跑一遍回归、 解释报错、写提交信息)。内容全部在 共生体设置里改。
"和
+菜单一样"是怎么做到的:不是插件自己拼出那个蓝色 chip(那确实拼不出来), 而是把这一次选择交回官方 —— 调用官方/菜单自己的 pick 决策表 (commandUi.dispatch),拿到结果后按官方菜单点击的同一条路径把 claim 交给会话输入框。 所以 chip 的外观、参数提示、以及无参数命令的执行方式,都是官方那一份,不是仿的。 走不通时(官方内部接口变了、目录还没就绪)会降级:有参数的命令填进草稿等你按回车, 没有参数的命令走宿主命令通道直接执行 —— 不会出现"点了没反应"。
为什么它挂在侧栏竖缝里而不是输入框上方:DSH 在输入框那一带留的槽是 会话级、会被替换的 —— 交互式问卷或审批一弹出,官方就把输入框整块换成别的内容, 挂在上面的东西会整条消失。所以竖条改挂常驻槽(会话标题栏那一排), 再用
fixed定位到侧栏右缝。代价是位置得自己量 DOM(_sidebarCol), 好处是问卷、审批、切换视图时它都在。另外它外面包了错误边界:渲染期抛错会被 React 静默卸载,界面上表现为"什么都没有",所以错误还会被存下来,在设置页里看得到。
设置 → 插件 → 共生体设置,三件事都在这一页:
/ 菜单里那 8 个排在最前)、
改类型(改了自动归到新组)、恢复默认。保存时会告诉你保存了几条、跳过了哪些空内容按钮。配置存在浏览器本地存储里,不写进 DSH 的配置文件;保存后立即生效,不用重启 App。
会话头部标题行最前(「对话 / 轨迹」那一行的正上方)常驻一枚读数:
⑂ main +346 −1,105 ?3
+N −M —— 工作区相对 HEAD 的增删行数;?N 是未跟踪文件数。git switch,成功后读数立刻刷新(只有一个分支时它是普通文字,不给没用的按钮)。
工作区改动会被覆盖时 git 会拒绝,菜单里原样显示它的解释;插件不用 -f、
--discard-changes、stash、reset —— 宁可不切,也不丢你的改动。GET 读数 + POST 切分支),客户端每 2 秒轮询一次、页面隐藏时停表、
切回前台/窗口聚焦/智能体一轮刚结束时立刻重读;
契约与字段清单见 AGENTS.md 的「公开契约」。侧栏每个会话行悬停时的「⋯」菜单,在官方四项(置顶 / 重命名 / 派生 / 归档)下方多出一组 (用一条分隔线隔开):
008cd494-8461-42e0-b050-3b71fa903448 这样的 UUID)放进剪贴板。
这是宿主给会话落盘用的名字,贴进命令行、日志、issue 都直接可用。复制的是裸 ID,不带标题。
复制失败时菜单不关,该行自己改成「复制失败,请手动选取」—— 不制造"点了但剪贴板是空的"。dsh-session-log-export 负责下载端点、预检、进度弹窗与并发折叠,这里只是多开一个入口,
所以下到的 ZIP 内容、文件名、弹窗反馈完全一致。正在下载时该行禁用并显示「正在导出…」。两条都是加法:官方布局与官方四项一个像素都不变。没装导出包时导出项不注册(不留空行), 复制项照常可用;官方 primitives 缺失时两项都不注册。
这是一个 DSH 组合包(bundle):装进来之后,profile 会把它自带的 cordis.patch.yml
合并进自己的 cordis 配置树,无需手工改配置。
# 从 GitHub 装(推荐)
dsh plugin --profile desktop add github:seeseeczl/dsh-sym
# 从 Release 附件里的 tarball
dsh plugin --profile desktop add /绝对路径/dsh-sym-<版本>.tgz
# 从本地目录
dsh plugin --profile desktop add /绝对路径/dsh-sym
本包没有发布到 npm:它是零依赖、无构建步骤的纯 JavaScript,从 git 或 tarball 安装与从 npm 安装没有区别。npm 上确实有一个叫
dsh-hud的同类包(另一个作者的项目), 与本项目无关。
也可以在 GUI 里走「设置 → 插件 → 安装」。
不装进 node_modules,直接让 profile 的 cordis.patch.yml 指向本地文件:
- id: sym-cost
name: 'file:///绝对路径/dsh-sym/lib/host-v19.js'
再在同一个文件末尾确保它是启用的:
- id: sym-cost
disabled: false
停用即可(GUI 里关掉,或在 patch 里写 disabled: true),然后删掉包。
本插件不会修改 profile 里任何其它条目。
| 模型 | cache hit | cache miss | output |
|---|---|---|---|
deepseek-flash | 0.04 | 2 | 8 |
deepseek-v4-flash | 0.04 | 2 | 8 |
deepseek-v4-flash-vision-exp | 0.04 | 2 | 8 |
deepseek-v4-pro | 0.30 | 9 | 27 |
谷时(空闲时段)价格 = 表中数字 × 0.5。
以北京时间为准:
09:00–12:00 与 14:00–18:00,法定节假日除外节假日表在 lib/prices.json 的 holidays 字段里,格式 YYYY-MM-DD,按年维护。
DSH 内置了一份 pi-ai 价目目录(42 家厂商、1046 个模型,美元计价,含 cacheRead/cacheWrite)。
当会话用的不是 DeepSeek 模型时,本插件按下面的顺序找价格:
lib/prices.json 的 models 覆盖 —— 你手工写的价目,优先级最高,改完存盘即生效。命中 2 或 3 时,美元价按 usdToCny 折算成人民币。
认不出的模型不会被计费,只会在悬停账单里标出来(unpriced)—— 宁可少算,也不瞎算。
编辑 lib/prices.json:
{
"usdToCny": 7,
"holidays": ["2026-01-01", "..."],
"models": {
"some-model": { "input": 1.5, "output": 6, "cacheRead": 0.15, "cacheWrite": 2 }
}
}
usdToCny —— 美元折算汇率holidays —— 法定节假日(峰谷判断用)models —— 覆盖价目;键是模型名,值是美元 / 每百万 token_readme 字段带着中文字段说明,不用另查文档本插件由两个半边组成,各自跑在不同的进程里:
┌─ 宿主(Electron 主进程,Cordis 插件树) ─────────────────────┐
│ lib/host-v19.js │
│ • sessionProjections 注册 sessionCost —— 会话事件的纯折叠 │
│ • 折叠 request/header、assistant/message、llm/retry-started │
│ • 只存 token 数(按峰/谷、按轮次、按模型分桶),不存金额 │
│ • 价目:DeepSeek → pi-ai 目录 → prices.json 覆盖 │
│ • 监听 session/event 维护 messageId → 正文索引 │
│ • 监听 agent/pre-step 展开引用短标记 │
└──────────────────────────────────────────────────────────────┘
↓ 投影(纯 JSON)
┌─ 浏览器(渲染进程,客户端插件) ─────────────────────────────┐
│ lib/client.js │
│ • conversation.composer.dock → 两个费用金额 │
│ • conversation.input.right → DeepSeek 余额 │
│ • conversation.chat.assistant-actions → @ 引用按钮 │
│ • 根上的 CSS 变量 + data 属性 → 品牌行的峰谷标记 │
└──────────────────────────────────────────────────────────────┘
为什么金额在客户端算、token 在宿主算:宿主只折叠 token 数(可序列化、可 checkpoint、
跨重启一致),价格表可能被 prices.json 随时改。客户端每次渲染时用当前价目重新折算,
所以改价格不需要重算历史、也不需要重启。
投影是纯折叠:sessionCost 对每个已提交的会话事件做一次 apply,不产生副作用。
会话恢复时从事件重放,结果一致;stateVersion 变化时注册表会拒绝旧 checkpoint 并重建。
界面上的东西在设置页里改:设置 → 插件 → 共生体设置(见第 7 节)。 显示哪些读数、快捷按钮条上有哪些按钮、每个按钮长什么样,都在那里;配置存在浏览器 本地存储里,保存立即生效,并且可以导出成 JSON 带走。
装好即用:不做任何设置也能正常工作(默认显示花费、余额、内存,竖条上默认是 3 个官方命令
仓库里唯一的配置文件是 lib/prices.json(见上)。
宿主半边改动(lib/host-v19.js)需要重启 App;客户端半边(lib/client.js)和
lib/prices.json 都是热生效的。
lib/host-v19.js 宿主半边:投影折叠 + 价目来源 + 引用展开
lib/client.js 客户端半边:各处读数 + 快捷按钮条 + 设置页 + 峰谷标记
lib/prices.json 价目覆盖 / 汇率 / 节假日
cordis.patch.yml 组合包补丁(让 profile 一次性装好)
test/ 仓库内回归(npm test,57 条,零依赖)
scripts/ 开发脚本(宿主换名助手 reload-host.mjs)
两个半边都是零依赖的纯 JavaScript(ESM),没有构建步骤,改完直接生效。
| 改动 | 生效方式 |
|---|---|
lib/client.js | 客户端插件热更新,页面自动重载该模块 |
lib/prices.json | 立即生效(宿主按 mtime 检测) |
lib/host-v19.js | 需要重启 App,或用「停用 → 换文件名 → 启用」绕开模块缓存 |
宿主半边改动之所以麻烦,是因为 DSH 的宿主热重载只监听配置与补丁文件,不监听插件代码。
换一个新的文件名(host-v12.js → host-v19.js)能拿到一个全新的模块实例,但必须等旧实例
完成 dispose,否则新旧注册会撞在一起。换名与同步引用已脚本化:
node scripts/reload-host.mjs --dry-run # 先看会改哪些文件与行
node scripts/reload-host.mjs --apply # 真改;停用/启用与重启仍由人完成
仓库内有回归,入口是 npm test(等价于 node --test,不要写成 node --test test/,
Node 24 会把目录当模块解析而失败):
| 文件 | 覆盖 |
|---|---|
test/host.test.mjs | 峰时边界、周末/节假日、引用展开与未知 id、索引淘汰、价目常量、内存读数、失败路径 |
test/client.test.mjs | describeScope 账单文本(峰谷拆分、谷时省钱、未收录模型、flat 厂商)、插槽注册幂等、降级日志 |
test/contracts.test.mjs | 跨端契约常量两端一致,客户端写出的引用标记宿主能展开,缓存省下文案不回流 |
回归只覆盖纯函数:渲染、插槽注册的实际效果、热更新仍要人工验证。开发时的其余做法:
test/helpers/load-client.mjs 用最小的 React hooks 替身物化模块工厂
(不引入 jsdom);要断言真实渲染仍需 CDP 驱动一个 headless 页面点击真实按钮curl 取页面里 plugins/??dsh-sym/client.js 的 bundle,确认各项注册都在[dsh-sym],每个来源只记一条,能看到是哪个可选能力缺席了—,不会显示 0;格子始终占位 —— 它忽隐忽现会推动旁边的模型选择器。holidays)。docs/01-architecture/adr-003-session-move-not-wired.md 作为设计记录,
不是已交付能力(ADR-003)。standardControls 容器隐藏,余额跟着模型选择器一起隐藏(它俩同进同退);没装那个实验 bundle 时永不隐藏。