dsh-bot-board
Verifieddsh-bot-board · v0.7.3 · MIT · Web UI
A DeepSeek Harness plugin: a floating board for DS Bot that shows who is running, who is stuck, and who needs you.
Install
dsh plugin add dsh-bot-board Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
Bot 看板(dsh-bot-board)
English · 中文
给 DS Bot 加一块浮动看板:谁在跑、跑了多久、卡在哪、谁在等谁交活。不用逐个点开他们的会话。

这是一个 DeepSeek Harness(DSH)插件,需要先装 DS Bot(包名 ds-bot)。没有 DS Bot 就没有东西可显示,面板不会出现。
它只读 DS Bot 的数据,不写:不改 state.json,不调会改状态的 api/bot,也不传 locale——那个字段会改掉你新建 Bot 的界面语言。
依赖:DSH(Developer Preview 即可)、DS Bot 0.1.7 或 0.2.0(开发时用的就是这两个)、Node ≥ 20(只有从源码构建需要)。
它显示什么
整体是一间 Bot 教室,从上到下三层,按「要不要你动手」递减:
- 需要你(0–3 行,44px/行):只有等你批 / 等你答 / 报错的 Bot。每行是团子小像(30px,就是教室里那张脸的裁剪)+ 名字 + 动作原文 + 已跑时长 + 状态灯。三个灯贴右对齐成一条竖轨,菱形和方形可以直接纵向比较。空的时候是一行「没有需要你的事」——不是空白,面板必须告诉你它查过了。超过 3 个时,第 3 行动作列的右端会出现一个
+Nchip,点它(或 Tab + Enter)就地展开全部,再点收起:不占一行高度,也不会把第 4 个人变成无名氏。 - 在动(整幅教室):正在跑、在等别的 Bot 交活、以及刚交完活(7 秒内)的 Bot,按名册序排进网格(窄窗口自动 2 列,可滚动)。交接边表长在这一层里面,位置在工位网格之前。
- 闲着:默认一行(24px),只剩名字,没有图像、没有表面、没有灯。当没人在跑、也没有未结交接时,它升级成名册——每个闲着的 Bot 一行,44px,和层 1 同一个节奏:头像 + 名字 + 角色(没设角色就用模型)+ 右侧的「闲着」。面板矮了按剩余高度自动分两列或三列,一个 Bot 都不会藏在滚动里;只有每格窄到 160px 以下时才先丢掉角色那一列,名字永远不丢。带「!」徽标的是「报错过但已经安静下来」的 Bot,徽标在任何形态下都不丢。
三层靠图像 / 行高 / 字重 / 表面四个通道递减,不用色块分区:层 1 有 30px 头像和白卡,层 2 是房间地面本身(无底色、无阴影),层 3 默认什么都没有;升级成名册时也只有 30px 头像和 500 字重的名字——没有白卡、没有左侧状态条、没有灯,所以「需要你」仍然是第一眼。状态色只出现在层 1 的左侧 3px 状态条和灯上。
每个工位从左到右是:椅子 + 木桌(桌上显示器、状态灯、桌面道具),桌后坐着一个圆圆的团子 Bot,工位下方是 Bot 名和已跑时长。
六种状态
| 状态 | 灯色 | 形状 | 含义 |
|---|---|---|---|
| 干活 | 绿 | 圆 | 正在跑(working / thinking / searching / sending / 转交中) |
| 等你 | 黄 | 方 | 有审批、提问,或新建的 Bot 在等你选模型 |
| 等交活 | 蓝 | 圆 | 它在等别的 Bot 把活交回来,不是等你 |
| 卡住 | 红 | 菱形 | 报错了,且当前没有在干活。超过 3 分钟仍无动静就降级成「闲着」+ 红色「!」徽标——红灯只表示现在卡住 |
| 闲着 | 灰 | 圆 | 什么都没干 |
| 干完 | 青 | 圆 | 刚接到一次干净的交接,7 秒后回落 |
「等你」和「等交活」是两个状态:一个要你动手,一个不用。混在一起会让黄灯天天误报,最后你就开始忽略它。
状态不只靠颜色:卡住是菱形、等你方形、其余圆形,每个灯都有描边(层 1 的 12px 灯描边 1.5px;工位里的小灯从 0.8 提到 1.2,因为在 138px 格宽下 0.8 只渲染成 0.65px,被抗锯齿摊成一层浅灰——闲着 的灰填充对比度只有 1.53:1,几乎看不见)。因为绿(#22C55E)和红(#EF4444)在红绿色盲下几乎是同一个点(ΔE 13.6),而它们在奶油底上的对比度只有 2.28:1 和 3.76:1——只靠颜色,最该分辨的一对反而最难分辨。显示器也分了六种画面:等你是一根竖线加感叹号,等交活是箭头,干活是三道进度条,干完是勾,卡住是红条,闲着是空屏。
格宽 138 / 格高 128 不是随手定的:480 − 2(面板边框) − 39(头) − 148(3 行需要你 + 间距 + 内边距) − 24(闲着) = 267 留给「在动」,两整行 + grid gap 8 + .bb-run 下内边距 4 必须装进 267,所以 2 × (0.7765W + 20) + 12 ≤ 267 → W ≤ 138.4。取 138 后灯轮廓 5.19px,仍远在 4px 下限之上。默认尺寸 560×480、3 件需要你 + 5 个在动 + 1 个闲着(9 个 Bot 满员),浏览器实测零滚动(npm run preview 生成的 docs/preview-0.7.0.html 里每个面板下方的灰字是 clientHeight / scrollHeight 量出来的,不是算出来的;实测余量 0.72px,算术上是 0.694px——差额来自浏览器把格子渲染成 127.14 而不是 127.153。离破很近,改任何 padding 都要重跑)。
工位上的信息
[团子 + 显示器 + 状态灯]
Bot2 4m12s
- 已跑时长:这一轮从什么时候开始,秒级刷新(只重绘这一个节点,SVG 不动)
- 当前动作:工位格只有 138px 宽,装不下第三行,所以动作在 hover 的 title 和抽屉里(
等你批 Bash/报错:模型超时/等 Chief、Bot5);层 1 的「需要你」列表里它是常显的一列文字。waiting状态的「等谁」只有 title 这一条常驻线索——交接边表是works派生的欠账,不是waiting派生的实时堵点,两回事 - 超时变色:干活超过 5 分钟,时长变琥珀色并加粗(可能卡住了,但还没报错)
- 失败徽标:报错后又重新干活的 Bot,右上角留一个红色「!」,主状态回到绿色。这个徽标会自己消失:失败时间早于本轮起点,或 Bot 已连续干活超过 1 分钟,就判定为「已经恢复了」
- hover 看完整 title;点击打开详情抽屉
Bot 的脸从哪来(DS Bot 说了算)
看板不发明身份。每个 Bot 的长相由 DS Bot 自己的两个字段决定,看板只是把它们画出来:
| 轴 | 来源 | 取值 |
|---|---|---|
| 角色(身体轮廓) | state.json 的 bots[].avatar.shape |
DS Bot 的七个角色:whale star moon wave squircle zed(Bull)hex(Blossom) |
| 颜色 | bots[].color |
19 个命名 ink(deepseek/blue/violet…)或任意 #rrggbb |
| 桌面道具 | 看板自己 | 10 种,按 Bot id 哈希(上游没有描述它的字段) |
七个轮廓是按 DS Bot 的形状重绘的:鲸鱼有尾鳍和圆身、星形有五角、花形有花瓣、牛形有角,脸盘和眼睛按各自轮廓内缩(星形的凹角撑不住一个 18px 的脸盘),有圆边的轮廓才有手。所以「我的 Bot 是蓝色鲸鱼」在两个界面上是同一句话。
DS Bot 只在用户显式选过时才存这两个字段——新建的 Bot 默认两个都没有。没有的部分按 Bot id 哈希补:12 色 × 7 轮廓 × 10 道具 = 840 种组合,同一个 Bot 每次刷新都是同一张脸,两个 Bot 撞到同一套组合时自动换一套。上游给的部分永远优先,哈希只填空白。
名字不参与身份。 一个叫 Chief 的 Bot 不会因此得到任何特殊长相——名字是自由文本,随语言变,而 avatar.shape 是闭集七项。
六态怎么认(图例是抽屉,不是常驻条)
头部那个 ? 按钮打开六态说明:每行一个灯 + 状态名 + 一句人话。它用的是和现场同一个 Lamp 组件(12px 竖轨 / 16px 图例 / 14px 抽屉头部),所以图例不会和现场漂移。
以前这条图例常驻在首屏,占 27px、每 1.5 秒重渲染一次、内容永不变化——最贵的像素给了最不需要看的东西。现在它按需打开,六态靠三层覆盖:层 1 的动作原文直接是词(等你批 / 报错)、工位的 title 带状态名、形状本身(菱形 / 方形 / 圆)。
详情抽屉
点工位打开,不用跳会话就能看到:
- 角色、模型、状态、已跑时长、当前动作
- 这一轮在哪跑:只在这一轮发生在群聊里时才显示(
work.bots[botId].roomId) - 等你审批时:这次调用要动的东西(等宽代码块,可能是命令、文件路径或 URL——DS Bot 的
detail.code取的是command|file_path|url|query里第一个非空值,所以标签写「具体内容」而不是「命令」)+ 审批原因 - 等你回答时:问题原文 + 说明 + 所有可选项。
multiSelect为真时编号换成☐(编号会读成「选一个」),allowCustom为真时末尾加一行「也可以自己说」 - 最近一次报错的完整消息
- 最近一次这个 Bot 交接回来的结果(
work.results[]每条带name,按名字挑,不是取最后一条) - 底部「打开聊天 ↗」跳到那个 Bot 的会话
正在交接(在「在动」里面,工位网格之前)
只列 open > 0 的活。按发送方分组,组头是「共 N 项」——组最大的那个就是瓶颈:
Bot1 · 共 7 项
→ Bot2 4 项待交 12 分钟前
→ Bot3 3 项待交 未开工 8 分钟前
Bot2 · 共 2 项
⇄ Bot1 2 项待交 互相等 5 分钟前
→是派活方向(谁派给谁),欠账方向是反的。- 「共 N 项」是这一组的答案,不是全局面板的总数:谁压着最多,谁就是瓶颈。
⇄+互相等表示 A→B 和 B→A 同时开着,也就是死锁。两侧各保留一条边,各自的数量都是真的,没有一边的数字被合并掉。被堵只给真的动不了的接收方(等你/卡住)。接收方在干活时绝不标「被堵」——他正在飞快还账,去催他是错的。接收方闲着但账还开着,标「未开工」:该看一眼,但没人在堵它。
点一行跳到接收方的抽屉。空的时候整块不渲染(连 0 高的空壳都没有)。
位置在工位网格之前,不是之后。 6 个 Bot 在跑时,两行工位已经占满这一层(266.31px 里的 267px,余 0.694px),边表和网格不可能同时完整可见——而那一刻正是它最要紧的时候:刚对 Bot1 说完「八份都过一遍」,8 个在跑、8 条边同时开着。既然滚动不可避免,就先看关系:工位位置固定、靠位置记人,滚下去看格子代价小;哪些账还开着是每次都要重新读的新信息。完全不滚动时,头部的 N 项在交接 仍然回答「有没有在交接」。
提醒
- 只在「等你审批/回答」和「报错」时弹系统通知,其他情况只变色,不打扰。
- 同一件事只弹一次;新的事才会再弹。
- 面板收起来时,胶囊上带「N 忙 · N 待 · N 等 · N 错」。
交互
- 拖动标题栏移动面板,位置记住。
- 拖右下角调整大小(默认 560×480,最小 420×320,最大 980×700),大小记住。
- × 收起成右下角胶囊,再点展开。
- ? 打开六态图例(抽屉),Esc 或 × 关掉。
- 点工位 / 点「需要你」里的一行 / 点「闲着」里的一个名字都打开详情抽屉(抽屉里再点「打开聊天」跳会话)。
- Esc 关闭抽屉;抽屉打开时焦点会移进去、Tab 在抽屉内循环,关掉后回到刚才那一行(不是回到网格)。交接边、列表行、闲着名字都可以 Tab + Enter 打开。
- 名册顺序永不改变,分层只是在渲染期对行做过滤:一个 Bot 只在真的开始/结束一轮时才在「在动 / 闲着」之间换层(还有 1.2 秒的稳定窗,一次轮询的抖动不搬家)。位置是你记人的方式。
- 「需要你」不走这个稳定窗。 它是这块面板唯一不许滞后的答案:审批一来就得进列表,一处理就得离开。给它加防抖会让面板和自己的头部计数互相打脸(头部说「1 等你」,列表说「没有需要你的事」),处理完的那一刻还会让那个 Bot 三层都不在。
数据从哪来
| 数据 | 来源 |
|---|---|
| 每个 Bot 的实时状态、当前工具、已跑时长、审批与提问、新建 Bot 待选模型 | DS Bot 的 POST api/bot(endpoint: "state"),有活在跑时 1.5 秒一次,全队闲着 90 秒后 6 秒一次 |
| 派活记录(from/to/open/results)、房间名 | 宿主侧读 ~/.dsh/bot/state.json,内存缓存,按 mtime+size 失效,5 秒一次 |
| 报错 | 宿主侧订阅 DSH 的 agent/error,按会话映射回 Bot,保留 15 分钟 |
只读取 DS Bot 的数据,不写它的任何文件。面板自己的位置、尺寸和折叠状态存在浏览器 localStorage,与 DS Bot 无关。
轮询会降频:面板收起或窗口隐藏时,两个轮询都降到 30 秒;连续失败时指数退避(最多 8 倍、上限 30 秒)。全队闲着(没有任何 Bot 在跑、没有审批、没有等交活)超过 90 秒后,实时轮询从 1.5 秒降到 6 秒——一旦有人动起来,下一次 tick 就回到 1.5 秒。间隔还带 ±10% 抖动,免得和 DS Bot 自己的前端锁相。
这不是省电,是因为 api/bot 的 state 端点在上游每次都会跑 refresh + bootstrap + catalog + loadSecrets,unchanged 只省了响应体积,没省这些。所以一个挂着不看、全员闲着的看板,每分钟仍然会让上游跑 40 次这几件事——降频降的是这个。
轮询循环是单飞的(src/client/pollLoop.js):请求在途时收到 wake 会被忽略,由请求自己在落地后排下一个 tick。早期版本会去清一个已经触发过的 timer 然后再 fire,结果是每折叠/展开一次就多挂一条永久轮询链。
看板不会给 DS Bot 传 locale:那个字段在上游是全局 uiLocale,会改掉新建 Bot 的招呼语言和记忆语言。只读插件不该有这个副作用。
安装
插件目录就是本仓库。装 DS Bot 之前它没有东西可画——看板的数据全部来自 DS Bot。
只是要用,在 profile 目录里装它,再把包名加进 bundles,然后完全退出 DSH 重开:
cd ~/.dsh/profiles/desktop
npm install dsh-bot-board # profile 里有 pnpm-lock.yaml 就用 pnpm add dsh-bot-board
{ "dsh": { "profile": { "bundles": ["...", "dsh-bot-board"] } } }
要改代码,按下面四步从源码装。
- 拿代码并构建浏览器侧产物(源码在
src/,client.js是生成的;仓库里已带一份与源码同步的产物,改了src/才需要重新构建):
git clone https://github.com/coii08o0o/dsh-bot-board.git
cd dsh-bot-board
npm install # 只装一个开发依赖 esbuild
npm run build
- 登记进 profile 的
package.json(位置在~/.dsh/profiles/desktop/package.json),把路径换成你的实际路径:
{
"dependencies": {
"dsh-bot-board": "file:/你的/路径/dsh-bot-board"
},
"dsh": {
"profile": {
"bundles": ["...", "dsh-bot-board"]
}
}
}
- 建立依赖链接(DSH 用 pnpm 管理 profile,
file:依赖也要链进node_modules才找得到):
ln -sfn /你的/路径/dsh-bot-board ~/.dsh/profiles/desktop/node_modules/dsh-bot-board
- 完全退出 DeepSeek Harness 再打开(直接
killall -9 "DeepSeek Harness"最干净;osascript quit会弹系统自动化权限窗),右下角出现「Bot 看板」面板。
客户端半边有 HMR:改完
src/client/**后npm run build即生效,不用重启;改src/host/**才需要重启 DSH。长相在 DS Bot 里设,不在看板里设:打开 Bot 的设置,给它选一个角色(七个轮廓之一)和颜色。看板下一次轮询就会跟着变——它只读,不写你的数据。没选过的 Bot 会按 id 哈希出稳定的脸,也完全能用。详见上面「Bot 的脸从哪来」。
开发
src/host/index.js 宿主侧:快照路由 + 失败存储
src/client/index.js apply / slot 注册
src/client/rows.js 行构造(纯函数,可单测)
src/client/scene.js 状态优先级(纯函数,可单测)
src/client/layers.js 三层归属 + 交接边表 + 闲着降级(纯函数,可单测)
src/client/station.js 工位 SVG + 灯组件 Lamp + 元信息行
src/client/attention.js 「需要你」列表 + 「闲着」一行
src/client/elapsed.js 每秒跳一次的时长节点(工位和抽屉共用)
src/client/drawer.js 详情抽屉
src/client/legend.js 六态图例(复用抽屉)
src/client/handoffs.js 交接分组边表
src/client/panel.js 外壳、拖动、缩放、折叠、三层主体
src/client/poll.js 两个轮询器(钩子)
src/client/pollLoop.js 轮询循环(纯对象,可用假时钟测试)
src/client/cadence.js 轮询节奏(纯函数:全队闲着就放慢)
src/client/clock.js 共享的一秒心跳
src/client/bridge.js 打开会话的桥
src/client/const.js 可调参数 + 六态 + 工位名册
src/client/format.js elapsed / ago / clip
src/client/style.js CSS
src/client/locale.js 中/英文案
tools/build.mjs esbuild 打包成 __ModuleLoader__ 格式
tools/smoke.mjs 宿主快照 + 纯逻辑 + 产物契约 + CSS
tools/render-check.mjs 用 React 垫片真渲染一遍组件树
npm run build # 改完 src/ 必须重新构建
npm run smoke
npm run render
npm run check # 上面三步一起(build + smoke + render)
npm run verify # 只查一件事:client.js 是不是当前 src/client/*.js 构建出来的
npm run ci # 发布前跑这个:verify + smoke + render,不前置 build
npm run freeze # 构建 + 打印 src/产物 两个指纹 + 跑两套检查(复核用)
verify 和 ci 存在的意义是它们不 build。check 永远先跑 build,所以「产物过期」这个状态在 check 里根本无法表达——把旧 client.js 放回 HEAD 再跑 check,531 条断言照样全绿。verify 拿 client.js banner 里的 src 指纹和现算的指纹比对(不比对字节,因为 esbuild 是 ^0.25.0,版本一变输出字节就变;package-lock.json 锁了当前版本,但锁文件本身也会更新);ci 在此基础上再跑两套检查,所以忘记 build 的产物会被 ci 拦在发布之前。改了 src/ 而没重新构建时,npm run ci 会直接失败在第一步。
531 条里有 9 条是恒真 tripwire(输出里带 (invariant) 标记):CSS 由常量插值生成,它们按构造成立,存在是为了抓住「有人把插值手抄成字面量」的那一天。它们不构成行为覆盖,读数时请把它们排除在外。
复核时注意指纹要说是算哪一批文件:src/ 单独算、src+tools+产物 一起算,得到的值完全不同。之前两个人对着报「哈希不一致」,其实是算法不同。
react / react-dom 在构建时保持 external,由 __ModuleLoader__ 注入——打进 bundle 会产生第二份 React,hooks 直接报错。
已踩过的坑(给下次写插件的人)
connection.fetch.register的path必须带前导斜杠(/api/bot-board),不带斜杠会 404;路由是精确路径名匹配,不是前缀。connection服务可能在apply()之后才就绪,必须用ctx.inject(['connection'], cb)等它,一次性if (!ctx.connection) return会让路由永不注册且毫无报错。- 客户端 bundle 的
__ModuleLoader__.load({id})里 id 必须等于包名,改名后不重打包会静默不加载。 - 面板挂在
shell.overlay插槽,order 取 500 以上,避开 ds-bot 的 50/55/60。 rooms[].log[]没有时间字段(只有seq),想显示「几分钟前」得自己记。works[].room是房间 id,不是 Bot id;用 Bot 表去查永远查不到名字。bots[].sessionId是当前分段,segments[]里只有已结束的分段——做会话→Bot 映射时两个都要收,否则 Bot 切过段之后新会话的报错会丢。api/bot的state不是纯读接口,每次都跑refresh/bootstrap/catalog/loadSecrets,别拿它做高频轮询。- 桌面端改完插件必须整个退出重开;
osascript quit会触发系统自动化权限弹窗,直接killall -9 "DeepSeek Harness"最干净。
已知边界
- 面板常驻所有会话,不只在 DS Bot 里。
- 派活只显示
state.json里还留着的记录;DS Bot 在子任务完成时会删掉子记录,历史链路会断,所以这里不做链路图,只做「正在交接」列表。 - 群聊面板没有做:
rooms[].log[]无时间字段,且本机实测群聊 0 使用率。 - 报错只能抓到 DSH
agent/error能覆盖的(模型/工具步骤失败);网络层中断另说。 - 教室是刻意的浅色岛,文字固定深色。暗色主题下房间仍是奶油色——这是设计选择,不是漏改。
- DS Bot 的
avatar.image不渲染:上游支持用户上传的图片(最大 160KB 的 data URL),看板只用shape和color。图片要缩进 138px 的工位、而大多数 Bot 根本没有上传图,收益不抵成本——真需要的话从抽屉里显示原图开始。 - DS Bot 升级后如果改了
api/bot的字段,看板需要跟着调整。color的取值域来自 ds-bot 的src/client/inks.js,上游加新色时这里要同步(加不上的未知名会安全地回落到哈希)。 - 快照对任何已认证的 DSH 会话可见:
/api有 HttpOnly cookie 和 owner 校验、默认只绑回环,但同源的插件脚本、远程trustedHosts和 API operator token 都能读到它。 state.json按半可信输入处理:字段漂移或类型不对时会降级显示(空字符串 / null),不会崩,但可能读到的值是错的——dataVersion不匹配时面板会明说。- 报错原文会进系统通知:通知可能出现在锁屏、录屏或投屏上;敏感环境请在系统设置里关掉这个插件的通知。
卸载
从 profile 的 bundles 和 dependencies 里删掉 dsh-bot-board,删掉软链,重启。
反馈与贡献
提 Issue 请带上三个信息:DSH 版本、ds-bot 版本、面板头部显示的内容(或截图)。DS Bot 升级后改字段是这类插件最常见的问题,版本号能省掉一轮问答。提 PR 前跑一遍 npm run ci——它会先校验 client.js 与 src/ 是否同步,忘 build 的产物到不了主分支。
许可与致谢
MIT。
看板里的七个角色轮廓取自 DS Bot(Apache-2.0)的 CHARACTERS,19 个颜色 ink 的色值取自它的 inks.js;看板把它们按自己的画法重绘成团子(脸盘、眼睛、手、桌面道具都是本项目的)。上游是 Apache-2.0,这里保留出处与许可说明。
版本
0.7.0— 「闲着」在退化态升级成名册。触发条件是「没人在跑、没有未结交接、上面那层留下的空间够放两行」:这时每个闲着的 Bot 各占一行 44px(和层 1 同一个节奏),头像 + 名字 + 角色(没角色用模型)+ 右侧「闲着」。列数由剩余高度算出而不是断点:560×480 单列九行刚好不滚(475 ≤ 480),420×320 自动分两列、带通知时三列,一个 Bot 都不会藏在滚动里;只有每格窄到 160px 以下才先丢角色列,名字不丢。名册不是层 1 的灰色版——没有白卡(白卡在奶油底上是 1.07:1,不编码任何信息)、没有左侧状态条、没有灯(九个灰圆是墙纸不是通道)、名字 500 字重,所以「需要你」仍然是第一眼。混合态(有人在跑)保持一行 24px。断言 465 → 514(smoke 264 + render 250),新增 P2-1…P2-4 与 C4d 预算。0.6.0— 身份改成 DS Bot 自己的两轴:avatar.shape(七个角色轮廓,按 DS Bot 的形状重绘)×color(19 ink 或 #hex),桌面道具仍由 id 哈希。按名字的名册整个删掉——名字不再参与身份,公开版的默认值里不再有作者自己的团队。哈希兜底覆盖没设过长相的 Bot,撞车自动重排(12×7×10 = 840)。灯、尺寸、三层轴不变。上线前全检后断言 465(原 0.5.0 变更日志里的 437 是手抄数字,实测 435)。0.5.0— Bot 的身份改成三级解析:上游color(用户在 DS Bot 界面里选的颜色,19 个命名 ink 或#rrggbb)压过本地名册,名册压不过就按 id 哈希;名册名字被认领后不再发出(防冒充),哈希层新增(色, 头饰, 道具)三重去重(原来两个陌生人有约 1/960 概率画成同一张脸)。INKS表逐字取自 ds-bot 的src/client/inks.js,看板与 DS Bot 同色。DS Bot 的avatar不渲染(见已知边界)。新增预览场景「身份来源」。断言 424 → 437。0.4.3— 审计修复 + 文档对齐。代码:格宽 139 → 138(480 − 39 − 148 − 24 = 269漏了PANEL_BORDER2px,层 2 实际只有 267px,139 宽会让第二行工位溢出 0.9px;改 138 后 C4c = 479.31 ≤ 480,余 0.694px,浏览器实测 0.72px);SCENE_VB成为viewBox与cellHeight()的唯一来源,RUNNING_CELL_H改由Math.ceil(cellHeight())推导;IDLE_BUDGET从裸的 380 改成SIZE_MIN.w − PANEL_BORDER − 20 − IDLE_SLOP并补上界断言;nameWidth补 CJK 扩展B;waitSince改用审批到达时刻(approval.at),attentionOf在名额 ≥2 时保留最老的 error;层 1 展开态的−移到第一行(420×320 下原本被裁掉、无法撤销);交接表的「还有 N 项」变成可点击展开的控件;灯加浅色底座(轮廓 1.45:1 → 7.20:1);同名 Bot 不再能冒用同一个座位;宿主侧works上限修好、bots/rooms/works/segments四类脏数据不再让快照永久失效、readAt驱动stale、控制字符与 RLO 被剔除;新增npm run verify(client.jsbanner 带src指纹,防止产物落后源码而检查全绿)。断言 378 → 424。文档:设计规范与需求文档的数字全部按const.js重算,交接表位置、§8.3 跨层错误、420 与 560 的定位一并改正。0.4.0 – 0.4.2— 三层注意力轴:条件渲染的「需要你」短列表(0–3 行 ++N)、按发送方分组的交接边表、闲着压成一行名字;常驻图例降级成抽屉;格宽从 176 收到 139/128。两轮审计补上的:C4c 原先漏算 grid gap 8 与.bb-run下内边距 4;attentionOf在名额 ≥2 时保留最老的 error;waitSince改用审批到达时刻;idleLine的预算从裸的 380 改成从SIZE_MIN.w推导并加上界;nameWidth补上 CJK 扩展B;灯底座圆盘盖住灯的外径。新增npm run verify:client.jsbanner 带 src 指纹。0.3.0— 拆分源码 + 构建;修 8 个 P0(报错压制、暗色可读、工位跳动、waiting 语义、灯色、room 查错表、切段丢报错、群聊死代码);新增已跑时长、审批详情、详情抽屉、交接列表;六态形状通道;轮询单飞 + 降频;i18n。0.3.4— 键盘可用:抽屉加aria-modal、打开时移入焦点、Tab 在抽屉内循环、Esc 关闭、关闭后焦点回到原工位;交接行补role/tabIndex和 Enter/Space;所有控件补:focus-visible焦点环。报错红灯改为只表示「现在」:安静超过 3 分钟降级成「闲着」+!徽标,消息留在抽屉里。删掉sinceKnown死字段;失败表按at淘汰而不是插入序;字典里没有的 activity 兜底成「干活中」而不是打印原始 token。0.3.3— 把上游已经给了但被丢掉的数据接上:新建 Bot 等你选模型时亮黄灯(bot.waiting)、问题的说明行与「可多选/可自己说」、这一轮是否在群里跑、交活结果按 Bot 名归属;「正在交接」先按open > 0排再截断,老但没交完的活不再被新的已结束记录挤掉。轮询改为自适应:全队闲着 90 秒后从 1.5 秒降到 6 秒,带 ±10% 抖动。0.3.2— 修 3 处看得见的错:干完不再盖掉干活(交接落地同一秒接了下一轮的 Bot 会被画成干完 7 秒)、抽屉的已跑时长改为跟心跳走(此前打开后就冻住)、面板位置恢复和缩放后夹回视口(此前窗口变小可能拖不回来)。补 4 条回归断言。0.3.1— 修 0.3.0 引入的轮询双链;去掉会改写上游uiLocale的locale字段;头部计数不再硬编码颜色;抽屉标签按真实契约改为「具体内容」;ago()走 i18n;交接记录按 owners 表解析归属(切段后不再显示 session id);失败徽标随恢复自动消失;默认高度 480。0.2.0— 教室场景、六种表情动作、拖动缩放。