dsh-dl-server 0.0.0-stage → 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jestions
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,47 @@
1
- # Temporary Holding Version
1
+ # dsh-dl-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English | [中文](README.zh.md)
4
+
5
+ **DLS** — DSH Download Server
6
+ A global download service for DeepSeek Harness: visible across sessions, owned per session, persistent queue, pausable and resumable.
7
+
8
+ > DLS is all you need
9
+
10
+ ---
11
+
12
+ ## Why
13
+
14
+ Every existing DSH downloader is one tool function that blocks until the file lands. They make downloads **fast**; they don't solve three things:
15
+
16
+ - the download dies when the session or the turn ends
17
+ - there is no queue, no pause, no resume
18
+ - nothing survives a restart
19
+
20
+ DLS is the **queue layer**, not the transport layer.
21
+
22
+ ## Status
23
+
24
+ `v0.1` — scaffold. Design: [SPEC.md](SPEC.md).
25
+
26
+ | Module | State |
27
+ | --- | --- |
28
+ | Data model | done |
29
+ | Persistent domain | done |
30
+ | Transport (aria2 JSON-RPC + single-stream fallback) | done |
31
+ | Queue engine (scheduling, ownership, pause reasons, checkpoints, recovery) | done |
32
+ | Seven tools | done |
33
+ | Real DSH install / verification | done (DSH 0.2.1-rc.5) |
34
+ | Hermetic tests (`npm test`) | done |
35
+ | User panel (v3) | later |
36
+
37
+ ## Tools (planned)
38
+
39
+ ```
40
+ download_enqueue download_status download_wait
41
+ download_pause download_resume download_cancel
42
+ download_cleanup
43
+ ```
44
+
45
+ ## License
46
+
47
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,45 @@
1
+ # dsh-dl-server
2
+
3
+ [English](README.md) | 中文
4
+
5
+ **DLS** — DSH Download Server
6
+ 大肥鱼自己的下载服务器插件
7
+
8
+ > DLS is all you need
9
+
10
+ ---
11
+
12
+ ## 为什么
13
+
14
+ 现成的 DSH 下载插件都是「一个工具函数 + 阻塞等到下完」。它们让下载**变快**,但没解决三件事:
15
+
16
+ - 会话或回合结束,下载就断了
17
+ - 不能排队、不能暂停恢复
18
+ - 进程重启,什么都没了
19
+
20
+ DLS 做的是**队列层**,不是传输层。
21
+
22
+ ## 状态
23
+
24
+ `v0.1` 脚手架阶段。完整设计见 [SPEC.md](SPEC.md)。
25
+
26
+ | 模块 | 状态 |
27
+ | --- | --- |
28
+ | 数据模型 | ✅ |
29
+ | 持久化 domain | ✅ |
30
+ | `download_status` | 最小实现 |
31
+ | 队列引擎 | 待做 |
32
+ | 传输层(aria2 / Node 兜底) | 待做 |
33
+ | 其余六个工具 | 待做 |
34
+
35
+ ## 工具(规划)
36
+
37
+ ```
38
+ download_enqueue download_status download_wait
39
+ download_pause download_resume download_cancel
40
+ download_cleanup
41
+ ```
42
+
43
+ ## 许可
44
+
45
+ MIT
@@ -0,0 +1,16 @@
1
+ # dsh-dl-server bundle patch
2
+ - insert:
3
+ - id: dsh-dl-server
4
+ name: 'dsh-dl-server'
5
+ config:
6
+ maxConcurrentJobs: 3
7
+ connectionsPerJob: 16
8
+ aria2Path: ''
9
+ aria2Port: 16801
10
+ speedLimitKbps: 0
11
+ proxy: ''
12
+ defaultDownloadDir: ''
13
+ checkpointSeconds: 30
14
+ autoResumeOnSessionReopen: true
15
+ autoResumeOnHostRestart: true
16
+ keepCompletedRecords: 200
package/lib/bootlog.js ADDED
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+
5
+ /**
6
+ * Minimal boot trace. Overwritten every boot, so it always describes the latest
7
+ * start. A support report is "send me %TEMP%/dls-boot.log".
8
+ */
9
+ export const BOOT_LOG = path.join(os.tmpdir(), 'dls-boot.log')
10
+
11
+ let started = false
12
+
13
+ /** @param {string} step @param {string} [detail] */
14
+ export function mark(step, detail) {
15
+ try {
16
+ if (!started) {
17
+ fs.writeFileSync(BOOT_LOG, `dsh-dl-server boot trace\n`)
18
+ started = true
19
+ }
20
+ const line = `${new Date().toISOString()} ${step}${detail ? ` ${detail}` : ''}`
21
+ fs.appendFileSync(BOOT_LOG, `${line}\n`)
22
+ } catch {
23
+ /* tracing must never break the plugin */
24
+ }
25
+ }
26
+
27
+ /** @param {string} step @param {unknown} error */
28
+ export function markFailure(step, error) {
29
+ const stack = error instanceof Error ? (error.stack ?? error.message) : String(error)
30
+ mark(`FAILED at ${step}`, stack.split('\n').slice(0, 6).join(' | '))
31
+ }
package/lib/domain.js ADDED
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod'
2
+ import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'
3
+
4
+ const JobRecordSchema = z.object({
5
+ id: z.string(),
6
+ seq: z.number().int().nonnegative(),
7
+
8
+ ownerWorkspace: z.string(),
9
+ ownerSession: z.string(),
10
+ origin: z.string().optional(),
11
+
12
+ urls: z.array(z.string()).min(1),
13
+ destPath: z.string(),
14
+
15
+ state: z.enum(['queued', 'running', 'paused', 'completed', 'failed', 'canceled']),
16
+ pauseReason: z.enum(['user', 'session_closed', 'host_restart']).optional(),
17
+
18
+ createdAt: z.number(),
19
+ updatedAt: z.number(),
20
+
21
+ bytesTotal: z.number(),
22
+ bytesCheckpoint: z.number(),
23
+
24
+ attempts: z.number().int().nonnegative(),
25
+ lastError: z.string().optional(),
26
+
27
+ expectSha256: z.string().optional(),
28
+ finalBytes: z.number().optional(),
29
+ })
30
+
31
+ /**
32
+ * The persistent queue domain.
33
+ *
34
+ * per-record: one corrupt job must not make the whole queue unreadable.
35
+ * backup-and-skip: move the bad document aside and keep booting.
36
+ * global.nextSeq: the global counter behind J# ids.
37
+ */
38
+ export const JobDomain = defineDomain({
39
+ // Domain names must match /^[a-z][a-z0-9_]*$/ — hyphens are rejected at load.
40
+ name: 'dsh_dl_jobs',
41
+ version: 1,
42
+ layout: 'per-record',
43
+ invalidRecords: 'backup-and-skip',
44
+ tables: {
45
+ jobs: domainTable(JobRecordSchema),
46
+ },
47
+ global: {
48
+ initial: { nextSeq: 1 },
49
+ schema: z.object({ nextSeq: z.number().int().positive() }),
50
+ },
51
+ })
package/lib/index.js ADDED
@@ -0,0 +1,92 @@
1
+ import { JobDomain } from './domain.js'
2
+ import { Queue } from './queue.js'
3
+ import { Transport } from './transport.js'
4
+ import { registerTools } from './tools.js'
5
+ import { mark, markFailure } from './bootlog.js'
6
+
7
+ export const name = 'dsh-dl-server'
8
+ export const inject = ['tools', 'storageDomain']
9
+
10
+ const DEFAULTS = Object.freeze({
11
+ maxConcurrentJobs: 3,
12
+ connectionsPerJob: 16,
13
+ aria2Path: '',
14
+ aria2Port: 16801,
15
+ speedLimitKbps: 0,
16
+ proxy: '',
17
+ defaultDownloadDir: '',
18
+ checkpointSeconds: 30,
19
+ autoResumeOnSessionReopen: true,
20
+ autoResumeOnHostRestart: true,
21
+ keepCompletedRecords: 200,
22
+ })
23
+
24
+ /**
25
+ * Use the host logger when one is available so INFO lines never land on stdout.
26
+ * @param {any} ctx
27
+ */
28
+ function makeLog(ctx) {
29
+ try {
30
+ const factory = ctx?.logger
31
+ if (typeof factory === 'function') {
32
+ const logger = factory('dsh-dl-server')
33
+ if (logger && typeof logger.info === 'function') {
34
+ return (level, message) => {
35
+ const fn = logger[level] ?? logger.info
36
+ fn.call(logger, message)
37
+ }
38
+ }
39
+ }
40
+ } catch {
41
+ /* fall through to the console fallback */
42
+ }
43
+ return (level, message) => {
44
+ // DLS_DEBUG=1 surfaces INFO lines on stderr; useful when no host logger is wired.
45
+ if (process.env.DLS_DEBUG === '1' || level === 'warn' || level === 'error') {
46
+ console.error(`[dsh-dl-server] ${message}`)
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * @param {import('@deepseek-ai/cordis').Context} ctx
53
+ * @param {Partial<typeof DEFAULTS>} [rawConfig]
54
+ */
55
+ export async function apply(ctx, rawConfig) {
56
+ mark('apply enter')
57
+
58
+ const config = { ...DEFAULTS, ...(rawConfig ?? {}) }
59
+ const log = makeLog(ctx)
60
+
61
+ try {
62
+ mark('opening domain')
63
+ const domain = await ctx.storageDomain.open(JobDomain)
64
+ mark('domain open')
65
+
66
+ const transport = new Transport(config, log)
67
+ await transport.init()
68
+ mark('transport ready', `mode=${transport.mode}`)
69
+
70
+ const queue = new Queue({ domain, transport, config, log })
71
+ await queue.recover()
72
+ mark('queue ready')
73
+
74
+ registerTools({ ctx, queue, config })
75
+ // The tools themselves register inside the deferred ctx.inject callback;
76
+ // this mark only records that the subscription was installed.
77
+ mark('registerTools dispatched')
78
+
79
+ ctx.effect(() => async () => {
80
+ await queue.close()
81
+ await transport.dispose()
82
+ await domain.close()
83
+ })
84
+ mark('effect installed')
85
+
86
+ log('info', `ready (transport: ${transport.mode}, concurrency: ${config.maxConcurrentJobs})`)
87
+ mark('apply done')
88
+ } catch (error) {
89
+ markFailure('apply', error)
90
+ throw error
91
+ }
92
+ }