dsh-session-progress
大鲸鱼在线写插件 · 让鲸鱼去监督鲸鱼:在 DSH 侧栏每条会话行上铺一条半透明进度带,每 30 秒用一个轻量模型读上下文估进度;可选监督模型、可调思考强度、可改颜色与样式,左侧栏还能看到判断模型的每一次原始输出。
- Stars
- 2
- Language
- JavaScript
- Created
- Oct 24, 2026
- Updated
- Oct 26, 2026
Introduction
大鲸鱼在线写插件
dsh-session-progress
你是不是还在为了不知道大肥鱼在吃多少白饭,到底干活干到哪了而烦恼?别担心!让我来助你!全新进度条让你实时了解大鲸鱼在干嘛!将进度条接入大模型注入上下文,让鲸鱼去监督鲸鱼!可选监督大模型、可调思考强度、可变进度条颜色、可变进度条样式,懂鱼更懂你!

一条会自己爬的色带,长在 DSH 侧栏的每一行会话上。它不催你,也不打断任何东西——只是有个便宜的小模型每 30 秒瞄一眼鲸鱼在干嘛,然后把答案画成一行像素。
先说清楚它是什么
AI 干活最难受的地方不是慢,是不知道它在哪。
一轮跑了二十分钟,你只能盯着最后一个工具调用猜:是卡住了,是在跑测试,还是已经快好了?点进去翻日志,翻完发现它只是在等一个超时。
这个插件把那个答案变成侧栏上一条会动的东西。你刷手机回来,扫一眼色带的位置,就知道该继续等还是该进去看看。
它和普通的进度条不一样在三件事:
- 色带铺在整行底下,不是另一根独立的条。标题、时间、悬停按钮照常可读可点——一个 DOM 节点都没往行里插,用的是
::after和两个 CSS 变量。 - 判断的也是模型。真正干活的那个没空汇报,就派一个最轻的读一遍当前轮次的上下文,让它说「大概到 62% 了」。提示里带着它自己上一次给的数字和阶段名,所以卡住了它会往下调,不会为了好看一路抬到 95。
- 它自己在花钱。每 30 秒一次小调用,约 1–2k 输入 token,默认最多同时盯 3 个会话。这笔钱单独记账,面板顶栏能看到判断模型累计吃掉了多少 token——你主对话的账单不受影响。
装
dsh plugin --profile desktop add github:Yu-jianhai/dsh-session-progress
然后完全退出 DSH 再打开。hmr 在我们这台机器上是关的,只刷新窗口不会加载新代码。
想锁版本就跟 commit:
dsh plugin --profile desktop add github:Yu-jianhai/dsh-session-progress#<commit-sha>
纯 JavaScript、lib/ 已提交,没有构建步骤,不需要给 pnpm 授权跑 prepare。卸载:
dsh plugin --profile desktop remove dsh-session-progress
用起来是什么样
| 位置 | 你会看到什么 |
|---|---|
| 侧栏会话行 | 整行宽的色带,宽度就是进度。默认 #90AFFF、60% 不透明、900ms 缓动——每采一次样,它往前跳一步 |
| 设置 → 插件 → 会话进度条 | 模型、思考强度、采样间隔、同屏数量、颜色、不透明度、样式、动画、彩虹,改哪个下面那行预览就跟着变;「保存」后表单显示的是宿主实际存下的值,「恢复默认」把全部打回出厂 |
| 左侧栏「进度日志」 | 判断模型的每一次采样,一个会话一列、从左到右并排;最左边是历史轮次列 |
| 设置页里那张同名卡片 | 只是个入口——点按钮直接打开上面那块面板,不在这儿再复制一份数据 |

