DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Sun-0306 /

Sun-0306/dsh-plugin-dock

Verified

One dock for every DSH plugin: a right-side plugin dock beside the conversation plus a left-sidebar hub entry, so plugins can be called without leaving the chat. · 插件坞:把 DSH 全部插件入口收进右侧窗口,聊天时随手调用,不必来回切页面。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@531607cb

dsh-plugin-dock · 插件坞 (Plugin Dock)

一个坞,装下所有插件。 插件坞把 DSH 已安装插件的入口收进右侧栏窗口,让你在对话旁边直接调用插件、不必来回切页面;左侧栏的「插件坞」是它的总入口,输入框左侧的按钮是一键直达。

英文名:Plugin Dock · 包名 dsh-plugin-dock · 版本 0.1.0 · 双半插件(Host + Web Client)。

一句话介绍

插件坞 = 右侧随手调用面板 + 左侧全局入口页 + 输入框一键按钮。它不重写任何插件的界面,只通过 DSH 的公开插槽发现入口、通过公开 API 打开它们,并把「上下文归纳」作为内置快捷动作示范跨插件调用。

入口与界面

四个入口,同一个产品:

位置 形态 作用
左侧栏面板列表(与「插件」「定时任务」同级) 插件坞 行(sidebar.panellist,id plugin-dock,order 60) 全局门户:默认点击即跳转右栏
右侧栏 插件坞 标签页(tab type id dsh-plugin-dock,kind plugin-dock) 主要工作面板:搜索、快捷动作、右侧面板、插件页、已安装插件
右侧栏「+」指南 右侧栏指南条目(order 45) 从右栏指南里再次打开插件坞
对话框输入框工具行左侧 插件坞按钮(conversation.input.left,id plugin-dock-launcher,order 35) 一键打开右栏插件坞,最快的「一边对话一边调用」路径

左侧栏入口(门户行为)

  • 点左侧栏「插件坞」:默认不会停在整页,而是把主栏切回对话,并在右栏打开插件坞(leftEntryOpensDock 默认 true)——因为插件的价值在于「不离开对话」。
  • 需要完整控制中心时,在插件坞页或面板底部点「打开完整设置页」:此时才真正进入整页(状态栏 / 目录 / 设置 / 自检 / API 文档都在整页里)。
  • 如果当前没有会话、右栏无法打开,会留在整页并给出温和提示,页面内容照常可用;若在这约 0.5 秒里你已经切到别的面板,则不会被强行拉回(导航守卫)。

右栏插件坞(工作面板)

右栏面板从上到下:

  1. 标题行:图标 + 插件坞 + 会话标识 + 搜索框 + 刷新 + 设置页(右栏的收起/关闭由 DSH 外壳控件提供,插件坞不重复实现,CONTRACT §1.2 已同步);
  2. 快捷动作:内置「上下文归纳」卡片(用量条 + 一键归纳)等适配器驱动动作;
  3. 右侧面板:枚举其他插件注册过的右栏 tab 类型,点一下就在右栏打开该插件;
  4. 插件页:枚举其他插件注册的左侧栏页面,提供「整页打开」;若该插件同时有右栏 tab,还提供「右侧打开」;
  5. 已安装插件:来自 Host 目录 API 的完整清单(图标、名称、版本、安装方式徽章、client/host 能力徽章、收藏星标、描述、来源包),点行展开详情与可用入口;
  6. 底部状态:偏好同步状态(已保存 / 本地缓存 / 无宿主)、设置页入口、刷新。

搜索框同时过滤上述所有区块(名称 / 标题 / 描述)。面板在 320–900 px 宽度与全屏下均可用,窄屏自动全屏,密集模式(density=compact)会收紧行高。

输入框一键按钮

输入框左侧(+ 按钮附近)的插件坞按钮:一次点击把插件坞开在右栏,不打断当前对话。没有会话、右栏打不开时,按钮会退到插件坞整页(sessionStorage 一次性允许标记),真正的调用入口在页内显示「先打开一个会话」的空状态,而不是静默失败。

右栏指南入口

右栏标签条上的「+」会打开右栏指南;指南里有「插件坞」条目(order 45),点击即打开插件坞标签页。插件坞标签页 keepMounted:切走再切回不会丢失搜索词和展开状态。

安装与卸载

