@having5548/dsh-downloader 0.5.2 → 0.5.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 +25 -0
- package/README.en.md +14 -6
- package/README.md +13 -5
- package/lib/core/proxy-server.js +8 -1
- package/lib/core/transports.js +55 -22
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,31 @@
|
|
|
9
9
|
|
|
10
10
|
## [未发布]
|
|
11
11
|
|
|
12
|
+
## [0.5.3] - 2026-10-11
|
|
13
|
+
|
|
14
|
+
### 修复
|
|
15
|
+
|
|
16
|
+
- **上游断管会把整个 DSH 宿主崩掉**(严重,宿主连续崩了两次)。
|
|
17
|
+
现象:`crash-2026-10-11T03-46-46-081Z-host.log` 与 `03-48-03-999Z-host.log` 的栈指向同一处:
|
|
18
|
+
```
|
|
19
|
+
dsh: fatal uncaught exception: Error: write EPIPE
|
|
20
|
+
at Writable.write (node:internal/streams/writable:508:10)
|
|
21
|
+
at Object.write (.../@having5548/dsh-downloader/lib/core/transports.js:598:28)
|
|
22
|
+
at Socket.<anonymous> (.../@having5548/dsh-downloader/lib/core/proxy-server.js:154:9)
|
|
23
|
+
```
|
|
24
|
+
根因:`nativeConnect()` 用 `stdio: ["pipe","pipe","pipe"]` 起 Go 连接器,但**三根管道一个 `error` 监听都没挂**。
|
|
25
|
+
连接器进程一死(连接失败 / 超时 / 被 kill),管道随即断裂,之后任何一次写入都会让 `child.stdin` 抛出
|
|
26
|
+
`EPIPE` —— 而在 Node 里**没有监听者的 `error` 事件就是未捕获异常**,直接把宿主带走。
|
|
27
|
+
原有的 `ended` 标志挡不住这个竞态:写入可能排在一个已经断掉的管道上,而 `exit` 事件还没派发。
|
|
28
|
+
修法(三层):
|
|
29
|
+
1. 新增 `stdioFace(child)`:给 `stdin` / `stdout` / `stderr` 与子进程本身都挂 `error` 监听,
|
|
30
|
+
写入再包一层 try/catch(防同步抛出),并保留连接器最后的 stderr 供排查;
|
|
31
|
+
2. `ProxyServer.#relay()` 里的 `face.write(chunk)` 包 try/catch,失败即关闭上游并销毁客户端连接;
|
|
32
|
+
3. 新增**独立子进程**回归测试:让子进程关掉 stdin 但继续活着(于是 `ended` 不会置位、管道却已断),
|
|
33
|
+
再灌 1.25 MB 数据。旧写法在该探针下必崩(实测 `exit=1`、`code: 'EOF'`),修复后存活。
|
|
34
|
+
|
|
35
|
+
> 测试 366 → 368 项。
|
|
36
|
+
|
|
12
37
|
## [0.5.2] - 2026-10-10
|
|
13
38
|
|
|
14
39
|
两处都是把 `dsh_run_proxied` 真正用起来时才暴露的:`git push` 打插件内核**必定**失败。
|
package/README.en.md
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
[简体中文](README.md) | English
|
|
8
8
|
|
|
9
|
-

|
|
10
|
+

|
|
10
11
|

|
|
11
12
|

|
|
12
|
-

|
|
13
14
|