它是怎么想的
- 你发一条消息 → 这个会话开始采样,色带从
seedP(4%)起步,先给你一点"动了"的反馈; - 之后每 30 秒一次。提示里带着上一次的数字 + 当时它写的阶段名(「当前进度:45%(当时阶段是「改接口」)」),请它对照这半小时的记录自己判断:又推进了多少、有没有停滞返工、上次是不是估高了。 往前往后都照单全收,宿主只封顶 95——方向是它的,不是宿主的。
- 你在一轮没跑完时又插嘴 → 剩余工作量变了,
interjectDelayMs(2.5 秒)后补采一次,并明确告诉它"目标变了,按新要求重估",日志里那条会挂一个橙色「重估」。 turn/end(不管什么原因结束)→ 立刻补满 100,停 700ms,再花 420ms 淡出。- 采样失败 → 保留上一次的值。色带只在会话真的在跑的时候存在。
被中断的轮次会把当时到几成记下来。下次同一个会话再开跑,色带从那个数字接着爬,而不是退回 0;判断模型也会被告知"上一轮被打断,已到 X%",免得它把接续的活当成新活估成个位数。
面板
一个会话一列,列头两行:色点 + 会话名,下面是「当前百分比 · 采样次数」、最近一次的时间,右边一条最近 24 次估计的曲线(最旧在左)。一条采样一行:时间 / 百分比 / 涨跌(+7 绿、-5 橙)/ 阶段名 / 耗时;最右边的小箭头表示能点开,点开才看模型、档位、token 拆分和 JSON 原文。
最左边是历史轮次列:一个结束(或被打断)的回合一小块,写着 #轮次、完成/中断徽章和最后一次的百分比。它跟着首屏一起加载——但走的是裁剪过的行,只带列要画的字段,不带每轮 2 KB 的模型原文。同一份数据实测 223 KB → 74.5 KB。
列本身贴着内容长,不会在屏幕下半截拖出三个空盒子;条目多了就在列内滚动,「还有 N 条」往下放。
配置
配置文件:~/.dsh/session-progress.config.json(UI 保存的值优先于 cordis.patch.yml)。
| 键 | 默认 | 范围 | 说明 |
|---|---|---|---|
enabled | true | — | 总开关 |
provider | deepseek-account | — | 模型提供方 |
model | deepseek-flash | — | 判断用的模型 |
reasoningEffort | off | — | 思考强度;模型没有这档时按档位距离取最近的一档并打日志 |
intervalMs | 30000 | 5000–3600000 | 采样间隔 |
interjectDelayMs | 2500 | 500–30000 | 插嘴后多久补采一次 |
firstDelayMs | 8000 | 0–3600000 | 本轮首次采样前的等待,让模型先做点事 |
maxSessions | 3 | 1–10 | 同屏显示的进度条数量,也是同时采样的上限 |
maxContextChars | 6000 | — | 送进模型的上下文上限(超出截尾) |
timeoutMs | 30000 | — | 单次采样超时 |
seedP | 4 | — | 首次采样回来之前的起步宽度 |
color | #90AFFF | #RRGGBB | 色带颜色;非十六进制会被忽略并保留旧值 |
opacity | 0.6 | 0.05–1 | 色带不透明度 |
style | band | 六种之一 | 色带形状,见下节 |
flow | true | 布尔 | 色带里那道流动的光开不开 |
flowSpeed | slow | slow / normal / fast | 光走一圈:慢 10.5s、标准 5.9s、快 3s |
flowQuality | medium | refract / high / medium / low / off | 表面那层的质感,见「流动的质感」 |
rainbow | false | 布尔 | 实验:色带换成会走的彩虹 |
思考强度以模型目录为准。 设置页的下拉、采样时传给 adapter 的值、启动时打印的 offered: …,都来自同一份 listModels();resolveModelInfo() 跟它对不上时以目录为准(deepseek-flash 的目录里就有 off)。
外观
六个选项:颜色、不透明度、样式、流动动画、动画速率、动画质量。下面那行预览是用当前数值真画出来的,拖颜色的时候侧栏的色带会跟着变。
预览是实时的,也是临时的:不保存就离开,会还原成已保存的值。宿主每 2 秒会广播一次自己保存的样式,插件把"未保存的预览"和"已保存的值"分开记,所以广播顶不回去。
六种形状(style):
| 值 | 样子 | 什么时候用 |
|---|---|---|
band | 渐变横幅,右端 10px 渐隐 | 默认。最像"进度条",高对比但不抢标题 |
solid | 纯色实条,右端 1px 亮边 | 最克制;侧栏窄、颜色又淡时比 band 更清楚 |
underline | 3px 细线贴行底 | 不想压住标题和时间的读法 |
stripe | 45° 斜纹叠在底色上 | 进度变化慢、但希望"在跑"这件事一直显眼 |
glow | 左淡右亮 + 内侧亮边 + 柔光 | 视觉最重,适合只在少数会话上开 |
segments | 按 20% 切格 | 想一眼看出"到第几格" |
颜色要写成 #RRGGBB。取色器(包括那支吸管)偶尔会回 rgb(18, 171, 52) 这种写法,插件会先自己归一化;实在解析不出来就保留旧值,不会把配置写坏。
想先肉眼挑,打开 dev/styles.html(node dev/gen-styles.mjs 从真实源码生成:六种样式 × 三种进度 × 三种不透明度 × 三种颜色)。
流动的质感
色带里有一道光在走。判断模型半分钟才给一次新数字,中间这段时间靠它让人知道"还在动"。质感分五档:
| 值 | 效果 | 代价 |
|---|---|---|
refract 实验 · 流光 | 两道低对比的光带反向平移(一亮一暗,各自一个周期刚好接上),填充层带玻璃浮雕边——上沿提亮、下沿压暗,看起来像表面自己在流 | 多一层 blur(1.5px) 合成;卡就往 medium 退。它永远在文字下面,不糊字 |
high 高级 | 水波 + 光学:feTurbulence + feDisplacementMap 让光的边缘随水波摆,再叠 backdrop-filter 把底下颜色晕开、提亮 | GPU 吃最多 |
medium 中等(默认) | 只有水波扭曲,边缘像水面裂纹那样左右摆 | 中等 |
low 低 | 扁平高光,形状不动(0.10 及以前的样子) | 几乎为零 |
off 关 | 没有光 | 零 |
那几张 SVG 滤镜由插件挂在 <body> 里,启动时建、卸载时删,规则里只是 filter: url(#…) 引用。不联网、不依赖外部资源。万一壳子造不出滤镜,会静默退回纯色高光——引用一个不存在的 filter 会让整个元素都不画,这种失败必须挡住。
想看五档差别,打开 dev/water.html(node dev/gen-water.mjs):上面等比例、下面放大 2.6 倍,底部写着每档 filter / backdrop-filter 的实际取值。
实验 · 彩虹走马灯
颜色那栏底下的开关。打开后色带不是涂单色,而是一段会走的彩虹:
- 七段色等距排开、交给浏览器插值(红橙黄绿青蓝紫再回红),所以是化开的,不是六条硬边。循环 168px,用一个周期 170.592px(
168 / sin(100°))走出去,首尾同相,多宽都看不到接缝; - 速度跟着「动画速率」:慢 7s、标准 4.4s、快 2.6s;
- 表面那层同时被借来当光晕:同一份光谱
blur(6px)之后用mix-blend-mode: screen叠上去,是加光不是加颜料——用普通透明度整条会糊成粉彩; - 光晕被裁在色带里,不漏到旁边的行;彩虹层上压 12% 黑纱,否则白字走到黄绿段上会看不清;
- 关掉「流动动画」它就变成静止的彩虹——那个开关管的是"动不动",不是"有没有颜色"。
换一台机器 / 别的 provider
provider / model 的两个默认值只是偏好,不是硬依赖。宿主第一次读到模型目录时会核对:
- 配置的模型在 → 直接用;
- 不在 → 按名字挑个便宜的顶上(
mini/lite/small/nano/haiku优先,其次flash/fast/instant,最后turbo,都没有取目录第一个),实际生效的路线写进/config的effectiveRoute,设置页显示一行「原配置的模型在本机不存在,已自动改用 X」; - 目录是空的(这台机器还没配任何 provider)→ 一次模型都不调,
/config回catalogEmpty: true,界面直接告诉你「先去配一个 provider」。
所以 cordis.patch.yml 里不写死 provider/model:别人 clone 下来装上,第一次运行就知道该用什么。想锁定,在设置卡里选一次,值会落到本机配置文件。
原理(为什么不用 React 组件)
DSH 给每个会话行渲染了稳定属性 data-row-key="session:<id>",但没有一个常显的行内插槽:running 时 sidebar.session.row.leading 被状态点占着,行尾按钮只在悬停时出现。
所以本插件一个 DOM 节点都不往行里插,只做两件事:
- 注入一张固定样式表:给行加
position: relative; isolation: isolate,用::after画色带(宽度取--dsp-p、不透明度取--dsp-o,z-index: -1待在行背景之上、文字之下),用::before画表面那层; - 每次进度变化时重写另一张很小的样式表,按 id 写
[data-row-key="session:<id>"]{--dsp-p:42%;--dsp-o:.6}。
--dsp-p 用 @property 注册成 <percentage>,于是"改变量"就等于"CSS 原生过渡",不需要 React 重绘,也不受会话行重渲染影响。(inherits 必须是 true:变量写在行上、由伪元素读取,否则伪元素拿到初始值 0%,宽度永远是 0。)
运行态不轮询,只听宿主事件:
ctx.on('session/event', (session, event) => event.type === 'turn/start' ? run() : event.type === 'turn/end' ? idle() : null)
ctx.on('api-session/status', (sessionId, running) => …)
ctx.on('agent/status', ({ agent, status }) => …)
动画也不是散的:色带的动作全部由一张表决定(SURFACES:动作、图案、有没有滤镜、浮雕),周期 264px、位移 268.073px 只算一次,两段 @keyframes 由这个数生成后写进样式表。加一档只改表,改一下周期也只改一个数。
端点(只监听回环地址)
| 路径 | 用途 |
|---|---|
/plugins/dsh-session-progress/events | SSE,首帧全量,之后每次变化一帧(内容没变就不发) |
/plugins/dsh-session-progress/snapshot | 一次性 JSON 快照 |
/plugins/dsh-session-progress/log | 采样历史(环形缓冲 300 条 + 关闭的 600 条),支持 since 增量 |
/plugins/dsh-session-progress/config | 读 / 写配置 |
/plugins/dsh-session-progress/debug | 运行态、采样器、实际档位、模型目录、客户端版本 |
这些端点拒绝非本机 Host 的请求,不会对外网暴露。
已知取舍
- 依赖
data-row-key这个内部属性。 宿主改渲染方式时色带会静默消失——不崩溃、不错位、不污染别人的界面。 - 列头用会话名,不用 id。 取值顺序:
session/title的标题 → 该会话第一条用户消息(截 40 字)→ 短 id。新会话在自动命名之前,会先用你发的那句话当名字。 - 自动换模型只看名字。 名字里没有便宜档线索时取目录第一个,可能正好是贵的那个;设置页改一次即可锁定。
- 拖拽排序时会闪一下。 宿主用
::before/::after画插入指示线,跟我们的伪元素撞一瞬。 - 颜色是字面色,不走主题 token。
#90AFFF是需求指定的值,暗色主题下偏亮;只有下拉菜单这类原生控件交给--dsw-alias-*。 - 会真的花 token。 每会话每 30 秒一次真实调用(约 1–2k 输入 token),默认最多 3 个会话。把
intervalMs调大或enabled: false随时停。 - 采样不并发。 同一会话上一拍没回来,这一拍就跳过。
开发
node scripts/snapshot.cjs pre-<你要干什么> # 开工先拍快照,崩了一键回滚
npm test # 十套
npm run check
- 改代码前先读
AGENTS.md(60 秒开工流程)和ARCHITECTURE.md(符号索引),报错先查PROBLEMS.md(每条都真炸过一次:现象 → 原因 → 修法 → 哪条测试守着)。 node scripts/snapshot.cjs <label>把源码、测试、探针页、文档打包到backups/(不进 git,带 git rev 与逐文件 sha256);node scripts/restore.cjs latest原地恢复,并删掉快照之后新增的散文件。test/host-smoke.mjs:宿主半侧冒烟,用临时 state 文件,不碰你真实的~/.dsh/*.json。test/direction.mjs:nextProgress的方向规则——上下都收、封顶 95、解析失败保留原值。test/client-boot.mjs:桩 DOM 把浏览器半侧整个启动,喂真实 SSE 帧,断言样式表真的写出来、清理真的收干净。纯函数测试看不到这类错——render()里一个未定义标识符会让色带整条消失,控制台只有一行bad frame。test/render.mjs:四个 slot(main/sidebar.panellist/plugins.bundle.config/settings.plugins.tab)用空 payload 与满 payload 各渲染一次。0.11.2 那次白屏就是data.active空指针——Number(null) === 0骗过了守卫,只有真渲染才发现。test/route-fallback.mjs:模拟别人的机器——目录里没有默认模型时自动改用便宜模型、目录为空时一次模型都不调。test/perf-contract.mjs:空转成本三条线(增量/log、SSE 去重、落盘瘦身)、客户端自报版本、历史行裁剪。dev/release.cjs:发版用,「大鲸鱼在线写插件」那句由它加到正文开头,别再手拼curl。dev/harness.html、dev/flow.html、dev/water.html、dev/panel.html、dev/styles.html:把真实行结构、真实 CSS、真实采样数据拉进独立页面,没有宿主也能比对观感。- 浏览器侧没有 JSX、没有构建:
lib/client.js是window.__ModuleLoader__.load({ id, factory })形式,React 从require('react')拿。
License
MIT © 2026 Ne_Rn