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 可以直接用。
使用
- 拖动:按住按钮拖到任意位置。位移超过 4px 才判定为拖动,因此不会误触发点击。
- 位置记忆:松手即写入
localStorage,键名dsh-web-close-pos(结构{ v: 2, left, top })。 想恢复默认位置(top 1234 / left 306),在浏览器控制台执行:
然后刷新页面。位置按当前视口夹取,所以窗口比localStorage.removeItem('dsh-web-close-pos')1234px矮时按钮会被收到可视区域内。 - 关闭:点击按钮 → 弹窗显示目标进程
node.exe · PID … · http://127.0.0.1:…→ 点「确认关闭」。约 0.9 秒后 DSH 开始退出,随后自动尝试关闭本页面。 若浏览器拒绝脚本关闭(只允许关闭脚本打开的窗口),弹窗会给出「关闭本页」按钮兜底。 - 点「取消」、点弹窗外的遮罩、或按
Esc都会关闭弹窗,不会执行任何操作。
路由
宿主半边在 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。
No comments yet. Be the first to write one.