dsh-autonomous-restart 0.0.0-stage → 1.2.2
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 +21 -0
- package/README.md +222 -2
- package/SETUP.md +148 -0
- package/client.js +927 -0
- package/cordis.patch.yml +14 -0
- package/icon.svg +6 -0
- package/index.js +1798 -0
- package/lib/launch-plan.js +303 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +43 -4
- package/scripts/Restart-DSH.ps1 +803 -0
- package/scripts/Start-DSH.ps1 +501 -0
- package/scripts/say.js +117 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 meng-lx
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,223 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-autonomous-restart
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
当前版本 **1.2.2**
|
|
4
|
+
|
|
5
|
+
**已适配官方桌面端与官方网页端(`dsh web`)**,两种载体都跑通过真机重启。
|
|
6
|
+
第三方载体(非官方 Electron 壳、`UtilityProcess` 里跑 CLI、自定义启动方式)不在默认支持
|
|
7
|
+
范围内:插件检测出差异后不会贸然动手,由 agent 照 `SETUP.md` 征得你同意,再写一次启动计划。
|
|
8
|
+
|
|
9
|
+
**由 agent 自己发起的重启,不是按钮。** agent 调一个工具,之后由插件接管:等这台机器上
|
|
10
|
+
所有 agent 与后台任务都空闲,走完可取消的倒计时,再重启 DSH,并唤醒原会话让 agent 接着说下去。
|
|
11
|
+
|
|
12
|
+
插件只负责判断「什么时候可以动手」;杀进程、跑命令、重启、唤醒,交给包里的
|
|
13
|
+
PowerShell 脚本。两边通过一个交接文件通信,谁也不猜对方的状态。
|
|
14
|
+
|
|
15
|
+
## 前置条件
|
|
16
|
+
|
|
17
|
+
- DeepSeek Harness **官方桌面端**,或**官方网页端**(`dsh web`)
|
|
18
|
+
- Windows
|
|
19
|
+
|
|
20
|
+
重启动作由包里的 PowerShell 脚本完成。插件只在倒计时结束后调用它,
|
|
21
|
+
设置页里能看到它当前解析到的路径。想换成自己的实现也只要接住同样的参数。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
1. 把本目录放到任意位置(例如 `D:\DeepSeek Harness\dsh-auto-restart`);
|
|
26
|
+
2. 在 DSH 里执行插件安装:`plugin_manager install_bundle`,`target` 填本目录的绝对路径;
|
|
27
|
+
3. 重启 DSH(Host 半边改动需要重启才生效)。
|
|
28
|
+
|
|
29
|
+
也可以直接改 profile 的 `package.json` 加一条 `link:` 依赖并写进
|
|
30
|
+
`dsh.profile.bundles`,效果一样。
|
|
31
|
+
|
|
32
|
+
## 用起来是这样
|
|
33
|
+
|
|
34
|
+
1. agent 调 `restart_dsh` 工具(唯一触发方式,没有开关、没有自动触发);
|
|
35
|
+
2. 工具**立刻返回**,倒计时在后台走,界面浮出一张毛玻璃卡片;
|
|
36
|
+
3. 倒计时期间点一下鼠标、滚一下滚轮、按任意键 → 本次取消,界面给一张「已取消重启」页;
|
|
37
|
+
4. 倒计时走完 → 判一次空闲(这是唯一的判定点):
|
|
38
|
+
- 还有别的东西在跑 → 不重启,给一张「现在不能重启」页,列出是谁;
|
|
39
|
+
- 全部闲着 → 写交接文件、拉起脚本,工具所在的这一轮可以继续说;
|
|
40
|
+
5. 脚本关掉 DSH、执行命令(如果有)、重新启动。关窗口走的是「请求你自己退出」,
|
|
41
|
+
宿主真的退不掉才强杀(默认给它 8 秒);
|
|
42
|
+
6. 新进程里的插件读到交接文件,把唤醒词投进原会话,然后删掉它。
|
|
43
|
+
|
|
44
|
+
从触发到唤醒词出现,通常 **15–20 秒**。
|
|
45
|
+
|
|
46
|
+
## 调用参数
|
|
47
|
+
|
|
48
|
+
| 参数 | 必填 | 说明 |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `command` | 否 | DSH 关闭之后、重启之前执行的命令。**可以留空**——留空即纯重启,什么也不执行。没有默认值。 |
|
|
51
|
+
| `autoSay` | 否 | 重启后发给原会话的那句话,默认「已通过插件重启」。 |
|
|
52
|
+
| `countdownSec` | 否 | 倒计时秒数,3–600,默认 10。 |
|
|
53
|
+
| `timeoutSec` | 否 | 命令最多跑多少秒,10–3600,默认 600。超时就杀掉整棵进程树,照样启动 DSH。 |
|
|
54
|
+
| `wakeMode` | 否 | 重启后由谁唤醒原会话:`plugin`(默认,插件直投,不碰页面、不依赖调试端口)/ `script`(外部脚本用 CDP 往输入框注入,仅在插件不可用时才需要)。 |
|
|
55
|
+
|
|
56
|
+
命令失败(`exit ≠ 0`)或超时都**不会**阻止重启:退出码写进日志与
|
|
57
|
+
`last-run.json`,唤醒词自动补上「(命令失败:退出码 N)」或「(命令超时被中止)」。
|
|
58
|
+
|
|
59
|
+
## 空闲怎么判
|
|
60
|
+
|
|
61
|
+
**倒计时结束的那一刻**判一次,任一成立就算忙(宁可不动手):
|
|
62
|
+
|
|
63
|
+
- `agents.list()` 里有 agent 的 `status === 'running'` —— **包括正在调用本工具的这一个**。
|
|
64
|
+
子代理也在同一个注册表里,所以不需要另外接 `subagents` 服务;
|
|
65
|
+
- `jobs` 里任一 job 处于 `running` / `stopping`(逐个 owner 查,因为 `jobs.list()`
|
|
66
|
+
默认只返回 unowned 的);
|
|
67
|
+
- 有消息在消息队列里排队:`agent.inbox.nextTurn` / `nextStep` 非空
|
|
68
|
+
(宿主是在队列被消费完之后才把状态置回 idle 的,这中间有一瞬间状态已经是 idle
|
|
69
|
+
但这一轮还没走完,得单独看一眼);
|
|
70
|
+
- 该会话上挂着已武装的定时提醒:走宿主自己的 `workspace/session-activity` 瀑布取,
|
|
71
|
+
返回里有 `kind === 'schedule'` 的条目就算忙;
|
|
72
|
+
- 拿不到 `agents` 服务(没法确认,就当它在忙)。
|
|
73
|
+
|
|
74
|
+
这两项的取舍来自宿主 `dsh-desktop-host` 里 `hasDesktopActiveTasks()` 的实现,
|
|
75
|
+
那边是给「关闭窗口前能不能收摊」用的,口径一致。
|
|
76
|
+
|
|
77
|
+
插件自己创建的 job / 子代理不计入 —— 否则它会把自己锁死。
|
|
78
|
+
「这个会话上还挂着什么」这条瀑布如果抛错或超时,按「没挂东西」处理:
|
|
79
|
+
宁可偶尔多放行一次,也不要让重启被一个取不到数据的接口永久卡死。
|
|
80
|
+
|
|
81
|
+
占着忙就不重启,也不排队、不自动重试:提示页只负责告诉你是谁挡着。
|
|
82
|
+
|
|
83
|
+
## 为什么是倒计时结束才判
|
|
84
|
+
|
|
85
|
+
倒计时的作用就是给 agent 留时间把这一轮说完。所以工具不做拦截、立刻返回,
|
|
86
|
+
让调用它的这一轮继续输出;到点才判,此时自己也已经空闲了。
|
|
87
|
+
|
|
88
|
+
入口处既不判、也不预扫一遍忙闲——调用它的 agent 此刻必然在跑,扫出来永远有它自己。
|
|
89
|
+
**能不能停,只由倒计时结束那一次决定。**
|
|
90
|
+
|
|
91
|
+
## 倒计时被取消
|
|
92
|
+
|
|
93
|
+
**任何输入都取消,不区分来源。** 这张卡片问的是「现在还有没有人在操作这台机器」,
|
|
94
|
+
远程桌面、别的自动化工具、任何软件发出的键鼠输入,同样意味着有人在动。
|
|
95
|
+
|
|
96
|
+
所以判断只看有没有 `pointerdown` / `wheel` / `keydown`,不看事件来源。
|
|
97
|
+
|
|
98
|
+
## 设置页
|
|
99
|
+
|
|
100
|
+
「设置 → DSH 自动重启」里有五个默认值:唤醒方式、默认唤醒词、默认倒计时、
|
|
101
|
+
默认超时、日志保留份数。
|
|
102
|
+
|
|
103
|
+
**没有「默认命令」,这是故意的。** 命令只能由调用方当次传入。一条留在设置页里的
|
|
104
|
+
命令,最坏会在某次纯重启时被悄悄执行,而没人记得自己设过它。
|
|
105
|
+
|
|
106
|
+
**它只是默认值**:agent 每次调 `restart_dsh` 传的参数优先,没传才落回这里。
|
|
107
|
+
改动会存进 `~/.dsh/dsh-auto-restart/settings.json`,DSH 重启后仍然有效;
|
|
108
|
+
文件损坏或字段非法时逐项回落到代码默认值。
|
|
109
|
+
|
|
110
|
+
页面下半截是只读的:脚本路径、状态目录、两个日志、交接文件,以及上一次运行的
|
|
111
|
+
结果(命令、退出码、是否超时)。
|
|
112
|
+
|
|
113
|
+
## 状态路由
|
|
114
|
+
|
|
115
|
+
路由前缀 `/auto-restart/api`,只服务于本插件自己的界面,不是对外 API:
|
|
116
|
+
|
|
117
|
+
| 路由 | 用途 |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `GET /state` | 状态机快照(`idle` / `counting` / `launching` / `blocked` / `cancelled` / `failed`) |
|
|
120
|
+
| `GET /probe` | 现扫一遍宿主:此刻谁在忙、各 agent 的状态 |
|
|
121
|
+
| `GET` / `POST /settings` | 读 / 写五个默认值 |
|
|
122
|
+
| `GET /last-run` | 上一次脚本运行的结果 |
|
|
123
|
+
| `POST /cancel` | 取消倒计时(body: `{ reason }`) |
|
|
124
|
+
| `POST /dismiss` | 关掉终止提示页 |
|
|
125
|
+
|
|
126
|
+
**这个路由是加了闸的**,因为一条 `POST /restart` 就能让整台机器的 DSH 重启一次,
|
|
127
|
+
而降级到一个「什么都不带」的简单 POST 处理器,等于给本机任何网页开了一个重启开关。
|
|
128
|
+
所以每个请求都得同时满足:
|
|
129
|
+
|
|
130
|
+
- 来源地址是本机回环,且**没有** `X-Forwarded-For` / `X-Real-IP` 这类反向代理头
|
|
131
|
+
(带了说明前面有代理,`socket.remoteAddress` 到底是谁已经不可信);
|
|
132
|
+
- 带 `X-Auto-Restart: 1` —— 浏览器跨站发请求必带预检,自定义头过不了预检;
|
|
133
|
+
- 带了 `Origin` 时,它必须与 `Host` 同源。
|
|
134
|
+
|
|
135
|
+
不满足就 `403`,并记一行日志。只挡「网页脚本」这一类:本机 curl 或本机其它进程
|
|
136
|
+
只要补上那个头就能调 —— 它们本来就有能力杀进程,这里不假装能防住。
|
|
137
|
+
|
|
138
|
+
## 文件
|
|
139
|
+
|
|
140
|
+
| 文件 | 干什么 |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `index.js` | Host 半边:工具、空闲判定、倒计时状态机、生成启动计划、拉起脚本、状态路由 |
|
|
143
|
+
| `lib/launch-plan.js` | 启动计划:从宿主自述(argv/execPath/cwd/origin)算出「怎么再起来一次」,并校验 |
|
|
144
|
+
| `client.js` | Client 半边:`shell.overlay` 上的倒计时页与终止页,`settings.section` 里的设置页 |
|
|
145
|
+
| `scripts/Restart-DSH.ps1` | 真正动手:先验计划 → 关 DSH → 跑命令 → 照计划启动 → 补交接状态 |
|
|
146
|
+
| `scripts/Start-DSH.ps1` | 被上一个脚本调用:照计划启动(exe/参数/cwd/端口/环境变量),等就绪 |
|
|
147
|
+
| `scripts/say.js` | 只在 `wakeMode: 'script'` 时用:CDP 往输入框注入 |
|
|
148
|
+
| `SETUP.md` | 给 agent 的说明书:默认覆盖不到的环境怎么一次性配好 |
|
|
149
|
+
| `cordis.patch.yml` | bundle patch,把本包插进 profile 的 cordis 树 |
|
|
150
|
+
| `install.ps1` | 把插件装进 profile(`-Profile web\|desktop\|both`) |
|
|
151
|
+
| `locale/*.json`、`icon.svg` | 插件市场与设置页里显示的标题、描述、图标 |
|
|
152
|
+
|
|
153
|
+
## 状态与日志
|
|
154
|
+
|
|
155
|
+
都在 `~/.dsh/dsh-auto-restart/` 下:
|
|
156
|
+
|
|
157
|
+
| 路径 | 内容 |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `settings.json` | 设置页的五个默认值 + 两个适配兜底(`launchCommand` / `allowAutoRestart`) |
|
|
160
|
+
| `launch-plan.json` | 启动计划,每次重启前重写;脚本只照它执行 |
|
|
161
|
+
| `wake-request.json` | 交接文件,只在一次重启流程中存在,投递完即删 |
|
|
162
|
+
| `last-run.json` | 上一次脚本运行的结果(命令、退出码、是否超时、时间) |
|
|
163
|
+
| `logs/plugin.log` | 插件自己的日志(加载、倒计时、投递、失败原因) |
|
|
164
|
+
| `logs/restart-*.log` | 每次重启流程一个文件,保留最近 N 份 |
|
|
165
|
+
|
|
166
|
+
脚本自己的日志在 `Restart-DSH.ps1` 同级的 `logs/` 下。
|
|
167
|
+
|
|
168
|
+
## 交接文件怎么工作
|
|
169
|
+
|
|
170
|
+
只有一个文件,它的 `phase` 就是全部状态:
|
|
171
|
+
|
|
172
|
+
| phase | 谁写 | 含义 |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| `starting` | 插件 | 已经拉起脚本,但还没确认 DSH 启动成功 |
|
|
175
|
+
| `ready` | 脚本 | DSH 真起来了,该投递了 |
|
|
176
|
+
| `failed` | 脚本 | 启动失败,不应该唤醒 |
|
|
177
|
+
|
|
178
|
+
插件读到 `ready` 就用 `sessionController.prompt()` 把唤醒词投进原会话 —— 走的是
|
|
179
|
+
正常的用户消息通道,不碰页面 DOM、不需要调试端口。投递失败退回 `agent.followup()`。
|
|
180
|
+
|
|
181
|
+
**文件在哪都有讲究,处理完就删。** 所以「文件在 = 有欠账,文件不在 = 没事做」,
|
|
182
|
+
状态目录不会积累。只有一种情况不删:读到 `starting` 说明脚本还没确认启动,
|
|
183
|
+
这时候删掉就再也等不到 `ready` 了。
|
|
184
|
+
|
|
185
|
+
## 两种载体
|
|
186
|
+
|
|
187
|
+
宿主不需要知道自己是哪一边。每次动手前,它把自己**实际是怎么跑起来的**写成
|
|
188
|
+
`launch-plan.json`(`process.execPath` + `process.argv` + `cwd` + `webServer.getOrigin()`),
|
|
189
|
+
脚本照计划执行:该重放什么 exe、带什么参数、在哪个目录、绑哪个端口、给什么环境变量。
|
|
190
|
+
|
|
191
|
+
「dsh 装在哪」因此不再是一个需要回答的问题:shim(`dsh.cmd` / `dsh.ps1`)只是转发,
|
|
192
|
+
`argv[1]` 已经是 `…/@deepseek-ai/dsh/lib/bin.js` 的绝对路径,原样重放即可。
|
|
193
|
+
|
|
194
|
+
**计划不可用就不杀进程**:校验(exe/入口存在、mode 认识、端口像样)发生在关进程之前,
|
|
195
|
+
校验不过这次重启直接作废 —— 先杀再发现起不来,是这类工具最伤人的失败形态。
|
|
196
|
+
|
|
197
|
+
真正的差别都在计划里:
|
|
198
|
+
|
|
199
|
+
| | desktop | web |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| 怎么关 | 按进程名(取自计划里的 exe 名)全杀 | 按谁占着计划里的端口找 PID,`taskkill /T /F` |
|
|
202
|
+
| 怎么起 | 重放外壳 exe(删掉 `ELECTRON_RUN_AS_NODE`) | 重放 `node <bin.js> web …` |
|
|
203
|
+
| 就绪判据 | 主窗口出现 | 端口有响应(401 也算,说明服务在) |
|
|
204
|
+
|
|
205
|
+
**唤醒链路两种载体完全一样**:交接文件 + `sessionController.prompt()` 直投。
|
|
206
|
+
`wakeMode: 'script'` 只有桌面端有。
|
|
207
|
+
|
|
208
|
+
网页端重启后,标签页会自己连回去(端口固定、cookie 持久、连接层带指数退避重连),
|
|
209
|
+
不需要刷新;桌面端用随机端口,旧窗口等不到,由脚本重新开一个。
|
|
210
|
+
|
|
211
|
+
## 已知限制
|
|
212
|
+
|
|
213
|
+
- 官方桌面端与 `dsh web` 都在真机上完整跑通过;第三方 Electron 壳(`UtilityProcess` 里跑 CLI)
|
|
214
|
+
未经实测,插件判定不出「怎么原样再起来一次」时会拒绝动手,而不是猜着杀进程;
|
|
215
|
+
- 默认只覆盖**官方桌面端**与 `dsh web` 两种形态。其余环境由 agent 照 `SETUP.md` 征得你同意后
|
|
216
|
+
配一次 `launchCommand`(一条完整命令),插件不再猜安装位置;
|
|
217
|
+
- 容器 / systemd / Windows 服务等「杀不死」的托管形态不支持,检测不出时会由用户在
|
|
218
|
+
`allowAutoRestart=false` 上明确关掉,而不是把 DSH 停在起不来的状态;
|
|
219
|
+
- 真正动手那一刻需要一个「不在 DSH 进程树里」的子进程,否则 `taskkill /T` 会把它一起带走。
|
|
220
|
+
插件按 **计划任务 → schtasks → WMI(Win32_Process.Create)** 三级阶梯尝试,实测某些
|
|
221
|
+
机器上计划任务被策略拒绝(`Access is denied.`),此时自动落到 WMI;三级都不通会直接
|
|
222
|
+
报错并**保持 DSH 运行**,不会出现「关掉了但起不来」;
|
|
223
|
+
- 桌面端重启后窗口位置、大小由 DSH 自己决定,插件不介入。
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# 一次配好这台机器(给 agent 看的说明书)
|
|
2
|
+
|
|
3
|
+
**给谁看**:在这台机器上跑的 DSH agent。
|
|
4
|
+
**为什么有这份文件**:dsh-auto-restart 默认只覆盖两种启动方式 —— 官方桌面端、`dsh web`(任何安装方式)。
|
|
5
|
+
其余环境(npx、自制包装、换过的外壳、托管进程)由 **agent 按本文配置一次**,用户只负责确认。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、默认就能用的范围
|
|
10
|
+
|
|
11
|
+
宿主会把自己「怎么跑起来的」写成启动计划(`lib/launch-plan.js`),依据只有四个真实值:
|
|
12
|
+
`process.execPath`、`process.argv`、`process.cwd()`、`webServer.getOrigin()`。
|
|
13
|
+
|
|
14
|
+
| 形态 | 计划 | 是否需要配置 |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `dsh web`(npm / pnpm / volta / scoop / 源码 / npx) | 重放 `node <bin.js> web …` | 否 |
|
|
17
|
+
| 官方桌面端 | 重放外壳 exe,并删掉 `ELECTRON_RUN_AS_NODE` | 否 |
|
|
18
|
+
|
|
19
|
+
`argv` 重放天然与「dsh 装在哪」无关:shim(`dsh.cmd` / `dsh.ps1`)只是转发,`argv[1]` 已经是
|
|
20
|
+
`…/@deepseek-ai/dsh/lib/bin.js` 的绝对路径。所以绝大多数机器**不需要任何设置**。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 二、什么时候需要你介入
|
|
25
|
+
|
|
26
|
+
`restart_dsh` 返回 `reason: "needsSetup"`。
|
|
27
|
+
|
|
28
|
+
这时候:
|
|
29
|
+
- DSH **没有被关闭**,也没有进入倒计时(校验发生在杀进程之前);
|
|
30
|
+
- message 里带着**宿主自述**:`argv` / `execPath` / `cwd` / `origin` / `profile` / `dshVersion` / `electron`。
|
|
31
|
+
|
|
32
|
+
这些是这台机器上的**真实值**,不要用你记忆里的默认路径去猜。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 三、三步(顺序不能变)
|
|
37
|
+
|
|
38
|
+
1. **拼一条命令**:用下面的规则,从自述里抄,不要发明路径。
|
|
39
|
+
2. **让用户在对话里确认**:把命令**原文**贴出来,并明确说一句「这条命令会在每次重启时以你的权限执行」。
|
|
40
|
+
等到他明确同意再往下。
|
|
41
|
+
3. **写配置并重试**:把命令写进设置文件,然后再调一次 `restart_dsh`(它会先验证计划,再进倒计时)。
|
|
42
|
+
|
|
43
|
+
> 为什么必须让用户确认:agent 会读到网页等不可信内容,而 `launchCommand` 是每次重启都会执行的
|
|
44
|
+
> 任意命令。静默写入 = 持久化后门。这一步不是形式。
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 四、配置写在哪、写什么
|
|
49
|
+
|
|
50
|
+
文件:`%USERPROFILE%\.dsh\dsh-auto-restart\settings.json`
|
|
51
|
+
(`DSHAR_STATE_DIR` 环境变量存在时以它为准;文件不存在就新建。)
|
|
52
|
+
|
|
53
|
+
只加/改这两个键,**其余键不要动**(`autoSay` / `timeoutSec` / `countdownSec` / `logKeep` / `wakeMode`):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"launchCommand": "\"C:\\Program Files\\nodejs\\node.exe\" \"C:\\Users\\me\\AppData\\Roaming\\npm\\node_modules\\@deepseek-ai\\dsh\\lib\\bin.js\" web --no-open",
|
|
58
|
+
"allowAutoRestart": true
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `launchCommand`:空字符串 = 回到自动(默认)。写一条**完整命令**,由插件用 `cmd /d /s /c` 执行,
|
|
63
|
+
所以 `%APPDATA%` 这类变量会展开。
|
|
64
|
+
- `allowAutoRestart`:`false` 时 `restart_dsh` 直接拒绝,什么也不做。默认 `true`。
|
|
65
|
+
- 写合法 JSON、反斜杠要转义、**不要 BOM**(Node 的 `JSON.parse` 见 BOM 会抛错)。
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 五、命令怎么写
|
|
70
|
+
|
|
71
|
+
规则,按重要性排序:
|
|
72
|
+
|
|
73
|
+
1. **`argv` 原文照抄**:命令 = `"<execPath>" "<argv[1]>"` + `argv[2..]` 逐字保留。
|
|
74
|
+
这是最可靠的一条 —— 它就是这台机器当前的启动方式。
|
|
75
|
+
2. **profile 和端口必须与自述一致**:`origin` 里的端口,命令里必须有同值的 `--port`;
|
|
76
|
+
profile 名同理。端口变了页面就连不回来,profile 变了就不是同一个 DSH。
|
|
77
|
+
3. **一条能在 cmd 里直接跑的命令**(`cmd /d /s /c` 执行,不是 PowerShell 语法)。
|
|
78
|
+
4. 建议加 `--no-open`:重启时页面已经开着,再弹一个标签页只是噪音。
|
|
79
|
+
5. 命令只负责「从零起来」;杀旧进程、等端口都是插件的事,命令里不要写 `taskkill`。
|
|
80
|
+
|
|
81
|
+
例子(`<…>` 换成自述里的真实值):
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
# npm 全局安装的 web(推荐:直接用自述里的两个绝对路径)
|
|
85
|
+
"C:\Program Files\nodejs\node.exe" "C:\Users\me\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js" web --port 3080 --no-open
|
|
86
|
+
|
|
87
|
+
# 只信 PATH(npx 缓存被清、路径搬过家时用这个)
|
|
88
|
+
dsh web --port 3080 --no-open
|
|
89
|
+
|
|
90
|
+
# 官方桌面端(外壳 exe,无参数)
|
|
91
|
+
"%LOCALAPPDATA%\Programs\DeepSeek Harness\DeepSeek Harness.exe"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 六、写完怎么验证
|
|
97
|
+
|
|
98
|
+
再调一次 `restart_dsh`。如果还是 `needsSetup`,看 message 里的 reason:
|
|
99
|
+
|
|
100
|
+
| reason | 含义 | 怎么修 |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `no-plan` | 计划没生成(一般伴随写盘失败) | 看插件日志里的写盘错误 |
|
|
103
|
+
| `exe-missing` | 命令里的 exe 不存在 | 用自述里的 `execPath` 原文 |
|
|
104
|
+
| `entry-missing` | web 的 `argv[1]`(bin.js)不存在 | npx 缓存被清 / 源码被移走 → 改成 PATH 形式 |
|
|
105
|
+
| `empty-command` | `launchCommand` 是空串却判成自定义 | 空串本就是「自动」,不会走到这 |
|
|
106
|
+
| `bad-port` | 端口不在 0..65535 | 用 `origin` 里的端口 |
|
|
107
|
+
| `unknown-mode` | 计划文件被改坏 | 删掉 `launch-plan.json` 让它重新生成 |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 七、不要做的事
|
|
112
|
+
|
|
113
|
+
- **不要改插件里的 `.ps1` / `index.js`**:升级会被覆盖,而且脚本是唯一的执行点。
|
|
114
|
+
- **不要自己 `taskkill` / `Stop-Process`**:关进程有先后顺序和端口释放等待,手杀容易留下半死状态。
|
|
115
|
+
- **不要静默写 `launchCommand`**:必须先让用户看到命令原文并同意。
|
|
116
|
+
- **不要把 `allowAutoRestart=false` 当成修复**:那是关掉功能,不是适配。
|
|
117
|
+
- **不要为了让校验通过去改 `validatePlan` 的判据**:校验失败说明计划确实不可用。
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 八、文件与日志
|
|
122
|
+
|
|
123
|
+
| 文件 | 内容 |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `~/.dsh/dsh-auto-restart/settings.json` | 设置(含 `launchCommand` / `allowAutoRestart`) |
|
|
126
|
+
| `~/.dsh/dsh-auto-restart/launch-plan.json` | 启动计划,**每次重启前重写** |
|
|
127
|
+
| `~/.dsh/dsh-auto-restart/logs/plugin.log` | 插件日志(含计划生成与校验结果) |
|
|
128
|
+
| `~/.dsh/dsh-auto-restart/last-run.json` | 上次重启期间那条命令的结果 |
|
|
129
|
+
| `<插件目录>/scripts/logs/restart-*.log` | 重启脚本日志(杀进程、启动、唤醒) |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 九、明确不支持的形态
|
|
134
|
+
|
|
135
|
+
容器(docker / k8s)、systemd / Windows 服务、任何「杀了会被外部守护进程重新拉起」的托管。
|
|
136
|
+
这些环境里「重启」的语义不归本插件管:把 `allowAutoRestart` 设成 `false`,让运维去重启。
|
|
137
|
+
插件不会假装能处理 —— 它会拒绝,而不是把 DSH 停在一个起不来的状态。
|
|
138
|
+
|
|
139
|
+
## 十、看到「无法创建独立进程」时
|
|
140
|
+
|
|
141
|
+
重启的真实动作必须由一个**不在 DSH 进程树里**的子进程完成(否则 `taskkill /T` 会把它一起
|
|
142
|
+
杀掉)。脚本按 计划任务 → `schtasks` → WMI(`Win32_Process.Create`)三级阶梯尝试。三级全
|
|
143
|
+
失败才会报 `无法创建独立进程(三种方式都不通)`,此时**DSH 没有被关闭**。
|
|
144
|
+
|
|
145
|
+
这一条不是 `launchCommand` 能修的,`launchCommand` 只解决「用什么命令把 DSH 拉起来」。
|
|
146
|
+
遇到它:如实告诉用户这台机器不允许插件创建独立进程(常见于被组策略锁死计划任务的机器),
|
|
147
|
+
建议把 `allowAutoRestart` 设为 `false`,重启由用户自己做。**不要**改成自己 `Start-Process`
|
|
148
|
+
或手写 `taskkill` 来绕过 —— 那会把 DSH 停在一个没人拉起来的状态。
|