前置条件

  • DSH 0.2.1 系 Web/Desktop 组合,desktop profile 已由 DeepSeek Harness 初始化一次;
  • Host 半要求 Node ≥ 20(随 DSH 运行,无需你自己起进程);
  • 本仓库无构建步骤、无第三方依赖,直接以本地目录安装。

推荐安装(桌面版:一行脚本,不需要 pnpm / CLI)

在你自己的终端里:

cd /Users/sunchujun/Desktop/deepseek/dsh-plugin-dock
sh scripts/install-manual.sh

这个脚本不依赖 pnpm 或 dsh plugin(Desktop profile 由 Electron 应用托管,CLI 可能拒绝写入),而是手工完成 link: 安装的等价动作:备份 profile 的 package.json → 把本仓库软链进 <profile>/node_modules/dsh-plugin-dock → 写入 link: 依赖与 dsh.profile.bundles 行 → 验证 JSON 与 Node 解析。本机就是用它安装的(2026-10-28 已装入真实 desktop profile)。运行中的 DSH 可能通过 profile HMR 直接热加载 Host 半;客户端界面在**刷新 GUI(⌘/Ctrl+R)**后出现,若仍未出现则彻底退出并重启 DSH。快速自检:不带 token 请求 http://127.0.0.1:57598/dsh-plugin-dock/api/health 返回 401 JSON 说明 Host 半已加载;带页面 token 请求则返回 ok:true。

也可以直接用打包好的安装包(同样不碰源码目录):

dsh plugin --profile desktop add file:/Users/sunchujun/Desktop/deepseek/dsh-plugin-dock/dist/dsh-plugin-dock-0.1.0.tgz

备用安装(CLI / pnpm)

在你自己的终端里(不要在沙箱内、不要写 ~/.dsh 以外的路径):

cd /Users/sunchujun/Desktop/deepseek/dsh-plugin-dock
sh scripts/install-local.sh

脚本会:

  1. 校验仓库(index.js / client.js / package.json);
  2. 备份 profile 的 package.json 为 package.json.bak.<时间戳>;
  3. 调用 DSH 插件管理器做本地 link 安装(link: 不下载 registry 包);
  4. 幂等地把 dsh-plugin-dock 补进 dsh.profile.bundles(保留 2 空格缩进);
  5. 打印重启与卸载说明。

桌面版注意:npm 全局安装的 dsh 会拒绝 desktop profile(报 “profile "desktop" is managed exclusively by the Electron application”)。install-local.sh 会自动优先使用 DeepSeek Harness 自带的 CLI(自带 pnpm):

"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" \
  plugin --profile desktop add "link:/Users/sunchujun/Desktop/deepseek/dsh-plugin-dock"

手动安装命令

如果不用脚本,等价的核心命令是:

dsh plugin --profile desktop add link:/Users/sunchujun/Desktop/deepseek/dsh-plugin-dock

然后确认 profile 的 package.json(~/.dsh/profiles/desktop/package.json)里有 bundle 行:

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "dsh-plugin-dock"
      ]
    }
  }
}

dsh plugin add link:… 在插件管理器可用时会自动补这一行;若没有,手动把 "dsh-plugin-dock" 追加到 dsh.profile.bundles 末尾。

重启 DSH 后生效:完全退出 DeepSeek Harness 再打开(web profile 则重启 dsh web)。启动后左侧栏出现「插件坞」,输入框左侧出现插件坞按钮,右栏「+」指南里出现插件坞条目。

手动兜底

CLI 装不上(无 pnpm / 离线 / 权限)时,按顺序试:

  1. DeepSeek Harness:插件 → 添加插件 → 本地目录,选择 /Users/sunchujun/Desktop/deepseek/dsh-plugin-dock;

  2. 用应用自带 CLI(见上);

  3. 在 profile 目录手动 link:

    cd ~/.dsh/profiles/desktop
    pnpm add "link:/Users/sunchujun/Desktop/deepseek/dsh-plugin-dock"
    

    再手动把 dsh-plugin-dock 加进 dsh.profile.bundles,重启 DSH。

卸载

桌面版(与 install-manual.sh 配套,同样不需要 pnpm):

cd /Users/sunchujun/Desktop/deepseek/dsh-plugin-dock
sh scripts/uninstall-manual.sh

其它 profile(CLI / pnpm):

sh scripts/uninstall-local.sh

