dsh-workbench
DSH Web 插件:把「AI 改 md → 生成 Word/PDF/Excel → 你审这一版」变成一条看得见的时间轴(含技能库与文档配对)。自带演示数据、零依赖、只读为主。
- Stars
- 0
- Language
- JavaScript
- Created
- Sep 17, 2026
- Updated
- Sep 19, 2026
Introduction
dsh-workbench
给 DeepSeek Harness Web 端加一个「工作台」面板: 迭代(AI 改 md → 生成 Word → 你审这一版,一条时间轴看全)、 技能库(看机器上所有技能、读全文)、 文档(md 源与 docx/pdf 产物配对,把「产物比源旧」挑到最前面)。 可以停在右栏,于是对话留在中间、时间轴在右手边;左栏图标上还有「待我审」角标。
DSH Web 插件 · 零依赖 · 只读为主(会写文件的只有三处:记审批、收编一版、打开文件)· 左栏一个图标 + 右栏一个页签,主区域一个面板。
它解决什么
一、AI 把文档迭代得像代码,但你看不见这条线。
AI 改 30 次 .md,每次生成一份 Word 给你 —— 中间那些版本全部同名覆盖,消失了;
你看到永远只有最新一版,也不知道这版跟前一版差在哪。
二、技能散在好几个地方,看不见也搜不到。
一个 DSH 技能可能来自项目层(.agents/skills)、用户层(~/.agents/skills)、内置层……想知道
「我到底装了哪些技能、那个技能到底写了什么」,得挨个目录翻。
三、用 AI 改文档时,源和产物会脱节。 Markdown 是源,docx/pdf 是产物。改完 md 忘了重新生成 → 交出去的 Word 里是旧内容。
这个插件把这三件事放进同一个面板。
界面
点左栏的「工作台」图标,主区域出现面板,三个页签。标题行两端各有一组按钮:
- 左上 ← → —— 面板里的浏览历史:切页签、换目录、打开技能、点开某一轮、改筛选,每一步都能退回来再进回去 (悬停显示还剩几步;只有「用户做的一步」入栈,连续打字不算)。
- 右上 ← 返回对话 —— 把中央区域切回当前会话,不改变当前会话。
- 右上 ⇥ 搬到右栏 —— 把工作台停到右侧栏(DSH 官方的右栏),于是对话留在中间、时间轴在右手边, 一边让 AI 改、一边扫这一版。它同时是右栏的一种页签类型,右栏「+」的引导页里也能找到它; 已经在右栏那一份里时这枚按钮自己收起来。
- 左栏图标上的数字 —— 「待我审」还有几轮,不用点开就知道有没有活干(0 时不显示)。
⌘ 右栏与主区域是同一份面板:位置不同,状态是同一个(目录、筛选、选中轮次都跟着走)。
迭代(默认)
把「AI 改文档」这件事变成一条看得见的时间轴:
- 按产线分组(毕业论文 / Aspen 流程模拟 / 经济分析…),产线内按天分小节,一轮一行;
- 目录不用手打:点 浏览… 开宿主的目录选择——挂原生选择器就弹系统对话框, 挂 browse 后端(远程/SSH 部署)就开插件自绘的应用内浏览器(面包屑 + 子目录 + 手输路径);选中即读取;
- 每轮显示:时间、产物类型(Word / PDF / Excel)、这一版干了什么、改自哪几个 md、审批状态;
- 鼠标悬停任一版 → 浮出摘要卡:摘要、产出时间与体积、生成方式(哪条命令)、改动来源、快照路径、你的历史意见;
- 点开一轮 → 【通过】【要改】【重置为待审】,写进记录文件;
- 看完能直接打开:同一行上还有【打开这一版】(那一版的快照)、【打开成品】(产线目录里那个随时被覆盖的最新成品)、 【在文件夹里】(打开文件管理器并选中它);
- 目录里多了内容会自动发现:目录行的档位按钮默认
自动:10 秒(可切 30 秒 / 1 分钟 / 关), 探到变化时顶栏给一条提示「多了 N 轮」+【现在刷新】【稍后】——不擅自改你正在看的那一屏; - 顶部按状态(待我审 / 已通过 / 要改)、产线、类型筛选。
「载入演示」 一键加载插件自带的假数据(examples/thesis-workbench/,3 条产线 27 轮),
先看效果再决定要不要用。

