dsh-remote-bridge 2.4.1 → 2.4.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/README.en.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <img alt="Version" src="https://img.shields.io/badge/version-v2.4.1-087f8c">
12
+ <img alt="Version" src="https://img.shields.io/badge/version-v2.4.3-087f8c">
13
13
  <img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
14
14
  <img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
15
15
  <img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
@@ -27,6 +27,18 @@ The interface is bilingual and defaults to Chinese. Prebuilt packages run on Win
27
27
  > with `dsh-api-gateway`, leaving the upstream release unable to start on 0.2.x — it surfaces as
28
28
  > "adding an authorized root returns 405". Background, rationale, changes, and test results are in
29
29
  > [DSH 0.2.x compatibility notes](./docs/project/DSH-0.2-COMPAT.md).
30
+ >
31
+ > **Adaptation to DSH 0.2.1-rc.2** (rechecked 2026-10-20; see `docs/project/DSH-0.2.1-ADAPTATION.md`):
32
+ > this plugin **deliberately imports no `@deepseek-ai/*` package** — every host service is consumed
33
+ > through a structural type injected by the running Host (see the header comment in
34
+ > `src/host/dsh-0.2-bridge.ts`). Checked one by one against the installed `0.2.1-rc.2` packages:
35
+ > all 10 `inject` service names in `cordis.patch.yml` **still exist with unchanged signatures**, and
36
+ > `engines.dsh` is recognized by the 0.2.1 kernel (`prepareProfileEntries` in
37
+ > `dsh-app-boot/lib/index.js`) — an unmet range only sets that entry to `disabled` and prints to
38
+ > stderr; **the host process does not exit**. So no code change is needed to run on `0.2.1-rc.2`.
39
+ > The only point still needing on-device confirmation is the injection surface of
40
+ > `permissionPresets` (never injectable on 0.2.0 either, where the plugin already falls back to a
41
+ > built-in catalog).
30
42
 
31
43
  ## What it looks like
32
44
 
@@ -63,7 +75,12 @@ So seeing `dsh-workspace` in a REST address or the state directory is **expected
63
75
  ### Requirements
64
76
 
65
77
  - A working DeepSeek Harness WebUI installation
