dsh-remote-bridge 2.4.3 → 2.5.1
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 +31 -10
- package/README.md +28 -10
- package/cordis.patch.yml +14 -10
- package/docs/api/asyncapi.yaml +1 -1
- package/docs/api/openapi.yaml +22 -3
- package/kotlin-sdk/src/main/kotlin/ai/deepseek/dsh/workspace/DshWorkspaceClient.kt +33 -0
- package/lib/client.cjs +12 -0
- package/lib/index.d.ts +107 -8
- package/lib/index.js +837 -450
- package/package.json +1 -1
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.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.5.1-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">
|
|
@@ -36,9 +36,17 @@ The interface is bilingual and defaults to Chinese. Prebuilt packages run on Win
|
|
|
36
36
|
> `engines.dsh` is recognized by the 0.2.1 kernel (`prepareProfileEntries` in
|
|
37
37
|
> `dsh-app-boot/lib/index.js`) — an unmet range only sets that entry to `disabled` and prints to
|
|
38
38
|
> stderr; **the host process does not exit**. So no code change is needed to run on `0.2.1-rc.2`.
|
|
39
|
-
>
|
|
40
|
-
>
|
|
41
|
-
>
|
|
39
|
+
>
|
|
40
|
+
> **Functional differences between the two versions (important)**: the `inject` list holds **only
|
|
41
|
+
> services that exist in both versions** — cordis treats `inject` as a **hard dependency**, and a
|
|
42
|
+
> single missing service leaves the whole plugin inactive (proven by CI on the 0.2.0-rc.2 baseline:
|
|
43
|
+
> writing a 0.2.1-only service into `inject` once made the plugin fail to activate on 0.2.0).
|
|
44
|
+
> The services that only 0.2.1 provides (**background jobs** `jobController`, the **permission
|
|
45
|
+
> catalog** `permissionPresets`, file upload `fileUploads`, attachment storage `attachments`) are
|
|
46
|
+
> therefore read at runtime through `ctx.reflect.get(name, false)`: **all of them work on
|
|
47
|
+
> 0.2.1-rc.2 and degrade automatically on 0.2.0-rc.2** (the jobs endpoints answer 404 there, the
|
|
48
|
+
> permission catalog falls back to its built-in one) — the plugin itself runs on both kernels.
|
|
49
|
+
> `GET /api/v1/debug/host-services` reports which optional capabilities the current machine has.
|
|
42
50
|
|
|
43
51
|
## What it looks like
|
|
44
52
|
|
|
@@ -94,10 +102,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
94
102
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
95
103
|
|
|
96
104
|
# 2. tarball from the GitHub release
|
|
97
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
105
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.5.1/dsh-remote-bridge-v2.5.1.tgz
|
|
98
106
|
|
|
99
107
|
# 3. tarball from the Gitee release (faster from mainland China)
|
|
100
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
108
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.5.1/dsh-remote-bridge-v2.5.1.tgz
|
|
101
109
|
|
|
102
110
|
npx @deepseek-ai/dsh web
|
|
103
111
|
```
|
|
@@ -114,7 +122,7 @@ Restart DSH after installation, then open settings — The section is titled **
|
|
|
114
122
|
To install a downloaded release archive:
|
|
115
123
|
|
|
116
124
|
```sh
|
|
117
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
125
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.5.1.tgz
|
|
118
126
|
```
|
|
119
127
|
|
|
120
128
|
## First connection
|
|
@@ -346,13 +354,26 @@ pnpm pack
|
|
|
346
354
|
|
|
347
355
|
`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).
|
|
348
356
|
|
|
349
|
-
`npm pack` produces roughly a **0.55 MB** archive (v2.
|
|
357
|
+
`npm pack` produces roughly a **0.55 MB** archive (v2.5.0 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.
|
|
358
|
+
|
|
359
|
+
v2.5.0 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 upstream `dsh-v0.2.1-rc.2` (commit `687f282e8625c6b8d11ff58211a90524be2e4317`); see `engines.dsh` above for the supported range. CI covers Windows, Ubuntu, and macOS.
|
|
350
360
|
|
|
351
|
-
v2.
|
|
361
|
+
**v2.5.1 verification status**: 47 test files / 398 cases green (including the tunnel-topology
|
|
362
|
+
management regression, the desktop-panel pass-through assertion, and the `it.fails` conversions),
|
|
363
|
+
`typecheck` and `docs:check` pass, and the audit report's §3.1 attack chain was replayed against a real
|
|
364
|
+
`RemoteApiServer` (now 403 for every step). **Not yet verified on real hardware**, and not published to
|
|
365
|
+
npm or either release site — i.e. "not published ≠ not verified ≠ users have it".
|
|
352
366
|
|
|
353
367
|
## Project
|
|
354
368
|
|
|
355
|
-
- Version: `v2.
|
|
369
|
+
- Version: `v2.5.1`
|
|
370
|
+
- **v2.5.1 is a security release**: it closes a remote privilege escalation where `/manage/**`
|
|
371
|
+
and the loopback admin header were granted to any request whose TCP source looked like loopback —
|
|
372
|
+
which is exactly what a Cloudflare tunnel (or any reverse proxy) makes every public request look like.
|
|
373
|
+
It also fixes `.heif` handoff images, which were always rejected with 415 by the read-only route.
|
|
374
|
+
After upgrading, **`/manage` returns 403 whenever the tunnel is running** (the only exceptions are the
|
|
375
|
+
read-only tunnel-status endpoints, which hide the tunnel URL from non-local callers); the desktop panel
|
|
376
|
+
is unaffected. See `docs/project/SECURITY.md` §「管理面的信任边界」.
|
|
356
377
|
- Author: upstream [Hakunm](https://github.com/Hakunm); this fork maintained by [westanke](https://github.com/westanke)
|
|
357
378
|
- Repository: [GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
|
|
358
379
|
- 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.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.5.1-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">
|
|
@@ -26,13 +26,21 @@
|
|
|
26
26
|
> 0.2.x 上无法启动(表现为「添加根目录返回 405」)。兼容层的完整说明——基于什么、为什么、改了什么、
|
|
27
27
|
> 测了什么——见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md)。
|
|
28
28
|
>
|
|
29
|
-
> **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据
|
|
29
|
+
> **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据 `docs/project/DSH-0.2.1-ADAPTATION.md`):
|
|
30
30
|
> 本插件**刻意不 import 任何 `@deepseek-ai/*` 包**,全部宿主服务经结构类型 + Host 注入消费
|
|
31
31
|
> (见 `src/host/dsh-0.2-bridge.ts` 头注释)。逐一到 `0.2.1-rc.2` 实装包核对:`cordis.patch.yml`
|
|
32
32
|
> 的 10 项 `inject` 服务名**全部仍存在且签名未变**,`engines.dsh` 在 0.2.1 被内核识别
|
|
33
33
|
> (`dsh-app-boot/lib/index.js` 的 `prepareProfileEntries`),不满足时只把该 entry 置
|
|
34
34
|
> `disabled` 并打 stderr,**宿主进程不退出**。故本插件无需改码即可运行于 `0.2.1-rc.2`。
|
|
35
|
-
>
|
|
35
|
+
>
|
|
36
|
+
> **两版的功能差异(重要)**:`inject` 里只放**两个版本都存在**的服务 —— cordis 的 inject 是
|
|
37
|
+
> **硬依赖**,任何一个服务缺失就整个插件不激活(CI 在 0.2.0-rc.2 基线上实锤过:
|
|
38
|
+
> 一度因为把 0.2.1 才有的服务写进 inject 而让插件在 0.2.0 上整体不激活)。
|
|
39
|
+
> 因此 0.2.1 才提供的服务(**后台任务** `jobController`、**权限档位目录**
|
|
40
|
+
> `permissionPresets`、文件上传 `fileUploads`、附件存储 `attachments`)改为运行时经
|
|
41
|
+
> `ctx.reflect.get(name, false)` 读取:**0.2.1-rc.2 上这些功能全部可用,0.2.0-rc.2 上
|
|
42
|
+
> 自动降级**(如后台任务端点返回 404、权限档位走内置目录),插件本体两种内核都能跑。
|
|
43
|
+
> 用 `GET /api/v1/debug/host-services` 可以查这台机器上哪些可选能力可用。
|
|
36
44
|
|
|
37
45
|
## 实际效果
|
|
38
46
|
|
|
@@ -85,10 +93,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
85
93
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
86
94
|
|
|
87
95
|
# ② GitHub Release 的 tarball
|
|
88
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
96
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.5.1/dsh-remote-bridge-v2.5.1.tgz
|
|
89
97
|
|
|
90
98
|
# ③ Gitee Release 的 tarball(国内访问更快)
|
|
91
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
99
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.5.1/dsh-remote-bridge-v2.5.1.tgz
|
|
92
100
|
|
|
93
101
|
npx @deepseek-ai/dsh web
|
|
94
102
|
```
|
|
@@ -104,7 +112,7 @@ npx @deepseek-ai/dsh web
|
|
|
104
112
|
从 GitHub Release 下载离线安装包时,可直接安装 tarball:
|
|
105
113
|
|
|
106
114
|
```sh
|
|
107
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
115
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.5.1.tgz
|
|
108
116
|
```
|
|
109
117
|
|
|
110
118
|
## 第一次使用
|
|
@@ -332,15 +340,25 @@ pnpm pack
|
|
|
332
340
|
|
|
333
341
|
`pnpm check` 覆盖类型检查、文档一致性与测试;当前 **30 个测试文件 / 226 个用例**,外加 **4 个浏览器层用例**(Playwright 实测量出的计算样式),全部通过。
|
|
334
342
|
|
|
335
|
-
`npm pack` 的产物约 **0.55 MB**(v2.
|
|
343
|
+
`npm pack` 的产物约 **0.55 MB**(v2.5.0 实测 572,951 字节,SHA-256 `38f25c10…299c93`):2.2.0 删除独立页及其编辑器依赖、并关闭 source map 之后从 1.72 MB 降下来,之后随投影透传与权限 BFF 小幅增长。
|
|
336
344
|
|
|
337
|
-
v2.
|
|
338
|
-
|
|
345
|
+
v2.5.0 在 DSH **0.2.1-rc.2** 上完成真机验证:14/14 宿主服务全部可注入、三个 jobs 端点实测通过、
|
|
346
|
+
权限档位目录返回内核真实三档。开发基线为上游 `dsh-v0.2.1-rc.2`(commit `687f282e8625c6b8d11ff58211a90524be2e4317`),
|
|
339
347
|
兼容范围见上文 `engines.dsh`。CI 覆盖 Windows、Ubuntu 和 macOS。
|
|
340
348
|
|
|
349
|
+
**v2.5.1 的验证状态**:47 个测试文件 / 398 个用例全绿(含隧道拓扑下的管理面回归、
|
|
350
|
+
桌面端放行断言、`it.fails` 转正)、`typecheck` 与 `docs:check` 通过,并用真实
|
|
351
|
+
`RemoteApiServer` 重打了审计报告 §3.1 的攻击链(逐条 403)。**尚未做真机验收**,
|
|
352
|
+
也未发布到 npm / 两站 Release —— 按「未发布 ≠ 已验证 ≠ 用户吃到了」记三态。
|
|
353
|
+
|
|
341
354
|
## 项目信息
|
|
342
355
|
|
|
343
|
-
- 当前版本:`v2.
|
|
356
|
+
- 当前版本:`v2.5.1`
|
|
357
|
+
- **v2.5.1 是安全修复版**:堵住「开隧道/反代后管理面免凭据对公网开放」的远程提权(`/manage/**`
|
|
358
|
+
与回环 admin 头此前只按「TCP 来源是否回环」判定,而隧道转发进来的请求来源恒为 `127.0.0.1`),
|
|
359
|
+
并修复 `.heif` 交接图必被只读路由 415 的问题。升级后**开隧道时 `/manage` 一律 403**
|
|
360
|
+
(唯一例外是只读的隧道状态端点,且对非本机调用方隐去隧道地址);桌面端面板不受影响。
|
|
361
|
+
见 `docs/project/SECURITY.md` §管理面的信任边界。
|
|
344
362
|
- 作者:上游 [Hakunm](https://github.com/Hakunm),本 fork 维护 [westanke](https://github.com/westanke)
|
|
345
363
|
- 仓库:[GitHub](https://github.com/westanke/dsh-remote-bridge) · [Gitee](https://gitee.com/westanke/dsh-remote-bridge)
|
|
346
364
|
- npm:[`dsh-remote-bridge`](https://www.npmjs.com/package/dsh-remote-bridge)([国内镜像](https://registry.npmmirror.com/dsh-remote-bridge))
|
package/cordis.patch.yml
CHANGED
|
@@ -16,6 +16,20 @@
|
|
|
16
16
|
# 这一条曾长期掩盖真相:`permissionPresets` 一直以为「web profile 注不进、
|
|
17
17
|
# 只能走内置兜底目录」,实际是这里没声明它。`jobController` 加进来之后
|
|
18
18
|
# 同样注不进,才顺着 `_refresh()` 的 INACTIVE 逻辑反查到这条覆盖规则。
|
|
19
|
+
# ⚠️ 这份列表**只能放「两个支持版本上都存在的服务」**。
|
|
20
|
+
# cordis 的 inject 是**硬依赖**:任何一个服务缺失 → 整个 entry 永久 INACTIVE。
|
|
21
|
+
# 2026-10-20 CI 实锤:把 jobController / fileUploads / attachments /
|
|
22
|
+
# permissionPresets 加进来后,插件在 **0.2.0-rc.2**(smoke-profile-install
|
|
23
|
+
# 检出的上游基线)上整体不激活,报
|
|
24
|
+
# "waiting for services: ..., jobController, fileUploads" ——
|
|
25
|
+
# 那一版内核的 web profile 没挂这些服务。
|
|
26
|
+
#
|
|
27
|
+
# 可选服务(permissionPresets / jobController / fileUploads / attachments /
|
|
28
|
+
# workingDirectory)**不在这里声明**,由插件在运行时经
|
|
29
|
+
# `ctx.reflect.get(name, false)` 读取(cordis 给「不带 inject 要求」读服务
|
|
30
|
+
# 准备的正路,见 dsh-0.2-bridge.ts 的 optionalService 注释):
|
|
31
|
+
# 0.2.1-rc.2 上读到就走全功能,0.2.0-rc.2 上读不到就按能力位降级。
|
|
32
|
+
# 探针(GET /debug/host-services)继续报告这 5 个名字的真实可用性。
|
|
19
33
|
inject: [
|
|
20
34
|
sessionController,
|
|
21
35
|
workspaceController,
|
|
@@ -23,20 +37,10 @@
|
|
|
23
37
|
settingsController,
|
|
24
38
|
credentialsController,
|
|
25
39
|
agentPresets,
|
|
26
|
-
# 权限档位目录(`ctx.permissionPresets`):内核把它做成独立的 Typert
|
|
27
|
-
# 远程服务,会话投影那条 wire 视图只给 currentValue、不带候选项。
|
|
28
|
-
permissionPresets,
|
|
29
40
|
llm,
|
|
30
41
|
webServer,
|
|
31
42
|
agents,
|
|
32
43
|
commands,
|
|
33
|
-
# 后台任务(`ctx.jobController`):远程场景下任务跑飞了要能停掉。
|
|
34
|
-
jobController,
|
|
35
|
-
# 文件上传服务(`ctx.fileUploads`):用它而不是内核那条只绑 loopback 的
|
|
36
|
-
# /api/session/uploadFileBinary HTTP 路由。
|
|
37
|
-
fileUploads,
|
|
38
|
-
# 附件存储(`ctx.attachments`):会话历史里的图片以 attachmentId 引用存在。
|
|
39
|
-
attachments,
|
|
40
44
|
]
|
|
41
45
|
config:
|
|
42
46
|
# 状态目录**不改名**:已装的机器上有既有数据库与回收站,换目录等于让用户
|
package/docs/api/asyncapi.yaml
CHANGED
package/docs/api/openapi.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: DeepSeek Harness Mobile API
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.1
|
|
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,7 @@ paths:
|
|
|
24
24
|
properties:
|
|
25
25
|
ok: { type: boolean, const: true }
|
|
26
26
|
version: { type: string, const: v1 }
|
|
27
|
-
pluginVersion: { type: string, const: 2.
|
|
27
|
+
pluginVersion: { type: string, const: 2.5.1 }
|
|
28
28
|
/debug/host-services:
|
|
29
29
|
get:
|
|
30
30
|
operationId: hostServices
|
|
@@ -1408,8 +1408,26 @@ components:
|
|
|
1408
1408
|
configurable: { type: boolean }
|
|
1409
1409
|
configured: { type: boolean }
|
|
1410
1410
|
removable: { type: boolean }
|
|
1411
|
+
defaultBaseURL: { type: string, description: '目录供应商的适配器默认 API 地址(未配置时使用)。' }
|
|
1412
|
+
baseURLEnv:
|
|
1413
|
+
type: array
|
|
1414
|
+
items: { type: string }
|
|
1415
|
+
description: '可能覆盖默认地址的环境变量,按优先级排序。'
|
|
1416
|
+
nativeAuth: { $ref: '#/components/schemas/NativeAuthHint' }
|
|
1417
|
+
settingsNs:
|
|
1418
|
+
type: string
|
|
1419
|
+
description: 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。
|
|
1411
1420
|
credential: { $ref: '#/components/schemas/CredentialState' }
|
|
1412
1421
|
config: { $ref: '#/components/schemas/ProviderConfig' }
|
|
1422
|
+
NativeAuthHint:
|
|
1423
|
+
type: object
|
|
1424
|
+
required: [env, oauth]
|
|
1425
|
+
properties:
|
|
1426
|
+
env:
|
|
1427
|
+
type: array
|
|
1428
|
+
items: { type: string }
|
|
1429
|
+
description: '适配器原生认证依赖的环境变量。'
|
|
1430
|
+
oauth: { type: boolean, description: '是否支持 OAuth 登录。' }
|
|
1413
1431
|
ProviderSettings:
|
|
1414
1432
|
type: object
|
|
1415
1433
|
required: [writable, revisionByNamespace, customProvider, providers]
|
|
@@ -1433,7 +1451,8 @@ components:
|
|
|
1433
1451
|
thinking: { type: [string, 'null'] }
|
|
1434
1452
|
reasoningEffort: { type: [string, 'null'] }
|
|
1435
1453
|
models:
|
|
1436
|
-
type: array
|
|
1454
|
+
type: [array, 'null']
|
|
1455
|
+
description: '数组=整体替换模型目录(空数组=自定义空目录);`null`=恢复适配器默认目录(unset)。'
|
|
1437
1456
|
items: { $ref: '#/components/schemas/ProviderModel' }
|
|
1438
1457
|
expectedRevision: { type: integer, minimum: 0 }
|
|
1439
1458
|
CustomProviderCapability:
|
|
@@ -206,6 +206,12 @@ data class CredentialState(
|
|
|
206
206
|
val writable: Boolean,
|
|
207
207
|
)
|
|
208
208
|
|
|
209
|
+
@Serializable
|
|
210
|
+
data class NativeAuthHint(
|
|
211
|
+
val env: List<String> = emptyList(),
|
|
212
|
+
val oauth: Boolean = false,
|
|
213
|
+
)
|
|
214
|
+
|
|
209
215
|
@Serializable
|
|
210
216
|
data class ProviderView(
|
|
211
217
|
val id: String,
|
|
@@ -215,6 +221,11 @@ data class ProviderView(
|
|
|
215
221
|
val configurable: Boolean,
|
|
216
222
|
val configured: Boolean,
|
|
217
223
|
val removable: Boolean,
|
|
224
|
+
val defaultBaseURL: String? = null,
|
|
225
|
+
val baseURLEnv: List<String>? = null,
|
|
226
|
+
val nativeAuth: NativeAuthHint? = null,
|
|
227
|
+
/** 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。 */
|
|
228
|
+
val settingsNs: String? = null,
|
|
218
229
|
val credential: CredentialState,
|
|
219
230
|
val config: ProviderConfig,
|
|
220
231
|
)
|
|
@@ -242,6 +253,7 @@ data class ProviderPatch(
|
|
|
242
253
|
val apiKey: String? = null,
|
|
243
254
|
val thinking: String? = null,
|
|
244
255
|
val reasoningEffort: String? = null,
|
|
256
|
+
/** 数组=整体替换(空数组=自定义空目录);恢复默认目录用 [DshWorkspaceClient.resetProviderModels](wire `models: null`)。 */
|
|
245
257
|
val models: List<ProviderModel>? = null,
|
|
246
258
|
val expectedRevision: Int? = null,
|
|
247
259
|
)
|
|
@@ -426,6 +438,27 @@ class DshWorkspaceClient(
|
|
|
426
438
|
return executeJson("settings/providers/$providerId", "PATCH", body, ProviderSettings.serializer())
|
|
427
439
|
}
|
|
428
440
|
|
|
441
|
+
/**
|
|
442
|
+
* 恢复供应商的适配器默认模型目录(wire `models: null`)。data class 无法把
|
|
443
|
+
* null 默认值与「字段缺席」区分开,所以这里手工拼 body。
|
|
444
|
+
*/
|
|
445
|
+
fun resetProviderModels(providerId: String, expectedRevision: Int? = null): ProviderSettings {
|
|
446
|
+
val body = buildString {
|
|
447
|
+
append("{\"models\":null")
|
|
448
|
+
if (expectedRevision != null) append(",\"expectedRevision\":").append(expectedRevision)
|
|
449
|
+
append('}')
|
|
450
|
+
}
|
|
451
|
+
return executeJson("settings/providers/$providerId", "PATCH", body, ProviderSettings.serializer())
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fun deleteProvider(providerId: String, expectedRevision: Int? = null) {
|
|
455
|
+
val relative = buildString {
|
|
456
|
+
append("settings/providers/").append(providerId)
|
|
457
|
+
if (expectedRevision != null) append("?expectedRevision=").append(expectedRevision)
|
|
458
|
+
}
|
|
459
|
+
executeUnit(relative, "DELETE", null)
|
|
460
|
+
}
|
|
461
|
+
|
|
429
462
|
fun createCustomProvider(provider: CustomProviderCreate): ProviderSettings {
|
|
430
463
|
val body = json.encodeToString(CustomProviderCreate.serializer(), provider)
|
|
431
464
|
return executeJson("settings/providers", "POST", body, ProviderSettings.serializer())
|
package/lib/client.cjs
CHANGED
|
@@ -30231,6 +30231,10 @@ window.__ModuleLoader__.load({
|
|
|
30231
30231
|
errorNotFound: "请求的文件或资源不存在。",
|
|
30232
30232
|
errorConflict: "目标已存在或当前状态发生冲突。",
|
|
30233
30233
|
errorRateLimited: "请求过于频繁,请稍后重试。",
|
|
30234
|
+
errorTunnelModeAdminDisabled: "云隧道正在运行,管理面已停用。请先在本机面板里停止隧道,或直接在本机 WebUI 操作。",
|
|
30235
|
+
errorAdminProxyForwarded: "管理面不接受经代理转发的请求。请在本机用 127.0.0.1 或 localhost 直接打开。",
|
|
30236
|
+
errorAdminHostNotLoopback: "管理面只在本机回环地址下可用。请用 127.0.0.1 或 localhost 打开本页。",
|
|
30237
|
+
errorAdminLoopbackRequired: "此操作只能在本机执行。",
|
|
30234
30238
|
author: "作者 Github@Hakunm",
|
|
30235
30239
|
version: "当前版本"
|
|
30236
30240
|
},
|
|
@@ -30403,6 +30407,10 @@ window.__ModuleLoader__.load({
|
|
|
30403
30407
|
errorNotFound: "The requested file or resource was not found.",
|
|
30404
30408
|
errorConflict: "The target already exists or its state has changed.",
|
|
30405
30409
|
errorRateLimited: "Too many requests. Try again shortly.",
|
|
30410
|
+
errorTunnelModeAdminDisabled: "The Cloudflare tunnel is running, so management endpoints are disabled. Stop the tunnel in the local panel, or use the local WebUI.",
|
|
30411
|
+
errorAdminProxyForwarded: "Management endpoints do not accept proxied requests. Open them locally via 127.0.0.1 or localhost.",
|
|
30412
|
+
errorAdminHostNotLoopback: "Management endpoints are available only under a loopback address. Open this page via 127.0.0.1 or localhost.",
|
|
30413
|
+
errorAdminLoopbackRequired: "This operation is available only from the local machine.",
|
|
30406
30414
|
author: "Author Github@Hakunm",
|
|
30407
30415
|
version: "Current version"
|
|
30408
30416
|
}
|
|
@@ -31614,6 +31622,10 @@ window.__ModuleLoader__.load({
|
|
|
31614
31622
|
if (error.code === "LISTENER_PORT_INVALID") return "listenerPortInvalid";
|
|
31615
31623
|
if (error.code === "REMOTE_ENABLED") return "disableToEditListener";
|
|
31616
31624
|
if (error.code === "NO_ENDPOINT") return "configTextNoEndpoints";
|
|
31625
|
+
if (error.code === "TUNNEL_MODE_ADMIN_DISABLED") return "errorTunnelModeAdminDisabled";
|
|
31626
|
+
if (error.code === "ADMIN_PROXY_FORWARDED") return "errorAdminProxyForwarded";
|
|
31627
|
+
if (error.code === "ADMIN_HOST_NOT_LOOPBACK") return "errorAdminHostNotLoopback";
|
|
31628
|
+
if (error.code === "ADMIN_LOOPBACK_REQUIRED") return "errorAdminLoopbackRequired";
|
|
31617
31629
|
if (error.code.includes("PATH") || error.code.includes("SYMLINK") || error.code.includes("REPARSE")) return "errorPathInvalid";
|
|
31618
31630
|
if (error.status === 403) return "errorForbidden";
|
|
31619
31631
|
if (error.status === 404) return "errorNotFound";
|
package/lib/index.d.ts
CHANGED
|
@@ -78,7 +78,9 @@ declare class WorkspaceDatabase {
|
|
|
78
78
|
readonly dataDir: string;
|
|
79
79
|
readonly trashDir: string;
|
|
80
80
|
private readonly db;
|
|
81
|
+
private closed;
|
|
81
82
|
constructor(dataDir: string);
|
|
83
|
+
/** 关闭数据库。**幂等** —— 重复调用不抛错。 */
|
|
82
84
|
close(): void;
|
|
83
85
|
/**
|
|
84
86
|
* 导出 `roots` 的建表语句(只读,诊断与迁移测试用)。
|
|
@@ -218,6 +220,12 @@ interface ConfigurableProvider {
|
|
|
218
220
|
settingsPath: string[];
|
|
219
221
|
active: boolean;
|
|
220
222
|
declared?: boolean;
|
|
223
|
+
defaultBaseURL?: string;
|
|
224
|
+
baseURLEnv?: string[];
|
|
225
|
+
nativeAuth?: {
|
|
226
|
+
env?: string[];
|
|
227
|
+
oauth?: boolean;
|
|
228
|
+
};
|
|
221
229
|
}
|
|
222
230
|
interface SettingsApiProxy extends DshApiProxy {
|
|
223
231
|
settings: {
|
|
@@ -326,6 +334,14 @@ interface ProviderView {
|
|
|
326
334
|
configurable: boolean;
|
|
327
335
|
configured: boolean;
|
|
328
336
|
removable: boolean;
|
|
337
|
+
defaultBaseURL?: string;
|
|
338
|
+
baseURLEnv?: string[];
|
|
339
|
+
nativeAuth?: {
|
|
340
|
+
env?: string[];
|
|
341
|
+
oauth?: boolean;
|
|
342
|
+
};
|
|
343
|
+
/** 目录条目的设置命名空间;客户端用它捕获 expectedRevision(打开卡片那一刻的 revision)。 */
|
|
344
|
+
settingsNs?: string;
|
|
329
345
|
credential: {
|
|
330
346
|
ref: string;
|
|
331
347
|
configured: boolean;
|
|
@@ -341,7 +357,8 @@ interface ProviderPatch {
|
|
|
341
357
|
apiKey?: string | null;
|
|
342
358
|
thinking?: string | null;
|
|
343
359
|
reasoningEffort?: string | null;
|
|
344
|
-
|
|
360
|
+
/** `null` 恢复适配器默认模型目录(unset),`[]` 写入自定义空目录。 */
|
|
361
|
+
models?: ProviderModelView[] | null;
|
|
345
362
|
expectedRevision?: number;
|
|
346
363
|
}
|
|
347
364
|
interface CustomProviderCreate {
|
|
@@ -738,6 +755,14 @@ interface LlmLike {
|
|
|
738
755
|
settingsNs: string;
|
|
739
756
|
settingsPath: readonly string[];
|
|
740
757
|
declared?: boolean;
|
|
758
|
+
defaultBaseURL?: string;
|
|
759
|
+
/** Environment variables that may override the default base URL, in priority order. */
|
|
760
|
+
baseURLEnv?: readonly string[];
|
|
761
|
+
/** Native authentication the adapter can fall back to when no key is stored. */
|
|
762
|
+
nativeAuth?: {
|
|
763
|
+
env?: readonly string[];
|
|
764
|
+
oauth?: boolean;
|
|
765
|
+
};
|
|
741
766
|
error?: string;
|
|
742
767
|
}[];
|
|
743
768
|
discoverModels(settingsNs: string, request: unknown, signal?: AbortSignal): Promise<readonly unknown[]>;
|
|
@@ -761,7 +786,14 @@ interface Dsh02HostServices {
|
|
|
761
786
|
settingsController: SettingsControllerLike;
|
|
762
787
|
credentialsController: CredentialsControllerLike;
|
|
763
788
|
agentPresets: AgentPresetsLike;
|
|
764
|
-
|
|
789
|
+
/**
|
|
790
|
+
* 权限档位目录(`ctx.permissionPresets`)——**可选**。
|
|
791
|
+
*
|
|
792
|
+
* 0.2.1 的 web profile 上有;0.2.0-rc.2 上没有。inject 是硬依赖,
|
|
793
|
+
* 所以它不能进 inject,只能声明成可选,由 `optionalService` 运行时读;
|
|
794
|
+
* 读不到时权限档位走 `FALLBACK_PERMISSION_CATALOG` + BFF。
|
|
795
|
+
*/
|
|
796
|
+
permissionPresets?: PermissionPresetsLike;
|
|
765
797
|
/**
|
|
766
798
|
* 后台任务控制器(`ctx.jobController`,`@deepseek-ai/dsh-api-job-controller`)。
|
|
767
799
|
*
|
|
@@ -771,7 +803,14 @@ interface Dsh02HostServices {
|
|
|
771
803
|
* 与 `sessionController` 不同,这个是 `TypertRemoteService`(不是 `@Remote`
|
|
772
804
|
* 控制器集合),但结构类型消费方式完全一样:只声明用到的成员,缺了就明确报错。
|
|
773
805
|
*/
|
|
774
|
-
|
|
806
|
+
/**
|
|
807
|
+
* 后台任务控制器(`ctx.jobController`)——**可选**。
|
|
808
|
+
*
|
|
809
|
+
* 只有 DSH 0.2.1 的 web profile 挂了这个服务;0.2.0-rc.2 上没有,
|
|
810
|
+
* 而 inject 是硬依赖(缺一个整个插件不激活),所以它**不能**出现在 inject 里,
|
|
811
|
+
* 只能在这里声明成可选,由 `optionalService` 运行时读。
|
|
812
|
+
*/
|
|
813
|
+
jobController?: JobControllerLike;
|
|
775
814
|
llm: LlmLike;
|
|
776
815
|
/**
|
|
777
816
|
* 内核附件存储(`ctx.attachments`);会话历史里的图片要经它取字节。
|
|
@@ -782,14 +821,23 @@ interface Dsh02HostServices {
|
|
|
782
821
|
* `cannot get property "attachments" without inject` —— 请求链路本身没问题,
|
|
783
822
|
* 挂在服务端、而且只在真的去取图时才炸,所以很难在本地测出来。
|
|
784
823
|
*/
|
|
785
|
-
attachments: AttachmentStoreLike;
|
|
786
824
|
/**
|
|
787
|
-
*
|
|
825
|
+
* 内核附件存储(`ctx.attachments`)——**可选**。
|
|
826
|
+
*
|
|
827
|
+
* 0.2.0-rc.2 的 web profile 上没有这个服务(诊断日志实测 MISSING),
|
|
828
|
+
* 而 inject 是硬依赖,所以它只能声明成可选,由 `optionalService` 运行时读;
|
|
829
|
+
* 读不到时附件读取走内核 `sessionController.attachment()`。
|
|
830
|
+
*/
|
|
831
|
+
attachments?: AttachmentStoreLike;
|
|
832
|
+
/**
|
|
833
|
+
* Kernel file-upload service (`ctx.fileUploads`)——**可选**。
|
|
788
834
|
*
|
|
789
835
|
* 为什么用它而不是内核那条 `/api/session/uploadFileBinary` HTTP 路由:那条路由
|
|
790
836
|
* 只绑 loopback(手机根本到不了),而这个 cordis 服务就在同一个进程里,插件可直接调。
|
|
837
|
+
*
|
|
838
|
+
* 可选的原因同 [attachments]:0.2.0-rc.2 的 web profile 上没有它。
|
|
791
839
|
*/
|
|
792
|
-
fileUploads
|
|
840
|
+
fileUploads?: FileUploadsLike;
|
|
793
841
|
/**
|
|
794
842
|
* Cordis event bus of the host context. Used for the two facts the old
|
|
795
843
|
* `apiProxy` event streams carried that no controller returns: session
|
|
@@ -1573,16 +1621,40 @@ declare class FixedWindowLimiter {
|
|
|
1573
1621
|
}
|
|
1574
1622
|
//#endregion
|
|
1575
1623
|
//#region src/host/auth.d.ts
|
|
1624
|
+
interface AuthServiceOptions {
|
|
1625
|
+
/**
|
|
1626
|
+
* 隧道运行状态探针(`CloudflaredTunnelManager.tunnelActive`)。
|
|
1627
|
+
*
|
|
1628
|
+
* 用函数而不是快照:隧道随时启停,鉴权发生在请求那一刻,读的必须是当下状态。
|
|
1629
|
+
* 不注入(测试、未接隧道的构建)即为「没有隧道」,判据 0 恒不触发。
|
|
1630
|
+
*/
|
|
1631
|
+
tunnelActive?: () => boolean;
|
|
1632
|
+
}
|
|
1576
1633
|
declare class AuthService {
|
|
1577
1634
|
private readonly database;
|
|
1635
|
+
private readonly options;
|
|
1578
1636
|
private readonly authFailures;
|
|
1579
1637
|
readonly pairingAttempts: FixedWindowLimiter;
|
|
1580
1638
|
readonly requests: FixedWindowLimiter;
|
|
1581
|
-
constructor(database: WorkspaceDatabase);
|
|
1639
|
+
constructor(database: WorkspaceDatabase, options?: AuthServiceOptions);
|
|
1640
|
+
/**
|
|
1641
|
+
* 隧道是否在跑。探针抛错时**按「在跑」处理**(fail closed):探针读的是我们自己
|
|
1642
|
+
* 对象上的 getter,它抛错意味着状态不可知;状态不可知时放行管理面正是这次修复要
|
|
1643
|
+
* 消灭的那类决定。
|
|
1644
|
+
*/
|
|
1645
|
+
private tunnelRunning;
|
|
1582
1646
|
principal(req: IncomingMessage, options?: {
|
|
1583
1647
|
allowAdmin?: boolean;
|
|
1584
1648
|
}): Principal;
|
|
1585
|
-
|
|
1649
|
+
/**
|
|
1650
|
+
* `/manage/**` 的守门人。
|
|
1651
|
+
*
|
|
1652
|
+
* @param options.allowTunnelMode - 只有「隧道状态下读/停隧道」这几个端点会传 true。
|
|
1653
|
+
* 见 `adminRefusal` 的同名说明与 `router.ts` 的 `TUNNEL_MODE_EXEMPT_PATHS`。
|
|
1654
|
+
*/
|
|
1655
|
+
requireAdmin(req: IncomingMessage, options?: {
|
|
1656
|
+
allowTunnelMode?: boolean;
|
|
1657
|
+
}): Principal;
|
|
1586
1658
|
requireScope(principal: Principal, scope: DeviceScope): void;
|
|
1587
1659
|
requireRoot(principal: Principal, rootId: string): void;
|
|
1588
1660
|
}
|
|
@@ -1802,6 +1874,17 @@ declare class CloudflaredTunnelManager {
|
|
|
1802
1874
|
reportActivity(a: TunnelActivity): void;
|
|
1803
1875
|
/** 任务结束(成功或失败)时清空进行中进度。 */
|
|
1804
1876
|
clearActivity(): void;
|
|
1877
|
+
/**
|
|
1878
|
+
* 是否有隧道在运行 —— 管理面鉴权的**判据 0**。
|
|
1879
|
+
*
|
|
1880
|
+
* 判据取自**进程持有状态**,不是请求头,所以它对「隧道把外部流量转进 127.0.0.1」
|
|
1881
|
+
* 这条绕过路径 100% 可靠:不管转发进来的请求长什么样,只要隧道开着,管理面就关门。
|
|
1882
|
+
* 见 `auth.ts` 的 `adminRefusal` 与 `PITFALLS.md` §隧道让回环判据失效。
|
|
1883
|
+
*
|
|
1884
|
+
* `running` 与 `publicState.running` 是同一个表达式(同一份真相),这里单独暴露是为了
|
|
1885
|
+
* 让鉴权层不必构造一个含 url 的视图对象 —— 少一处把地址带进意外地方的机会。
|
|
1886
|
+
*/
|
|
1887
|
+
get tunnelActive(): boolean;
|
|
1805
1888
|
get publicState(): TunnelPublicState;
|
|
1806
1889
|
/** 订阅状态变化;返回取消函数。 */
|
|
1807
1890
|
onStatus(cb: () => void): () => void;
|
|
@@ -1937,6 +2020,22 @@ declare class ApiRouter {
|
|
|
1937
2020
|
handle(req: IncomingMessage, res: ServerResponse, stripPrefix?: string): Promise<void>;
|
|
1938
2021
|
private dispatch;
|
|
1939
2022
|
private dispatchManagement;
|
|
2023
|
+
/**
|
|
2024
|
+
* 隧道状态视图。
|
|
2025
|
+
*
|
|
2026
|
+
* **这里不做脱敏,因为用不上**:判据 1 已经把 Cloudflare 边缘头(`Cf-Connecting-IP` /
|
|
2027
|
+
* `Cf-Ray` / …)算作「前面有代理」的证据,于是任何真实隧道来源的请求在
|
|
2028
|
+
* `requireAdmin` 就被 403 挡住了,根本到不了这个方法。能走到这里的只有真·本机请求
|
|
2029
|
+
* (判据 1/2/3 全过),所以 `url` 可以照常给它 —— 那是桌面端「用隧道地址生成配置文本」
|
|
2030
|
+
* 的依据。
|
|
2031
|
+
*
|
|
2032
|
+
* 为什么仍然保留这个方法而不是直接 `publicState`:脱敏的位置要留在一个**语义明确**的
|
|
2033
|
+
* 出口上。将来若有人为了「让远程客户端也能读到隧道是否开着」而放宽判据 1,
|
|
2034
|
+
* 必须在这里重新定义脱敏,而不是顺手把 `url` 一起放出去。
|
|
2035
|
+
* 评估结论见报告:本轮选择「远程一律 403」而不是「远程读得到但 `url` 为 null」,
|
|
2036
|
+
* 因为后者需要为状态端点在判据 1 上再开一个口子,而那正是这次修复要消灭的东西。
|
|
2037
|
+
*/
|
|
2038
|
+
private tunnelStateFor;
|
|
1940
2039
|
private dispatchEntries;
|
|
1941
2040
|
private dispatchContent;
|
|
1942
2041
|
private requireRoot;
|