|
|
14
15
|
|
|
15
16
|
</div>
|
|
@@ -50,20 +51,27 @@ The two can be used together; they do not interfere. This plugin **never mutates
|
|
|
50
51
|
|
|
51
52
|
## 📦 Install
|
|
52
53
|
|
|
54
|
+
From npm (published to the official registry):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
dsh plugin --profile desktop add @having5548/dsh-downloader
|
|
58
|
+
```
|
|
59
|
+
|
|
53
60
|
From the GitHub release:
|
|
54
61
|
|
|
55
62
|
```bash
|
|
56
|
-
dsh plugin --profile desktop add https://github.com/having5548/dsh-downloader/releases/latest/download/having5548-dsh-downloader-0.5.
|
|
63
|
+
dsh plugin --profile desktop add https://github.com/having5548/dsh-downloader/releases/latest/download/having5548-dsh-downloader-0.5.3.tgz
|
|
57
64
|
```
|
|
58
65
|
|
|
59
66
|
Or build and install locally:
|
|
60
67
|
|
|
61
68
|
```bash
|
|
62
69
|
npm pack
|
|
63
|
-
dsh plugin --profile desktop add having5548-dsh-downloader-0.5.
|
|
70
|
+
dsh plugin --profile desktop add having5548-dsh-downloader-0.5.3.tgz
|
|
64
71
|
```
|
|
65
72
|
|
|
66
73
|
> Profile name: the current Electron desktop app uses `desktop`; the older web CLI used `web`.
|
|
74
|
+
> The README shown on the npm page is the one captured **at publish time** — later documentation edits only reach it with the next release.
|
|
67
75
|
|
|
68
76
|
Then open **Settings → Download proxy**.
|
|
69
77
|
|
|
@@ -257,7 +265,7 @@ The upstream is resolved in this order; the first hit wins:
|
|
|
257
265
|
| `downloadDir` | empty | empty = `$DSH_HOME/downloads` |
|
|
258
266
|
| `maxDownloadMb` | `512` | per-file size limit |
|
|
259
267
|
| `downloadTimeoutS` | `600` | per-download wall-clock budget |
|
|
260
|
-
| `threads` | `4` | segmented connection count; `1` = single stream. Falls back to one connection when the server lacks `Range`, answers it dishonestly, or the file is under 1 MiB |
|
|
268
|
+
| `threads` | `4` | segmented connection count; `1` = single stream. Falls back to one connection when the server lacks `Range`, answers it dishonestly, or the file is under 1 MiB |
|
|
261
269
|
| `stallTimeoutS` | `30` | abort after this long with no data |
|
|
262
270
|
| `insecureTls` | `false` | skip TLS verification (corporate TLS interception only) |
|
|
263
271
|
| `allowOutsideWorkspace` | `false` | allow `save_path` outside the workspace and download directory |
|
|
@@ -351,7 +359,7 @@ The core (rule engine, CN data, loopback mixed proxy, node transports, subscript
|
|
|
351
359
|
|
|
352
360
|
```bash
|
|
353
361
|
npm install
|
|
354
|
-
node test/smoke.mjs #
|
|
362
|
+
node test/smoke.mjs # 368 checks: rules / subscriptions / profile import / routing / HTTP client / end-to-end / session guard / token gate
|
|
355
363
|
node --check lib/client.js
|
|
356
364
|
npm pack
|
|
357
365
|
```
|
package/README.md
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
简体中文 | [English](README.en.md)
|
|
8
8
|
|
|
9
|
-

|
|
10
|
+

|
|
10
11
|

|
|
11
12
|

|
|
12
|
-

|
|
13
14
|

|
|
14
15
|
|
|
15
16
|
</div>
|
|
@@ -50,20 +51,27 @@ NO_PROXY=localhost,127.0.0.1,::1,deepseek.com,.deepseek.com
|
|
|
50
51
|
|
|
51
52
|
## 📦 安装
|
|
52
53
|
|
|
54
|
+
从 npm 安装(已发布到官方源):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
dsh plugin --profile desktop add @having5548/dsh-downloader
|
|
58
|
+
```
|
|
59
|
+
|
|
53
60
|
从 GitHub Release 安装:
|
|
54
61
|
|
|
55
62
|
```bash
|
|
56
|
-
dsh plugin --profile desktop add https://github.com/having5548/dsh-downloader/releases/latest/download/having5548-dsh-downloader-0.5.
|
|
63
|
+
dsh plugin --profile desktop add https://github.com/having5548/dsh-downloader/releases/latest/download/having5548-dsh-downloader-0.5.3.tgz
|
|
57
64
|
```
|
|
58
65
|
|
|
59
66
|
本地打包安装:
|
|
60
67
|
|
|
61
68
|
```bash
|
|
62
69
|
npm pack
|
|
63
|
-
dsh plugin --profile desktop add having5548-dsh-downloader-0.5.
|
|
70
|
+
dsh plugin --profile desktop add having5548-dsh-downloader-0.5.3.tgz
|
|
64
71
|
```
|
|
65
72
|
|
|
66
73
|
> profile 名:新版桌面端是 `desktop`,旧版 Web 端是 `web`。
|
|
74
|
+
> npm 页面上显示的 README 是**发布那一刻**的版本 —— 后来改的文档要等下一次发版才会同步过去。
|
|
67
75
|
|
|
68
76
|
装完打开 **设置 → 下载代理**。
|
|
69
77
|
|
|
@@ -356,7 +364,7 @@ dsh_run_proxied({ command: "git clone https://github.com/x/y.git" })
|
|
|
356
364
|
|
|
357
365
|
```bash
|
|
358
366
|
npm install
|
|
359
|
-
node test/smoke.mjs #
|
|
367
|
+
node test/smoke.mjs # 368 项:规则引擎 / 订阅解析 / 配置导入 / 路由 / HTTP 客户端 / 端到端下载 / 会话守卫 / 令牌鉴权
|
|
360
368
|
node --check lib/client.js
|
|
361
369
|
npm pack
|
|
362
370
|
```
|
package/lib/core/proxy-server.js
CHANGED
|
@@ -151,7 +151,14 @@ export class ProxyServer {
|
|
|
151
151
|
}).catch(() => client.destroy());
|
|
152
152
|
client.on("data", (chunk) => {
|
|
153
153
|
this.counters.up += chunk.length;
|
|
154
|
-
|
|
154
|
+
// [dsh-downloader] 上游可能已经断了(连接器进程退出、socket 被销毁)。
|
|
155
|
+
// 写入必须兜住:让它抛出去就是未捕获异常,会崩掉整个宿主进程。
|
|
156
|
+
try {
|
|
157
|
+
face.write(chunk);
|
|
158
|
+
} catch {
|
|
159
|
+
try { face.close(); } catch { /* already gone */ }
|
|
160
|
+
client.destroy();
|
|
161
|
+
}
|
|
155
162
|
});
|
|
156
163
|
client.on("end", () => face.close());
|
|
157
164
|
client.on("close", () => face.close());
|
package/lib/core/transports.js
CHANGED
|
@@ -552,6 +552,60 @@ export function hasNativeConnector() {
|
|
|
552
552
|
try { return existsSync(CONNECTOR_PATH); } catch { return false; }
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Wrap a spawned connector's stdio as a duplex face.
|
|
557
|
+
*
|
|
558
|
+
* [dsh-downloader] 这三根管道**必须**在第一时间挂上 `error` 监听。连接器进程一死,
|
|
559
|
+
* 管道就断了,之后任何一次 `write` 都会让 `child.stdin` 抛出 `EPIPE`;而在 Node 里
|
|
560
|
+
* **没有监听者的 `error` 事件是未捕获异常**,会直接把宿主进程带走 ——
|
|
561
|
+
* 实测把 DSH 宿主崩掉两次(crash-2026-10-11T03-46 / 03-48,栈就落在下面这行 write)。
|
|
562
|
+
*
|
|
563
|
+
* 另外 `ended` 标志挡不住这个竞态:`write` 可能排在一个已经断掉的管道上,
|
|
564
|
+
* 而 `exit` 事件还没派发。所以写入本身也要兜住。
|
|
565
|
+
*
|
|
566
|
+
* 导出只为测试:生产路径只有 `nativeConnect` 调用它。
|
|
567
|
+
*/
|
|
568
|
+
export function stdioFace(child) {
|
|
569
|
+
let ended = false;
|
|
570
|
+
let stderrTail = "";
|
|
571
|
+
const markEnded = () => {
|
|
572
|
+
ended = true;
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
child.stdin.on("error", markEnded);
|
|
576
|
+
child.stdout.on("error", markEnded);
|
|
577
|
+
child.stderr.on("error", markEnded);
|
|
578
|
+
child.on("error", markEnded);
|
|
579
|
+
child.on("exit", markEnded);
|
|
580
|
+
child.stderr.on("data", (chunk) => {
|
|
581
|
+
stderrTail = (stderrTail + chunk.toString()).slice(-512);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
return {
|
|
585
|
+
read: (async function* () {
|
|
586
|
+
for await (const chunk of child.stdout) {
|
|
587
|
+
yield chunk;
|
|
588
|
+
}
|
|
589
|
+
})(),
|
|
590
|
+
write: (data) => {
|
|
591
|
+
if (ended) return;
|
|
592
|
+
try {
|
|
593
|
+
child.stdin.write(data);
|
|
594
|
+
} catch {
|
|
595
|
+
// 管道已断(连接器已退出):丢掉这一块,绝不让它冒泡成未捕获异常。
|
|
596
|
+
ended = true;
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
close: () => {
|
|
600
|
+
try { child.stdin.end(); } catch { /* already closed */ }
|
|
601
|
+
try { child.stdout.destroy(); } catch { /* already closed */ }
|
|
602
|
+
if (!ended) setTimeout(() => child.kill(), 500);
|
|
603
|
+
},
|
|
604
|
+
/** 连接器最后的 stderr,排查「节点为什么连不上」时有用。 */
|
|
605
|
+
stderrTail: () => stderrTail
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
555
609
|
/**
|
|
556
610
|
* Connect through the self-compiled Go connector via stdio: stdin feeds the
|
|
557
611
|
* remote target, stdout carries the reply. Supports `hysteria2` and `reality`.
|
|
@@ -581,28 +635,7 @@ function nativeConnect(proto, node, host, port) {
|
|
|
581
635
|
stdio: ["pipe", "pipe", "pipe"],
|
|
582
636
|
windowsHide: true
|
|
583
637
|
});
|
|
584
|
-
|
|
585
|
-
let stderrTail = "";
|
|
586
|
-
child.stderr.on("data", (chunk) => {
|
|
587
|
-
stderrTail = (stderrTail + chunk.toString()).slice(-512);
|
|
588
|
-
});
|
|
589
|
-
child.on("exit", () => { ended = true; });
|
|
590
|
-
|
|
591
|
-
return {
|
|
592
|
-
read: (async function* () {
|
|
593
|
-
for await (const chunk of child.stdout) {
|
|
594
|
-
yield chunk;
|
|
595
|
-
}
|
|
596
|
-
})(),
|
|
597
|
-
write: (data) => {
|
|
598
|
-
if (!ended) child.stdin.write(data);
|
|
599
|
-
},
|
|
600
|
-
close: () => {
|
|
601
|
-
try { child.stdin.end(); } catch { /* already closed */ }
|
|
602
|
-
try { child.stdout.destroy(); } catch { /* already closed */ }
|
|
603
|
-
if (!ended) setTimeout(() => child.kill(), 500);
|
|
604
|
-
}
|
|
605
|
-
};
|
|
638
|
+
return stdioFace(child);
|
|
606
639
|
}
|
|
607
640
|
|
|
608
641
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@having5548/dsh-downloader",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "DeepSeek Harness 下载代理插件:注册 dsh_download 等工具,让模型下载境外文件时自动走插件自带的代理内核(订阅自包含,或显式 proxyUrl),境内直连;带 sha256、大小上限、停滞检测与直连失败自动回退,另附「设置 → 下载代理」管理面板。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"license": "MIT",
|
|
85
85
|
"publishConfig": {
|
|
86
|
-
"access": "public"
|
|
86
|
+
"access": "public",
|
|
87
|
+
"registry": "https://registry.npmjs.org"
|
|
87
88
|
}
|
|
88
89
|
}
|