dsh-remote-agent 0.9.1 → 0.9.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.
- package/CHANGELOG.md +102 -0
- package/README.en.md +10 -3
- package/README.md +37 -10
- package/cordis.patch.yml +31 -24
- package/lib/client.js +9 -2
- package/lib/env-check.js +24 -5
- package/lib/hub/index.js +150 -3
- package/lib/index.js +24 -0
- package/lib/instance-config.js +207 -0
- package/lib/listen-server.js +262 -0
- package/lib/setup-routes.js +48 -0
- package/package.json +1 -1
- package/scripts/check-doc-drift.mjs +85 -15
- package/scripts/diag-worker.ps1 +148 -0
|
@@ -929,37 +929,55 @@ check('65', 'eslint 严格档:lint script + 配置文件 + CI job 三件套齐
|
|
|
929
929
|
'lib/env-check.js(可达性/inject/roots/role + fix + 不代下载边界)')
|
|
930
930
|
}
|
|
931
931
|
|
|
932
|
-
/* ── 79~80. 开箱即用(2026-10-17
|
|
932
|
+
/* ── 79~80. 开箱即用(2026-10-17,2026-10-18 修订)──
|
|
933
933
|
*
|
|
934
934
|
* 产品标准:用户装完就该能用,不该读文档、改 YAML、猜字段。
|
|
935
935
|
*
|
|
936
|
-
* 此前三道门槛:
|
|
937
|
-
* 1. `role` 默认 '' → 两台机器都要操作一次
|
|
938
|
-
* 2. `webserver` 没绑 0.0.0.0 → 副机永远连不上(且完全静默)
|
|
939
|
-
* 3. 副机表单 4 个字段 → 小白不知道节点 ID / 工作区填什么
|
|
940
|
-
*
|
|
941
936
|
* 核心设计:`role` 默认 **auto**,按 `hubUrl` 推断。
|
|
942
937
|
* · 默认 hub → 副机装完以 hub 身份启动,注册无鉴权端点(**攻击面**)
|
|
943
938
|
* · 默认 '' → 两台都要操作,不是开箱即用
|
|
944
939
|
* · 默认 auto → 主机零操作、副机贴完配对码零操作,且不给副机开错端点
|
|
940
|
+
*
|
|
941
|
+
* ⚠ **2026-10-18 事故修订**:79 号断言原来**要求**包内 patch 绑
|
|
942
|
+
* `host: '0.0.0.0'` —— 那正是导致 **DSH 启动崩溃**的写法。新版
|
|
943
|
+
* `dsh-host-webserver` 在 schema 校验阶段就硬拒绝通配地址:
|
|
944
|
+
*
|
|
945
|
+
* host "0.0.0.0" is an unspecified (wildcard) address, which is not
|
|
946
|
+
* supported: binding every interface would expose remote code execution
|
|
947
|
+
* to the network; bind one concrete IPv4 or IPv6 address ...
|
|
948
|
+
*
|
|
949
|
+
* 后果:webserver(required)激活失败 → webServer 服务缺失 → 至少 6 个插件
|
|
950
|
+
* 排队等它 → host 启动失败(实测 10 份 crash 日志)。
|
|
951
|
+
*
|
|
952
|
+
* 所以断言方向**反过来**:现在要求包内 patch **不碰** webserver 的任何字段,
|
|
953
|
+
* 且所有提示文案都不得建议 0.0.0.0。
|
|
945
954
|
*/
|
|
946
955
|
{
|
|
947
956
|
const indexSrc = read('lib/index.js')
|
|
948
957
|
const patchSrc = read('cordis.patch.yml')
|
|
949
958
|
const clientSrc3 = read('lib/client.js')
|
|
950
959
|
const setupSrc = read('lib/setup-routes.js')
|
|
951
|
-
|
|
952
|
-
|
|
960
|
+
const envSrc = read('lib/env-check.js')
|
|
961
|
+
const hubSrc = read('lib/hub/index.js')
|
|
962
|
+
|
|
963
|
+
// ⚠ 只扫**非注释行**:注释里必须保留那次事故的原文(含 `0.0.0.0` 与
|
|
964
|
+
// `- id: webserver` 示例),那是给后来者的警示 —— 不该被断言判违规。
|
|
965
|
+
// 这一点和 L3 断言踩过的坑同源(扫全文会命中"正确留档的注释")。
|
|
966
|
+
const patchCode = patchSrc
|
|
967
|
+
.split('\n')
|
|
968
|
+
.filter((l) => !/^\s*#/.test(l))
|
|
969
|
+
.join('\n')
|
|
970
|
+
|
|
971
|
+
check('79', '开箱即用:role 默认 auto;且包内 patch **不得**绑 0.0.0.0(会让 DSH 崩溃)',
|
|
953
972
|
/\.default\('auto'\)/.test(indexSrc)
|
|
954
973
|
&& /ROLE\.AUTO/.test(indexSrc)
|
|
955
974
|
&& /config\.hubUrl/.test(indexSrc)
|
|
956
|
-
//
|
|
957
|
-
&&
|
|
958
|
-
&&
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
'lib/index.js(auto 推断)+ cordis.patch.yml(webserver 0.0.0.0:19387 + 安全披露)')
|
|
975
|
+
// ⚠ 关键:包内 patch 的**生效行**不得 override webserver 的 host
|
|
976
|
+
&& !/^- id: webserver$/m.test(patchCode)
|
|
977
|
+
&& !/host:\s*'0\.0\.0\.0'/.test(patchCode)
|
|
978
|
+
// 必须把这个坑写进注释(防止后来者又"帮用户改好")
|
|
979
|
+
&& /启动崩溃/.test(patchSrc),
|
|
980
|
+
'lib/index.js(auto 推断)+ cordis.patch.yml(生效行不碰 webserver + 事故留档)')
|
|
963
981
|
|
|
964
982
|
check('80', '开箱即用:副机表单简化 + 工作区自动创建 + 首次自动体检',
|
|
965
983
|
// 工作区预填
|
|
@@ -972,6 +990,19 @@ check('65', 'eslint 严格档:lint script + 配置文件 + CI job 三件套齐
|
|
|
972
990
|
&& /mkdirSync\(r, \{ recursive: true \}\)/.test(setupSrc)
|
|
973
991
|
&& /warnings: applyWarnings/.test(setupSrc),
|
|
974
992
|
'lib/client.js(预填/折叠/自动体检)+ lib/setup-routes.js(自动建目录)')
|
|
993
|
+
|
|
994
|
+
// ── 79b:**所有面向用户的修法文案**都不得建议 0.0.0.0 ──────────────
|
|
995
|
+
//
|
|
996
|
+
// 这是本次事故最有价值的一条守卫:代码自己不能犯那个错,提示文案也不能。
|
|
997
|
+
// 检查的是字符串字面量(`host: '0.0.0.0'` 这种形态),不是注释里的说明。
|
|
998
|
+
check('79b', '所有修法文案都不得建议 host 0.0.0.0(会让 DSH 启动崩溃)',
|
|
999
|
+
!/host:\s*'0\.0\.0\.0'/.test(clientSrc3)
|
|
1000
|
+
&& !/host:\s*'0\.0\.0\.0'/.test(envSrc)
|
|
1001
|
+
&& !/加 host:\s*'0\.0\.0\.0'/.test(hubSrc)
|
|
1002
|
+
// 但要**明确警告不能用**它(否则用户可能自己想到这个"标准做法")
|
|
1003
|
+
&& /不能用 0\.0\.0\.0/.test(envSrc)
|
|
1004
|
+
&& /不能用 0\.0\.0\.0/.test(clientSrc3),
|
|
1005
|
+
'lib/client.js + lib/env-check.js + lib/hub/index.js(修法不含 0.0.0.0,且有明确警告)')
|
|
975
1006
|
}
|
|
976
1007
|
|
|
977
1008
|
/* ── 81~82. 工作区 SMB 共享(2026-10-17)──
|
|
@@ -1098,6 +1129,45 @@ check('65', 'eslint 严格档:lint script + 配置文件 + CI job 三件套齐
|
|
|
1098
1129
|
'lib/client.js(dir: shareDir + nodeRoots UI)+ bin/dsh-remote-setup.js(envChecks)')
|
|
1099
1130
|
}
|
|
1100
1131
|
|
|
1132
|
+
/* ── 86~87. 独立监听端口(2026-10-18,架构级修复)──
|
|
1133
|
+
*
|
|
1134
|
+
* 副机端点原来注册在 `ctx.webServer` 上 → 副机必须访问 DSH 的 webserver 端口。
|
|
1135
|
+
* 而那个端口:
|
|
1136
|
+
* · **DSH 每次启动重写 profile patch**,把写进去的 `webserver` 段删掉
|
|
1137
|
+
* (实测:3962B/15 条 → 661B/3 条)→ 绑回 127.0.0.1 → 副机永远连不上
|
|
1138
|
+
* · `webServer` 激活即 listen,没有 rebind 方法
|
|
1139
|
+
* · 新版硬拒绝 0.0.0.0(会让 DSH 启动崩溃,踩过 10 份 crash 日志)
|
|
1140
|
+
*
|
|
1141
|
+
* 所以插件自己开一个 node:http server 服务节点端点,完全绕开 DSH 的 webserver。
|
|
1142
|
+
* 这两条断言钉住"这条通道存在且不依赖 webServer"。
|
|
1143
|
+
*/
|
|
1144
|
+
{
|
|
1145
|
+
const hubSrc2 = read('lib/hub/index.js')
|
|
1146
|
+
const listenSrc = read('lib/listen-server.js')
|
|
1147
|
+
|
|
1148
|
+
check('86', '独立监听:插件自己开端口服务节点端点(不依赖 webServer 配置)',
|
|
1149
|
+
/startListenServer/.test(hubSrc2)
|
|
1150
|
+
&& /resolveListenHost/.test(hubSrc2)
|
|
1151
|
+
// 默认端口 19387(与历史配置/配对码/文档一致)
|
|
1152
|
+
&& /DEFAULT_LISTEN_PORT/.test(listenSrc)
|
|
1153
|
+
&& /19387/.test(listenSrc)
|
|
1154
|
+
// 必须真的用 node:http 起 server(不是只 import 不用)
|
|
1155
|
+
&& /createServer/.test(listenSrc)
|
|
1156
|
+
&& /node:http/.test(listenSrc)
|
|
1157
|
+
// 路径精确匹配(只承载节点端点,不做通用 web server)
|
|
1158
|
+
&& /pathname !== upgradePath/.test(listenSrc),
|
|
1159
|
+
'lib/hub/index.js + lib/listen-server.js(node:http + acceptUpgrade + 精确路径)')
|
|
1160
|
+
|
|
1161
|
+
check('87', '独立监听:拒绝 0.0.0.0 + Tailscale 优先 + 测试环境不占端口',
|
|
1162
|
+
// 拒绝通配地址(与新版 DSH 同规则;0.0.0.0 会让 DSH 崩溃,我们踩过)
|
|
1163
|
+
/h !== '0\.0\.0\.0'/.test(listenSrc)
|
|
1164
|
+
// 自动选地址时 Tailscale 优先(本插件的目标场景就是跨网)
|
|
1165
|
+
&& /isTailscaleIp/.test(listenSrc)
|
|
1166
|
+
// 测试环境下不真的监听(否则 npm test 会挂住 —— 实测过)
|
|
1167
|
+
&& /NODE_TEST_CONTEXT/.test(hubSrc2),
|
|
1168
|
+
'lib/listen-server.js(拒 0.0.0.0 + Tailscale 优先)+ hub/index.js(测试环境跳过)')
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1101
1171
|
/* ── 输出 ── */
|
|
1102
1172
|
const failed = results.filter((r) => !r.ok)
|
|
1103
1173
|
if (process.argv.includes('--json')) {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# dsh-remote-agent worker diagnostic
|
|
2
|
+
#
|
|
3
|
+
# Read-only: changes nothing. Paste into PowerShell on the WORKER machine:
|
|
4
|
+
# powershell -ExecutionPolicy Bypass -File .\diag-worker.ps1
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
7
|
+
$profiles = Join-Path $env:USERPROFILE '.dsh\profiles'
|
|
8
|
+
|
|
9
|
+
function Line { param($t) Write-Host $t }
|
|
10
|
+
|
|
11
|
+
Write-Host ''
|
|
12
|
+
Write-Host '=========================================='
|
|
13
|
+
Write-Host ' dsh-remote-agent WORKER DIAGNOSTIC'
|
|
14
|
+
Write-Host '=========================================='
|
|
15
|
+
Write-Host ''
|
|
16
|
+
|
|
17
|
+
# ---- 1. profile dirs ----
|
|
18
|
+
Write-Host '[1] DSH profile directories'
|
|
19
|
+
if (Test-Path $profiles) {
|
|
20
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
21
|
+
$patch = Join-Path $_.FullName 'cordis.patch.yml'
|
|
22
|
+
if (Test-Path $patch) {
|
|
23
|
+
$f = Get-Item $patch
|
|
24
|
+
Line (" {0} patch={1}B mtime={2}" -f $_.Name, $f.Length, $f.LastWriteTime.ToString('MM-dd HH:mm:ss'))
|
|
25
|
+
} else {
|
|
26
|
+
Line (" {0} (no cordis.patch.yml)" -f $_.Name)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if ($env:DSH_PROFILE) {
|
|
30
|
+
Line (" env DSH_PROFILE = " + $env:DSH_PROFILE)
|
|
31
|
+
} else {
|
|
32
|
+
Line ' env DSH_PROFILE = (not set -> code defaults to "desktop")'
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
Line ' ERROR: no profile directory'
|
|
36
|
+
}
|
|
37
|
+
Write-Host ''
|
|
38
|
+
|
|
39
|
+
# ---- 2. remote-agent config per profile ----
|
|
40
|
+
Write-Host '[2] remote-agent config in each profile'
|
|
41
|
+
if (Test-Path $profiles) {
|
|
42
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
43
|
+
$patch = Join-Path $_.FullName 'cordis.patch.yml'
|
|
44
|
+
if (-not (Test-Path $patch)) { return }
|
|
45
|
+
$c = Get-Content $patch -Raw
|
|
46
|
+
if ($c -notmatch 'remote-agent') { return }
|
|
47
|
+
|
|
48
|
+
$roleM = [regex]::Match($c, '(?m)^\s*role:\s*(\S+)')
|
|
49
|
+
$injectM = [regex]::Match($c, '(?m)^\s*inject:\s*(.+)$')
|
|
50
|
+
$hubM = [regex]::Match($c, '(?m)^\s*hubUrl:\s*(\S+)')
|
|
51
|
+
$wsM = [regex]::Match($c, '(?ms)- id:\s*webserver.*?host:\s*(\S+)')
|
|
52
|
+
$tokM = [regex]::Match($c, '(?m)^\s*nodeToken:\s*(\S+)')
|
|
53
|
+
|
|
54
|
+
Line (' --- ' + $_.Name + ' ---')
|
|
55
|
+
if ($roleM.Success) { Line (' role = ' + $roleM.Groups[1].Value) } else { Line ' role = (NOT SET -> package default)' }
|
|
56
|
+
if ($injectM.Success) { Line (' inject = ' + $injectM.Groups[1].Value.Trim()) } else { Line ' inject = (not set)' }
|
|
57
|
+
if ($hubM.Success) { Line (' hubUrl = ' + $hubM.Groups[1].Value) } else { Line ' hubUrl = (not set)' }
|
|
58
|
+
if ($wsM.Success) { Line (' webserver = host=' + $wsM.Groups[1].Value) } else { Line ' webserver = (no webserver section)' }
|
|
59
|
+
if ($tokM.Success) { Line ' nodeToken = (set)' } else { Line ' nodeToken = (not set)' }
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
Line ' (skipped)'
|
|
63
|
+
}
|
|
64
|
+
Write-Host ''
|
|
65
|
+
|
|
66
|
+
# ---- 3. installed plugin version ----
|
|
67
|
+
Write-Host '[3] installed dsh-remote-agent version'
|
|
68
|
+
$found = $false
|
|
69
|
+
if (Test-Path $profiles) {
|
|
70
|
+
Get-ChildItem $profiles -Directory | ForEach-Object {
|
|
71
|
+
$nm = Join-Path $_.FullName 'node_modules\dsh-remote-agent'
|
|
72
|
+
$pj = Join-Path $nm 'package.json'
|
|
73
|
+
if (Test-Path $pj) {
|
|
74
|
+
$script:found = $true
|
|
75
|
+
$v = (Get-Content $pj -Raw | ConvertFrom-Json).version
|
|
76
|
+
$hasEnvCheck = Test-Path (Join-Path $nm 'lib\env-check.js')
|
|
77
|
+
$hasShare = Test-Path (Join-Path $nm 'lib\node\share-service.js')
|
|
78
|
+
$pkgPatch = Join-Path $nm 'cordis.patch.yml'
|
|
79
|
+
$pkgRole = ''
|
|
80
|
+
$hasWs = $false
|
|
81
|
+
if (Test-Path $pkgPatch) {
|
|
82
|
+
$pc = Get-Content $pkgPatch -Raw
|
|
83
|
+
$m = [regex]::Match($pc, '(?m)^\s*role:\s*(\S+)')
|
|
84
|
+
if ($m.Success) { $pkgRole = $m.Groups[1].Value }
|
|
85
|
+
$hasWs = $pc -match '(?m)^- id:\s*webserver'
|
|
86
|
+
}
|
|
87
|
+
Line (' ' + $_.Name + ': version ' + $v)
|
|
88
|
+
Line (' has lib/env-check.js (0.9+) : ' + $hasEnvCheck)
|
|
89
|
+
Line (' has lib/node/share-service.js : ' + $hasShare)
|
|
90
|
+
if ($pkgRole -ne '') { Line (' package default role : ' + $pkgRole) } else { Line ' package default role : (not found)' }
|
|
91
|
+
Line (' package has webserver binding : ' + $hasWs)
|
|
92
|
+
if (-not $hasEnvCheck) {
|
|
93
|
+
Line ' WARNING: this is pre-0.9, no "works out of the box"'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (-not $script:found) {
|
|
99
|
+
Line ' ERROR: dsh-remote-agent not found under any profile'
|
|
100
|
+
}
|
|
101
|
+
Write-Host ''
|
|
102
|
+
|
|
103
|
+
# ---- 4. panel endpoints ----
|
|
104
|
+
Write-Host '[4] panel endpoint probe (is the plugin loaded at all?)'
|
|
105
|
+
$ports = @()
|
|
106
|
+
$dshPids = (Get-Process 'DeepSeek Harness').Id
|
|
107
|
+
if ($dshPids) {
|
|
108
|
+
$ports += (Get-NetTCPConnection -State Listen | Where-Object { $dshPids -contains $_.OwningProcess } | ForEach-Object { $_.LocalPort })
|
|
109
|
+
}
|
|
110
|
+
$ports += 19387
|
|
111
|
+
$ports = $ports | Sort-Object -Unique
|
|
112
|
+
foreach ($port in $ports) {
|
|
113
|
+
foreach ($path in @('/api/remote-agent/setup/state', '/api/remote-agent/status')) {
|
|
114
|
+
$url = 'http://127.0.0.1:' + $port + $path
|
|
115
|
+
try {
|
|
116
|
+
$r = Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 3
|
|
117
|
+
$body = $r.Content
|
|
118
|
+
if ($body.Length -gt 240) { $body = $body.Substring(0, 240) + '...' }
|
|
119
|
+
Line (' [' + $port + '] ' + $path + ' -> HTTP ' + $r.StatusCode)
|
|
120
|
+
Line (' ' + $body)
|
|
121
|
+
} catch {
|
|
122
|
+
$code = $_.Exception.Response.StatusCode.value__
|
|
123
|
+
if ($code) {
|
|
124
|
+
Line (' [' + $port + '] ' + $path + ' -> HTTP ' + $code)
|
|
125
|
+
} else {
|
|
126
|
+
Line (' [' + $port + '] ' + $path + ' -> no listener')
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
Write-Host ''
|
|
132
|
+
|
|
133
|
+
# ---- 5. recent backups ----
|
|
134
|
+
Write-Host '[5] recent config backups (was the file rewritten?)'
|
|
135
|
+
if (Test-Path $profiles) {
|
|
136
|
+
Get-ChildItem $profiles -Recurse -File -Filter 'cordis.patch.yml*' |
|
|
137
|
+
Sort-Object LastWriteTime -Descending |
|
|
138
|
+
Select-Object -First 8 |
|
|
139
|
+
ForEach-Object {
|
|
140
|
+
Line (' ' + $_.LastWriteTime.ToString('MM-dd HH:mm:ss') + ' ' + $_.Length + 'B ' + $_.Name)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
Write-Host ''
|
|
144
|
+
|
|
145
|
+
Write-Host '=========================================='
|
|
146
|
+
Write-Host ' END. Paste the whole output back.'
|
|
147
|
+
Write-Host '=========================================='
|
|
148
|
+
Write-Host ''
|