脚本会从 dsh.profile.bundles 删掉 dsh-plugin-dock,再尽力调用 dsh plugin --profile desktop remove dsh-plugin-dock(pnpm 报错不会中断卸载),最后提示重启。

等价手动卸载:

dsh plugin --profile desktop remove dsh-plugin-dock

再把 "dsh-plugin-dock" 从 profile package.json 的 dsh.profile.bundles 中删掉,重启 DSH。插件自己的数据目录 <profile>/.dsh-plugin-dock/ 默认保留(重装后收藏/最近/设置还在);要彻底清空可手动 rm -rf ~/.dsh/profiles/desktop/.dsh-plugin-dock。

回滚:脚本留下 ~/.dsh/profiles/desktop/package.json.bak.<时间戳>,直接拷回即可恢复安装前的 package.json。

使用流程

聊天中调用

  1. 打开一个会话,点输入框左侧的插件坞按钮(或右栏「+」→ 插件坞);
  2. 「快捷动作」里点「一键归纳」:对当前会话执行上下文归纳,显示用量与释放量(需已安装上下文归纳插件,见下);
  3. 「右侧面板」里点任意插件 chip:该插件直接在右栏打开,对话保持可见;
  4. 「插件页」里点「整页打开」:跳转到该插件的整页(这是诚实的兜底,不假装能塞进右栏);点「右侧打开」:优先用右栏 tab。

收藏与最近

  • 已安装插件行右侧星标可收藏;快捷区与列表会尊重收藏与顺序;
  • 打开过的插件会进入最近(最多 50 条),面板重新打开时优先展示;
  • 所有偏好通过 Host 的 /api/prefs 持久化(见下),右栏、整页、多个窗口共享;Host 不在时退回 localStorage 缓存并在底栏显示「本地缓存」。

整页打开

左侧栏「插件坞」默认是门户(开右栏);在面板底部点「设置页」或页面上点「打开完整设置页」即可进入整页控制中心:

  • 顶栏:标题、副标题、「在右侧打开插件坞」主按钮、「刷新目录」次按钮;
  • 状态条:Host 健康(目录数量 / prefs 版本 / profile)、发现数量(左侧面板 N · 右侧面板 M · 已安装插件 K);
  • 统一目录:快捷动作 / 右侧面板 / 插件页 / 已安装插件四段,带搜索、收藏;
  • 设置卡:左栏入口行为、自动打开、是否显示插件页、密度;
  • 自检卡:调用 /api/self-check 显示 pass/warn/fail 与 API 索引;
  • 帮助卡:安装、使用、接口速查(完整文档即本 README)。

设置与数据

设置项 默认 说明
leftEntryOpensDock true 点左侧栏入口时直接开右栏并回到对话
autoOpenDock true 安装后首次打开页面时自动打开一次右栏(有 localStorage 标记,不会反复弹)
showPageEntries true 面板中是否展示「插件页」区块
density comfortable comfortable / compact
lastSection quick 上次停留区块;仅作 UI 记忆

上下文归纳快捷动作

  • 需要已安装 dsh-context-summarizer(上下文归纳)插件——本机工作区版本在 /Users/sunchujun/Desktop/deepseek/dsh-context-summarizer;
  • 卡片显示当前上下文占用(tokens / 窗口 / 比例),「一键归纳」按钮在请求中禁用,成功后显示释放的 tokens 与摘要摘录;
  • 错误码映射:busy → 「会话正在归纳或运行中」、noop → 「历史太短,暂不需要」、unauthorized → 「刷新页面后再试」、其它 → 可读错误;
  • 未安装 / token 缺失 / 无会话:卡片显示安静的空状态与「打开插件页」兜底,绝不阻塞整个面板。

设计原则

为什么是右侧窗口

插件坞最核心的判断:调用插件的时机,几乎总在对话进行中。把插件页面塞进主栏意味着离开对话、丢失上下文、回来还要重新定位。DSH 的右侧栏本来就是「与对话并排的工作面」,因此插件坞把自己做成右栏的一个 tab 类型(sidebarRightTabs.register)——打开任何插件都不替换对话,主栏永远是会话。窄屏时右栏自动全屏,保证 320 px 也点得到;桌面宽屏时保持并排。

左栏门户行为

