dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
给 DSH 顶端加一个可扩展的「功能」栏:插件注册的一键动作都在这里,支持分级确认与二级菜单。0.4.0 起自带了「重启 DSH」功能,不再需要单独安装重启插件。
给 DeepSeek Harness 客户端顶端加一个可扩展的 「功能」 标签: 插件注册进来的一键功能都在这儿,用户不用再记着「去哪儿点 / 输入什么口令」。
0.4.0 起这个包自带「重启 DSH」 —— 装这一个包,功能栏里就有「重启 DSH」(两级确认,有任务在跑会拦一下);
不再需要单独安装 dsh-function-restart(已并入本包,见下面「一键重启 DSH」一节)。[▢] 应用 编辑 功能 3 ▾ +----------------------------------------+ 功能 插件在这里挂上自己的一键功能 加载功能 让会话去找非官方插件 读取当前会话 让当前窗口的会话读一次 换会话 > 真交接或演练,选一条 插件市场 逛社区插件、一键装 重启 DSH 重启 DSH,让设置生效 +----------------------------------------+
(示意图来自作者本机安装的一组插件;只装本包时,面板里有内置的「加载功能」和「重启 DSH」(0.4.0 起)。)
下面是真机截图(Windows 桌面版,装了本包 + 几个社区插件):
重启后(0.5.0):置顶的「加载功能」下面多了一条分隔线,和其余选项分开; 「加载功能」的说明也补上了「也可以按你的描述直接加一条功能」。
不 patch DSH 本体,不 require React,不引任何 Harness 客户端包 —— 它就是一个普通插件:一行 Loader 条目 + 宿主半边 + 一个自带样式的客户端 bundle。
# 从 npm 装(推荐,版本/下载量在 npm 页可见)
dsh plugin --profile desktop add dsh-functions
# 或者直接从 GitHub 装
dsh plugin --profile desktop add github:Num86/dsh-functionsDSH → 插件 → 添加插件,填这个包的路径(tarball 或解包目录都行),然后重启 DSH (服务端不热更新;客户端那半边刷新页面就有)。
手动装也行:profile 的 package.json 里加一条依赖 + dsh.profile.bundles 一行, 再在 node_modules 里放一个指向它的 Junction/软链。
装好后插件页会用它包里的展示元数据(locale/zh.json、locale/en.json 里的 meta.title / meta.description)显示标题与描述,并显示包内的 icon.svg。 package.json 里对应的是顶层 icon 字段、exports 里的 ./locale/*.json, 以及 files 里的 locale/*.json 与 icon.svg(0.3.1 起;0.4.0 起还带 restart/ 的 图标与 locale,见下面「一键重启 DSH」一节)。
// 你自己的插件 index.js export const inject = ['functions'];
export function apply(ctx) { ctx.functions.register({ id: 'my.tool', title: '干点什么', description: '一句话说明\n它调用的插件名', // 第二行惯例:写它依赖的插件 group: '我的', order: 10, confirm: { title: '干点什么', message: '现在跑?' }, async run() { return { message: '干完了' }; }, }); }
run() 返回的 message 会弹在界面上;返回 { warnAfterMs, warnMessage } 的话, 到点还活着就再弹一条(给「重启这种会把自己关掉的功能」兜底)。
字段 · 类型 · 说明 id · string · 必填,唯一。 title · string · 必填,菜单显示名。 description · string · 说明。可以带换行:第一行讲做什么,第二行写它调用的插件(面板渲染两行,右对齐)。 group · string · 分组,默认「通用」。只参与排序,标签默认不画。 order · number · 组内排序,小在前,默认 100。 pinned · boolean · 置顶:永远排最上面(不受分组与 order 影响)。 parent · string · 挂到某个父项的子菜单里(二级菜单)。写了它就不出现在顶层。 submenu · boolean · 标记这一条是二级菜单的父项(容器):自己没有动作,run 可以不写。 icon · string · 预留(2026-10 之后面板不再显示单字图标,写了不报错)。 confirm · object · 一级确认窗:{ title, message, detail, confirmText, cancelText }。 confirms · array · 多级确认(静态)。只有第 0 级用得到。 prepare(stage, context) · function · 动态确认:第 N 级的文案在第 N-1 级点掉之后才算(「有没有任务在跑」这种话隔一次就会过期)。返回 null = 没有更多确认。 run(job) · function · 执行。job = { id, ctx, log, session, confirm, alert, toast, get, post }。不写 run 就必须写 `shell` 或 `submenu: true`。 shell · object · 只想跑命令:{ command, args, cwd },结果整理成一句话显示。 hidden · boolean · 不进菜单(仍可用 API 调)。
二级菜单长这样(照 Windows 右键菜单的逻辑:悬停 120ms 展开、离开 180ms 收起,点父项也能展开):
ctx.functions.register({ id: 'family', title: '换会话', submenu: true, group: '会话' }); ctx.functions.register({ id: 'family.go', title: '真交接', parent: 'family', run() {...} });
本插件还认自己这一行 Loader config 里的 config.functions,不写代码也能加功能:
name: dsh-functions config: functions:
title: 打开插件目录 group: 工具 confirm: { title: '打开插件目录', message: '用资源管理器打开?' } shell: { command: 'explorer.exe', args: ['D:/some/plugin'] }
字段与 ctx.functions.register 完全一样(见上面的字段表)。
限制(重要):
cordis 会把这一行的 config 原样传进来,所以写错了只在宿主 console 留一行日志, 菜单里静默少一条。
provide('functions') 与三条同 path 路由都会抛,整个插件激活失败,功能栏直接消失。 正确做法是在 profile 补丁里按行 id 覆盖已有的那一行,把 config.functions 写在它的 config 下。
ctx.functions.register(...) —— 有代码可测。GET /api/dshFunctions/list 列宿主注册的功能 POST /api/dshFunctions/prepare { id, stage, sessionId } -> 确认窗 POST /api/dshFunctions/run { id, confirm: 'yes', sessionId }
sessionId 是「用户正在看哪个会话」,浏览器那半边会自动带上(读客户端服务 uiSession)。 需要往会话里送东西的功能用它 —— 拿不到就拒,别猜(指令发错窗口代价太大)。
功能本质上是「打开某个界面」时(打开设置里某一节、打开某个面板)才用客户端注册:
window.__ModuleLoader__.load({ id: 'my-plugin', factory(require) { return { inject: ['functions'], apply(ctx) { ctx.functions.register({ id: 'my.open', title: '打开某面板', run: async (job) => { /* job.post / job.confirm / job.toast */ }, }); }, }; }, });
面板是纯 DOM(shadow root 隔离),升级后最坏是外观退化,不会白屏。
「功能」标签不是官方 slot:它站在 Windows 桌面应用原生「应用 / 编辑」菜单的右边, 位置靠量桌面外壳 preload 注入的私有 DOM [data-windows-menu] 得到。
dsh-app://app 页面);普通浏览器、macOS 桌面没有这个锚点。外壳改了这段 DOM,顶多位置退化(最坏挪到页面左上角),面板本身照常工作,不会白屏。
(--dsh-windows-titlebar-height / --dsh-windows-menu-start)停位;量到锚点矩形就贴它的右边; 其它环境保留 best-effort 位置。位置更新是事件驱动的(属性变化 / 窗口 resize / 启动期有限重试),不轮询。
这个插件自己内置了一条置顶功能 加载功能:dsh 没有「在工作区新开会话」的能力, 所以它走「把一段指令当成用户消息送进当前会话」这条路 —— 让会话里的 dsh 去插件库里找非官方插件、列出数字编号给用户挑,再读 README 把它们接进「功能」栏。
0.4.1 起,这条指令改成可移植写法(以前的版本里带了几处作者本机的假设,换台机器会误导 agent):
DSH_PROFILE_DIR 环境变量;没有就按通用位置找:Windows %USERPROFILE%.dsh\profiles、macOS/Linux ~/.dsh/profiles/ (桌面版通常叫 desktop)。插件源码放用户自己的工作区(例如当前目录下 dsh-plugins/),明确说明不要放进 DSH 安装目录(升级会覆盖)或 profile 目录本身。
plugin_manager 工具(创造模式):用 install_bundle 装插件目录的绝对路径;不要手改 profile 的 package.json / cordis.patch.yml。
plugin_manager:用 DSH 插件页「添加插件」填插件目录绝对路径(官方入口,最稳)。link: 依赖 + dsh.profile.bundles 一行 + Junction 手动装,装完重启 DSH。
共同要求没变:目标插件目录必须由外部指定(按编号或路径,不猜「最近动过的」); 接进「功能」栏前先读对方 README;接口拿不到就照实说「没找到」,不编。 确认窗里的第一级一直是「发一条指令进当前会话」,真正发不发由你点「发指令」决定。
指令里点名了下面这一节。
功能栏 -> 系统 -> 重启 DSH。两次确认才算数:
就列出来并把确认键换成「仍要重启」(红色);没有就写「现在没有检测到正在进行中的任务」; 状态接口没应答时照实说「无法确认」,不假装没有任务。
桌面端的「关闭」本质是强制关:外壳会把主窗口拦下并缩进托盘,所以插件用 taskkill /T /F 关整棵进程树。应用带起来的子进程(终端、后台任务、MCP 等)会一起结束, 没有收尾;重启后助手会再把窗口叫到前台。
叫窗口不依赖端口探测:0.2.1-rc.2 桌面版用的是动态端口,新实例端口通常和旧的不一样。 端口只当「早点叫窗口」的快路径(最多等 6 秒);等不到就固定等 8 秒再叫窗口 —— 第二次启动只会把已经开着的窗口叫到前台,不会多开实例。另有一层安全网:外层把内层助手的 pid 交给它,盯到内层退出后才兜底拉起一次(应用已经开着时同样只会被叫到前台)。
平台支持:
平台 · 支持 · 行为 Windows 桌面版 · 完整支持 · 关整棵树 -> 重新拉起 -> 第二次启动把窗口叫到前台(动态端口下按固定延时兜底);安全网盯内层助手 pid 兜底 dsh CLI / headless · 支持 · 不依赖 taskkill:本进程按计划退出,助手用同样的命令重新拉起 macOS / Linux 桌面 · 不支持 · 按钮标题带「仅 Windows 桌面版」;点了只说明原因,不执行、不假装重启
不需要再单独安装 `dsh-function-restart`。 0.4.0 起它已经并入本包(子路径插件 dsh-functions/restart),装本包一次就同时得到「功能」栏和「重启 DSH」。 以前装过独立包的:本包启动后会把功能接管过来,独立包看到已有 restart-dsh 就不再注册; 确认本包的重启能用之后,把 dsh-function-restart 从 profile 里移除,再重启一次 DSH 即可。
日志:默认落在 DSH_HOME/dsh-functions-restart.log;没有 DSH_HOME 就用 %USERPROFILE%.dsh\dsh-functions-restart.log,都没有就落系统临时目录。 可以在 loader 行的 config 里用 logPath 覆盖。排查「按了没反应」先看这份日志。
关闭这一条:在 profile 补丁里按行 id 覆盖已有的 functions-restart 行,写 disabled: true(不要新增第二行同名插件):
name: dsh-functions/restart disabled: true
这一行的 config 还支持 initialDelayMs / graceMs / pollMs / waitExitMs / waitPortMs / guardDelayMs / focusNudge / focusWaitMs / exitDelayMs / dryRun / logPath,默认值见包内 restart.js 的 DEFAULTS。其中 guardDelayMs 只是安全网开始 接管的延时(之后它盯内层助手的 pid,内层退出后才兜底,不再需要覆盖最坏耗时)。
插件自己多半没为「功能」写接口。接它的时候,最常遇到的不是「接不上」,而是对方没写文档。 纪律一句话:
推断可以,但必须留下证据链;证据不够就照实说「没找到」,不要编。