dsh-plugin-easy2restart 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +100 -7
  2. package/lib/client.js +208 -208
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # dsh-plugin-easy2restart
2
2
 
3
+ <details open>
4
+ <summary><b>🀄 中文(默认展开)</b></summary>
5
+
6
+ # 已发布到官方NPM
7
+
3
8
  > **DSH 重启插件:双击侧边栏重启按钮即可整体重启 DeepSeek Harness,会话运行时按钮置灰禁用。**
4
- > **DSH plugin: double-click the sidebar restart button to fully relaunch DeepSeek Harness; it is greyed out while an agent session is running.**
5
9
 
6
- DeepSeek Harness 重启插件:在左侧边栏**账号头像/设置上方**放一个「🔄 重启 DSH」按钮,**双击**即可整进程重启 DeepSeek Harness;当有 Agent 会话正在运行时,按钮自动置灰禁用,避免打断进行中的工作。
10
+ DeepSeek Harness 重启插件:在左侧边栏**账号头像/设置上方**放一个「⟳ 重启 DSH」按钮,**双击**即可整进程重启 DeepSeek Harness;当有 Agent 会话正在运行时,按钮自动置灰禁用,避免打断进行中的工作。
7
11
 
8
12
  ## 功能
9
13
 
@@ -26,10 +30,10 @@ DeepSeek Harness 重启插件:在左侧边栏**账号头像/设置上方**放
26
30
  > 前提:`dsh` 命令在 PATH 中(DeepSeek Harness 自带)。
27
31
 
28
32
  ```bash
29
- # 用 link 方式装本地副本(推荐,便于维护/调试)
30
- git clone https://github.com/SMPcosy/dsh-plugin-easy2restart
31
- dsh plugin --profile desktop add link:<克隆或本目录的绝对路径>
33
+ dsh-plugin-easy2restart
32
34
  ```
35
+ 复制上述内容(插件名),进行如下操作:
36
+ <img width="2558" height="1640" alt="dsh-plugin-easy2restart安装图示​" src="https://github.com/user-attachments/assets/6b719ee2-7ed9-4913-8c3b-99709d5b5e06" />
33
37
 
34
38
  若发布到 registry:
35
39
 
@@ -37,11 +41,11 @@ dsh plugin --profile desktop add link:<克隆或本目录的绝对路径>
37
41
  dsh plugin --profile desktop add dsh-plugin-easy2restart