左栏「插件坞」对 DSH 用户来说是发现入口,不是又一个整页应用。所以默认行为是「门户」:短暂挂载整页 → 把主栏切回对话(ctx.layout.selectPanel(null),null 即 Conversation)→ 在 0 / 120 / 400 ms 重试打开右栏 tab;成功即完成跳转,失败才留在整页并提示(例如当前没有会话)。失败回退带导航守卫:如果在这约 0.5 秒里你已经切去了别的页面,插件坞不会把你强行拉回来,只是安静结束。「打开完整设置页」按钮用 sessionStorage['dsh-plugin-dock.allow-page']='1' 明确表达「这次我要整页」,mount 后即清除标记,之后不会粘住。

为什么不重写别的插件 UI

  • 只枚举入口,不复制界面:左侧入口来自 ctx.slots.entriesOfSlot('sidebar.panellist'),右栏 tab 来自 ctx.sidebarRightTabs.entries();插件坞只渲染入口按钮/卡片,真正的 UI 仍由原插件渲染。
  • 只调公开 API:打开右栏用 ctx.sidebarRight.openTab(kind),打开整页用 ctx.layout.selectPanel(id);跨插件动作只走对方公开的 HTTP API(如上下文归纳的 /dsh-ctx-summary/api/*),不读对方 DOM、不 import 对方内部模块、不改全局样式。
  • 失败即降级:某能力缺失(无右栏 tab、无页面、无 token、无会话)时只损失对应按钮,不抛异常、不破坏应用其余部分;错误进入页面内的轻量提示条,不使用 alert()。
  • 主题一致:颜色只用 var(--dsh-…) 主题变量(带中性 fallback),深浅色与窄屏跟随应用;样式类统一 dpd- 前缀,只挂在自己的插槽根节点内。

数据与存储

Host 持久化(权威)

  • 路径:<profileDir>/.dsh-plugin-dock/state.json(例如 ~/.dsh/profiles/desktop/.dsh-plugin-dock/state.json);Host 运行时优先用 DSH boot 提供的 profileContext 服务({name, dir, home})定位 profile,DSH_* 环境变量只在没有该服务时兜底;
  • 目录 0700、文件 0600;写入为同目录临时文件 + rename 的原子替换;损坏文件回退默认值并在自检里给出 warn;
  • 文件形如 {"revision":N,"updatedAt":<ms>,"prefs":{…}},revision 单调递增,多个标签页/窗口通过 mtime 校验共享;
  • 内容:
{
  "version": 1,
  "favorites": ["dsh-code-runner", "dsh-context-summarizer"],
  "order": ["dsh-code-runner"],
  "recent": [{ "id": "code-runner", "at": 1730000000000 }],
  "settings": {
    "leftEntryOpensDock": true,
    "autoOpenDock": true,
    "showPageEntries": true,
    "density": "comfortable",
    "lastSection": "quick"
  }
}
  • 校验:favorites/order ≤ 200 条去重字符串(≤ 160 字符);recent ≤ 50 条 {id, at};settings 布尔 + density∈{comfortable,compact} + lastSection∈{quick,tabs,pages,plugins};未知键丢弃;非法 patch 返回 400 bad-request。
  • 合并语义:POST 的 patch 局部覆盖当前值;recent 按 id 合并并保留最新 50 条。
  • 不存任何密钥;不写 profileDir 之外的路径。

Client 缓存(即时绘制)

localStorage 键 用途
dsh-plugin-dock.cache.v1 prefs 本地镜像({prefs, savedAt, revision}),Host 不可用时即时绘制
dsh-plugin-dock.autopened.v1 自动打开只做一次的标记
dsh-plugin-dock.allow-page(sessionStorage) 允许打开整页的门闩,mount 后清除

清空方法:浏览器 DevTools 删除上述键,或删除 Host 的 state.json(两者互相独立,Client 会在下次请求时以 Host 为准)。

对外开放接口

Host 半注册一个前缀路由 /dsh-plugin-dock,所有响应都是 JSON(图标除外),统一信封:

{ "ok": true, ... }                                        // 成功
{ "ok": false, "error": { "code": "unauthorized", "message": "…" } }  // 失败

鉴权

  • 每个 Host 进程启动时生成随机 token(crypto.randomBytes(24)),通过 ctx.webServer.tapIndex 注入页面:window.__DSH_PLUGIN_DOCK__ = { token: "…" };
  • 所有 /api/* 请求必须带请求头 x-dsh-plugin-dock-token: <token>(常量时间比较);
  • 唯一例外:GET /api/icon/<id> 也接受 ?token= 查询参数——<img> 标签无法附加请求头,所以图标 URL 用查询参数携带同一个 token(Origin / sec-fetch-site 校验不变)。页面内部使用这种 URL;不要把它截图/贴到聊天里,token 等同页面 API 的调用凭证;
  • 若请求带 Origin,必须与 ctx.webServer.getOrigin() 同源(或 loopback 的 host 一致);sec-fetch-site: cross-site 一律拒绝;
  • 错误码:401 unauthorized、403 forbidden、404 not-found、405 method-not-allowed、413 too-large、500 internal;Host 侧请求体上限 64 KiB;页面侧对插件坞 Host API 的单次请求超时 15 s(上下文归纳适配器另有 12 s / 10 分钟预算(与归纳插件自身的 10 分钟预算一致,避免慢模型下假超时);超时与传输错误在客户端映射为 timeout / network,不会抛出未处理异常);所有 JSON 响应 cache-control: no-store。

路由表

方法 路径 请求 响应
GET /dsh-plugin-dock/api – {ok:true,name,version,endpoints:[{method,path,description}]}
GET /dsh-plugin-dock/api/health – {ok:true,name,version,profile,profileDir,dshHome,platform,node,pid,time,catalogCount,prefsRevision}
GET /dsh-plugin-dock/api/catalog – {ok:true,profile,profileDir,generatedAt,scanMs,counts:{plugins,bundles,deps,client,host},plugins:[Plugin]}
GET /dsh-plugin-dock/api/prefs – {ok:true,revision,updatedAt,prefs}
POST /dsh-plugin-dock/api/prefs {patch:{…}} {ok:true,revision,updatedAt,prefs}
GET /dsh-plugin-dock/api/self-check – {ok:boolean,time,checks:[{id,label,status,detail}],summary:{pass,warn,fail}}
GET /dsh-plugin-dock/api/icon/<node> 头或 ?token= SVG/PNG 字节(image/svg+xml / image/png,cache-control: private, max-age=300;<img> 用查询参数认证,因 URL 携带凭证而用 private 避免共享缓存)
GET /dsh-plugin-dock/api/docs – {ok:true,markdown:"…"}(页面内的短接口文档)

/api/icon/<node> 的 <node> 是插件 id 的 URL 编码(如 %40linxin666%2Fdsh-client-ui-task-board),Host 从最新扫描结果映射到图标文件,从不接受任意路径。该路由是唯一接受 ?token= 的端点:<img src> 无法设置请求头,页面把同一个 token 放进查询参数;响应本身不含 token,但 URL 会出现在浏览器网络面板里,按凭证对待即可。

Plugin 条目:

{
  "id": "@linxin666/dsh-client-ui-task-board",
  "name": "dsh-client-ui-task-board",
  "displayName": "任务板",
  "version": "0.4.11",
  "description": "…",
  "icon": "/dsh-plugin-dock/api/icon/…",   // 仅当图标文件可读且 ≤ 256 KiB
  "install": "link",                        // link | file | registry | unknown
  "bundle": true,
  "dependency": true,
  "client": true,
  "host": true,
  "enabled": true,
  "self": false,                            // 本插件自己为 true
  "dir": "/abs/path"                        // 真实路径,仅供参考
}

目录规则:profile dependencies 与 dsh.profile.bundles 的并集;排除 @deepseek-ai/ 开头的宿主自带组合(计数保留、列表不显示);本插件标 self:true;按 bundles 优先、再按显示名排序。profile 不可读时降级为空目录 + 警告,不报 500。

curl 示例

从 DSH 页面 DevTools 控制台取得 token 与页面地址:

window.__DSH_PLUGIN_DOCK__.token   // 只读,勿外传
location.origin                     // 例如 http://127.0.0.1:57598
ORIGIN='http://127.0.0.1:57598'
TOKEN='<从上一步取得的 token>'

# 健康检查
curl -sS \
  -H "x-dsh-plugin-dock-token: $TOKEN" \
  -H "Origin: $ORIGIN" \
  "$ORIGIN/dsh-plugin-dock/api/health"

# 插件目录
curl -sS \
  -H "x-dsh-plugin-dock-token: $TOKEN" \
  -H "Origin: $ORIGIN" \
  "$ORIGIN/dsh-plugin-dock/api/catalog"

# 读偏好
curl -sS \
  -H "x-dsh-plugin-dock-token: $TOKEN" \
  -H "Origin: $ORIGIN" \
  "$ORIGIN/dsh-plugin-dock/api/prefs"

# 改偏好(局部 patch)
curl -sS -X POST \
  -H "x-dsh-plugin-dock-token: $TOKEN" \
  -H "Origin: $ORIGIN" \
  -H "content-type: application/json" \
  --data '{"patch":{"settings":{"density":"compact"}}}' \
  "$ORIGIN/dsh-plugin-dock/api/prefs"

# 自检
curl -sS \
  -H "x-dsh-plugin-dock-token: $TOKEN" \
  -H "Origin: $ORIGIN" \
  "$ORIGIN/dsh-plugin-dock/api/self-check"

token 每次 Host 进程启动都会变;重启 DSH 后请重新取值。不要把它写进日志、脚本或截图。

扩展适配器

插件坞的默认扩展面是 DSH 的公开插槽——大多数第三方插件什么都不用做:

  1. 左侧页面:注册 sidebar.panellist({id, order, label})并挂 main 页面,插件坞自动把它列入「插件页」,提供「整页打开」;
  2. 右侧面板:通过 ctx.sidebarRightTabs.register({id, kind, title, guide?, keepMounted?}) + sidebar.right.pane.tab body 注册 tab 类型,插件坞自动把它列入「右侧面板」,点 chip 即 openTab(kind);
  3. 已安装插件清单:不依赖 UI——只有 Host 半的插件也会出现在「已安装插件」里;同时具备 client/host 能力时会显示两枚徽章。

内置快捷动作仍是「适配器」形态(client.js 内的 ADAPTERS 表):每个适配器描述 { id, titleKey, subtitleKey, usesSession } 并有自己的调用/状态映射函数。v1 只注册 context-summarizer 一个适配器,其余入口全部是通用发现。

第三方扩展边界(重要):

  • 宿主注入的 window.__DSH_PLUGIN_DOCK__ 当前只含 { token },是插件坞与 Host 之间的凭证载体;不要覆盖它或把它当公共注册表(写入即破坏自己的面板鉴权);
  • v1 未开放运行时 push 适配器的公共 API;想给插件坞加「快捷动作卡」的插件,请把适配器按上面的 shape 提交到本仓库(或作为未来版本的扩展点约定),而不是依赖未文档化的内部字段;
  • 跨插件调用只接受对方公开 HTTP API,并显式处理 token 缺失/未安装/错误码。上下文归纳卡片就是范例:能力缺失只降级为安静空状态,绝不影响插件坞本身。

测试与自检

三条命令,全部离线、零依赖、确定性:

cd /Users/sunchujun/Desktop/deepseek/dsh-plugin-dock

npm test                          # Host + Client 的 node:test 套件(94 项)
node tests/self-check.mjs         # 静态自检(68 项;--live 可选)
sh scripts/e2e-profile.sh         # 隔离的 profile 组装证明(不碰 live profile)

# 或一次跑完:
npm run check                     # = npm test && npm run self-check
npm run verify                    # = npm run check && npm run e2e

npm test

  • tests/host.test.mjs:假 Cordis 上下文上跑真实 apply(),覆盖 token 注入、鉴权(无 token / origin 不匹配 / cross-site)、路由分发(404/405)、health/catalog 形状与排除规则、prefs 默认值/合并/持久化/非法 patch/超大 body、icon 字节与路径穿越、self-check 信封、profile 缺失时的降级、临时目录清理;
  • tests/client.test.mjs:在 vm + 最小 React/Hook/槽位替身上加载真实 client.js,断言 loader id、模块形状、四个入口注册(左栏行 / main 页 / 输入框按钮 / 右栏 tab + 指南 + body)、最小 ctx 与完整 ctx 下 apply() 均不抛、disposer 可卸载、纯函数行为;测试结束即退出(无残留计时器)。

npm test 需要在仓库内、无环境变量、无网络的条件下全绿(当前 94 项:Host 45 + Client 49)。npm run check 串上静态自检,npm run verify 再串隔离 e2e。

node tests/self-check.mjs

静态、离线、无依赖;逐项打印 PASS / WARN / FAIL 表格与计数,有 FAIL 时退出码 1,只有 WARN 时退出码 0:

  • 包身份与打包元数据(name / version / type / exports / files / dsh.bundle.patch / dsh.client.platform / inject 列表);
  • 必需文件存在(两半、patch、icon、README、CONTRACT、tests、scripts,含 install-manual.sh / uninstall-manual.sh);shell 脚本可执行且 sh -n 通过;
  • cordis.patch.yml 的 insert 行(id + name 恰好一条);
  • 语法:node --check index.js;client.js 用 vm.Script 按 classic script 编译并做一次 loader 冒烟;
  • Client 静态守卫:只 require('react')、无 import/export、无 alert()/confirm()/document.write、固定契约字面量、公开 API 调用、只 fetch 同源、契约存储键;
  • Host 静态守卫:只 import node:*/相对路径、路由前缀、token 头/全局、webServer.register/tapIndex、状态目录、无 child_process/eval、无第三方 require、name/inject/apply/__test__ 导出;
  • 图标(<svg + viewBox + xmlns,< 64 KiB);
  • CONTRACT 固定标识逐个 grep(路由前缀、token 头、token 全局、tab kind/id、panel id、composer id、order 60/45/35);
  • README 章节词、安装命令(含 manual 脚本)、状态路径、curl 示例、全部 8 个路由行、英文摘要;
  • 测试文件存在且非平凡、测试只 import node:*/相对路径、不碰网络与 live profile、package.json 的 test/self-check/check 脚本。

可选 live 模式(对一个正在运行的 DSH 断言真实信封):

node tests/self-check.mjs --live http://127.0.0.1:57598 --token '<页面 token>'

不带 --live/--token 时该组显示 WARN live/skipped,不影响静态结果。

sh scripts/e2e-profile.sh

在 mktemp -d 出来的隔离 DSH_HOME 里做最佳努力的组装证明,绝不触碰真实 profile:

  1. DSH_HOME=$TMP dsh plugin --profile e2e add "link:<repo>"(可用应用自带 CLI,带 pnpm);
  2. 把 dsh-plugin-dock 写进 dsh.profile.bundles;
  3. DSH_HOME=$TMP dsh e2e --dump-config,断言输出含 # == dsh-plugin-dock、- id: dsh-plugin-dock、name: dsh-plugin-dock;
  4. 无论成败都删除临时目录。

离线 / pnpm 缺失导致 CLI 装不上时,脚本会退化为手写 link(等价于 pnpm link: 的 node_modules 软链)后继续断言——这是「证明 cordis.patch.yml 能被正确组装」,不是安装测试。环境完全不支持 --dump-config 时打印 SKIP 并退出 0;只有 dump 成功但插件行缺失(真正的组装失败)才退出 1。

验收

node tests/self-check.mjs   # 无 FAIL(当前 67 pass / 1 warn / 0 fail)
npm test                    # 全绿(94/94)
sh scripts/e2e-profile.sh   # PASS 或 SKIP(带原因)
# 或:npm run verify

已知限制

  • 需要 DSH 0.2.1 系客户端契约(slots / sidebarRightTabs / sidebarRight / layout / locale);旧版本组合上相关入口会安静降级。
  • 只接受公开插槽发现:插件若没有注册 sidebar.panellist 页面也没有右栏 tab,就只在「已安装插件」清单中出现,不能被直接打开(这是诚实的能力边界,不伪造入口)。
  • 右栏 tab 需要当前有会话:没有会话时快捷/右侧打开会提示「先打开一个会话」,而不是假装成功。
  • 上下文归纳适配器依赖 dsh-context-summarizer 已安装、页面 token 全局存在;不同版本响应的嵌套/扁平形状已做兼容,但插件被禁用/版本不兼容时卡片显示未安装空状态。
  • 自动打开只在安装后第一次打开页面触发一次(localStorage 标记),且仅在对话为主栏时;清掉浏览器站点数据后会再触发一次。
  • 面板只在自身插槽内渲染,不接管应用外壳;右栏的收起/关闭由外壳控件负责,插件坞不重复提供(CONTRACT §1.2);右栏宽度低于 320 px 时以应用自身的窄屏行为为准。
  • 图标服务只覆盖已安装插件中带可读 icon.svg/icon.png(≤ 256 KiB)的包;没有图标的插件使用内置中性图标。
  • 偏好数据按 profile 隔离;token 每进程随机、不持久化;/api/* 仅本机页面可用,不做远程访问。
  • 桌面版安装优先用 scripts/install-manual.sh(无需 pnpm/CLI),也可用应用内插件管理;npm 全局 dsh 会拒绝 desktop profile,见「安装与卸载」。

文件结构

文件 作用
package.json bundle 清单:dsh.bundle.patch(Host 半)+ dsh.client(Web 半),exports 导出两半与图标
cordis.patch.yml 把 { id: dsh-plugin-dock, name: dsh-plugin-dock } 插入 profile 的 bundle patch
index.js Host 半(ESM):token 注入、/dsh-plugin-dock 路由、插件目录扫描、prefs 持久化、icon 服务、self-check
client.js Web 半(classic script):左栏入口 + 整页、右栏 tab + 指南、输入框按钮、上下文归纳适配器、发现/收藏/搜索/设置 UI
icon.svg 原创图标(左侧栏 + 右侧面板二层结构)
tests/host.test.mjs Host 半 node:test 套件(假 Cordis 上下文 + 临时 profile fixture)
tests/client.test.mjs Client 半 node:test 套件(vm + React/槽位替身)
tests/self-check.mjs 静态自检 + 可选 live 模式(本文档「测试与自检」)
scripts/install-local.sh 本地 link 安装(自动选 CLI、备份、幂等补 bundle 行)
scripts/uninstall-local.sh 卸载(删 bundle 行、尽力移除包、保留数据)
scripts/install-manual.sh 桌面版安装(无 pnpm/CLI:软链 + 写依赖与 bundle 行 + 校验)
scripts/uninstall-manual.sh 桌面版卸载(与 install-manual.sh 配套)
scripts/e2e-profile.sh 隔离 profile 的组装证明(不碰 live profile)
dist/dsh-plugin-dock-0.1.0.tgz 打包产物:可 dsh plugin add file:… 或归档分发的可移植安装包
CONTRACT.md 实现契约(标识、API、行为、测试要求的唯一事实来源)
README.md 本文档

English summary

Plugin Dock (dsh-plugin-dock) is a DSH bundle plugin with two halves. The Host half (index.js, ESM, zero dependencies) injects a per-process random token into the Web page and exposes a token-gated, same-origin /dsh-plugin-dock HTTP surface: installed-plugin catalog with icons, its own persisted dock preferences, a self-check endpoint, and a short docs endpoint. State lives in <profileDir>/.dsh-plugin-dock/state.json (atomic write, no secrets).

The browser half (client.js, one classic script, React only) contributes three surfaces without touching the app shell: a left-sidebar 插件坞 row (its default behaviour is a door — open the dock in the right column and return the main column to the conversation), a right-sidebar dock tab with a guide entry (search, quick adapters, other plugins' right-panel chips and left-sidebar pages, installed-plugin catalog with favourites/recent, prefs-sync footer), and a composer launcher button that opens the dock in one click. Other plugins are discovered through public slots (sidebar.panellist, sidebarRightTabs) and opened through public APIs (sidebarRight.openTab, layout.selectPanel); nothing re-implements or DOM-hacks another plugin's UI. The built-in flagship adapter calls the installed context-summarizer's own HTTP API (/dsh-ctx-summary/api/*, its own token) and degrades to a quiet empty state when that plugin is absent.

Install locally with sh scripts/install-manual.sh (no pnpm or CLI needed: symlink + link: dependency + bundle row, then refresh/restart DSH); scripts/install-local.sh covers the CLI/pnpm path used by non-reserved profiles, and dist/dsh-plugin-dock-0.1.0.tgz is a portable package. The Desktop application's bundled CLI is preferred over an npm-installed dsh, which refuses the desktop profile. Verify with npm test, node tests/self-check.mjs (add --live <origin> --token <token> to probe a running instance) and sh scripts/e2e-profile.sh (isolated --dump-config composition proof; prints SKIP when the environment cannot compose at all). The icon route additionally accepts the same token as ?token= because an <img> cannot set headers; every other route is header-only.

License: MIT.

—/ 5

No ratings yet

Verified DSH bundle

Commit 531607cbd12c

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