66
- - DSH kernel `0.1.x` or `0.2.x` (0.2.x is supported by this branch's compatibility layer — see [DSH 0.2.x compatibility notes](./docs/project/DSH-0.2-COMPAT.md))
78
+ - DSH kernel **`>=0.2.0-rc.2 <0.3.0`** (compatible with `0.2.0-rc.2` and every later `0.2.x`, including
79
+ `0.2.1-rc.2`; supported by this branch's compatibility layer — see
80
+ [DSH 0.2.x compatibility notes](./docs/project/DSH-0.2-COMPAT.md)).
81
+ `package.json` declares the same range under `engines.dsh`; the kernel resolves it with semver and
82
+ only **disables** the plugin when unmet (the host process keeps running), with the user able to
83
+ grant an exact-version exemption as the kernel suggests.
67
84
  - Node.js `^22.19.0` or `>=24.0.0`
68
85
  - Port `3090`, or another available port
69
86
 
@@ -77,10 +94,10 @@ dsh plugin --profile web add dsh-remote-bridge
77
94
  dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
78
95
 
79
96
  # 2. tarball from the GitHub release
80
- dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.4.1/dsh-remote-bridge-v2.4.1.tgz
97
+ dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.4.3/dsh-remote-bridge-v2.4.3.tgz
81
98
 
82
99
  # 3. tarball from the Gitee release (faster from mainland China)
83
- dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.4.1/dsh-remote-bridge-v2.4.1.tgz
100
+ dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.4.3/dsh-remote-bridge-v2.4.3.tgz
84
101
 
85
102
  npx @deepseek-ai/dsh web
86
103
  ```
@@ -97,7 +114,7 @@ Restart DSH after installation, then open settings — The section is titled **
97
114
  To install a downloaded release archive:
98
115
 
99
116
  ```sh
100
- dsh plugin --profile web add ./dsh-remote-bridge-v2.4.1.tgz
117
+ dsh plugin --profile web add ./dsh-remote-bridge-v2.4.3.tgz
101
118
  ```
102
119
 
103
120
  ## First connection
@@ -329,13 +346,13 @@ pnpm pack
329
346
 
330
347
  `pnpm check` covers type-checking, documentation consistency, and tests. It currently passes **30 test files / 226 cases**, plus **4 browser-layer cases** (Playwright measuring real computed styles).
331
348
 
332
- `npm pack` produces roughly a **0.29 MB** archive: 2.2.0 dropped the standalone page and its editor dependencies and turned source maps off, down from 1.72 MB.
349
+ `npm pack` produces roughly a **0.55 MB** archive (v2.4.3 measured 572,951 bytes, SHA-256 `38f25c10…299c93`): 2.2.0 dropped the standalone page and its editor dependencies and turned source maps off, down from 1.72 MB; it has since grown slightly with projection passthrough and the permission BFF.
333
350
 
334
- v2.4.1 was developed against DSH `master@47f943859bef60e4160492346772ded9b24f765a`. CI covers Windows, Ubuntu, and macOS.
351
+ v2.4.3 was verified on DSH **0.2.1-rc.2** on real hardware: all 14/14 host services injected, the three jobs endpoints exercised, and the permission catalog returned the kernel's real three presets. The development baseline is `master@47f943859bef60e4160492346772ded9b24f765a` (kernel 0.2.0-rc.2); see `engines.dsh` above for the supported range. CI covers Windows, Ubuntu, and macOS.
335
352
 
336
353
  ## Project
337
354
 
338
- - Version: `v2.4.1`
355
+ - Version: `v2.4.3`
339
356
  - Author: upstream [Hakunm](https://github.com/Hakunm); this fork maintained by [westanke](https://github.com/westanke)
340
357
  - Repository: [GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
341
358
  - npm: [`dsh-remote-bridge`](https://www.npmjs.com/package/dsh-remote-bridge) ([mainland-China mirror](https://registry.npmmirror.com/dsh-remote-bridge))
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <img alt="Version" src="https://img.shields.io/badge/version-v2.4.1-087f8c">
12
+ <img alt="Version" src="https://img.shields.io/badge/version-v2.4.3-087f8c">
13
13
  <img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
14
14
  <img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
15
15
  <img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
@@ -25,6 +25,14 @@
25
25
  > 0.2.x 用 `dsh-api-gateway` 替换了提供 `apiProxy` 服务的 `dsh-host-apiproxy`,导致上游版本在
26
26
  > 0.2.x 上无法启动(表现为「添加根目录返回 405」)。兼容层的完整说明——基于什么、为什么、改了什么、
27
27
  > 测了什么——见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md)。
28
+ >
29
+ > **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据 `/media/wangke/OFFICE/workspace/dsh-remote-bridge/docs/project/DSH-0.2.1-ADAPTATION.md`):
30
+ > 本插件**刻意不 import 任何 `@deepseek-ai/*` 包**,全部宿主服务经结构类型 + Host 注入消费
31
+ > (见 `src/host/dsh-0.2-bridge.ts` 头注释)。逐一到 `0.2.1-rc.2` 实装包核对:`cordis.patch.yml`
32
+ > 的 10 项 `inject` 服务名**全部仍存在且签名未变**,`engines.dsh` 在 0.2.1 被内核识别
33
+ > (`dsh-app-boot/lib/index.js` 的 `prepareProfileEntries`),不满足时只把该 entry 置
34
+ > `disabled` 并打 stderr,**宿主进程不退出**。故本插件无需改码即可运行于 `0.2.1-rc.2`。
35
+ > 唯一需真机定论的是 `permissionPresets` 的注入面(0.2.0 上即注不进,已降级到内置目录)。
28
36
 
29
37
  ## 实际效果
30
38
 
@@ -62,7 +70,8 @@
62
70
  ### 环境要求
63
71
 
64
72
  - 已安装并能正常启动的 DeepSeek Harness WebUI
65
- - DSH 内核 `0.1.x` 或 `0.2.x`(0.2.x 由本分支的兼容层支持,见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md))
73
+ - DSH 内核 **`>=0.2.0-rc.2 <0.3.0`**(兼容 `0.2.0-rc.2` 及其后全部 `0.2.x`,含 `0.2.1-rc.2`;由本分支的兼容层支持,见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md))。
74
+ `package.json` 的 `engines.dsh` 已声明同一范围 —— 内核按 semver 裁决,不满足时该插件被**禁用**(宿主进程不退出),用户可按内核提示授予精确版本豁免。
66
75
  - Node.js `^22.19.0` 或 `>=24.0.0`
67
76
  - 默认端口 `3090` 可用,或准备一个其他端口
68
77
 
@@ -76,10 +85,10 @@ dsh plugin --profile web add dsh-remote-bridge
76
85
  dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
77
86
 
78
87
  # ② GitHub Release 的 tarball
79
- dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.4.1/dsh-remote-bridge-v2.4.1.tgz
88
+ dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.4.3/dsh-remote-bridge-v2.4.3.tgz
80
89
 
81
90
  # ③ Gitee Release 的 tarball(国内访问更快)
82
- dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.4.1/dsh-remote-bridge-v2.4.1.tgz
91
+ dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.4.3/dsh-remote-bridge-v2.4.3.tgz
83
92
 
84
93
  npx @deepseek-ai/dsh web
85
94
  ```
@@ -95,7 +104,7 @@ npx @deepseek-ai/dsh web
95
104
  从 GitHub Release 下载离线安装包时,可直接安装 tarball:
96
105
 
97
106
  ```sh
98
- dsh plugin --profile web add ./dsh-remote-bridge-v2.4.1.tgz
107
+ dsh plugin --profile web add ./dsh-remote-bridge-v2.4.3.tgz
99
108
  ```
100
109
 
101
110
  ## 第一次使用
@@ -323,13 +332,15 @@ pnpm pack
323
332
 
324
333
  `pnpm check` 覆盖类型检查、文档一致性与测试;当前 **30 个测试文件 / 226 个用例**,外加 **4 个浏览器层用例**(Playwright 实测量出的计算样式),全部通过。
325
334
 
326
- `npm pack` 的产物约 **0.29 MB**:2.2.0 删除独立页及其编辑器依赖、并关闭 source map 之后,从 1.72 MB 降下来。
335
+ `npm pack` 的产物约 **0.55 MB**(v2.4.3 实测 572,951 字节,SHA-256 `38f25c10…299c93`):2.2.0 删除独立页及其编辑器依赖、并关闭 source map 之后从 1.72 MB 降下来,之后随投影透传与权限 BFF 小幅增长。
327
336
 
328
- v2.4.1 基于 DSH `master@47f943859bef60e4160492346772ded9b24f765a` 开发,CI 覆盖 Windows、Ubuntu 和 macOS。
337
+ v2.4.3 在 DSH **0.2.1-rc.2** 上完成真机验证:14/14 宿主服务全部可注入、三个 jobs 端点实测通过、
338
+ 权限档位目录返回内核真实三档。开发基线为 `master@47f943859bef60e4160492346772ded9b24f765a`(内核 0.2.0-rc.2),
339
+ 兼容范围见上文 `engines.dsh`。CI 覆盖 Windows、Ubuntu 和 macOS。
329
340
 
330
341
  ## 项目信息
331
342
 
332
- - 当前版本:`v2.4.1`
343
+ - 当前版本:`v2.4.3`
333
344
  - 作者:上游 [Hakunm](https://github.com/Hakunm),本 fork 维护 [westanke](https://github.com/westanke)
334
345
  - 仓库:[GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
335
346
  - npm:[`dsh-remote-bridge`](https://www.npmjs.com/package/dsh-remote-bridge)([国内镜像](https://registry.npmmirror.com/dsh-remote-bridge))
package/cordis.patch.yml CHANGED
@@ -6,7 +6,38 @@
6
6
  # DSH 0.2.x host services (see src/host/dsh-0.2-bridge.ts). The pre-0.2
7
7
  # `apiProxy` facade is rebuilt from these; `@deepseek-ai/dsh-host-apiproxy`
8
8
  # stopped shipping at 0.1.1-rc.2 and nothing provides `apiProxy` after it.
9
- inject: [sessionController, workspaceController, workspaceRegistry, settingsController, credentialsController, agentPresets, llm, webServer, agents, commands]
9
+ #
10
+ # ⚠️ 这份列表**必须**与 `src/index.ts` 导出的 `inject` 完全一致。
11
+ # 原因(2026-10-20 实测,代价是三次重启):patch 里的 `inject` **覆盖**
12
+ # 插件自己导出的 `inject`,不是取并集。于是插件声明了、这里漏了的服务
13
+ # 读起来会报 `cannot get property "x" without inject` ——
14
+ # 与「内核压根没有这个服务」的报错**一模一样**,肉眼分不出来。
15
+ #
16
+ # 这一条曾长期掩盖真相:`permissionPresets` 一直以为「web profile 注不进、
17
+ # 只能走内置兜底目录」,实际是这里没声明它。`jobController` 加进来之后
18
+ # 同样注不进,才顺着 `_refresh()` 的 INACTIVE 逻辑反查到这条覆盖规则。
19
+ inject: [
20
+ sessionController,
21
+ workspaceController,
22
+ workspaceRegistry,
23
+ settingsController,
24
+ credentialsController,
25
+ agentPresets,
26
+ # 权限档位目录(`ctx.permissionPresets`):内核把它做成独立的 Typert
27
+ # 远程服务,会话投影那条 wire 视图只给 currentValue、不带候选项。
28
+ permissionPresets,
29
+ llm,
30
+ webServer,
31
+ agents,
32
+ commands,
33
+ # 后台任务(`ctx.jobController`):远程场景下任务跑飞了要能停掉。
34
+ jobController,
35
+ # 文件上传服务(`ctx.fileUploads`):用它而不是内核那条只绑 loopback 的
36
+ # /api/session/uploadFileBinary HTTP 路由。
37
+ fileUploads,
38
+ # 附件存储(`ctx.attachments`):会话历史里的图片以 attachmentId 引用存在。
39
+ attachments,
40
+ ]
10
41
  config:
11
42
  # 状态目录**不改名**:已装的机器上有既有数据库与回收站,换目录等于让用户
12
43
  # 的授权根配置和回收站全部消失。改名只动「装的时候叫什么、bundle 叫什么」。
@@ -1,7 +1,7 @@
1
1
  asyncapi: 3.0.0
2
2
  info:
3
3
  title: DeepSeek Harness Mobile Events
4
- version: 2.4.1
4
+ version: 2.4.3
5
5
  description: |
6
6
  Live file and authorized DSH chat events. Reconnect clients must refresh
7
7
  REST state; unbounded replay is not provided.
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: DeepSeek Harness Mobile API
4
- version: 2.4.1
4
+ version: 2.4.3
5
5
  description: Versioned file and chat BFF for mobile and third-party DSH clients. HTTP and HTTPS are both supported.
6
6
  servers:
7
7
  - url: http://127.0.0.1:3090/api/v1
@@ -24,7 +24,63 @@ paths:
24
24
  properties:
25
25
  ok: { type: boolean, const: true }
26
26
  version: { type: string, const: v1 }
27
- pluginVersion: { type: string, const: 2.4.1 }
27
+ pluginVersion: { type: string, const: 2.4.2 }
28
+ /debug/host-services:
29
+ get:
30
+ operationId: hostServices
31
+ summary: Host service injection probe
32
+ description: |
33
+ Reports which DSH host services the plugin actually resolved on the running
34
+ kernel. This plugin deliberately imports no `@deepseek-ai/*` package and
35
+ consumes every host service through a structural type injected by the Host,
36
+ so "is the service there at all" is otherwise unanswerable without guessing.
37
+
38
+ `injected` covers the services named in the plugin's `inject` declaration;
39
+ `observed` covers services confirmed present in the web profile but not yet
40
+ consumed (currently only `workingDirectory`, added by DSH 0.2.1).
41
+
42
+ Requires a device token or loopback admin, like every other authenticated
43
+ endpoint: it exposes host internals.
44
+ responses:
45
+ '200':
46
+ description: Injection probe snapshot taken while the plugin applied.
47
+ content:
48
+ application/json:
49
+ schema:
50
+ type: object
51
+ required: [pluginVersion, injected, observed, summary]
52
+ properties:
53
+ pluginVersion: { type: string }
54
+ injected:
55
+ type: array
56
+ items:
57
+ type: object
58
+ required: [name, available, resolvable]
59
+ properties:
60
+ name: { type: string }
61
+ available: { type: boolean }
62
+ resolvable: { type: boolean }
63
+ error: { type: string }
64
+ observed:
65
+ type: array
66
+ items:
67
+ type: object
68
+ required: [name, available, resolvable]
69
+ properties:
70
+ name: { type: string }
71
+ available: { type: boolean }
72
+ resolvable: { type: boolean }
73
+ error: { type: string }
74
+ summary:
75
+ type: object
76
+ required: [injectedTotal, injectedAvailable, injectedMissing, observedAvailable]
77
+ properties:
78
+ injectedTotal: { type: integer }
79
+ injectedAvailable: { type: integer }
80
+ injectedMissing: { type: array, items: { type: string } }
81
+ observedAvailable: { type: array, items: { type: string } }
82
+ '404':
83
+ description: The probe is not compiled into this build.
28
84
  /pairings/exchange:
29
85
  post:
30
86
  security: []
@@ -307,6 +363,28 @@ paths:
307
363
  items:
308
364
  type: array
309
365
  items: { $ref: '#/components/schemas/AgentPreset' }
366
+ /chat/permission-presets:
367
+ get:
368
+ operationId: listPermissionPresets
369
+ description: >-
370
+ Lists the permission presets a session can switch between (the catalog behind
371
+ DSH WebUI's access-mode selector). Session projections only carry the *current*
372
+ value, so clients cannot enumerate the choices without this endpoint.
373
+ responses:
374
+ '200':
375
+ description: Permission preset choices and the deployment default.
376
+ content:
377
+ application/json:
378
+ schema:
379
+ type: object
380
+ required: [options, defaultPreset]
381
+ properties:
382
+ options:
383
+ type: array
384
+ items: { $ref: '#/components/schemas/PermissionOption' }
385
+ defaultPreset:
386
+ type: string
387
+ '403': { $ref: '#/components/responses/Error' }
310
388
  /settings/providers:
311
389
  get:
312
390
  operationId: listProviderSettings
@@ -561,6 +639,94 @@ paths:
561
639
  responses:
562
640
  '200': { description: Session archived. }
563
641
  '404': { $ref: '#/components/responses/Error' }
642
+ /chat/sessions/{sessionId}/jobs:
643
+ parameters:
644
+ - name: sessionId
645
+ in: path
646
+ required: true
647
+ schema: { type: string }
648
+ get:
649
+ operationId: listSessionJobs
650
+ summary: Background jobs a session can see
651
+ description: |
652
+ The roster this session can observe: its own jobs plus every unowned job.
653
+ The kernel's owner fence is the only access rule, so `sessionId` is
654
+ required and must not be guessed by the client.
655
+ responses:
656
+ '200':
657
+ description: The complete roster (whole-set, not a page).
658
+ content:
659
+ application/json:
660
+ schema:
661
+ type: object
662
+ required: [jobs]
663
+ properties:
664
+ jobs:
665
+ type: array
666
+ items: { $ref: '#/components/schemas/JobView' }
667
+ '404': { $ref: '#/components/responses/Error' }
668
+ /chat/sessions/{sessionId}/jobs/{jobId}/output:
669
+ parameters:
670
+ - name: sessionId
671
+ in: path
672
+ required: true
673
+ schema: { type: string }
674
+ - name: jobId
675
+ in: path
676
+ required: true
677
+ schema: { type: string }
678
+ - name: from
679
+ in: query
680
+ required: false
681
+ description: Absolute byte offset to resume from (a prior response's `next`). Omitted starts at the oldest retained byte.
682
+ schema: { type: integer }
683
+ get:
684
+ operationId: readJobOutput
685
+ summary: Retained output of one background job
686
+ responses:
687
+ '200':
688
+ description: Flattened output plus the offset to resume from.
689
+ content:
690
+ application/json:
691
+ schema:
692
+ type: object
693
+ required: [jobId, text, next, lossy]
694
+ properties:
695
+ jobId: { type: string }
696
+ text: { type: string }
697
+ next: { type: integer }
698
+ lossy: { type: boolean }
699
+ status: { $ref: '#/components/schemas/JobStatus' }
700
+ '404': { $ref: '#/components/responses/Error' }
701
+ /chat/sessions/{sessionId}/jobs/{jobId}/kill:
702
+ parameters:
703
+ - name: sessionId
704
+ in: path
705
+ required: true
706
+ schema: { type: string }
707
+ - name: jobId
708
+ in: path
709
+ required: true
710
+ schema: { type: string }
711
+ post:
712
+ operationId: killSessionJob
713
+ summary: Stop one background job on a human's behalf
714
+ description: |
715
+ Records `cancelled by the user` as the reason. The owning agent still
716
+ receives its completion notice, because a human kill is not one the
717
+ model requested.
718
+ responses:
719
+ '200':
720
+ description: The registry's admission.
721
+ content:
722
+ application/json:
723
+ schema:
724
+ type: object
725
+ required: [jobId, outcome]
726
+ properties:
727
+ jobId: { type: string }
728
+ outcome: { type: string, enum: [requested, already-finished] }
729
+ '404': { $ref: '#/components/responses/Error' }
564
730
  /chat/sessions/{sessionId}/messages:
565
731
  parameters:
566
732
  - name: sessionId
@@ -1307,3 +1473,21 @@ components:
1307
1473
  message: { type: string }
1308
1474
  requestId: { type: string, format: uuid }
1309
1475
  details: { type: object, additionalProperties: true }
1476
+ JobStatus:
1477
+ type: string
1478
+ description: Lifecycle state. `running` and `stopping` are live; the rest are terminal.
1479
+ enum: [running, stopping, completed, killed, failed]
1480
+ JobView:
1481
+ type: object
1482
+ description: Read-only projection of one background job.
1483
+ required: [id, kind, label, status, startedAt]
1484
+ properties:
1485
+ id: { type: string, description: 'Registry-issued id, e.g. `bash-<short-uuid>`.' }
1486
+ kind: { type: string, description: 'Producer kind; `bash` is the only host-registered one today.' }
1487
+ label: { type: string, description: Producer-supplied one-line label. }
1488
+ owner: { type: string, description: Owning session; absent for an unowned job. }
1489
+ status: { $ref: '#/components/schemas/JobStatus' }
1490
+ progress: { type: string, description: 'Live progress line (`3/10`); cleared at settlement.' }
1491
+ detail: { type: string, description: 'Terminal reason (`exit code: 3`); a recorded kill reason is merged in.' }
1492
+ startedAt: { type: integer, format: epoch-ms }
1493
+ finishedAt: { type: integer, format: epoch-ms, description: Absent while live. }
@@ -4,7 +4,7 @@ plugins {
4
4
  }
5
5
 
6
6
  group = "ai.deepseek.dsh"
7
- version = "2.4.1"
7
+ version = "2.4.2"
8
8
 
9
9
  repositories { mavenCentral() }
10
10
 
@@ -116,6 +116,30 @@ data class AgentPreset(
116
116
  @Serializable
117
117
  data class ModelSelection(val provider: String, val model: String, val reasoningEffort: String? = null)
118
118
 
119
+ /**
120
+ * 一个可选的权限档位。
121
+ *
122
+ * [name] 是**非空**的:服务端 `normalizePermissionCatalog` 保证名字缺失时用 `value` 顶上,
123
+ * 空名字会在严格反序列化器上失败,或在 UI 上留下一行没有文字的选项。
124
+ */
125
+ @Serializable
126
+ data class PermissionOption(
127
+ val value: String,
128
+ val name: String,
129
+ val description: String? = null,
130
+ )
131
+
132
+ /**
133
+ * 权限档位清单(`GET /chat/permission-presets`)。
134
+ *
135
+ * [defaultPreset] 是部署默认,**不等于**某个会话的当前档位 —— 当前值只认会话投影。
136
+ */
137
+ @Serializable
138
+ data class PermissionCatalog(
139
+ val options: List<PermissionOption> = emptyList(),
140
+ val defaultPreset: String = "",
141
+ )
142
+
119
143
  @Serializable
120
144
  data class ModelReasoningEffort(val id: String, val name: String, val description: String? = null)
121
145
 
@@ -343,6 +367,19 @@ class DshWorkspaceClient(
343
367
  AgentPresetList.serializer(),
344
368
  ).items
345
369
 
370
+ /**
371
+ * 权限档位候选清单。
372
+ *
373
+ * 必须单开一条:会话投影里那条 `permissions` 视图**只给 currentValue**,
374
+ * 候选项在内核的独立远程服务上,所以客户端列不出档位菜单。
375
+ */
376
+ fun listPermissionPresets(): PermissionCatalog = executeJson(
377
+ "chat/permission-presets",
378
+ "GET",
379
+ null,
380
+ PermissionCatalog.serializer(),
381
+ )
382
+
346
383
  fun selectSessionAgentPreset(sessionId: String, agentPreset: String): String {
347
384
  val body = json.encodeToString(AgentPresetSelection.serializer(), AgentPresetSelection(agentPreset))
348
385
  return executeJson(
@@ -477,6 +514,51 @@ class DshWorkspaceClient(
477
514
  executeUnit("chat/sessions/$sessionId/archive", "POST", null)
478
515
  }
479
516
 
517
+ /**
518
+ * 这个会话可见的后台任务清单(它自己的 + 所有无主任务)。
519
+ *
520
+ * 服务端只读内核流的第一帧 —— 内核保证首帧就是全量 truth,
521
+ * 所以这里返回的是完整清单,不是一个分页。
522
+ */
523
+ fun listSessionJobs(sessionId: String): List<JobView> = executeJson(
524
+ "chat/sessions/$sessionId/jobs",
525
+ "GET",
526
+ null,
527
+ JobListEnvelope.serializer(),
528
+ ).jobs
529
+
530
+ /**
531
+ * 读一个后台任务已保留的输出。
532
+ *
533
+ * [from] 是**绝对字节偏移**,传上一次返回的 [JobOutput.next] 即可增量续读;
534
+ * 传 null 从最旧的保留字节开始。
535
+ *
536
+ * [JobOutput.lossy] 为 true 时,请求偏移与返回内容之间有字节被留存策略吃掉 ——
537
+ * 这段输出不是完整的,调用方应当提示用户。
538
+ */
539
+ fun readJobOutput(sessionId: String, jobId: String, from: Long? = null): JobOutput {
540
+ val query = if (from == null) "" else "?from=$from"
541
+ return executeJson(
542
+ "chat/sessions/$sessionId/jobs/$jobId/output$query",
543
+ "GET",
544
+ null,
545
+ JobOutput.serializer(),
546
+ )
547
+ }
548
+
549
+ /**
550
+ * 终止一个后台任务。
551
+ *
552
+ * 返回 `requested`(已受理)或 `already-finished`(已是终态、无需再杀)——
553
+ * 调用方应据此显示「已终止」或「它已经结束了」,后者不是前者的成功说法。
554
+ */
555
+ fun killSessionJob(sessionId: String, jobId: String): JobKillResult = executeJson(
556
+ "chat/sessions/$sessionId/jobs/$jobId/kill",
557
+ "POST",
558
+ null,
559
+ JobKillResult.serializer(),
560
+ )
561
+
480
562
  fun listPendingApprovals(sessionId: String): List<PendingApproval> = executeJson(
481
563
  "chat/sessions/$sessionId/approvals",
482
564
  "GET",
@@ -689,3 +771,59 @@ class EventSubscription internal constructor(
689
771
  @Serializable private data class SendMessageRequest(val text: String, val mode: String, val clientRequestId: String)
690
772
  @Serializable private data class ErrorEnvelope(val error: ApiErrorBody)
691
773
  @Serializable private data class ApiErrorBody(val code: String, val message: String, val requestId: String)
774
+
775
+ /** 后台任务的生命周期状态。`running` / `stopping` 存活中,其余为终态。 */
776
+ @Serializable
777
+ enum class JobStatus {
778
+ @SerialName("running") RUNNING,
779
+ @SerialName("stopping") STOPPING,
780
+ @SerialName("completed") COMPLETED,
781
+ @SerialName("killed") KILLED,
782
+ @SerialName("failed") FAILED,
783
+ }
784
+
785
+ /**
786
+ * 一个后台任务的只读投影。
787
+ *
788
+ * 服务端逐个字段校验过类型,所以这里除 [status] / [startedAt] / [id] / [kind] / [label]
789
+ * 之外的字段都可空 —— 内核没给就是没有,客户端不该看到一个 `owner: undefined`。
790
+ */
791
+ @Serializable
792
+ data class JobView(
793
+ val id: String,
794
+ val kind: String,
795
+ val label: String,
796
+ val owner: String? = null,
797
+ val status: JobStatus,
798
+ /** 生产者的实时进度行(`3/10`);终态时被清空。 */
799
+ val progress: String? = null,
800
+ /** 终态原因(`exit code: 3`);记录过的人工取消原因会并进来。 */
801
+ val detail: String? = null,
802
+ /** 注册时刻(epoch ms)。 */
803
+ val startedAt: Long,
804
+ /** 落定时刻(epoch ms);存活中为 null。 */
805
+ val finishedAt: Long? = null,
806
+ )
807
+
808
+ @Serializable private data class JobListEnvelope(val jobs: List<JobView>)
809
+
810
+ /** 一个后台任务已保留的输出。 */
811
+ @Serializable
812
+ data class JobOutput(
813
+ val jobId: String,
814
+ val text: String,
815
+ /** 绝对字节偏移;下次增量续读传它。 */
816
+ val next: Long,
817
+ /** true = 请求偏移与返回内容之间有字节被留存策略吃掉,这段输出不完整。 */
818
+ val lossy: Boolean,
819
+ /** 读到终态帧时带上;流被提前切断时为 null。 */
820
+ val status: JobStatus? = null,
821
+ )
822
+
823
+ /** 终止结果。 */
824
+ @Serializable
825
+ data class JobKillResult(
826
+ val jobId: String,
827
+ /** `requested` 已受理;`already-finished` 已是终态、无需再杀。 */
828
+ val outcome: String,
829
+ )