dshost-plugin 0.1.9 → 0.1.11
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/README.md +7 -3
- package/lib/core.js +17 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://github.com/NoelJudeNoel/dshost-plugin)
|
|
7
7
|
|
|
8
8
|
**DSHost 官方 DSH 远程中继插件** — 在你的 dsh(DeepSeek Harness)实例上运行一个 Agent,
|
|
9
|
-
通过 `wss` 主动外连
|
|
9
|
+
通过 `wss` 主动外连 DSHost 中继,让你在世界任何地方用浏览器安全访问自己的 dsh Web UI,
|
|
10
10
|
无需公网 IP、无需端口映射。
|
|
11
11
|
|
|
12
12
|
*Official remote cloud relay plugin for DSHost (dshost.me): securely access your
|
|
@@ -15,7 +15,7 @@ dsh Web UI from anywhere.*
|
|
|
15
15
|
## 特性
|
|
16
16
|
|
|
17
17
|
- **零公网 IP**:Agent 主动外连中继(`wss://dshost.me/agent`),本机不开任何入站端口
|
|
18
|
-
- **零破坏性多版本兼容**:同时支持 dsh `0.0.1-rc.*` / `0.1.0-rc.*` / `0.1.1-*`
|
|
18
|
+
- **零破坏性多版本兼容**:同时支持 dsh `0.0.1-rc.*` / `0.1.0-rc.*` / `0.1.1-*` ~ `0.1.5-*`
|
|
19
19
|
- **安全**:中继层 Host/Origin 重写遵守 dsh 信任栅栏;`settings.describe` 密钥脱敏下发;
|
|
20
20
|
生产环境强制 `wss://`
|
|
21
21
|
- **随 dsh 启动**:以 cordis 插件身份随 `dsh web` 加载,自动重连、休眠省流
|
|
@@ -30,7 +30,10 @@ dsh Web UI from anywhere.*
|
|
|
30
30
|
### 2. 一键安装(推荐)
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
+
# macOS / Linux
|
|
33
34
|
curl -fsSL https://dshost.me/install.sh | bash
|
|
35
|
+
# Windows (PowerShell)
|
|
36
|
+
$env:RELAY_HOST='dshost.me'; irm https://dshost.me/install.ps1 | iex
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
脚本会安装 pnpm、通过 `dsh plugin add dshost-plugin` 把插件装入当前 profile,
|
|
@@ -47,7 +50,7 @@ dsh plugin add dshost-plugin
|
|
|
47
50
|
|
|
48
51
|
```yaml
|
|
49
52
|
- insert:
|
|
50
|
-
- id:
|
|
53
|
+
- id: dshost-agent
|
|
51
54
|
name: 'dshost-plugin'
|
|
52
55
|
config:
|
|
53
56
|
token: dsh_your_token_here
|
|
@@ -76,6 +79,7 @@ sudo systemctl restart dsh # 或你的启动方式
|
|
|
76
79
|
|
|
77
80
|
| dsh 版本 | 支持 |
|
|
78
81
|
|---|---|
|
|
82
|
+
| `0.1.5-*`(已实证 `0.1.5-rc.1`) / `0.1.4-*` / `0.1.3-*` | ✔ |
|
|
79
83
|
| `0.1.2-*`(含 `--trusted-host`、BrowserAuth、remote.mux) | ✔ |
|
|
80
84
|
| `0.1.1-*` / `0.1.0-rc.7+`(insert 块 patch 格式) | ✔ |
|
|
81
85
|
| `0.1.0-rc.2 ~ rc.6` / `0.0.1-rc.*` | ✔(旧包名 `@noeljude/dsh-remote-plugin` 亦可解析) |
|
package/lib/core.js
CHANGED
|
@@ -267,6 +267,9 @@ export function collectSystemInfo(defaultVersion) {
|
|
|
267
267
|
diskFree,
|
|
268
268
|
dshVersion: defaultVersion || meta.dshVersion,
|
|
269
269
|
plugins: meta.plugins,
|
|
270
|
+
// 服务旁挂:独立 services agent(DSH_SERVICES_ONLY=1,无本地 dsh 承载)
|
|
271
|
+
// 上报 role=services,relay 不将其注册为主实例,只挂到同 hostname 的主实例下
|
|
272
|
+
role: process.env.DSH_SERVICES_ONLY === '1' ? 'services' : 'dsh',
|
|
270
273
|
// 无头检测:供 relay 注入脚本决定产物点击行为(新窗口HTTP直出 vs 本地打开)
|
|
271
274
|
headless: !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY,
|
|
272
275
|
};
|
|
@@ -334,6 +337,7 @@ export function normalizeServices(raw, log) {
|
|
|
334
337
|
auth: s.auth === 'sso' ? 'sso' : 'open',
|
|
335
338
|
entry: typeof s.entry === 'string' && s.entry.startsWith('/') ? s.entry : '/',
|
|
336
339
|
ssoSecret: typeof s.ssoSecret === 'string' && s.ssoSecret ? s.ssoSecret : '',
|
|
340
|
+
spa: s.spa === true,
|
|
337
341
|
basicUser: typeof s.basicUser === 'string' ? s.basicUser : '',
|
|
338
342
|
basicPassword: typeof s.basicPassword === 'string' ? s.basicPassword : '',
|
|
339
343
|
host: up.host,
|
|
@@ -470,9 +474,11 @@ export function startAgent(opts) {
|
|
|
470
474
|
log.log?.(`[agent] Registered as ${msg.username} (session ${msg.sessionId || ''})`);
|
|
471
475
|
send(ws, { type: 'system-info', info: collectSystemInfo(dshVersion) });
|
|
472
476
|
// 插件工作区:上报本实例声明的服务清单(relay 存实例维度,供 /p 路由校验)
|
|
477
|
+
// DSH_SERVICES_ONLY=1:独立 services agent,relay 不注册主实例(服务旁挂到同机 dsh 实例)
|
|
473
478
|
send(ws, {
|
|
474
479
|
type: 'services',
|
|
475
|
-
|
|
480
|
+
servicesOnly: process.env.DSH_SERVICES_ONLY === '1' || undefined,
|
|
481
|
+
services: svcList.map(({ id, name, desc, icon, auth, entry, spa }) => ({ id, name, desc, icon, auth, entry, spa })),
|
|
476
482
|
});
|
|
477
483
|
startHeartbeat(ws);
|
|
478
484
|
return;
|
|
@@ -561,9 +567,17 @@ export function startAgent(opts) {
|
|
|
561
567
|
|
|
562
568
|
function startHeartbeat(ws) {
|
|
563
569
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
570
|
+
// 半开死链看门狗:只发不收时 TCP 仍 ESTAB(FIN 被代理链路吞掉的场景),
|
|
571
|
+
// close 事件永远不来,agent 变僵尸。每次 ping 记账,收到任意下行帧清零;
|
|
572
|
+
// 连续 3 个周期(45s)无下行则主动 terminate() 触发重连。
|
|
573
|
+
let lastRx = Date.now();
|
|
574
|
+
ws.on('message', () => { lastRx = Date.now(); });
|
|
564
575
|
heartbeatInterval = setInterval(() => {
|
|
565
|
-
if (ws.readyState
|
|
566
|
-
|
|
576
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
577
|
+
send(ws, { type: 'heartbeat', ts: Date.now() });
|
|
578
|
+
if (Date.now() - lastRx > 45000) {
|
|
579
|
+
log.log?.('[agent] heartbeat watchdog: no inbound for 45s, forcing reconnect');
|
|
580
|
+
try { ws.terminate(); } catch (eT) {}
|
|
567
581
|
}
|
|
568
582
|
}, 15000);
|
|
569
583
|
}
|
package/package.json
CHANGED