dsh-shutdown 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 replicant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # dsh-shutdown
2
+
3
+ <!-- Hero -->
4
+ <div align="center">
5
+ <b style="font-size: 1.15em;">一键结束 DSH 服务</b><br /><br />
6
+ Settings「关机」页 · 二次确认 · 优雅退出<br /><br />
7
+ <b>关掉浏览器不再留下后台进程</b>。
8
+ </div>
9
+
10
+ <div align="center">
11
+ 🌏 <a href="./README.md"><b>中文</b></a> · <a href="./README_EN.md">English</a>
12
+ </div>
13
+
14
+ ## ✨ 为什么需要
15
+
16
+ 关闭浏览器**不会**结束 dsh 的后台服务进程——服务继续占用端口与内存。本插件在页面内提供一键关机入口,无需命令行。
17
+
18
+ ## ✨ 功能一览
19
+
20
+ - **🖥️ Settings「关机」页**:侧栏最底部新增「关机」页签
21
+ - **🛡️ 二次确认**:点「关闭 DSH 服务」→ 出现「确认关闭 / 取消」双按钮,防误触
22
+ - **✅ 成功反馈**:确认后页面立即显示绿色「关闭成功」面板(并尝试自动关闭标签页)
23
+ - **🎨 原生风格**:按钮与配色全部引用 DSH 主题 token(`--dsw-alias-*`),明暗自适应
24
+
25
+ ## 📦 安装
26
+
27
+ ```bash
28
+ dsh plugin --profile web add <本仓库 / npm 包名>
29
+ # 重启 dsh web 生效
30
+ ```
31
+
32
+ ## 🧠 设计要点
33
+
34
+ - 关机端点:`/_dsh/shutdown`(仅 POST)——服务先返回「正在关闭」响应,500ms 后 `process.exit(0)` 优雅退出
35
+ - 会话日志为逐事件落盘,空闲时退出安全;正在进行的对话请等待其完成后再关闭
36
+ - 浏览器安全策略下脚本只能关闭自己打开的窗口——手动打开的标签页会保留「关闭成功」面板,两种情况下你都能确认结果
37
+
38
+ ## 🧩 生态
39
+
40
+ 打上 <a href="https://github.com/topics/dsh-shutdown"><code>dsh-shutdown</code></a> topic 的插件可接入同一生态。欢迎通过 PR 改进本插件。
41
+
42
+ ## 📄 许可
43
+
44
+ [MIT](./LICENSE)
package/README_EN.md ADDED
@@ -0,0 +1,44 @@
1
+ # dsh-shutdown
2
+
3
+ <!-- Hero -->
4
+ <div align="center">
5
+ <b style="font-size: 1.15em;">One-click shutdown for DeepSeek Harness</b><br /><br />
6
+ Settings page · double confirmation · graceful exit<br /><br />
7
+ <b>Closing the browser no longer leaves a background process behind</b>.
8
+ </div>
9
+
10
+ <div align="center">
11
+ 🌏 <a href="./README.md"><b>中文</b></a> · <a href="./README_EN.md">English</a>
12
+ </div>
13
+
14
+ ## ✨ Why
15
+
16
+ Closing the browser does **not** stop the dsh background service——it keeps holding the port and memory. This plugin adds an in-page shutdown entry, no CLI needed.
17
+
18
+ ## ✨ Features
19
+
20
+ - **🖥️ Settings page**: a new "Shutdown" section at the bottom of the sidebar
21
+ - **🛡️ Double confirmation**: click "Shut down DSH service" → "Confirm / Cancel" buttons appear, preventing accidents
22
+ - **✅ Success feedback**: a green "shut down successfully" panel renders immediately (and tries to close the tab)
23
+ - **🎨 Native look**: all colors reference DSH theme tokens (`--dsw-alias-*`), light/dark adaptive
24
+
25
+ ## 📦 Install
26
+
27
+ ```bash
28
+ dsh plugin --profile web add <this repo / npm package>
29
+ # restart dsh web
30
+ ```
31
+
32
+ ## 🧠 Design notes
33
+
34
+ - Shutdown endpoint: `/_dsh/shutdown` (POST only) —— responds "shutting down" first, then `process.exit(0)` after 500ms
35
+ - Session logs are appended per event; exiting while idle is safe. Let an in-flight turn finish first
36
+ - Browsers only allow scripts to close windows they opened——manually opened tabs keep the success panel, so the result is always visible
37
+
38
+ ## 🧩 Ecosystem
39
+
40
+ Tag your own plugins with <a href="https://github.com/topics/dsh-shutdown"><code>dsh-shutdown</code></a> to join the ecosystem. PRs welcome.
41
+
42
+ ## 📄 License
43
+
44
+ [MIT](./LICENSE)
@@ -0,0 +1,4 @@
1
+ # dsh-shutdown bundle patch: mounts the plugin into a profile layer stack.
2
+ - insert:
3
+ - id: shutdown
4
+ name: 'dsh-shutdown'
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "dsh-external/dsh-shutdown",
3
+ "version": "0.1.0",
4
+ "main": "./lib/index.js",
5
+ "description": "一键关机:Settings 新增「关机」页,二次确认后优雅退出 DSH 服务进程",
6
+ "engines": {
7
+ "dsh": ">=0.0.1"
8
+ },
9
+ "contributes": {
10
+ "tools": [],
11
+ "skills": []
12
+ },
13
+ "client": {
14
+ "main": "./lib/client.js"
15
+ }
16
+ }
package/lib/client.js ADDED
@@ -0,0 +1,155 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-shutdown", factory: (require) => {
2
+ var module = { exports: {} }; var exports = module.exports;
3
+ "use strict";
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.apply = apply;
6
+
7
+ /**
8
+ * dsh-shutdown 浏览器半部:Settings「关机」页面。
9
+ * 确认模式:点「关闭 DSH 服务」→ 出现「确认关闭 / 取消」双按钮;
10
+ * 确认后请求关机并渲染成功面板。样式引用 DSH 主题 token。
11
+ */
12
+ const react_1 = require("react");
13
+
14
+ const NS = 'shutdown';
15
+ const CSS = '' +
16
+ '.sd-root{display:flex;flex-direction:column;gap:12px;color:var(--dsw-alias-label-primary);font-size:14px}' +
17
+ '.sd-title{font-size:16px;font-weight:600;margin:0}' +
18
+ '.sd-sub{color:var(--dsw-alias-label-secondary);font-size:12px}' +
19
+ '.sd-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}' +
20
+ '.sd-btn{padding:8px 18px;border-radius:6px;cursor:pointer;font-size:13px;background:transparent}' +
21
+ '.sd-btn:disabled{opacity:0.5;cursor:not-allowed}' +
22
+ '.sd-btn-danger{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}' +
23
+ '.sd-btn-danger:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}' +
24
+ '.sd-btn-plain{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}' +
25
+ '.sd-btn-plain:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-primary)}' +
26
+ '.sd-confirm{color:var(--dsw-alias-label-secondary);font-size:13px}' +
27
+ '.sd-ok{color:var(--dsw-alias-state-success-primary);font-weight:600}' +
28
+ '.sd-err{color:var(--dsw-alias-state-error-primary)}';
29
+
30
+ function ShutdownSection() {
31
+ const [confirmOpen, setConfirmOpen] = react_1.useState(false);
32
+ const [closing, setClosing] = react_1.useState(false);
33
+ const [done, setDone] = react_1.useState(false);
34
+ const [error, setError] = react_1.useState(null);
35
+
36
+ function handleCancel() {
37
+ setConfirmOpen(false);
38
+ setError(null);
39
+ }
40
+
41
+ async function handleConfirm() {
42
+ if (closing) return;
43
+ setClosing(true);
44
+ setError(null);
45
+ try {
46
+ const res = await fetch('/_dsh/shutdown', {
47
+ method: 'POST',
48
+ headers: { accept: 'application/json' },
49
+ });
50
+ const body = await res.json();
51
+ if (body && body.ok) {
52
+ setDone(true);
53
+ // 尝试关闭标签页(仅对脚本打开的窗口生效,手动打开的标签页静默失败)
54
+ window.setTimeout(function () { try { window.close(); } catch (e) { /* 忽略 */ } }, 800);
55
+ return;
56
+ }
57
+ setError(String((body && body.error) || '关机请求失败'));
58
+ setClosing(false);
59
+ setConfirmOpen(false);
60
+ } catch (e) {
61
+ // 进程可能已退出导致 fetch 失败——这其实意味着关机成功
62
+ setDone(true);
63
+ }
64
+ }
65
+
66
+ if (done) {
67
+ const okTitle = react_1.createElement('h3', { className: 'sd-title' }, '关机');
68
+ const okLine = react_1.createElement(
69
+ 'div',
70
+ { className: 'sd-ok' },
71
+ '✅ 关闭成功——DSH 服务已退出。'
72
+ );
73
+ const okDesc = react_1.createElement(
74
+ 'div',
75
+ { className: 'sd-sub' },
76
+ '本标签页可以安全关闭。如需再次使用,请重新启动 dsh web。'
77
+ );
78
+ return react_1.createElement('div', { className: 'sd-root' }, okTitle, okLine, okDesc);
79
+ }
80
+
81
+ const title = react_1.createElement('h3', { className: 'sd-title' }, '关机');
82
+ const desc = react_1.createElement(
83
+ 'div',
84
+ { className: 'sd-sub' },
85
+ '关闭 DSH 服务进程。关闭后需要重新启动 dsh web 才能继续使用。'
86
+ );
87
+
88
+ const errLine = error
89
+ ? react_1.createElement('div', { className: 'sd-err' }, '错误:' + error)
90
+ : null;
91
+
92
+ if (confirmOpen) {
93
+ const confirmLine = react_1.createElement(
94
+ 'div',
95
+ { className: 'sd-confirm' },
96
+ '确定要关闭 DSH 服务吗?正在进行的对话将被中断。'
97
+ );
98
+ const confirmBtn = react_1.createElement(
99
+ 'button',
100
+ { className: 'sd-btn sd-btn-danger', onClick: handleConfirm, disabled: closing },
101
+ closing ? '正在关闭…' : '确认关闭'
102
+ );
103
+ const cancelBtn = react_1.createElement(
104
+ 'button',
105
+ { className: 'sd-btn sd-btn-plain', onClick: handleCancel, disabled: closing },
106
+ '取消'
107
+ );
108
+ const btnRow = react_1.createElement('div', { className: 'sd-row' }, confirmBtn, cancelBtn);
109
+ return react_1.createElement(
110
+ 'div',
111
+ { className: 'sd-root' },
112
+ title,
113
+ desc,
114
+ confirmLine,
115
+ btnRow,
116
+ errLine
117
+ );
118
+ }
119
+
120
+ const shutdownBtn = react_1.createElement(
121
+ 'button',
122
+ { className: 'sd-btn sd-btn-danger', onClick: function () { setConfirmOpen(true); } },
123
+ '关闭 DSH 服务'
124
+ );
125
+
126
+ return react_1.createElement(
127
+ 'div',
128
+ { className: 'sd-root' },
129
+ title,
130
+ desc,
131
+ shutdownBtn,
132
+ errLine
133
+ );
134
+ }
135
+
136
+ function apply(ctx) {
137
+ const slots = ctx.get('slots');
138
+ if (slots === undefined) return;
139
+
140
+ const styleEl = document.createElement('style');
141
+ styleEl.setAttribute('data-dsh-plugin', NS);
142
+ styleEl.textContent = CSS;
143
+ document.head.appendChild(styleEl);
144
+
145
+ slots.inject('settings.section', () => slots.register(
146
+ { name: 'settings.section', id: 'dsh-shutdown', order: 999, label: () => '关机' },
147
+ () => react_1.createElement(ShutdownSection, null)
148
+ ));
149
+
150
+ ctx.effect(() => () => {
151
+ if (styleEl.parentNode) styleEl.parentNode.removeChild(styleEl);
152
+ }, NS + ':css');
153
+ }
154
+
155
+ return module.exports; } });
package/lib/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * dsh-shutdown — DeepSeek Harness 关机插件(Host 半部)。
3
+ *
4
+ * 提供 `/_dsh/shutdown` 端点:收到 POST 后先返回确认响应(让浏览器
5
+ * 拿到"正在关闭"的反馈),500ms 后优雅退出宿主进程。会话日志为逐事件
6
+ * 落盘,空闲时退出安全;正在进行的 turn 请等待其完成后再关闭。
7
+ * @module dsh-shutdown
8
+ */
9
+ export const name = 'dsh-shutdown';
10
+ export const inject = [];
11
+
12
+ const EXIT_DELAY_MS = 500;
13
+
14
+ /** Plugin 入口。 */
15
+ export async function apply(ctx) {
16
+ ctx.inject(['webServer'], (webCtx) => {
17
+ const disposeRoute = webCtx.webServer.register({
18
+ kind: 'exact',
19
+ path: '/_dsh/shutdown',
20
+ handler: (req, res) => {
21
+ if (req.method !== 'POST') {
22
+ res.statusCode = 405;
23
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
24
+ res.end(JSON.stringify({ ok: false, error: 'method-not-allowed' }));
25
+ return;
26
+ }
27
+ res.statusCode = 200;
28
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
29
+ res.end(JSON.stringify({ ok: true, message: '正在关闭 DSH 服务' }));
30
+ // 留出响应送达浏览器的时间,再退出进程
31
+ setTimeout(() => {
32
+ process.exit(0);
33
+ }, EXIT_DELAY_MS);
34
+ },
35
+ });
36
+ return () => {
37
+ disposeRoute();
38
+ };
39
+ });
40
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "dsh-shutdown",
3
+ "version": "0.1.0",
4
+ "description": "DeepSeek Harness 关机插件:Settings 增加「关机」页,一键优雅退出 DSH 服务(先响应页面再退出进程)。",
5
+ "keywords": [
6
+ "deepseek",
7
+ "deepseek-harness",
8
+ "shutdown",
9
+ "exit"
10
+ ],
11
+ "type": "module",
12
+ "main": "lib/index.js",
13
+ "exports": {
14
+ ".": "./lib/index.js",
15
+ "./client": "./lib/client.js",
16
+ "./cordis.patch.yml": "./cordis.patch.yml",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "cordis.patch.yml",
22
+ "README.md",
23
+ "README_EN.md",
24
+ "dsh.plugin.json"
25
+ ],
26
+ "engines": {
27
+ "node": "^22.19.0 || >=24.0.0"
28
+ },
29
+ "dsh": {
30
+ "bundle": {
31
+ "patch": "./cordis.patch.yml"
32
+ },
33
+ "client": {
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-runtime",
36
+ "@deepseek-ai/dsh-client-ui-settings"
37
+ ],
38
+ "platform": "web"
39
+ }
40
+ },
41
+ "peerDependencies": {
42
+ "@deepseek-ai/cordis": ">=4.0.0-rc.7 <5.0.0",
43
+ "react": "^18.2.0"
44
+ },
45
+ "license": "MIT"
46
+ }