38
42
  ```
39
43
 
40
- 装完**重启 DeepSeek Harness**,侧边栏底部账号头像上方即出现「🔄」按钮。
44
+ 装完**重启 DeepSeek Harness**,侧边栏底部账号头像上方即出现「⟳」按钮。
41
45
 
42
46
  ## 使用
43
47
 
44
- - **双击**「🔄」按钮即可重启 DSH。
48
+ - **双击**「⟳」按钮即可重启 DSH。
45
49
  - 当有会话正在运行时按钮为灰色(不可点),会话空闲时可正常重启。
46
50
  - 重启由计划任务拉起,与 DSH 自身退出无关,即使 DSH 被强杀也能自动重新启动。
47
51
 
@@ -80,3 +84,92 @@ dsh-plugin-easy2restart/
80
84
  ## License
81
85
 
82
86
  MIT
87
+
88
+ </details>
89
+
90
+ <details>
91
+ <summary><b>🌐 English</b></summary>
92
+
93
+ # Published on official NPM
94
+
95
+ > **DSH restart plugin: double-click the sidebar restart button to fully relaunch DeepSeek Harness; the button is greyed out while an agent session is running.**
96
+
97
+ A DeepSeek Harness restart plugin: it places a "⟳ Restart DSH" button in the left sidebar **above the account avatar / settings**; **double-click** it to restart the whole DeepSeek Harness process. While an agent session is running, the button is automatically greyed out to avoid interrupting work in progress.
98
+
99
+ ## Features
100
+
101
+ - Injects the restart button into the `sidebar.footer.action` slot (above the account avatar / settings).
102
+ - Double-click → the host launches a PowerShell script **detached from the DSH process tree** via Windows Task Scheduler; the script uses `taskkill /T` to force-kill the whole DSH process tree, then relaunches `DeepSeek Harness.exe`.
103
+ - The button is greyed out while an agent session is **running** (based on the DSH session status `running`).
104
+ - When no session is running, the button is clickable; the host keeps a "restart pending" record so that flows such as "return to the original session after restart" can be added later.
105
+
106
+ <img width="2558" height="1638" alt="dsh-pluin-restart-02" src="https://github.com/user-attachments/assets/1cd364ed-3048-4104-928c-46566ba9bcc6" />
107
+
108
+ <img width="2561" height="1638" alt="dsh-pluin-restart-01" src="https://github.com/user-attachments/assets/93e5d1e5-6647-4e03-9f8f-e491e3a4640d" />
109
+
110
+ ## Requirements
111
+
112
+ - DeepSeek Harness desktop (0.2.0-rc or later)
113
+ - Node.js ≥ 22
114
+
115
+ ## Installation
116
+
117
+ > Prerequisite: the `dsh` command must be on PATH (bundled with DeepSeek Harness).
118
+
119
+ ```bash
120
+ dsh-plugin-easy2restart
121
+ ```
122
+ Copy the plugin name above, then follow the steps below:
123
+ <img width="2558" height="1640" alt="dsh-plugin-easy2restart install illustration" src="https://github.com/user-attachments/assets/6b719ee2-7ed9-4913-8c3b-99709d5b5e06" />
124
+
125
+ If publishing to a registry:
126
+
127
+ ```bash
128
+ dsh plugin --profile desktop add dsh-plugin-easy2restart
129
+ ```
130
+
131
+ After installing, **restart DeepSeek Harness**; the "⟳" button then appears at the bottom of the sidebar, above the account avatar.
132
+
133
+ ## Usage
134
+
135
+ - **Double-click** the "⟳" button to restart DSH.
136
+ - The button is greyed out (not clickable) while a session is running; it works normally when the session is idle.
137
+ - The restart is launched by a scheduled task and is independent of DSH exiting itself, so DSH restarts automatically even if it is force-killed.
138
+
139
+ ## Uninstall
140
+
141
+ ```bash
142
+ dsh plugin --profile desktop remove dsh-plugin-easy2restart
143
+ ```
144
+
145
+ Then restart DSH for the change to take effect.
146
+
147
+ ## How it works (brief)
148
+
149
+ - `lib/index.js` (host / Cordis plugin): registers the `/api/dsh-plugin-easy2restart/*` HTTP routes; `/restart` records the pending entry and calls `scheduleRestart`, which uses `schtasks` to create an immediately-running restart task. The task is started by the Task Scheduler service (**detached from the DSH process tree**) and executes `restart/restart.ps1`.
150
+ - `restart/restart.ps1`: `taskkill /PID <main process> /T /F` terminates the whole DSH process tree, waits for it to exit, then runs `Start-Process DeepSeek Harness.exe`.
151
+ - `lib/client.js` (Web client): injects into `sidebar.footer.action` to render the button; uses `useSessionStatus` to decide whether to disable it.
152
+
153
+ ## Directory structure
154
+
155
+ ```
156
+ dsh-plugin-easy2restart/
157
+ ├─ package.json # plugin manifest (dsh.client / dsh.bundle declarations)
158
+ ├─ cordis.patch.yml # bundle mount declaration
159
+ ├─ lib/
160
+ │ ├─ index.js # host side: HTTP routes + restart scheduling
161
+ │ └─ client.js # client side: sidebar button
162
+ └─ restart/
163
+ └─ restart.ps1 # restart script (kill process tree + relaunch exe)
164
+ ```
165
+
166
+ ## Notes
167
+
168
+ - This is an alternative to modifying the installed main program: the plugin is added to the profile directly as a local bundle, without touching `app.asar`.
169
+ - If you want "after the restart, automatically return to the original session and fill in 'restart complete'", you can extend it based on the pending record of `/restart` (`~/.dsh/storages/dsh-plugin-easy2restart/pending.json`); the current version does not fill the input box automatically.
170
+
171
+ ## License
172
+
173
+ MIT
174
+
175
+ </details>
package/lib/client.js CHANGED
@@ -1,209 +1,209 @@
1
- /**
2
- * dsh-plugin-easy2restart — Web 客户端(客户端半侧)
3
- *
4
- * 挂载点:sidebar.footer.action(侧边栏底部、账号头像/设置上方)。
5
- * 交互:
6
- * - 双击按钮 => 调用宿主 /restart 记录「重启完成」待办并重启 DSH。
7
- * - 有 Agent 会话运行中时按钮置灰禁用(通过宿主 /active 轮询判定)。
8
- * - 重启后,DSH 恢复会话;本组件的「自动发送」交给宿主端在频道
9
- * (apply 后检测 pending)尽力恢复会话并注入『重启完成』用户消息。
10
- *
11
- * 数据/动作走宿主 HTTP 路由:
12
- * GET /api/dsh-plugin-easy2restart/active -> { active }
13
- * GET /api/dsh-plugin-easy2restart/state -> { data: pending }
14
- * POST /api/dsh-plugin-easy2restart/restart -> { sessionId, text }
15
- * POST /api/dsh-plugin-easy2restart/clear
16
- */
17
-
18
- window.__ModuleLoader__.load({
19
- id: "dsh-plugin-easy2restart",
20
- factory: (require) => {
21
- const module = { exports: {} };
22
- const exports = module.exports;
23
-
24
- const { createElement: h, useState, useEffect, useRef, useCallback } = require("react");
25
-
26
- const NS = "dsh-plugin-easy2restart";
27
- const API = "/api/dsh-plugin-easy2restart";
28
- const POLL_MS = 1200;
29
-
30
- // 按钮图标:与插件列表共用同一张 ⟳ 图标(内联 data URI,随位置缩放)
31
- const ICON = "data:image/png;base64,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";
32
-
33
- const zh = {
34
- tooltip: "重启 DSH",
35
- tooltipDisabled: "有会话运行中,无法重启",
36
- done: "重启完成",
37
- restartHint: "双击重启",
38
- unavailable: "宿主不可用",
39
- };
40
-
41
- function RestartButton(props) {
42
- const { wide, useSessionStatus } = props || {};
43
- // 诊断回写宿主(host 写入 diag.log)
44
- function diag(msg) {
45
- try {
46
- fetch(API + "/diag", {
47
- method: "POST",
48
- headers: { "content-type": "application/json" },
49
- body: JSON.stringify({ msg }),
50
- }).catch(() => {});
51
- } catch (e) {
52
- /* 忽略 */
53
- }
54
- }
55
- // 是否正在重启(防重复触发)
56
- const [restarting, setRestarting] = useState(false);
57
- // 宿主是否可及(/health 轮询)
58
- const [alive, setAlive] = useState(true);
59
- const busyRef = useRef(false);
60
-
61
- // 是否有会话正在运行(禁用依据)。useSessionStatus 是 DSH 注入的快照 hook,
62
- // snapshot 为 Map:sessionId -> status(含 running 字段)。若未见注入则视为无运行。
63
- const running = (typeof useSessionStatus === "function" ? useSessionStatus : () => false)((snapshot) => {
64
- if (!snapshot || typeof snapshot.values !== "function") return false;
65
- for (const st of snapshot.values()) {
66
- if (st && st.running) return true;
67
- }
68
- return false;
69
- });
70
-
71
- const fetchJson = useCallback(async (path, options) => {
72
- const res = await fetch(`${API}${path}`, {
73
- headers: { "content-type": "application/json" },
74
- ...(options || {}),
75
- });
76
- return res.json().catch(() => ({}));
77
- }, []);
78
-
79
- // 轮询宿主存活性(存在 /health 路由 = alive)
80
- useEffect(() => {
81
- let stop = false;
82
- const tick = async () => {
83
- if (stop) return;
84
- try {
85
- const r = await fetch(`${API}/health`);
86
- if (!stop) setAlive(r.status >= 200 && r.status < 500);
87
- } catch {
88
- if (!stop) setAlive(false);
89
- }
90
- };
91
- tick();
92
- const timer = window.setInterval(tick, POLL_MS);
93
- return () => {
94
- stop = true;
95
- window.clearInterval(timer);
96
- };
97
- }, []);
98
-
99
- // 双击触发重启
100
- const onDoubleClick = async () => {
101
- if (busyRef.current || restarting || running) return;
102
- busyRef.current = true;
103
- try {
104
- const res = await fetch(`${API}/restart`, {
105
- method: "POST",
106
- headers: { "content-type": "application/json" },
107
- body: JSON.stringify({ sessionId: null, text: zh.done }),
108
- });
109
- const j = await res.json().catch(() => ({}));
110
- diag("double-click /restart status=" + res.status + " ok=" + !!j.ok);
111
- if (j && j.ok) setRestarting(true);
112
- } catch (e) {
113
- diag("double-click /restart fetch error: " + (e && e.message ? e.message : String(e)));
114
- setAlive(false);
115
- } finally {
116
- busyRef.current = false;
117
- }
118
- };
119
-
120
- const disabled = running || restarting || !alive;
121
- const label = running
122
- ? zh.tooltipDisabled
123
- : restarting
124
- ? zh.done
125
- : alive
126
- ? zh.tooltip
127
- : zh.unavailable;
128
-
129
- const style = {
130
- boxSizing: "border-box",
131
- width: wide ? "100%" : 30,
132
- height: 30,
133
- display: "inline-flex",
134
- alignItems: "center",
135
- justifyContent: "center",
136
- gap: 6,
137
- border: "none",
138
- borderRadius: 6,
139
- background: "transparent",
140
- color: disabled ? "var(--dsw-alias-label-dimmed, #9aa0a6)" : "var(--dsw-alias-label-secondary, #5f6368)",
141
- cursor: disabled ? "default" : "pointer",
142
- fontSize: 15,
143
- opacity: disabled ? 0.45 : 1,
144
- };
145
-
146
- return h(
147
- "button",
148
- {
149
- type: "button",
150
- "aria-label": label,
151
- title: `${label} — ${zh.restartHint}`,
152
- style,
153
- disabled,
154
- onDoubleClick,
155
- },
156
- wide ? [h("img", { key: "ic", src: ICON, alt: "", "aria-hidden": "true", style: { width: 14, height: 14, display: "block", objectFit: "contain" } }), h("span", { key: "tx", style: { fontSize: 12 } }, "重启 DSH")] : h("img", { src: ICON, alt: "", "aria-hidden": "true", style: { width: 16, height: 16, display: "block", objectFit: "contain" } }),
157
- );
158
- }
159
-
160
- exports.name = NS;
161
- exports.inject = ["slots", "locale"];
162
-
163
- exports.apply = function apply(ctx) {
164
- // 诊断回写宿主(host 写入 ~/.dsh/storages/dsh-plugin-easy2restart/diag.log),便于排查 client 加载链路。
165
- function diag(msg) {
166
- try {
167
- fetch(API + "/diag", {
168
- method: "POST",
169
- headers: { "content-type": "application/json" },
170
- body: JSON.stringify({ msg }),
171
- }).catch(() => {});
172
- } catch (e) {
173
- /* 诊断失败忽略 */
174
- }
175
- }
176
- diag("apply-called slots=" + Boolean(ctx && ctx.slots) + " inject=" + Boolean(ctx && ctx.slots && ctx.slots.inject));
177
- let t = function (key) {
178
- return key;
179
- };
180
- try {
181
- ctx.effect(() => ctx.locale.register(NS, { zh, en: { tooltip: "Restart DSH", tooltipDisabled: "Session running; cannot restart", done: "Restart done", restartHint: "Double-click to restart" } }), NS + ": dict");
182
- t = ctx.locale.bind(NS);
183
- } catch (e) {
184
- /* 降级 */
185
- }
186
- const label = () => t("tooltip");
187
-
188
- try {
189
- ctx.slots.inject("sidebar.footer.action", function () {
190
- try {
191
- const reg = ctx.slots.register(
192
- { name: "sidebar.footer.action", id: NS, order: -100, label, locale: NS },
193
- RestartButton,
194
- );
195
- diag("slot registered sidebar.footer.action");
196
- return reg;
197
- } catch (e) {
198
- diag("slot register fail: " + (e && e.message ? e.message : String(e)));
199
- return undefined;
200
- }
201
- });
202
- } catch (e) {
203
- try { if (typeof console !== "undefined" && console && typeof console.warn === "function") console.warn("[" + NS + "] inject fail: " + (e && e.message)); } catch (e2) {}
204
- }
205
- };
206
-
207
- return module.exports;
208
- },
1
+ /**
2
+ * dsh-plugin-easy2restart — Web 客户端(客户端半侧)
3
+ *
4
+ * 挂载点:sidebar.footer.action(侧边栏底部、账号头像/设置上方)。
5
+ * 交互:
6
+ * - 双击按钮 => 调用宿主 /restart 记录「重启完成」待办并重启 DSH。
7
+ * - 有 Agent 会话运行中时按钮置灰禁用(通过宿主 /active 轮询判定)。
8
+ * - 重启后,DSH 恢复会话;本组件的「自动发送」交给宿主端在频道
9
+ * (apply 后检测 pending)尽力恢复会话并注入『重启完成』用户消息。
10
+ *
11
+ * 数据/动作走宿主 HTTP 路由:
12
+ * GET /api/dsh-plugin-easy2restart/active -> { active }
13
+ * GET /api/dsh-plugin-easy2restart/state -> { data: pending }
14
+ * POST /api/dsh-plugin-easy2restart/restart -> { sessionId, text }
15
+ * POST /api/dsh-plugin-easy2restart/clear
16
+ */
17
+
18
+ window.__ModuleLoader__.load({
19
+ id: "dsh-plugin-easy2restart",
20
+ factory: (require) => {
21
+ const module = { exports: {} };
22
+ const exports = module.exports;
23
+
24
+ const { createElement: h, useState, useEffect, useRef, useCallback } = require("react");
25
+
26
+ const NS = "dsh-plugin-easy2restart";
27
+ const API = "/api/dsh-plugin-easy2restart";
28
+ const POLL_MS = 1200;
29
+
30
+ // 按钮图标:与插件列表共用同一张 ⟳ 图标(内联 data URI,随位置缩放)
31
+ const ICON = "data:image/png;base64,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";
32
+
33
+ const zh = {
34
+ tooltip: "重启 DSH",
35
+ tooltipDisabled: "有会话运行中,无法重启",
36
+ done: "重启完成",
37
+ restartHint: "双击重启",
38
+ unavailable: "宿主不可用",
39
+ };
40
+
41
+ function RestartButton(props) {
42
+ const { wide, useSessionStatus } = props || {};
43
+ // 诊断回写宿主(host 写入 diag.log)
44
+ function diag(msg) {
45
+ try {
46
+ fetch(API + "/diag", {
47
+ method: "POST",
48
+ headers: { "content-type": "application/json" },
49
+ body: JSON.stringify({ msg }),
50
+ }).catch(() => {});
51
+ } catch (e) {
52
+ /* 忽略 */
53
+ }
54
+ }
55
+ // 是否正在重启(防重复触发)
56
+ const [restarting, setRestarting] = useState(false);
57
+ // 宿主是否可及(/health 轮询)
58
+ const [alive, setAlive] = useState(true);
59
+ const busyRef = useRef(false);
60
+
61
+ // 是否有会话正在运行(禁用依据)。useSessionStatus 是 DSH 注入的快照 hook,
62
+ // snapshot 为 Map:sessionId -> status(含 running 字段)。若未见注入则视为无运行。
63
+ const running = (typeof useSessionStatus === "function" ? useSessionStatus : () => false)((snapshot) => {
64
+ if (!snapshot || typeof snapshot.values !== "function") return false;
65
+ for (const st of snapshot.values()) {
66
+ if (st && st.running) return true;
67
+ }
68
+ return false;
69
+ });
70
+
71
+ const fetchJson = useCallback(async (path, options) => {
72
+ const res = await fetch(`${API}${path}`, {
73
+ headers: { "content-type": "application/json" },
74
+ ...(options || {}),
75
+ });
76
+ return res.json().catch(() => ({}));
77
+ }, []);
78
+
79
+ // 轮询宿主存活性(存在 /health 路由 = alive)
80
+ useEffect(() => {
81
+ let stop = false;
82
+ const tick = async () => {
83
+ if (stop) return;
84
+ try {
85
+ const r = await fetch(`${API}/health`);
86
+ if (!stop) setAlive(r.status >= 200 && r.status < 500);
87
+ } catch {
88
+ if (!stop) setAlive(false);
89
+ }
90
+ };
91
+ tick();
92
+ const timer = window.setInterval(tick, POLL_MS);
93
+ return () => {
94
+ stop = true;
95
+ window.clearInterval(timer);
96
+ };
97
+ }, []);
98
+
99
+ // 双击触发重启
100
+ const onDoubleClick = async () => {
101
+ if (busyRef.current || restarting || running) return;
102
+ busyRef.current = true;
103
+ try {
104
+ const res = await fetch(`${API}/restart`, {
105
+ method: "POST",
106
+ headers: { "content-type": "application/json" },
107
+ body: JSON.stringify({ sessionId: null, text: zh.done }),
108
+ });
109
+ const j = await res.json().catch(() => ({}));
110
+ diag("double-click /restart status=" + res.status + " ok=" + !!j.ok);
111
+ if (j && j.ok) setRestarting(true);
112
+ } catch (e) {
113
+ diag("double-click /restart fetch error: " + (e && e.message ? e.message : String(e)));
114
+ setAlive(false);
115
+ } finally {
116
+ busyRef.current = false;
117
+ }
118
+ };
119
+
120
+ const disabled = running || restarting || !alive;
121
+ const label = running
122
+ ? zh.tooltipDisabled
123
+ : restarting
124
+ ? zh.done
125
+ : alive
126
+ ? zh.tooltip
127
+ : zh.unavailable;
128
+
129
+ const style = {
130
+ boxSizing: "border-box",
131
+ width: wide ? "100%" : 30,
132
+ height: 30,
133
+ display: "inline-flex",
134
+ alignItems: "center",
135
+ justifyContent: "center",
136
+ gap: 6,
137
+ border: "none",
138
+ borderRadius: 6,
139
+ background: "transparent",
140
+ color: disabled ? "var(--dsw-alias-label-dimmed, #9aa0a6)" : "var(--dsw-alias-label-secondary, #5f6368)",
141
+ cursor: disabled ? "default" : "pointer",
142
+ fontSize: 15,
143
+ opacity: disabled ? 0.45 : 1,
144
+ };
145
+
146
+ return h(
147
+ "button",
148
+ {
149
+ type: "button",
150
+ "aria-label": label,
151
+ title: `${label} — ${zh.restartHint}`,
152
+ style,
153
+ disabled,
154
+ onDoubleClick,
155
+ },
156
+ wide ? [h("img", { key: "ic", src: ICON, alt: "", "aria-hidden": "true", style: { width: 14, height: 14, display: "block", objectFit: "contain" } }), h("span", { key: "tx", style: { fontSize: 12 } }, "重启 DSH")] : h("img", { src: ICON, alt: "", "aria-hidden": "true", style: { width: 16, height: 16, display: "block", objectFit: "contain" } }),
157
+ );
158
+ }
159
+
160
+ exports.name = NS;
161
+ exports.inject = ["slots", "locale"];
162
+
163
+ exports.apply = function apply(ctx) {
164
+ // 诊断回写宿主(host 写入 ~/.dsh/storages/dsh-plugin-easy2restart/diag.log),便于排查 client 加载链路。
165
+ function diag(msg) {
166
+ try {
167
+ fetch(API + "/diag", {
168
+ method: "POST",
169
+ headers: { "content-type": "application/json" },
170
+ body: JSON.stringify({ msg }),
171
+ }).catch(() => {});
172
+ } catch (e) {
173
+ /* 诊断失败忽略 */
174
+ }
175
+ }
176
+ diag("apply-called slots=" + Boolean(ctx && ctx.slots) + " inject=" + Boolean(ctx && ctx.slots && ctx.slots.inject));
177
+ let t = function (key) {
178
+ return key;
179
+ };
180
+ try {
181
+ ctx.effect(() => ctx.locale.register(NS, { zh, en: { tooltip: "Restart DSH", tooltipDisabled: "Session running; cannot restart", done: "Restart done", restartHint: "Double-click to restart" } }), NS + ": dict");
182
+ t = ctx.locale.bind(NS);
183
+ } catch (e) {
184
+ /* 降级 */
185
+ }
186
+ const label = () => t("tooltip");
187
+
188
+ try {
189
+ ctx.slots.inject("sidebar.footer.action", function () {
190
+ try {
191
+ const reg = ctx.slots.register(
192
+ { name: "sidebar.footer.action", id: NS, order: -100, label, locale: NS },
193
+ RestartButton,
194
+ );
195
+ diag("slot registered sidebar.footer.action");
196
+ return reg;
197
+ } catch (e) {
198
+ diag("slot register fail: " + (e && e.message ? e.message : String(e)));
199
+ return undefined;
200
+ }
201
+ });
202
+ } catch (e) {
203
+ try { if (typeof console !== "undefined" && console && typeof console.warn === "function") console.warn("[" + NS + "] inject fail: " + (e && e.message)); } catch (e2) {}
204
+ }
205
+ };
206
+
207
+ return module.exports;
208
+ },
209
209
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-easy2restart",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "DSH plugin: double-click the sidebar restart button to fully relaunch DeepSeek Harness; it is greyed out while an agent session is running. (中文:DSH 重启插件:双击侧边栏重启按钮即可整体重启 DeepSeek Harness,会话运行时按钮置灰禁用。)",
6
6
  "keywords": [