产出画廊 · DeepSeek Harness (DSH) 插件
核心问题:Agent 干活的过程是黑箱。 DSH 默认只在任务结束时才列出产出的文件名和路径 —— 中间生成的东西你看不见, 出了偏差只能等它跑完再返工。
这个插件把产出目录变成右侧栏里的实时画廊:图片生成就可见、文件点开就预览、 视频音频能在面板里直接播放并拖进度条, 让你在任务进行中就能看到产出、当场校验,而不是结束后才发现做错了。
📌 当前是
0.x开发预览版:核心能力(实时看见、当场校验、就地处置)已可用。 v0.13.6 起,判定还能攒成一条结构化决策草稿写进输入框,由你确认后发送(L1 人工闸门); 自动 steer / 喊停正在跑的任务(L2 / L3)尚未实现,列在路线图里。一句话边界:能看见 → 能表态 → 能把判断写成草稿送回对话(人确认); 不能自动改变一个正在跑的任务。
关键词:DeepSeek Harness 插件 · DSH plugin · 产出画廊 · output gallery · 图片画廊 · image gallery · 文件预览 · file preview · 音视频画廊 · media gallery · 视频预览 · video preview · 反馈篮 · feedback basket · 决策草稿 · decision draft · 人工确认 · human-in-the-loop · 侧边栏面板 · sidebar panel · Agent 产出管理 · output artifacts
它解决什么问题
痛点:任务跑完你才知道结果
让 Agent 出 30 张图、改一份报告、跑一段脚本 —— 它埋头干几分钟甚至几十分钟, 你看不到中间产物。等到最后它念一串路径,你才发现:
- 第 3 张图的风格就跑偏了,30 张全废
- 报告第 2 段引用了错的资料,整篇要重写
- 脚本输出了 50 个中间文件,你不知道哪个是最终版
返工成本是「一次性验证」的好几倍。
解法:把产出目录变成实时画廊
插件直接盯磁盘上的产出目录(Agent 真正写文件的地方),做成右侧栏画廊:
| 效果 | 说明 |
|---|---|
| 图生成就可见 | 每 5 秒自动刷新,缩略图实时滚动,不顺眼当场喊停 |
| 文件点一下就开 | 调 DSH 原生侧栏预览(Markdown 渲染、代码高亮都是 DSH 自己的) |
| 只看本对话的 | 多个对话混在一起时,按「当前对话」筛,一眼分清谁做的 |
| 过程产物也算 | 脚本跑出来的中间图、Agent 打开看过的图,都算本对话产出 —— 过程不黑箱 |
和市面上的「对话图片查看器」不一样在哪
| 常见的对话图片灯箱 | 这个插件 | |
|---|---|---|
| 看哪里 | 对话里的图片附件 | 磁盘上的产出目录(Agent 真正写文件的地方) |
| 能看什么 | 只有图片 | 图片 + 49 种文档/代码/数据文件 |
| 怎么打开文件 | 无 | 调 DSH 原生侧栏预览 |
| 多个对话混在一起 | 不区分 | 可按「本对话产出」筛 |
| 过程产物 | 看不到 | 脚本生成的中间图也能看到 |
一句话安装:解压后在该目录执行
node install.mjs,然后重启 DSH。 详细步骤见安装,常见问题见已知限制。
功能
反馈与干预速览(v0.13.6)
过程可见只是第一步;看到不对的产出能当场表态、并把判断送回主线,才是这个插件的落点。
| 能力 | 状态 | 说明 |
|---|---|---|
| 过程可见 / 中间产出即时展现 | ✅ | 5 秒刷新;含脚本生成图、read_image 看过的图、子智能体会话产出 |
| 三类资产当场校验 | ✅ | 图片放大 8×、文件原生侧栏预览、音视频面板内播放 / 拖进度条 / 截帧 |
| 反馈篮 6 动作(表态 / 定稿) | ✅ | 通过 · 不要这张 · 定为最终版 · 单维重做 · 选择性重生成 · 设为参照 |
| 结构化决策草稿(L1 人工闸门) | ✅ | 攒成一条带 Annotation N 编号的反馈写进 composer 草稿,零自动发送、人确认再发 |
| 对外接口 seam v1 | ✅ | window.dshGallerySeam 暴露选择 / 标记 / 草稿只读能力,不含发送能力 |
| 按标记筛选 / 批量处置 | ❌ | 未实现 |
| 自动 steer / cancel 正在跑的任务(L2 / L3) | ❌ | 未实现;规划放独立包、默认关 |
完整约束见下文「反馈篮」「对外接口(seam v1)」「路线图」三节。
图片画廊
| 能力 | 说明 |
|---|---|
| 自动刷新 | 每 5 秒轮询,查看大图时自动暂停,不打断 |
| 缩略图 | 客户端 canvas 压缩到 200px(不是原图直塞),并发闸门 4,进入视口才加载 |
| 放大查看 | 滚轮缩放(最大 8×,以光标位置为中心)、左键或中键拖动平移 |
| 看原图 | 缩略图右键「查看原图」直接按原图像素打开;查看器内切换原图/预览(底部按钮、右键、O 键),切换时保留缩放与位置;按需加载并显示进度,不预加载 |
| 音视频画廊 | 视频/音频独立第三栏;视频首帧缩略图(进入视口才抽帧);点击面板内播放(原生 <video>/<audio>,支持拖动进度条);体积上限 200 MB 可调 |
| 右侧控制列 | 放大后出现 ▲ / ▼ 按钮 + 轨道 + 滑块,没有鼠标也能操作 |
| 键盘操作 | ↑↓ 平移、←→ 切图、+/- 缩放、0 复位、O 切原图、Esc 退出 |
| 退出回位 | 单击退出,回到原来那张缩略图的精确位置,不是列表顶部 |
文件画廊
列文档 / 代码 / 数据 / 压缩包 / 链接 / 设计文件,共 49 种扩展名(不含图片,图片归图片画廊)。
| 操作 | 行为 |
|---|---|
| 点整张卡片 / 点「👁 预览」 | 用 DSH 原生侧栏打开预览(Markdown 渲染、代码高亮都是 DSH 自己的) |
| 点「⧉ 复制路径」 | 复制绝对路径,按钮变 ✓ 已复制 反馈 1.6 秒 |
| 点「📂」 | 在资源管理器里定位该文件 |
音视频画廊
列视频 / 音频,共 16 种扩展名(视频 7 种、音频 9 种),独立第三栏,不混进文件列表。
| 操作 | 行为 |
|---|---|
| 点整张卡片 | 在面板内弹出播放器 |
| 视频卡片 | 显示首帧缩略图(进入视口才抽帧,避免一次解码几十个视频) |
| 拖进度条 | ✅ 实测 127 MB 可拖;GB 级未做大小分档 —— 面板内播放走 blob(整包载入后交给浏览器,可任意 seek) |
| 点开到可播 | ✅ 1.1 MB 视频与 96.5 秒音频约 250 ms;127 MB 实测 502 ms,可正常拖动;GB 级未做大小分档 |
| 播放器里「📷 截取当前帧」 | 把当前画面存成 PNG,文件名带时间码(如 片子_00-12-34.png) |
| 浏览器解不了格式 | 显示提示 +「▶ 用系统播放器打开」/「📂 打开所在文件夹」两个按钮 |
技术说明:媒体走双通道
- 独立 Range 路由(
ctx.webServer.register,Node 原生req/res): 支持Range: bytes=...的常规区间、开放区间、后缀区间,以及 416 不可满足。 用于视频首帧缩略图抽取、直链读取与其他需要分段读取的场景。- 面板内播放走
/api网关的raw+ blob:POST {op:'raw'}取完整字节,URL.createObjectURL(blob)交给<video>/<audio>。为什么播放要单独走 blob:DSH 桌面宿主在转发 Host 响应时会剥掉
Content-Length(见app.asar的lib/main.js,WITHHELD_RESPONSE_HEADERS), Chromium 因此拿不到文件总长度,<video>.seekable恒为[0,0]—— 能播但进度条拖不动。blob 是完整数据在内存,Chromium 知道确切大小,seekable覆盖全长。代价:播放时整包载入内存(等于文件大小)。实测 127 MB 用时 502 ms; GB 级文件暂未做大小分档,仍会走 blob。
体积上限默认 200 MB,可用
maxMediaBytes调整。
右键菜单(图片 / 文件 / 音视频通用)
在任意卡片上右键:
| 项目 | 行为 |
|---|---|
| 🚫 隐藏 | 从列表移除,文件仍在磁盘上(记录在 dsh-gallery.json 的 hidden 数组) |
| 📂 打开所在文件夹 | 资源管理器定位到该文件 |
| ⧉ 复制路径 | 复制绝对路径到剪贴板 |
| 🗑 删除文件 | 二次确认后删除,不可恢复 |
⚠️ 删除做了三层保护:前端确认弹窗 → API 强制
confirm: true→ 路径越界校验。
分页
三个画廊都是每页 60 条,网格下方常驻页码条:
第 1–60 条 · 共 114 条 还有 54 张
‹ 1 2 ›
- 页码点击切页,页数多时折叠为
1 … 5 6 7 … 20 - 切换「图片/文件」或「全局/当前对话」时自动回到第 1 页
- 列表右侧另有
▲/▼滚动辅助列(无滚轮时用)
分类开关
[全局资产] [当前对话]
- 全局资产:所有扫描目录下的全部内容,按时间倒序
- 当前对话:只显示本对话真正产出过的文件 —— 通过解析 DSH 会话日志得出,不是按时间瞎猜
「当前对话」的产出判定覆盖三类来源:
| 来源 | 为什么算 |
|---|---|
write / edit / present |
Agent 直接写文件 / 声明交付,路径必是产出 |
read_image 打开过的图片 |
Python 等脚本生成的图不在 write 调用里,但 Agent 一定会打开看效果 |
| 子智能体会话里的产出 | 子智能体是独立会话,按 parentSession 合并它的产出路径 |
这条设计是为了「任务进行中即时可见、便于及时校正」—— 中间产物、调试图、脚本输出都能在过程里看到,而不是等任务收尾。
多扫描目录
DSH 每个会话有独立的 cwd,产出可能落在任意工作区(如 G:\DSH任务文件\xxx),
不一定都在 G:\DSH产出。所以插件支持配置多个扫描目录:
{
"roots": [
"G:/DSH产出",
"G:/DSH任务文件"
]
}
设置面板(⚙)里可直接增删,每项显示 ✓(存在)/ ✗(缺失标红)。改完立即生效。
产出根目录可配置
装上不会空白。 插件按这个顺序确定扫描目录:
- 面板内设置的多目录列表(最高优先,改完立即生效,无需重启)
- 设置或
cordis.patch.yml里的单个root(旧写法,兼容) - 自动探测:先找
~/DSH产出、~/Documents/DSH产出、~/Desktop/DSH产出、~/Downloads/DSH产出,再遍历各盘符找<盘符>:\DSH产出(循环 A–Z,代码里不写死任何字母,所以装在哪个盘都能自动找到)
一个目录都没有时,面板会直接给出「修改产出根目录」的入口。
隐藏管理
设置面板(⚙)里有「已隐藏 N 个 · 全部恢复」:
- 列出被隐藏的文件(前 10 个,悬停看完整路径)
- 点「全部恢复」一键清空隐藏列表,被隐藏的重新出现
- 恢复不删除文件,只是清掉记录
安装
前置
已安装 DSH 桌面版或 Web 版。
方式零:DSH 插件页一行装(最省事)
打开 DSH → 插件页 → 添加插件 → 安装第三方插件,在输入框粘贴:
github:Hope2023Dcr/dsh-gallery
点安装。DSH 会自动从 GitHub 拉取并装好,装完重启 DSH。
想锁定版本(推荐,避免仓库更新导致行为变化)—— 钉 tag,不要钉 commit:
git+https://github.com/Hope2023Dcr/dsh-gallery.git#v0.13.6tag
v0.13.6对应本次发布(含 S3-a 反馈契约、blob 播放可拖、reveal 修复)。 钉 commit 会随文档回填提交而漂移,且不利于复现安装来源。 更推荐直接用 npm(npm i dsh-gallery)—— 版本号跟着latest走,不用每次手工核对哈希。
⚠️ 这种方式需要能访问 GitHub。DSH 的「国内镜像」只代理 npm 包,不代替 GitHub 下载。
方式一:一键脚本(推荐,可离线)
下载本仓库(Code → Download ZIP,或 git clone),解压后在解压出来的目录里执行:
node install.mjs
脚本会自动:
- 定位 DSH 主目录(优先
DSH_HOME环境变量,回退~/.dsh) - 列出可用 profile 让你选(只有一个时直接用它,多个时默认
desktop) - 校验源文件齐全
- 复制到
<profile>/node_modules/dsh-gallery/ - 在 profile 的
package.json的bundles里注册 - 自动备份原
package.json为package.json.bak-dsh-gallery
可选参数:
node install.mjs --profile desktop # 指定 profile
node install.mjs --dry-run # 只看会做什么,不写入
脚本幂等:重复运行不会重复注册 bundle。
装完必须重启 DSH(不是刷新页面)。
方式二:手动安装
1. 找到你的 profile 目录
| 平台 | 路径 |
|---|---|
| Windows | %USERPROFILE%\.dsh\profiles\desktop |
| macOS / Linux | ~/.dsh/profiles/desktop |
如果
desktop不存在,看你实际用的 profile 名(profiles/下有什么就用什么)。
2. 复制文件到插件目录
目标:<profile>/node_modules/dsh-gallery/
需要放进去的文件:index.js、client.js、package.json、cordis.patch.yml、icon.svg、locale/。
Windows PowerShell:
$target = "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-gallery"
New-Item -ItemType Directory -Force -Path $target | Out-Null
Copy-Item index.js,client.js,package.json,cordis.patch.yml,icon.svg -Destination $target
Copy-Item locale -Destination $target -Recurse
macOS / Linux:
target=~/.dsh/profiles/desktop/node_modules/dsh-gallery
mkdir -p "$target"
cp index.js client.js package.json cordis.patch.yml icon.svg "$target/"
cp -r locale "$target/"
3. 在 profile 的 package.json 里注册
编辑 <profile>/package.json,在 bundles 数组里加一项:
{
"bundles": [
"dsh-gallery"
]
}
4. 重启 DSH
必须完全退出再打开(不是刷新页面)。Host 半部只在进程启动时加载。
5. 打开面板
三个入口任选:
- 左侧边栏脚部的
▦按钮 - 对话输入框右侧的
▦按钮 - 右侧栏点
+,「开始」页里的「产出画廊」卡片
6. 设置产出目录
第一次打开如果显示「目录不存在」,点面板里的 ⚙ 设置,填入你的产出目录绝对路径,保存即可生效。
配置项
编辑 <插件目录>/cordis.patch.yml:
config:
# root: 'D:/产出' # 不填就走自动探测
maxItems: 60 # 每页显示多少条
maxDepth: 3 # 向下扫描几层目录
skipDirs: # 跳过这些目录(体积大、无产出)
- node_modules
- .git
- ...
⚠️ 不建议在这里写
windowHours。 它是「扫描多久以内的文件」,代码默认是一整年 (为了让「全局按对话分组」能覆盖历史对话)。在这里写死24会让分组只剩当天产出。 确实想改的话,写进<DSH_HOME>/dsh-gallery.json更合适。
改完重启 DSH 生效。只想改目录的话,建议用面板里的 ⚙ 设置,那个不用重启。
兼容性
| 项 | 说明 |
|---|---|
| 宿主 | DSH 桌面版 / Web 版,bundle 走 web 平台;本机已在 DSH 0.2.1-rc.5 上完整验证 |
| 最低宿主版本 | 未声明硬下限(不编造);0.2.2-rc.1 上未做完整回归,从新版回退过的用户请先看「已知限制」第 1 条 |
| Node | >= 20(仅安装 / 校验脚本需要;插件运行时零第三方依赖) |
| 系统 | Windows / macOS / Linux 都能装;「打开所在文件夹」定位到文件仅 Windows,其他平台打开所在目录 |
| 安装后 | 必须完全退出并重启 DSH —— Host 半部只在进程启动时加载,刷新页面不够 |
| 宿主内部格式依赖 | 「当前对话」判定与自动标记读 session.v4.jsonl.zstd 的 tool/call 事件,DSH 大版本升级后需回归验证;其余功能不受影响 |
| 权限边界 | 只读扫描目录,状态只写两个本地 JSON;删除文件需二次确认且不可逆。详见安全与隐私 |
更完整的兼容性说明(含媒体双通道与宿主
Content-Length剥除行为)见仓库内RELEASE.md的「兼容性」一节。
已知限制
- 「当前对话」分类依赖 DSH 会话日志格式。 它读
~/.dsh/sessions/<分组>/<sessionId>/session.v4.jsonl.zstd,解析tool/call事件。DSH 大版本升级后这个功能可能失效,需要回归验证;其余功能不受影响。 - 只扫一层层的目录,深度默认 3 层。 特别深的目录结构会漏。
- 扫描时间窗口默认一年。 文件多时会拖慢扫描(已做缓存,稳态约 150 ms)。
- 文件画廊故意不显示图片。 图片归图片画廊,避免两边重复。
reveal(资源管理器定位)仅 Windows 有效。 其他平台会走xdg-open打开所在目录。- 「未归类」是诚实的不知道。 会话日志里查不到归属的文件标为「未归类」, 而不是猜成某个类别。这类文件多来自早期对话或手动拷入。
安全
风险等级:低。 插件只读你本机的产出目录,不上传、不执行任何文件。
| 能力 | 做法 |
|---|---|
| 文件预览 | 全部委托 DSH 原生侧栏预览器,插件自己不渲染文件内容 —— 不引入第二套渲染栈 |
| 不执行任何文件 | 不运行脚本、不打开可执行文件、不解压压缩包。唯一的 spawn 是调起资源管理器定位(explorer.exe /select) |
| 删除操作 | 三层保护:前端二次确认 → API 强制 confirm: true → 路径越界校验(isInside(root)) |
| 路径穿越防护 | 所有文件操作都经 isInside(root, absolute) 校验,只允许在扫描目录内 |
| HTTP 路由 | POST /api/dsh-gallery(走 DSH /api 网关,需认证)负责列表与操作;另有一条独立 GET/HEAD 媒体路由用于缩略图抽取与分段读取(详见下方「媒体路由的安全边界」)。面板内播放的整包读取也走 /api 网关的 raw 分支,不经 dsh-app:// 媒体管线 |
删文件是唯一不可逆操作,所以做了三层保护;隐藏只是从列表移除,文件不动。
媒体路由的安全边界
音视频播放需要浏览器能直接 GET 一个 URL(<video src> 无法发 POST),
所以 v0.11.0 新增了一条独立的媒体路由。它不属于 /api 网关、不经过 DSH 的网关认证,
因此该路由自己做了三道防护:
| 防护 | 做法 | 不做的后果 |
|---|---|---|
| ① 只读 | 只接受 GET / HEAD,其余 405 |
可被当作写入跳板 |
| ② 跨站拒绝 | Sec-Fetch-Site: cross-site 或 Origin 与 Host 不匹配 → 403 |
任意网页都能盗读本机产出 |
| ③ 路径白名单 | 解析后必须落在扫描的 roots 内(isInside),.. 无法越界 |
能读磁盘任意文件 |
| 附带 | X-Content-Type-Options: nosniff |
浏览器可能把内容当 HTML 执行 |
这三道是必需的,因为路由绕过了网关认证。若在代码里看到这条路由却没有校验, 那是缺陷不是设计。
隐私
全部本地,不出机器。
| 项 | 说明 |
|---|---|
| 网络请求 | 零。插件不发起任何外部请求(已核查源码:无 http/https/axios/WebSocket 引用) |
| 上传 | 无。没有任何上报、遥测、统计 |
| 状态落盘 | 两个 JSON,都在 <DSH_HOME>/:dsh-gallery.json(扫描目录 / 隐藏列表等设置)、dsh-gallery-marks.json(v0.13.0 起新增,卡片判定标记)。没有第三个写入位置 |
| 写入方式 | 主路径走 tmp → rename 原子写:host/settings.js 的 writeSettingsFile、host/marks.js 的 writeMarks(后者 tmp 名带 pid + 随机后缀,并发写不会互踩)。host/http.js 另有若干处直接 writeFile |
| 会话日志 | 只读。解析 ~/.dsh/sessions/** 来判定「本对话产出」,不修改、不复制、不上传 |
| 文件内容 | 预览时实时读磁盘,不写进任何索引或缓存文件 |
| 内存缓存 | 会话日志解析结果只在内存里(按 路径+mtime+size 做键),不落盘 |
会话日志里包含你的对话内容,插件只抽取
tool/call事件里的文件路径, 不读取、不存储对话文本。
两条边界容易混淆,单独说明:
- 「📷 截取当前帧」不在上面这张表里 —— 它走浏览器原生
a.download(src/client/07-media.js),不经过插件服务端写盘,图片落到你浏览器默认的下载目录。 - 「🗑 删除文件」是显式不可逆操作 —— 需二次确认,属于用户主动发起的删除, 见安全章节;它不是"插件在后台落盘",不要和上面的状态文件混为一谈。
对外接口(seam v1)
插件在 window.dshGallerySeam 暴露一个窄接口,供独立包 / 扩展使用:
window.dshGallerySeam = {
version: 1,
getSelection(), // → { relPath, source: 'viewer'|'context-menu'|'focus' } | null
subscribe(fn), // → 退订函数;选中项变化时回调
getMarks(), // → Promise<{ [relPath]: mark }>
setMark(relPath, mark),// → Promise<boolean>
getFeedbackDraft(), // → 最近一次生成的草稿文本
}
卡片 DOM 兜底:所有卡片(图片 / 文件 / 音视频)都挂
data-gallery-relpath="<relPath>",独立包可直接按属性定位,不依赖 React 内部状态。
这个 seam 不含发送能力 —— 发送永远由用户在 composer 里手动确认。
反馈篮(v0.13.0 起)
标记产出 → 攒成一条结构化反馈 → 写进输入框草稿 → 由你确认后手动发送。
| 动作 | 用途 |
|---|---|
| 通过 / 不要这张 / 定为最终版 | 判定 |
| 单维重做 | 只改某一维(色调 / 材质…),其余锁死 |
| 选择性重生成 | 只重做选中的这几张 |
| 设为参照 | 作为同批的参照 |
设计约束(有意为之):
- 零自动发送:只写 composer 草稿,发送永远由人点。
- 零 inject:不声明
conversation.input/sessions/agents—— 实测声明它们会让插件在 web boot 阶段 pending、宿主判定启动失败。 草稿写入走纯 DOM 多路径,全部失败才落剪贴板(并明确提示「未写入输入框」)。 - 历史会话禁用提交:条目归属会话与当前会话不同时,提交按钮禁用。
- 契约不是自由文本:导出 JSON 与渲染文本同一 shape,带
Annotation N编号。
路线图
已实现(v0.13.6)
| 能力 | 说明 |
|---|---|
| 反馈篮 6 种动作 | 通过 / 不要这张 / 定为最终版 / 单维重做 / 选择性重生成 / 设为参照 |
| 结构化决策草稿(L1) | 攒批后按 Annotation 1..N 生成一条草稿,只写进 composer、零自动发送,由人确认 |
| 对外 seam v1 | window.dshGallerySeam —— 只读,不含任何发送能力 |
| 过程可见与即时校验 | 三类资产(图片 / 文件 / 音视频)实时刷新、面板内预览与播放 |
| 反馈篮独立入口 | 右下角常驻「反馈篮 · N 条」按钮,关掉也能重开(v0.13.5) |
未实现(诚实标注,不画大饼)
| 计划 | 状态 |
|---|---|
| 版本历史(同一文件跨 turn 的修改点,只存 size/mtime) | ❌ 未实现 |
交付物手动 pin(除 present 之外的标记方式) |
❌ 未实现 —— 注意区别:反馈篮的「定为最终版」产出的是一条判定草稿(文本,人要确认),并不等于把 pin 写进主线状态;真正的"标记进状态"还没做 |
| 按标记筛选 / 批量处置 | ❌ 未实现(能表态了,但还不能按判定筛出卡片、批量处理) |
| 图片框选批注 / A/B 对比 / 参数回灌(S3-b) | ❌ 未实现 |
| 自动 steer / cancel 独立包(S3-c) | ❌ 未实现 |
| 跨扫描目录合并视图(目前按目录分组) | ❌ 未实现 |
| 文件内容变更 diff 预览 | ❌ 未实现 |
已在考虑但不确定做不做:
- HTML/SVG 预览 —— 这类文件由模型生成,直接渲染有执行脚本的风险。当前策略是
「交给 DSH 原生预览器」,跟着宿主的安全策略走。如果将来自己做,会强制
<iframe sandbox="">。
和其他产出类插件的区别
DSH 生态里有几类「产出管理」插件,容易混淆。本插件的定位是「过程可见」,不是「事后归档」:
| 本插件 | 事后归档类插件(如 dsh-output-gallery) |
|
|---|---|---|
| 何时出现 | 实时,5 秒刷新 | 任务/turn 结束时批量扫描 |
| 要解决的问题 | 跑到一半发现方向错了要返工 | 任务产出了几十个文件,分不清哪个是最终版 |
| 交付物判定 | 自动(present 声明过) |
需要手动标记 |
| 典型场景 | 批量出图时边看边纠正提示词 | 项目结束整理归档 |
两者互补而非重复:想看过程用这个,想归档历史用那个。
开发:改源码后必须构建
client.js 是构建产物,改源码后不构建,改动不会生效:
npm run build # 生成 client.js(并同步生成 host/protocol.js)
npm run build -- --check # 只读比对,产物与源码有漂移会报错
源码在 src/:
| 路径 | 内容 |
|---|---|
src/client/0X-*.js |
13 个分片(主容器、组件、工具) |
src/client/styles.css |
面板样式(构建时内联进 client) |
src/shared/protocol.js |
协议单一真源:ROUTE / MEDIA_ROUTE / 扩展名表 |
src/build-manifest.json |
分片顺序与产物配置 |
为什么必须构建:DSH 加载器要求
client.js是单文件、单个__ModuleLoader__.load、无 ESM import/export —— 源码可以分模块,但产物必须合并。 Host 侧不同:Node 原生支持多文件,所以host/是源码即产物,零构建。
技术说明(给想改代码的人)

结构
dsh-gallery/
├── package.json # bundle 清单 + dsh.client(web 平台)
├── cordis.patch.yml # 插入 Host 行与默认配置
├── index.js # Host 半部:扫描、图片尺寸解析、会话日志解析、HTTP 路由
├── client.js # 浏览器半部:侧栏 tab UI、缩略图、放大、设置面板
├── locale/zh.json # 中文文案
├── locale/en.json
└── icon.svg
两端分工
| 半部 | 能做什么 | 不能做什么 |
|---|---|---|
index.js(Host) |
读文件、扫目录、注册 HTTP 路由、调 child_process |
不能碰 DOM |
client.js(浏览器) |
渲染 UI、拿 slot props、调 ctx.sidebarRight.* |
不能直接读磁盘 |
数据流:client fetch(ROUTE) POST 给 host → host 处理 → 返回 JSON / 字节。
⚠️ 改代码后怎么生效
| 改了哪个文件 | 生效方式 |
|---|---|
client.js |
刷新页面(秒级) |
index.js |
必须重启 DSH 进程(分钟级) |
判断「跑的是不是新代码」:哈希一致 + 进程启动时间 < 文件修改时间 → 运行的是旧模块,需要重启。
关键实现点
- 图片尺寸解析:Host 侧直接读 PNG IHDR / JPEG SOF / WebP VP8·VP8L·VP8X 头部,零依赖
- 缩略图压缩:客户端
createImageBitmap+OffscreenCanvas,不把原图塞进<img> - tab 保活:注册 tab 类型时声明
keepMounted: true,切到别的 tab 再回来状态不丢 - 原生预览地址:
dsh-resource://file/session/<sessionId>/<编码路径>,文档预览只认 session 作用域 - 会话日志多帧 zstd:DSH 日志每次追加写一帧,必须按魔数
0x28 0xB5 0x2F 0xFD切开逐帧解压,单次解压只能拿到第一帧
许可
MIT。随便用、改、分发。
No comments yet. Be the first to write one.