一个小而自洽的 DSH 电源控制插件:侧边栏电源按钮 + 上拉「重启/关机」菜单 + Windows 关机风格过渡遮罩。自带重启/关机引擎(不依赖其它插件),动态端口适配。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:keyiadiannao/dsh-power-button
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
DeepSeek Harness 的自包含电源与生命周期控制插件:侧边栏底部电源按钮 + 上拉重启/关机菜单 + 全屏过渡动画。重启引擎内置在本插件中,不依赖第三方插件。
由 DeepSeek AI 辅助开发,发布前经人工 review。
功能
- 侧边栏电源按钮:注册到页脚操作位(
sidebar.footer.action),主题自适应,外观与旁边的"设置"按钮一致 - 重启/关机菜单 + Windows 关机风格全屏过渡;重启确认后页面自动刷新
- 自包含重启引擎:写一个 detached 的
.cjshelper,经 ARM → COMMIT → ACK 握手接手拉起职责,等旧进程、端口与会话日志都落定后,以相同调用方式与原目录重新拉起 DSH;--port 0命令行会被固定为本进程实际绑定的端口。桌面版若宿主广播了重启协议,则改为交给 Electron 外壳重启。不使用 PowerShell、不使用taskkill /restart与/shutdown命令、restart_harness模型工具(名字已被其它插件占用时跳过注册),以及只读的restart_status诊断工具- 界面与宿主文案本地化(中文 / English),跟随 profile 的
locale.preference - 启动清理:自动清理运行目录下超过 7 天的 helper 日志与脚本、每次重启的握手记录,以及尚未投递的通知
截图
① 侧边栏电源按钮 —— 主题适配的底部常驻入口。

② 重启 / 关机菜单 —— 两个动作一次到位。

③ 关机确认对话框 —— 默认焦点在「取消」,只有显式确认才会停止进程。

④ 关机进度遮罩 —— 进程收尾时显示当前阶段。

⑤ 重启完成提示 —— 页面自动重载后确认 DSH 已恢复。

