dsh-web-close —— DSH Web 关闭按钮
DSH(DeepSeek Harness)Web 界面上的常驻关闭按钮:半透明红色、SVG 手绘电源图标、
可拖动并记忆位置,点击后弹出视口居中的二次确认(左「取消」灰 / 右「确认关闭」红),
确认后优雅关闭当前正在运行的 DSH 进程,并尝试关掉当前这个 DSH 页面
(只关本页,不会关掉浏览器或其他窗口)。
标准 DSH 插件包,通过 dsh plugin 安装与卸载,随 dsh web 每次启动自动加载。
特性
<<<<<<< HEAD
- 常驻自启:标准 DSH bundle 插件,装一次,之后每次启动都在
- 半透明红外观:
rgba(220,38,38,.42) + 毛玻璃 + 红色描边光晕,悬停加深
- SVG 手绘图标:电源符号、警告三角、叉号全部为内联 SVG,不含任何图片资源
- 可拖动 + 位置记忆:默认落在视口
top: 1234px / left: 306px
(等价于 inset: 1234px auto auto 306px);拖到哪就在哪,写入 localStorage,
刷新或重开浏览器后原样恢复;按当前视口夹取,窗口变小也不会跑到屏幕外
- 二次确认弹窗:视口居中,左「取消」灰、右「确认关闭」红,并显示将要关闭的 PID 与地址
- 优雅关闭:不是
kill -9,而是先卸载整棵插件树再退出(见下)
- 顺便关掉本页面:关闭请求被接受后自动
window.close() 当前标签页;
浏览器出于安全策略拒绝时,弹窗里会出现「关闭本页」按钮,退而把本页置为 about:blank。
全程只作用于当前这一个标签页,其他窗口与标签页不受影响
- 跨平台、零依赖:宿主侧只用 Node 内置模块,浏览器侧是纯原生 DOM;
=======
- 常驻自启:标准 DSH bundle 插件,装一次,之后每次启动都在
- 半透明红外观:
rgba(220,38,38,.42) + 毛玻璃 + 红色描边光晕,悬停加深
- SVG 手绘图标:电源符号、警告三角、叉号全部为内联 SVG,不含任何图片资源
- 可拖动 + 位置记忆:拖到哪就在哪,写入
localStorage,刷新或重开浏览器后原样恢复;
按当前视口夹取,窗口变小也不会跑到屏幕外
- 二次确认弹窗:视口居中,左「取消」灰、右「确认关闭」红,并显示将要关闭的 PID 与地址
- 优雅关闭:不是
kill -9,而是先卸载整棵插件树再退出(见下)
- 跨平台、零依赖:宿主侧只用 Node 内置模块,浏览器侧是纯原生 DOM;
e0f3bb049138f465ae5c900a80e6f292f6f4dfb3
Windows / macOS / Linux 行为一致,没有 PowerShell、没有构建步骤、没有运行时依赖
安装:从 GitHub 克隆安装
git clone https://github.com/Aclguh/dsh-web-close.git
cd dsh-web-close
dsh plugin --profile web add link:.
要点:
link:. 表示链接当前目录——仓库根目录本身就是插件包。
- ⚠️ 不要写成
link:.\dsh-web-close 这类带子目录的路径,那样会装成普通依赖而不是插件,
重启后按钮不会出现。
- 成功后
dsh plugin 会自动把 dsh-web-close 写进 profile 的 dsh.profile.bundles,
不需要手工改配置。
- 装完必须重启
dsh web,然后刷新浏览器。
- 如果之后移动了源码目录,要重新执行一次
add link:<新路径>;若提示冲突,
先 dsh plugin --profile web remove dsh-web-close 再重新 add。
卸载
dsh plugin --profile web remove dsh-web-close
然后确认 profile 的 dsh.profile.bundles 里不再有 "dsh-web-close"(没有就无需处理),
最后重启 dsh web。
环境要求
- 已安装 DSH,且能正常执行
dsh web
pnpm(dsh plugin 会把参数转发给 pnpm;缺失时先 npm install -g pnpm)
- 本插件只对 web profile 有意义(它依赖 DSH 的 Web 服务器服务)
- 没有其他依赖:不需要额外 npm 包、不需要构建工具、不需要任何 API Key
给 AI 助手的安装说明
如果想让另一个 DSH / AI 助手代装,把下面这段直接发给它:
请帮我安装 DSH 插件 dsh-web-close,来源是 GitHub 仓库 Aclguh/dsh-web-close。
步骤:
1. 确保 pnpm 可用(没有就先执行 npm install -g pnpm)
2. git clone https://github.com/Aclguh/dsh-web-close.git
3. 进入克隆出来的 dsh-web-close 目录,执行:
dsh plugin --profile web add link:.
(仓库根目录就是插件包,不要写成 link:.\dsh-web-close 这种带子目录的路径)
4. 重启 dsh web,然后刷新浏览器
安装后请验证:
- dsh --profile web --dump-config 的输出里能看到 - id: dsh-web-close
- 浏览器里出现半透明红色圆形按钮(默认在视口 top 1234px / left 306px)
- curl http://127.0.0.1:3080/dsh-web-close/info.json 返回含 "ok":true 的 JSON
它是怎么关闭 DSH 的
插件运行在 DSH 宿主进程内部,也就是承载 Web 服务与 Agent 的那个 node 进程本身。
因此它不需要去别的进程里"找到它再杀掉",而是调用 DSH 自己的有界优雅关闭:
| 顺序 | 路径 | 行为 |
|---|
| 1 | ctx.get('appExit')(0) | DSH 启动器通过 provideCmdline() 提供的退出请求 → shutdown.shutdown(0):先 dispose 整棵插件树(所有插件的 disposer 都会执行),再设 exitCode = 0 让 Node 自然退出 |
| 2 | process.emit('SIGINT') | 命中 DSH 自己注册的 process.on('SIGINT') → shutdown.interrupt(130),同样先 dispose 再退出 |
| 3 | process.exit(0) | 兜底强制退出 |
appExit 是 ctx.provide() 提供的裸值,不在 Service 目录里;因此本插件刻意不把它写进
inject(那样会有让整行永远停在 waiting for appExit 的风险),而是运行时用 ctx.get()
取值,并带上表里的降级链。
⚠️ Web 服务与 Agent 宿主是同一个 node 进程,所以关闭的是整个 DSH,当前所有会话都会中断——
这正是二次确认存在的原因。
<<<<<<< HEAD
关闭请求被接受后,浏览器侧还会做最后一件事:关掉当前这一个标签页。
它只调 window.close(),不碰 window.opener、不碰其他窗口;浏览器按安全策略拒绝时,
退化为把本页替换成 about:blank。
=======
e0f3bb049138f465ae5c900a80e6f292f6f4dfb3
目录结构
dsh-web-close/
├── package.json # 包声明;dsh.bundle.patch 指向 cordis.patch.yml
├── cordis.patch.yml # bundle patch:把 id/name 为 dsh-web-close 的一行插入配置树
├── lib/
│ ├── index.js # 宿主半边:HTTP 路由 + tapIndex 注入 <script>
│ └── widget.js # 浏览器半边:原生 DOM 的按钮与确认弹窗
├── README.md
└── LICENSE
验证
配置层——确认插件行被组合进 profile:
dsh --profile web --dump-config
输出中应能看到:
# == dsh-web-close
- id: dsh-web-close
name: dsh-web-close
服务层——需要 DSH 正在运行。端口按你自己的替换(DSH 默认 3080):
curl http://127.0.0.1:3080/dsh-web-close/info.json
# {"ok":true,"pid":12345,"url":"http://127.0.0.1:3080","profile":null,"uptimeSec":438,"inProcess":true}
curl -I http://127.0.0.1:3080/dsh-web-close/widget.js
# HTTP/1.1 200 OK
# Content-Type: application/javascript; charset=utf-8
界面层——重启并刷新后,页面里出现半透明红色圆形按钮
(默认在视口 top 1234px / left 306px)。
DSH 的 HTML 路由自身带访问鉴权(直接 curl 根路径会得到 401),但插件注册的同源路由不受影响,
所以上面两条 curl 可以直接用。
使用
路由
宿主半边在 DSH 的 Web 服务器上注册三个同源路由:
| 路由 | 方法 | 说明 |
|---|
/dsh-web-close/widget.js | GET | 浏览器半边脚本。每次请求都重新读盘,所以改完 lib/widget.js 只需刷新页面 |
/dsh-web-close/info.json | GET | 返回将被动到的进程:process.pid 与浏览器自己的 Host 头 |
/dsh-web-close/shutdown | POST | 接受关闭请求,延迟 0.9 秒后触发优雅关闭(其他方法返回 405) |
widget.js 由 ctx.webServer.tapIndex() 以 <script defer> 注入 index.html,并带重复注入保护。
/shutdown 是用 fetch(..., { keepalive: true }) 发出的,所以即使标签页随后立刻被关掉,
请求也一定能送达。
常见问题
- 按钮不出现
- 确认
dsh plugin --profile web add 成功,且 dsh --profile web --dump-config 里能看到
- id: dsh-web-close;
- 确认重启过
dsh web——插件只在启动时加载,热改配置不会生效;
- 重启后按
Ctrl/Cmd + Shift + R 强制刷新,避开浏览器缓存。
- 改了默认位置后按钮没动:
localStorage 里存的是旧结构(v: 1)坐标,会被自动作废并回落新默认位置;
若想彻底清干净,执行 localStorage.removeItem('dsh-web-close-pos') 再刷新。
- 拖动后位置没保留:无痕/隐私模式下
localStorage 不可用,这时只在本次会话内记忆位置。
- 点了「确认关闭」但页面没自动关闭:浏览器的安全策略只允许脚本关闭"由脚本打开的"窗口,
普通标签页会被忽略。这时弹窗会出现「关闭本页」按钮(置为空白页),或直接按
Ctrl / ⌘ + W。
DSH 进程本身不受影响,已经退出或正在退出。
- 点了「确认关闭」但没退出:查看
dsh web 所在终端的输出,关闭时会打印一行
[dsh-web-close] graceful shutdown requested via appExit(或 via SIGINT)。
- 端口不是 3080:把 curl 命令换成你自己的端口即可;界面里显示的地址来自浏览器实际请求,
始终是正确的。
- 改了代码不生效:改
lib/widget.js 只需刷新页面(路由每次重新读盘);
改 lib/index.js(宿主半边)需要重启 dsh web。
- 会和别的插件冲突吗:本插件只占用
/dsh-web-close/* 三个路由和一个
id: dsh-web-close 的配置行。与同生态插件的 tapIndex 注入是叠加关系,
例如可与 dsh-whale-widget 同时使用,两个 <script> 会并存于页面底部。
许可证
本项目基于 MIT License 开源,详见 LICENSE。