DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Yu-jianhai /

Yu-jianhai/dsh-session-progress

Verified

大鲸鱼在线写插件 · 让鲸鱼去监督鲸鱼:在 DSH 侧栏每条会话行上铺一条半透明进度带,每 30 秒用一个轻量模型读上下文估进度;可选监督模型、可调思考强度、可改颜色与样式,左侧栏还能看到判断模型的每一次原始输出。

★ 2 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@2a8f58c4

大鲸鱼在线写插件

dsh-session-progress

你是不是还在为了不知道大肥鱼在吃多少白饭,到底干活干到哪了而烦恼?别担心!让我来助你!全新进度条让你实时了解大鲸鱼在干嘛!将进度条接入大模型注入上下文,让鲸鱼去监督鲸鱼!可选监督大模型、可调思考强度、可变进度条颜色、可变进度条样式,懂鱼更懂你!

侧栏实机:一条淡蓝色带从左往右爬过会话行

一条会自己爬的色带,长在 DSH 侧栏的每一行会话上。它不催你,也不打断任何东西——只是有个便宜的小模型每 30 秒瞄一眼鲸鱼在干嘛,然后把答案画成一行像素。

先说清楚它是什么

AI 干活最难受的地方不是慢,是不知道它在哪。

一轮跑了二十分钟,你只能盯着最后一个工具调用猜:是卡住了,是在跑测试,还是已经快好了?点进去翻日志,翻完发现它只是在等一个超时。

这个插件把那个答案变成侧栏上一条会动的东西。你刷手机回来,扫一眼色带的位置,就知道该继续等还是该进去看看。

它和普通的进度条不一样在三件事:

  • 色带铺在整行底下,不是另一根独立的条。标题、时间、悬停按钮照常可读可点——一个 DOM 节点都没往行里插,用的是 ::after 和两个 CSS 变量。
  • 判断的也是模型。真正干活的那个没空汇报,就派一个最轻的读一遍当前轮次的上下文,让它说「大概到 62% 了」。提示里带着它自己上一次给的数字和阶段名,所以卡住了它会往下调,不会为了好看一路抬到 95。
  • 卡住了会明说。连续几次采样进度都没推进(±2% 以内),日志那一行会挂一个「停滞」标签;再拖下去变「卡住」。不靠猜:判断模型每轮都会被问「有没有停滞、返工」,它说没动、或者说一直失败,这两个信号一起看。插嘴或换轮次会自动清掉。
  • 它自己在花钱。每 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 缓动——每采一次样,它往前跳一步
设置 → 插件 → 会话进度条 模型、思考强度、采样间隔、同屏数量、颜色、不透明度、样式、动画、彩虹,改哪个下面那行预览就跟着变;「保存」后表单显示的是宿主实际存下的值,「恢复默认」把全部打回出厂
左侧栏「进度日志」 判断模型的每一次采样,一个会话一列、从左到右并排;最左边是历史轮次列
设置页里那张同名卡片 只是个入口——点按钮直接打开上面那块面板,不在这儿再复制一份数据

进度日志面板:两个会话并排,列头带进度曲线,一条采样一行

它是怎么想的

  1. 你发一条消息 → 这个会话开始采样,色带从 seedP(4%)起步,先给你一点"动了"的反馈;
  2. 之后每 30 秒一次。提示里带着上一次的数字 + 当时它写的阶段名(「当前进度:45%(当时阶段是「改接口」)」),请它对照这半小时的记录自己判断:又推进了多少、有没有停滞返工、上次是不是估高了。 往前往后都照单全收,宿主只封顶 95——方向是它的,不是宿主的。
  3. 你在一轮没跑完时又插嘴 → 剩余工作量变了,interjectDelayMs(2.5 秒)后补采一次,并明确告诉它"目标变了,按新要求重估",日志里那条会挂一个橙色「重估」。
  4. turn/end(不管什么原因结束)→ 立刻补满 100,停 700ms,再花 420ms 淡出。
  5. 采样失败 → 保留上一次的值。色带只在会话真的在跑的时候存在。

被中断的轮次会把当时到几成记下来。下次同一个会话再开跑,色带从那个数字接着爬,而不是退回 0;判断模型也会被告知"上一轮被打断,已到 X%",免得它把接续的活当成新活估成个位数。

面板

面板有两套皮肤,在设置里「面板样式」切换,只影响这块日志面板,侧栏色带不受影响:

皮肤 样子
新 · 分组卡片(默认) 每个会话一张圆角卡片,列头是一整块染了该会话色相的色块(标题、百分比、次数、曲线都在块里),行与行之间只有一条极淡的分隔线,按钮是胶囊形
旧 · 紧凑列表 0.20 那版:贴着内容的紧凑列,列头是中性色的一行字

一个会话一列,列头两行:色点 + 会话名,下面是「当前百分比 · 采样次数」、最近一次的时间,右边一条最近 24 次估计的曲线(最旧在左)。一条采样一行:时间 / 百分比 / 涨跌(+7 绿、-5 橙)/ 阶段名 / 耗时;最右边的小箭头表示能点开,点开才看模型、档位、token 拆分和 JSON 原文。

最左边是历史轮次列:一个结束(或被打断)的回合一小块,写着 #轮次、完成/中断徽章和最后一次的百分比。它跟着首屏一起加载,但一次只发 40 轮,列底还有「再看 40 轮」;下一页用 historyBefore=<最早那行的 at> 当游标,所以这期间新结束的回合不会把队列顶乱。