安装
dsh plugin --profile web add "github:keyiadiannao/dsh-power-button#master"
重启 DSH 后生效:侧边栏底部出现电源按钮。需要 Node ≥ 22.19。
配置
通过 profile 的 cordis 层配置(cordis.patch.yml 或设置界面):
| 键 | 默认 | 含义 |
|---|---|---|
enableModelTool |
true |
注册 restart_harness 模型工具。设 false 则重启仅保留在 GUI 按钮与 /restart。 |
maxDelayMs |
5000 |
模型工具 delayMs 参数的上限(ms)。有效下限为 1000 ms。 |
restartWakeMode |
quiet |
重启后如何告知发起它的那个会话。quiet 只把通知暂存给它的下一个 step,不唤醒任何东西;notify 额外在该会话变成 live 后唤醒它,模型无需用户发消息即可报告结果。两种模式都不会自行再次重启。 |
- id: dsh-power-button
config:
enableModelTool: true
工作原理
重启是显式 ARM → COMMIT → ACK 握手:只有活着的 helper 确认接手拉起职责,旧进程才会退出;ACK 之前的任何失败都会保留旧进程。拉起新实例后,helper 最多等待 180 秒确认就绪。浏览器为包含退出与准备阶段的完整重启提供五分钟的轮询预算。请求被拒绝后,即使后续健康探测失败,界面也会保留错误状态。
点击电源 → 重启
[宿主] POST /api/dsh-power-button/restart
写出本次重启专用的 helper .cjs(0600:内嵌拉起 argv)
spawn `node <helper>` detached
[助手] ARM → status { stage: 'armed' }
[宿主] 等到 ARMED(5 秒),写入本次 restartId 的 COMMIT 文件
[助手] ACK → status { stage: 'committed' }
[宿主] 此时才:unref helper → flush 活跃会话(≤5 秒)
→ delayMs 后请求退出,其后有 15 秒看门狗
[助手] 等旧 PID 退出、端口释放、会话日志落定
写 v2 marker,用相同 execPath/argv/cwd 拉起 DSH
轮询 /health 直到 instanceId 变化且 restart.restartId 匹配
[客户端] 自动刷新
五条不变量支撑这套流程:
- helper 必须脱离进程树(
detached+ 真实.cjs文件——多行node -e会被 WindowsCreateProcess破坏),否则终止 DSH 时 helper 一起被杀 - 重启成功以每次进程独立的
instanceId变化为准,短暂离线本身不算成功 - 启动器的退出通道在
ctx.get('appExit')读取,不能当属性读:它是本插件未在inject中声明的可选宿主服务,按属性读会得到undefined,从而跳过优雅销毁、直接硬杀 - 退出全链有界,且边界是推导出来的而非各写一份:优雅退出 15 秒看门狗、退出前 flush 上限 5 秒,helper 等旧 PID 的耐心由两者计算而来,避免日后漂移成"helper 先放弃"
- 新进程的环境从注册表重新读取,而不是继承:进程环境是启动器创建时的快照,否则用
setx或系统属性改过的变量永远到不了重启后的实例。环境块按 Windows 的顺序组合——以继承块为底层,系统值覆盖其上,用户值再覆盖系统值——REG_EXPAND_SZ只对它自己那一阶段可见的变量展开,因此系统值看不到仅用户存在的名字。这种隔离是注册表层之间的隔离,不是逐字节复现 Windows 构建环境块的过程:启动器原有进程块里的名字在每一阶段都可见——这是刻意的,因为其中有些(本机上的SystemRoot)只存在于进程环境里。PATH保留两半(系统在前),每段按各自类型展开后再去重。读取走reg export(UTF-16LE),非 ASCII 值在任何控制台代码页下都能保真;导出文件是 harness home 下的临时文件,同一次调用内删除,它继承该目录的 ACL——和.credentials.yaml完全一样,在 Windows 上文件模式并不构成隐私保证。从注册表删除的变量仍然看不到;从注册表PATH里删掉、但启动器进程块里仍有的目录也会保留——它和"包管理器在启动时注入的目录"无法区分,丢掉后者会让按名字解析工具的子进程直接失败。非 Windows 平台保持纯继承
关机 POST /api/dsh-power-button/shutdown,终止且不拉起。由于不可逆,电源按钮和 /shutdown 都会先弹确认对话框;模型不暴露关机。
桌面版
桌面应用中 Host 是 Electron 外壳的子进程:退出这个子进程会触发应用的崩溃恢复,重放它的命令行则会得到没有窗口、也没有原 IPC 通道的 Host。按宿主是否广播能力分两种情况:
- 宿主广播了重启协议(Host 环境里有
DSH_DESKTOP_RESTART_PROTOCOL,且 IPC 通道已连接):插件写入重启标记、刷盘在跑的会话,然后请外壳重启,由 Electron 接管拉起。若外壳 20 秒内没有接手,请求按失败返回、操作锁释放,按钮可再次使用。 - 宿主没有任何广播(目前所有已发布的 DeepSeek Harness):重启与关机都被拒绝,返回
503与reason: "desktop-unsupported",不会派生 helper、也不会安排退出。请用应用菜单或托盘的退出,然后重新打开;只关主窗口可能让应用继续留在托盘里。
要让第一种情况可用,必须修改 DeepSeek Harness 本身,这超出本插件的边界;步骤写在 docs/desktop-restart-protocol.zh-CN.md。
安全
- 破坏性 POST 由本部署自己的浏览器信任栅栏与认证保护:通过
ctx.connection.requestRejection取自宿主,与宿主/api路由用的是同一个调用。/api/dsh-power-button/*比官方/api前缀更长,最长前缀匹配不会把这些请求交给那道栅栏;委托给它,才能让端点始终处于运维者真实策略之下——包括 Web 运行时从"绑定所有网卡"推导出的 LAN 权威,以及任何trustedHosts/--trusted-host声明 - 宿主没有 connection 服务时(例如不含 web bundle 的 profile),插件退回自己那份规则副本:
Host必须是 loopback 权威或宿主声明过的权威,外加跨站与完整 authority 的Origin校验。该副本比官方更窄、只会拒绝,但它不提供浏览器会话认证 - 被拒时返回稳定的
reason码,并把观测到的Host/Origin/sec-fetch-site/remote 地址写进日志。host-fence与not-authenticated表示是部署自己的规则判的,不是插件 - at-most-once 锁:并发重复触发会被拒绝(第二次返回
409) - 模型工具
delayMs下限 1000 ms——模型无法在自身 turn 结束前杀掉进程 - 重启 marker 在启动时消费即删除,后续普通启动不会误报;命令行日志脱敏,helper 与 marker 以
0600写入,运行目录0700
重启后,会话能知道什么
三件独立的事,刻意不合并:
- 界面弹出本地化 toast。进程整个生命周期都通过
/health的restart.fromInstanceId、restart.restartId暴露重启身份;确认 toast 只清 pending 标志,不会抹掉身份 - 发起重启的那个会话收到通知,形态由
restartWakeMode决定:quiet用Agent.inject(),notify用Agent.followup()。只有记录了 causal session 的重启才会发;GUI 点击不记录会话,也就不会归到恰好打开着的那个会话上。每个会话最多排队一条,投递后即删除 - 其他会话可调用
restart_status,获取是否发生过重启、restartId、阶段、被替换与新实例、各阶段时间、发起者,以及当前进程是否就是该次重启的产物
本插件从不自己追加模型可见的消息、不伪造 turn:通知交给 Agent API,inject() 会持久登记一条 agent/inbox/spliced 记录交给下一个合法 step 认领。正常运行时每条通知只投递一次;若在 inbox 写入与删除之间崩溃则可能重复,通知文案已写成重放无害。
/health 另外报告 appExit: "available" | "missing"——本宿主里启动器的退出通道是否真的可用,把"重启偏慢"变成一次请求就能定位的问题。两条退出路径都会记录退出前 flush 的计数(live/fulfilled/rejected/timedOut),使被拒或部分失败不再是"看起来像干净退出"。
开发
pnpm build # tsdown:宿主 + 客户端 bundle(提交到 lib/)
pnpm typecheck # tsc --noEmit 检查 src
pnpm typecheck:tests # 根 tsconfig 不包含 tests/
pnpm test # vitest:单元 + 进程级验收
pnpm check # 以上按序全跑
CI 在 Windows / Ubuntu / macOS 上跑两套类型检查、测试与构建,并校验提交的 lib/ 与重新构建结果逐字节一致、pnpm pack 产物含 lib/ 与 cordis.patch.yml;另有独立 Linux job 在 Node 22.19(engines.node 声明的下限)重跑全部步骤。tests/restart-runtime.spec.ts 把宿主生成的那一份真实 helper 当独立进程对着真实 TCP 端口跑:完整拉起链、没有 COMMIT 就不拉起、spawn 重试上限、拒绝旧实例、以及各类失败诊断;测试经 vitest setup 隔离 DSH_HOME,不会碰你真实的 ~/.dsh。
License 与致谢
MIT。"detached helper 拉起"的思路参考 anweat/dsh-restart(MIT);实现为独立编写(真实 .cjs 文件、不使用 PowerShell、动态端口),未复制其代码。
感谢 @qq1131909103:PR #8 指出桌面端 Host 归 Electron 外壳所有。本插件在桌面端拒绝电源操作、而不是另起一个竞争 Host,以及 docs/desktop-restart-protocol.md 的出发点,都来自这个判断。
链接
同类插件
yjh051108/dsh-routing-suite★ 6994
一个仓库三件套:DSH 插件包的运行时注入器(注入、热重载、卸载、开发侧挂区一键转正、路由自愈,外带设置页插件管理:列出、卸载、拖入文件夹内化)、任务感知的思维模式路由 agent 预设(router-standard / router-spec / router-react)、以及分级两级任务协议(commit_star / lock_stage / revise_do / edit_plan / mark_task / redteam_verdict 六个工具,任务状态落盘)。注入器实现直接在库内,安装的是它自己的行为而不是一份依赖清单。
strukto-ai/mirage#dsh★ 3762
把文件系统与 bash 提供者换成 mirage 虚拟工作区:文件工具与 shell 命令作用于挂载的资源(RAM、S3、Redis、Slack、Gmail、Notion、Postgres)而非宿主磁盘,支持按挂载点设置读/写/执行模式、按命令选择沙箱(进程内 monty、pyodide、quickjs;远程 docker、e2b、daytona),并可在虚拟终端中安装 CLI(git、gh、slack、linear、ntn、gws,或自行注册的程序树)作为命令头词。
first-fluke/oh-my-agent#dsh★ 1357
在工具执行前后以及尝试结束当前轮次时运行项目 OMA 检查。随插件安装 OMA CLI 依赖,并在首次使用时初始化未配置的项目。需要 Node.js 22+;首次初始化需要网络连接和项目目录写入权限。
MichengAI/dsh-skills-manager★ 444
在设置里管理本地 DSH 技能,并只读查看公共 Agent 技能。
hust-open-atom-club/oh-dsh★ 324
社区发行版:TUI、桌面端与 Web UI 统一体验,分层安装、一步到位。
Fishquito7/dsh-skill-mcp-panel★ 248
在 DSH Web 设置中管理技能与 MCP 服务器:技能卡片热启停、工作区作用域、分组、批量迁移与拖拽导入,以及 stdio/HTTP MCP 增删改查、连接测试、密钥脱敏,并附带统一 dsh-panel 命令行。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。