截图里是插件自带的演示项目,内容全是假数据;右侧浮层是鼠标悬停在某一轮上时的摘要卡。
记录格式
时间轴不是猜出来的,是产出那一刻记下来的。每个产线目录下:
产线/
├── *.md ← 源(AI 改的就是这些)
├── 论文全文.docx ← 工作副本,名字永远不变(每次覆盖)
└── .versions/
├── produced.jsonl ← 产出记录:追加一行一条
├── decisions.jsonl ← 你的审批:追加一行一次
└── 20260911T2210-论文全文.docx ← 快照(这才叫版本存在)
produced.jsonl 一行:
{"id":"20260911T2210-毕业论文-3","at":1789000200000,"line":"毕业论文",
"artifact":"论文全文.docx","snapshot":".versions/20260911T2210-论文全文.docx",
"kind":"docx","bytes":38000,"tool":"python thesis_docx.py","by":"dsh",
"sources":[{"path":"03-第三章-物料衡算.md"}],
"summary":"第3章:修正乙苯转化率 0.62→0.58,重算全表"}
decisions.jsonl 一行:
{"id":"20260911T2210-毕业论文-3","at":1789007400000,"state":"rejected","by":"human","note":"热量衡算和物料衡算对不上"}
state ∈ approved / rejected / pending;同一个 id 以最后一条为准(追加式,不改历史)。
工作模式就是这条循环:
AI 改 .md → 跑脚本生成 docx/pdf/xlsx → 写 produced.jsonl + 存快照
↑ ↓
└────── 你给反馈 ←── 你在插件里审 Word ←──────────────┘
还没进记录的产物:把漏掉的版本捡回来
上面这条循环有个现实问题:记录得有人写。插件不猜、不补写,可真实项目里
往往一个脚本跑完就覆盖了 Word,.versions/produced.jsonl 一行都没有 —— 时间轴于是永远是空的。
所以迭代页顶栏有一个 未入记录 按钮,点开它做两件事:
- 找(宿主
POST /scan):扫产线目录,列出「文件躺在那里、记录里却没有」的产物。 判据只有一条:记录里有同名产物、且记录时间不早于文件时间(留 3 秒时钟差)。 同时去 DSH 会话日志里翻「最近一次产出它的命令」和「那前后改过哪几个 md」—— 只认本项目 cwd 的会话,且说得保守:soffice --convert-to pdf 论文.docx里那个 docx 是读入的一方, 不算生成;删除命令也不算。找不到就如实写「没找到生成命令」。 - 收编(宿主
POST /adopt):你补一句话、点【记一版】,它才真的收编成一版 —— 复制一份快照到.versions/,并往produced.jsonl追加一行。 快照同名时只新建、不覆盖(同一分钟内重复点会明确告诉你)。
这一步不会自动跑:展开才扫,因为扫一次要读目录、还可能翻会话日志。
技能库
- 列出模型实际会加载的那份技能目录(走核心
ctx.skills注册表,不另建索引,不会漂移) - 按技能名 / 描述 / 触发词搜索,按来源过滤(用户 ·
.agents、内置、项目层…) - 点任意一条,右侧显示它的
SKILL.md全文与来源路径
文档
- 填一个目录、点 浏览… 选一个,或用下拉里的已登记工作区,然后扫描
- 同样的自动档位按钮:源或产物变了会提示「多了 N 份文档」,点【现在刷新】才重扫
- 按主干名把 md/tex/qmd 等源与 docx/pdf/xlsx 等产物配对
- 状态从「该先处理」往下排:
| 徽章 | 含义 |
|---|---|
| 🔴 产物过期 | 产物比源旧 —— 改过 md 却没重新生成 |
| 🟠 无源产物 | 只有产物没有同名源(历史遗留) |
| ⚪ 未生成产物 | 源还没有对应产物 |
| 🟢 已同步 | 产物不比源旧 |
安装
# 从本仓库源码安装(link: 方式,改源码只需重启宿主)
dsh plugin --profile web add link:/绝对路径/dsh-workbench
# 或从 GitHub 源码入口安装
dsh plugin --profile web add github:liiydong/dsh-workbench
装完必须重启 dsh web:客户端 bundle 在组合阶段被快照进内存,刷新页面不够。
工作台出现在哪里
| 落点 | 出现在哪 | 条件 |
|---|---|---|
| 官方左栏面板座位 | 系统左栏的面板图标行(sidebar.panellist + main),图标上带「待我审」角标 | 总是注册 |
| 官方右栏页签 | 右侧栏的一种页签类型(sidebarRight.tabs + sidebar.right.pane.tab 座位),对话留在中间 | 宿主有右栏服务才注册 |
| 侧栏页签 | 若你装了 dsh-better-sidebar,工作台也会作为它的一个页签出现(角标报同一个数) | 装了才注册 |
三条路互不影响,注册都挂在插件 fiber 上,卸载即消失。右栏与 dsh-better-sidebar 都是按存在性接的:
宿主没有右栏服务(ctx.get('sidebarRight') 返回 undefined)、或没装 dsh-better-sidebar,
就只是少一个入口,不报错、也不会让插件挂起。
和「文件变动」的关系:做的过程中发现侧栏里已经有一个「文件变动」,它看的是 「这次会话里 AI 碰过哪些文件」;「迭代」页想要的比这多一层 —— 跨会话、跨天、跨产线的版本线, 每一版带着摘要、快照路径和你审批的状态。所以这块是在它旁边做的派生,工作台同时也注册成了
dsh-better-sidebar的一个页签,两者在同一个入口里并存。
卸载:
dsh plugin --profile web remove dsh-workbench
它怎么拿到数据
宿主半侧只有 10 条 JSON 路由,全部在 /api/dsh-workbench/ 下:
| 路由 | 方法 | 数据来源 |
|---|---|---|
/skills?cwd= | GET | ctx.skills.snapshot() —— 核心技能注册表 |
/skill?name=&cwd= | GET | ctx.skills.get() —— 单个技能全文 |
/docs?dir= | GET | node:fs/promises 扫目录 + 源产物配对 |
/workspaces | GET | ctx.workspaceRegistry.list() —— 目录下拉 |
/history?dir= | GET | 读各产线的 .versions/produced.jsonl + decisions.jsonl |
/demo | GET | 插件自带演示项目的绝对路径 |
/decide | POST | 写操作①:往 <产线>/.versions/decisions.jsonl 追加一行审批 |
/reveal | POST | 碰外界②:用系统默认程序打开一版产物,或在文件管理器里定位它 |
/scan | POST | 找「还没进记录」的产物:比对记录与磁盘 + 去 ctx.sessionQuery 翻生成命令 |
/adopt | POST | 写操作③:复制一份快照到 .versions/,并往 produced.jsonl 追加一行 |
/decide 的写路径做了越界检查(只允许写根目录的直接子目录,拒绝 ..、分隔符与非法状态)。
除此之外不写任何文件,不发任何模型调用,不采集遥测。
/adopt 的边界同样收死:产线名只能是根目录的直接子目录名、文件名只能是产线目录里的文件名
(拒绝路径穿越与 ..)、文件必须真的存在且是允许的产物类型、快照同名时只新建不覆盖。
它只往 <产线>/.versions/ 里写,不会碰你的 Word 本身。
/scan 是只读的,但会去读 DSH 的会话日志(ctx.sessionQuery):只挑 cwd 等于该项目目录 的会话,
最多读最近 40 个会話、20000 条事件,找不到就读不到(面板上如实写「没找到生成命令」)。
/reveal 会启动外部程序,所以边界收得很紧——它只认白名单里的文档类型
(docx / pdf / xlsx / pptx / md / txt / csv / html / png / jpg…)、只认项目根目录里的文件、
文件必须真的存在。放行任意路径等于给网页一个「执行任意程序」的接口(.exe / .bat / .lnk),
所以这不是偏好而是安全边界。三个平台各一套启动方式,且都不经过 shell(参数以数组传给 spawn),
路径里的空格或 & 不会被当成命令解析。
另外两件事不经过本插件,走的是 DSH 自己的客户端服务:
- 选目录(迭代页/文档页的「浏览…」)→
uiWorkspace:原生选择器还是应用内浏览由部署决定,插件只接一个绝对路径字符串; - 返回对话 →
layout.selectPanel(null):把中央区域切回当前会话,不改变当前会话。 - 自动检测新内容 → 就是把已有的
/history(或/docs)再读一遍、算个指纹比一比,没有新增路由、没有文件监听; 探到变化只给一条提示,点【现在刷新】才真正重读并换掉屏幕上的内容。 - 「哪条命令生成的」 → 走 DSH 自己的会话查询服务
ctx.sessionQuery(listSessions+readSession), 只看 cwd 等于该项目目录的会话,把tool/call事件折出来做归因;读不到就降级成「没找到生成命令」。
开发
dsh-workbench/
├── package.json # dsh.bundle.patch + dsh.client.platform + dsh.engines.dsh
├── cordis.patch.yml # 把宿主行插进 profile 名册
├── lib/
│ ├── index.js # 宿主半侧:10 条路由(ESM,零依赖)
│ └── client.js # 浏览器半侧:侧栏图标 + 三个页签(手写 __ModuleLoader__ bundle,无构建步骤)
├── examples/
│ ├── make-demo.mjs # 生成演示项目(假内容、真结构,文件时间也拨回记录里那一刻)
│ └── thesis-workbench/ # 演示数据:3 条产线 27 轮
└── test/
├── smoke.mjs # 组件树自检:桩 __ModuleLoader__ + 迷你 React,184 项
└── host-routes.mjs # 路由自检:假 req/res + 真实文件系统 + 真写审批与快照,101 项
node test/smoke.mjs
node test/host-routes.mjs # 可选参数:/docs 要扫描的目录
node examples/make-demo.mjs # 重新生成演示数据
两套自检都不需要浏览器:客户端 bundle 的 factory 与组件都是普通函数,在 Node 里可直接跑
(迷你 React 运行时按组件实例分配 hook 槽、按「组件类型 + hook 序号」去重 effect、在渲染循环里展开整棵树)。
host-routes.mjs 还会在系统临时目录里真写一次审批、真收编一版产物(快照 + 记录),
并用桩会话服务验证 /scan 的归因反例(读入方、删除命令、别的项目)。抓得到崩溃与状态流转错误,抓不到布局观感。
已知限制
- 界面观感只过了组件树自检,未做人工视觉验证;浅色/深色都做了适配但与真实主题的贴合度可能还要调。
- 记录要靠产出方写。 插件不会替你写记录 —— 它只做两件事:显示
.versions/produced.jsonl里已有的, 以及把「目录里有、记录里没有」的那些列出来(【未入记录】),由你点一下才收编成一版。 收编写进去的摘要就是你那一句话,插件不猜内容。 - 「还没进记录」的生成命令是靠会话日志反推的,只在两种情况下给得出:那次产出发生在 DSH 会话里、
且命令里带着输出路径(
--out/-o/ 重定向 / 构建脚本 + 文件名)。别的工具改的(WordBuddy、手动另存) 一律显示「没找到生成命令」——宁可不给,也不编。 - 历史上已经同名覆盖掉的版本,找不回来(快照是从设了记录器之后才开始存的); 但【未入记录】至少能把「现在这一版」先收编下来,从这一刻起它就有了快照。
- 「产物过期」按文件修改时间判定,不看内容。生成脚本若先 touch 产物再写内容,判定会偏乐观。
- 目录扫描不递归,只扫一层;一次最多返回 1000 轮。
- 自动检测是定时重读记录文件(默认 10 秒一次,可在档位按钮里放慢或关掉),不是操作系统的文件监听; 浏览器标签页在后台时那一拍会跳过,所以切回来可能要等一拍才提示。
- 审批写的是记录文件,不会真的去改动或回滚你的 Word。
- 「上一步/下一步」的历史只在面板活着的时候有效:刷新页面、或工作台被切走再切回来,那条历史就空了 (但位置会记住——目录落在 localStorage 里,技能库打开的技能、筛选、选中轮次也随面板状态保留)。
关于这个仓库:作者与 AI 披露
- 本仓库的代码由 DeepSeek Harness 中的 AI Agent 生成,由仓库所有者审阅、验证并发布;
每次提交的验证方式记录在
CHANGELOG.md(不需要浏览器的两套自检,共 285 项)。 - 演示数据全是编的(
examples/thesis-workbench/,3 条产线 27 轮),不含任何真实文档或个人信息; 仓库里不含本机路径、姓名或邮箱,提交作者统一使用 GitHub 的 noreply 邮箱。 - 以 MIT 发布(见
LICENSE)。 - 第三方接口说明:与
dsh-better-sidebar的集成使用它公开的客户端服务契约 (ctx.betterSidebar.registerTab),未复制其代码。该契约按其文档是版本化的(SIDEBAR_FEATURES), 所以本插件把它当作可选依赖:读不到就只走官方座位。
License
MIT