行是完整的——展开能看到模型、档位、耗时和 JSON 原文。0.20.1 曾经为了省流量把行裁成八个字段,结果详情面板里耗时全是 0s、展开是空的;四十行的分页比削字段划算得多(首屏约 18 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 布尔 实验:色带换成会走的彩虹
widget true 布尔 随 DSH 启动桌面常驻小窗;找不到 exe 时静默跳过
widgetPath '' 路径 小窗 exe 的显式路径;空则依次找 DSP_WIDGET_EXE、~/.dsh/widgets/、插件同级目录

桌面常驻小窗

widget 打开时,宿主会在启动后拉起 DSHProgressWidget.exe,把日志与配置文件路径、以及自己的 pid 交给它;DSH 退出,小窗跟着退出。小窗只读这两个文件,不额外开端口,也不碰采样逻辑:无会话时收成 36px 胶囊,最多显示 3 条会话(卡住 → 停滞 → 最近采样),点一下回到 DSH。exe 不在机器上就跳过,不影响插件本身。

思考强度以模型目录为准。 设置页的下拉、采样时传给 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 的两个默认值只是偏好,不是硬依赖。宿主第一次读到模型目录时会核对:

  1. 配置的模型在 → 直接用;
  2. 不在 → 按名字挑个便宜的顶上(mini/lite/small/nano/haiku 优先,其次 flash/fast/instant,最后 turbo,都没有取目录第一个),实际生效的路线写进 /config 的 effectiveRoute,设置页显示一行「原配置的模型在本机不存在,已自动改用 X」;
  3. 目录是空的(这台机器还没配任何 provider)→ 一次模型都不调,/config 回 catalogEmpty: true,界面直接告诉你「先去配一个 provider」。

所以 cordis.patch.yml 里不写死 provider/model:别人 clone 下来装上,第一次运行就知道该用什么。想锁定,在设置卡里选一次,值会落到本机配置文件。

原理(为什么不用 React 组件)

DSH 给每个会话行渲染了稳定属性 data-row-key="session:<id>",但没有一个常显的行内插槽:running 时 sidebar.session.row.leading 被状态点占着,行尾按钮只在悬停时出现。

所以本插件一个 DOM 节点都不往行里插,只做两件事:

  1. 注入一张固定样式表:给行加 position: relative; isolation: isolate,用 ::after 画色带(宽度取 --dsp-p、不透明度取 --dsp-o,z-index: -1 待在行背景之上、文字之下),用 ::before 画表面那层;
  2. 每次进度变化时重写另一张很小的样式表,按 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 的请求,不会对外网暴露。

WB 的进度

装了 dsh-workbuddy-panel 的话,面板最左边会多一列紫色的「WB 运行」——WorkBuddy 跑的那些任务。

它跟会话列不是一回事:

会话列 WB 列
数字 一个模型估出来的百分比 没有百分比
状态 在跑 / 停滞 / 卡住 运行中 / 完成 / 失败 / 被终止 / 已中断
侧栏色带 有 没有

WB 自己只报「跑到第几轮、跑了多久、拿了几次工具」,没有完成度这种概念。给它编一个百分比,等于把猜测伪装成测量,所以这一列只有状态。它也不出现在侧栏色带上——色带只表示 DSH 的轮次,把别的进程混进去会让那个数字失去意义。

那它的"实时进度"是什么

是轮次 + 一直在走的秒数。WB 只在跑完时写 seconds,所以宿主每 5 秒用日志名里的开始时间(20261105-004405)现算一次已跑时间,面板再叠加"距上次取数过了多久",用 1 秒的滴答把它推着走。你看到的 1m 12s 是真的在走,但每一格都不是猜的。

跑完之后的轮次才显示 seconds 的真实值,中途显示的是同一口径的推算,两者在卡片上长得一样——因为它们量的确实是同一件事。

历史比一屏长怎么办

跟会话历史列一样翻页:首屏拉 8 条,列底一颗「再看 40 轮」,游标是已显示最旧一条的开始时间,按开始时间往前翻。列头写着「1 条在跑 · 共 29 次」,先告诉你还剩多少。

这一列是最后才加载的东西:宿主读不到 ctx.workbuddy 服务,它就不出现(不是显示成空列),也不会拖慢色带和会话列。面板顶部那颗「刷新」按钮同时刷两个来源。

卡住的时候

进度条最怕的不是慢,是看着在动、其实在原地——测试一直失败、反复改同一处、工具报错又重试。

面板会对这种状态挂标签:

标签 什么时候出现 含义
停滞(琥珀) 连续 4 次采样几乎没动(±2% 之内,约 2 分钟) 这一会儿停住了,可能只是慢
卡住(红) 连续 8 次没动(约 4 分钟) 大概率真的死锁
打转(琥珀) 最近 8 次里有 ≥3 次掉头,来回走了 ≥18 个百分点 在试同一件事,做完又退回
死循环(红) 掉头 ≥4 次、来回 ≥30 个百分点 典型的「失败—重试—重估」循环

前两个看「数字动没动」,后两个看「数字是不是在原地来回」,两种都要:真的死锁常常数字一动不动,而反复失败重试的回合数字一直在动——它每次重估都把百分比往上提一点,再跌回去,只看「有没有动」永远抓不到。判定逻辑在宿主的 trackStall() 里,输入就是这一轮最近 8 次采样。

失败措辞始终只是副证:模型说一句「测试通过了」就能把死循环洗白,说一句「还是失败」也可能只是第一次失败。所以措辞只进标签的提示语(「记录里也一直在报失败」),不进判定。真实推进、你自己插嘴、或者换了一轮,计数都会清零——它不是永久污点。

已知取舍

  • 依赖 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

—/ 5

No ratings yet

Verified DSH bundle

Commit 2a8f58c416cc

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout