dsh-remote-bridge 2.8.2 → 2.9.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 CHANGED
@@ -1,56 +1,44 @@
1
1
  # dsh-remote-bridge
2
2
 
3
3
  <p align="center">
4
- <strong>Translates DSH's private remote protocol into a versioned REST API for mobile and third-party clients, plus a centralized settings center on the desktop.</strong>
4
+ <strong>You left the house. The desktop is still locked. Take out your phone and keep using that machine.</strong><br>
5
+ Translates DSH's private remote protocol into a versioned REST API so the Android client
6
+ <a href="https://github.com/westanke/dsh-companion">dsh-companion</a> keeps your chats,
7
+ sessions, files and devices on the same machine.
5
8
  </p>
6
9
 
7
10
  <p align="center">
8
- <a href="./README.md">中文</a> · English
11
+ <img src="assets/screenshots/panel-roots.png" alt="The 📱 Phone settings panel: five collapsible cards in the main list, plus an Advanced settings group (Roots / Image reading / Plugins)" width="420">
9
12
  </p>
10
13
 
11
14
  <p align="center">
12
- <img alt="Version" src="https://img.shields.io/badge/version-v2.8.2-087f8c">
15
+ <code>dsh plugin --profile web add dsh-remote-bridge</code>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="./README.md">中文</a> ·
20
+ <img alt="Version" src="https://img.shields.io/badge/version-v2.9.1-087f8c">
13
21
  <img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
14
22
  <img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
15
23
  <img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
16
24
  </p>
17
25
 
18
- `dsh-remote-bridge` is the remote-access bridge plugin for DeepSeek Harness (DSH). It translates DSH's private remote protocol into a versioned REST API so dsh-companion ([GitHub](https://github.com/westanke/dsh-companion) · [Gitee](https://gitee.com/westanke/dsh-companion)) and other trusted clients can keep using the same chat, workspace, and file capabilities while away from the machine. On the desktop it provides a **Phone settings** section inside your DSH settings, rendering seven collapsible cards inline (Roots / Remote / Devices / Image reading / Trash / Audit / App download), each folding on its own and several may stay open at once. **The desktop no longer offers a file-management UI**: the file tree and editor were deleted in 2.2.0 — manage files from the phone app, or ask the agent to use its tools in a conversation (see "No file-management UI on the desktop").
26
+ `dsh-remote-bridge` is the remote-access bridge plugin for DeepSeek Harness (DSH). It translates DSH's private remote protocol into a versioned REST API so dsh-companion ([GitHub](https://github.com/westanke/dsh-companion) · [Gitee](https://gitee.com/westanke/dsh-companion)) and other trusted clients can keep using the same chat, workspace, and file capabilities while away from the machine. On the desktop it provides a **Phone settings** section inside your DSH settings, rendering collapsible cards inline: five in the main list (Remote / Public tunnel / Devices / Trash / App download) plus a collapsed-by-default **Advanced settings** group holding Roots, Image reading and Plugins — every card folds on its own and several may stay open at once.
27
+
28
+ **Who it is for**: people who use DSH as their main tool but do not want to be tied to the desk. Remote access is **off by default** — until you enable it, the plugin listens on loopback only, so installing it never exposes the machine to the LAN by itself.
19
29
 
20
30
  The interface is bilingual and defaults to Chinese. Prebuilt packages run on Windows, macOS, and Linux without compiling on the target server.
21
31
 
22
32
  > **Version warning**: the published `v2.0.0` and `v2.0.1` have serious defects (`v2.0.0` fails to read any historical images; `v2.0.1` cannot activate the plugin). Use **`v2.2.0`** (or ≥ `v2.0.4`).
23
33
  > `v2.0.2` and `v2.0.3` were **never released** — do not look for installers under those version numbers.
24
-
25
- > **DSH 0.2.x compatibility**: this branch adds a compatibility layer for DSH `0.2.0-rc.2` on top of
26
- > upstream `v1.0.0`. DSH 0.2.x replaced `dsh-host-apiproxy` (which provided the `apiProxy` service)
27
- > with `dsh-api-gateway`, leaving the upstream release unable to start on 0.2.x — it surfaces as
28
- > "adding an authorized root returns 405". Background, rationale, changes, and test results are in
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
34
  >
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.
35
+ > **DSH 0.2.x compatibility** and the **0.2.1-rc.2 adaptation notes** involve the trade-offs of the
36
+ > injected services and run long, so they live in
37
+ > [Compatibility & known limits](#compatibility--known-limits) — worth a skim before installing.
50
38
 
51
39
  ## What it looks like
52
40
 
53
- The desktop entry lives **inside your DSH settings**: look for the section titled **📱 Phone settings**, which renders seven collapsible cards inline — no second dialog. It is settings-only and contains **no file tree**: the standalone page `/dsh-workspace` and its file tree and CodeMirror editor were deleted in 2.2.0. The reason and the cost are covered in "No file-management UI on the desktop" below.
41
+ The desktop entry lives **inside your DSH settings**: look for the section titled **📱 Phone settings**, which renders collapsible cards inline (five in the main list plus an Advanced settings group) — no second dialog. It is settings-only and contains **no file tree**: the standalone page `/dsh-workspace` and its file tree and CodeMirror editor were deleted in 2.2.0. The reason and the cost are covered in "No file-management UI on the desktop" below.
54
42
 
55
43
  Remote access remains disabled until a local administrator chooses a bind address, port, and initial device permissions.
56
44
 
@@ -159,7 +147,7 @@ The entry lives in DSH settings under the title **Phone settings**. It renders s
159
147
  | Devices | Inspect and revoke device scopes and per-root grants |
160
148
  | Image reading | Set the handoff directory (the image-reading path); shows the read-only factory default `.dsh-images` and the fallback directory |
161
149
  | Trash | View and restore deleted entries (only deletions made from the phone app appear here) |
162
- | Audit | Operation type and target audit records |
150
+ | Plugins | See what this profile has installed, enable or disable it, check for updates, and jump to the kernel's plugin list |
163
151
  | App download | Scan to install the Android client; the codes point at both hosts' Releases list pages |
164
152
 
165
153
  The sidebar gear button was **removed**: having the entry in both the sidebar and the settings page gave one thing two starting points. The in-conversation **Files** tab is gone too, so the desktop now has exactly one entry point.
@@ -402,7 +390,26 @@ inside the zero-change set, and 4 slots retained with 0 removed.
402
390
 
403
391
  ## Project
404
392
 
405
- - Version: `v2.8.2`
393
+ - Version: `v2.9.1`
394
+ - **v2.9.0 makes teammates (sub-sessions) readable**: the phone's team panel could list members and
395
+ sub-sessions but could not open a single one, because `sessions.list` filters out every row with
396
+ `origin === 'subagent'` (the bridge's own `dsh-0.2-bridge.ts:2327`, with a comment explaining that
397
+ exposing them "only yields rows that crash on tap"), so any sub-session id hitting `/messages` gets a
398
+ hard 404. Even past that visibility check the kernel's `validateAddress()` demands the
399
+ `{ kind:'subagent', parentSessionId, childSessionId, mode }` discriminated union instead of a plain
400
+ session address. This release adds a **read-only narrow port guarded by membership**:
401
+ `GET /api/v1/chat/sessions/{leadSessionId}/subagents/{subagentId}/messages`. Authorization takes two
402
+ steps — (1) the Lead session must be visible to the device (reusing `requireSession`, not a single rule
403
+ relaxed); (2) membership must be vouched for by a projection the kernel itself publishes
404
+ (`subagentCatalog[].id` or `agentTeam.members[].id`). Only then is history read directly, deliberately
405
+ **without** calling `requireSession(subagentId)` — removing the `origin` filter would put "crash on tap"
406
+ rows back into the list, while loosening `requireSession` would backdoor every session; the narrow port
407
+ has the cleanest boundary. Read-only (non-GET always 405), and `SUBAGENT_NOT_FOUND` deliberately does
408
+ not distinguish "does not exist" from "not your teammate", otherwise id existence becomes a probe
409
+ surface. New capability bit `subagent-history-v1` (it must be separate from `history-v1`, which always
410
+ 404s on sub-session ids and therefore cannot advertise this). The same release re-checked DSH
411
+ 0.2.1-rc.2 → rc.5: 18 of the 19 packages the bridge depends on are byte-identical, the only diff being
412
+ a single KDoc comment.
406
413
  - **v2.7.1 is a defect-fix release**: the kernel's `sessionController.resolveAgent()` returns an
407
414
  `{ agent }` / `{ error }` **envelope**, not the Agent itself (`dsh-api-session-controller/lib/index.js:365-368`
408
415
  `liveAgent()` and `:221-246` `resolve()` both have that shape). The bridge used to hand the envelope
package/README.md CHANGED
@@ -1,50 +1,43 @@
1
1
  # dsh-remote-bridge
2
2
 
3
3
  <p align="center">
4
- <strong>把 DSH 内核的私有 remote 协议翻译成版本化 REST,供手机与第三方客户端远程访问这台机器;桌面端则提供一个集中设置中心。</strong>
4
+ <strong>人已经出门,电脑还锁着 —— 掏出手机,继续用这台机器。</strong><br>
5
+ 把 DSH 内核的私有 remote 协议翻译成版本化 REST,配上 Android 客户端
6
+ <a href="https://github.com/westanke/dsh-companion">dsh-companion</a>,
7
+ 聊天、会话、文件、设备全程留在同一台机器上。
5
8
  </p>
6
9
 
7
10
  <p align="center">
8
- 中文 · <a href="./README.en.md">English</a>
11
+ <img src="assets/screenshots/panel-roots.png" alt="DSH 设置里的「📱 手机设置」面板:主列表五张折叠卡片,另有一个高级设置组(根目录/识图/插件)" width="420">
9
12
  </p>
10
13
 
11
14
  <p align="center">
12
- <img alt="Version" src="https://img.shields.io/badge/version-v2.8.2-087f8c">
15
+ <code>dsh plugin --profile web add dsh-remote-bridge</code>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="./README.en.md">English</a> ·
20
+ <img alt="Version" src="https://img.shields.io/badge/version-v2.9.1-087f8c">
13
21
  <img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
14
22
  <img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
15
23
  <img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
16
24
  </p>
17
25
 
18
- `dsh-remote-bridge` 是 DeepSeek Harness(DSH)的远程访问桥接插件:它把 DSH 内核的私有 remote 协议翻译成版本化的 REST 接口,让 dsh-companion([GitHub](https://github.com/westanke/dsh-companion) · [Gitee](https://gitee.com/westanke/dsh-companion))等受信任客户端在离开电脑后仍能使用同一套聊天、工作区与文件能力;桌面端在你的 DSH 设置里提供一个「手机设置」,内联七张设置卡片(根目录 / 远程访问 / 设备 / 识图 / 回收站 / 审计 / App 下载),每张卡片各自折叠、可多张同时展开。)。
26
+ `dsh-remote-bridge` 是 DeepSeek Harness(DSH)的远程访问桥接插件:它把 DSH 内核的私有 remote 协议翻译成版本化的 REST 接口,让 dsh-companion([GitHub](https://github.com/westanke/dsh-companion) · [Gitee](https://gitee.com/westanke/dsh-companion))等受信任客户端在离开电脑后仍能使用同一套聊天、工作区与文件能力;桌面端在你的 DSH 设置里提供一个「手机设置」,内联折叠卡片:主列表五张(远程访问 / 外网隧道 / 设备 / 回收站 / App 下载),另有默认收起的「高级设置」组收纳根目录、识图与插件三项,每张卡片各自折叠、可多张同时展开。
19
27
 
20
- 界面默认中文,可随时切换 English。插件支持 Windows、macOS 和 Linux,安装包已经包含编译产物,不要求用户在服务器上重新构建。
28
+ **它适合谁**:把 DSH 当主力工具、但不想被「必须坐在电脑前」绑住的人。远程访问**默认关闭**,未启用时只监听回环地址——装完不会自动把机器暴露到局域网。
21
29
 
22
- > **版本警告**:请务必使用 ≥ `v2.0.4`版本。
30
+ 界面默认中文,可随时切换 English。插件支持 Windows、macOS 和 Linux,安装包已经包含编译产物,不要求用户在服务器上重新构建。
23
31
 
24
- > **DSH 0.2.x 兼容性**:本分支在上游 `v1.0.0` 的基础上增加了对 DSH `0.2.0-rc.2` 的兼容层。
25
- > 0.2.x 用 `dsh-api-gateway` 替换了提供 `apiProxy` 服务的 `dsh-host-apiproxy`,导致上游版本在
26
- > 0.2.x 上无法启动(表现为「添加根目录返回 405」)。兼容层的完整说明——基于什么、为什么、改了什么、
27
- > 测了什么——见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md)。
28
- >
29
- > **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据 `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`。
32
+ > **版本警告**:请务必使用 ≥ `v2.0.4` 版本。
35
33
  >
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` 可以查这台机器上哪些可选能力可用。
34
+ > **DSH 0.2.x 兼容性**与 **0.2.1-rc.2 适配结论**涉及注入服务的取舍,篇幅较长,
35
+ > 统一放在 [兼容性与已知限制](#兼容性与已知限制) 一节,安装前建议扫一眼。
44
36
 
45
37
  ## 实际效果
46
38
 
47
- 桌面端的入口**就在你的 DSH 设置里**:打开设置,找到标题为「📱 手机设置」的一节,点进去直接内联渲染七张折叠卡片,不再另开弹窗。它只做设置。
39
+
40
+ 桌面端的入口**就在你的 DSH 设置里**:打开设置,找到标题为「📱 手机设置」的一节,点进去直接内联渲染折叠卡片(主列表五张 + 一个「高级设置」组),不再另开弹窗。它只做设置。
48
41
 
49
42
  远程访问默认关闭。管理员可在本机 WebUI 中设置绑定 IP、端口和新设备的初始权限。
50
43
 
@@ -140,7 +133,7 @@ dsh plugin --profile web add ./dsh-remote-bridge-v2.7.1.tgz
140
133
 
141
134
  ### 桌面端设置面板
142
135
 
143
- 入口在 DSH 设置里,标题为「手机设置」。设置区**内联渲染**,不另开弹窗;七张折叠卡片平铺,可多张同时展开,默认展开「根目录」:
136
+ 入口在 DSH 设置里,标题为「手机设置」。设置区**内联渲染**,不另开弹窗;主列表五张折叠卡片平铺,可多张同时展开,默认展开「远程访问」;授权根目录、识图与插件这三项收在「高级设置」组里(根目录排第一,因为没它就没有可授权的落点),点开照旧能改:
144
137
 
145
138
  | 分区 | 用途 |
146
139
  | --- | --- |
@@ -149,7 +142,7 @@ dsh plugin --profile web add ./dsh-remote-bridge-v2.7.1.tgz
149
142
  | 设备 | 查看设备权限与根目录授权,随时撤销 |
150
143
  | 识图 | 设置图片交接目录(识图路径),并只读展示出厂默认 `.dsh-images` 与兜底目录 |
151
144
  | 回收站 | 查看与恢复被删除的条目(只列出来自手机 App 的删除记录) |
152
- | 审计 | 查看操作类型与对象的审计记录 |
145
+ | 插件 | 查看本 profile 装了哪些插件、启用或停用、检查更新,并能跳转到内核的插件列表 |
153
146
  | App 下载 | 扫码下载 Android 客户端,二维码指向两个站点的 Releases 列表页 |
154
147
 
155
148
 
@@ -372,6 +365,29 @@ dsh plugin --profile web remove dsh-remote-bridge
372
365
 
373
366
  ## 兼容性与已知限制
374
367
 
368
+ ### DSH 0.2.0 / 0.2.1 两版的内核差异
369
+
370
+ 本分支在上游 `v1.0.0` 的基础上增加了对 DSH `0.2.0-rc.2` 的兼容层。0.2.x 用
371
+ `dsh-api-gateway` 替换了提供 `apiProxy` 服务的 `dsh-host-apiproxy`,导致上游版本在 0.2.x 上
372
+ 无法启动(表现为「添加根目录返回 405」)。兼容层的完整说明——基于什么、为什么、改了什么、
373
+ 测了什么——见 [DSH 0.2.x 兼容性适配说明](./docs/project/DSH-0.2-COMPAT.md)。
374
+
375
+ **对 DSH 0.2.1-rc.2 的适配结论**(2026-10-20 复核,依据 `docs/project/DSH-0.2.1-ADAPTATION.md`):
376
+ 本插件**刻意不 import 任何 `@deepseek-ai/*` 包**,全部宿主服务经结构类型 + Host 注入消费
377
+ (见 `src/host/dsh-0.2-bridge.ts` 头注释)。逐一到 `0.2.1-rc.2` 实装包核对:`cordis.patch.yml`
378
+ 的 10 项 `inject` 服务名**全部仍存在且签名未变**,`engines.dsh` 在 0.2.1 被内核识别
379
+ (`dsh-app-boot/lib/index.js` 的 `prepareProfileEntries`),不满足时只把该 entry 置
380
+ `disabled` 并打 stderr,**宿主进程不退出**。故本插件无需改码即可运行于 `0.2.1-rc.2`。
381
+
382
+ **两版的功能差异(重要)**:`inject` 里只放**两个版本都存在**的服务 —— cordis 的 inject 是
383
+ **硬依赖**,任何一个服务缺失就整个插件不激活(CI 在 0.2.0-rc.2 基线上实锤过:
384
+ 一度因为把 0.2.1 才有的服务写进 inject 而让插件在 0.2.0 上整体不激活)。
385
+ 因此 0.2.1 才提供的服务(**后台任务** `jobController`、**权限档位目录**
386
+ `permissionPresets`、文件上传 `fileUploads`、附件存储 `attachments`)改为运行时经
387
+ `ctx.reflect.get(name, false)` 读取:**0.2.1-rc.2 上这些功能全部可用,0.2.0-rc.2 上
388
+ 自动降级**(如后台任务端点返回 404、权限档位走内置目录),插件本体两种内核都能跑。
389
+ 用 `GET /api/v1/debug/host-services` 可以查这台机器上哪些可选能力可用。
390
+
375
391
  ### 某些 DSH 构建缺少附件存储服务
376
392
 
377
393
  DSH 的 cordis 注入属性(`ctx.attachments` / `ctx.fileUploads`)是惰性解析的:读取一个没有任何组件提供的服务会抛错。2.0.3 起改为**容错读取**——服务缺席时插件照常激活,只有真正用到该能力的接口返回可读错误:
@@ -433,7 +449,21 @@ DSH **0.2.1-rc.3** 隔离实例上**实跑验过**,不是静态推断:插件
433
449
 
434
450
  ## 项目信息
435
451
 
436
- - 当前版本:`v2.8.2`
452
+ - 当前版本:`v2.9.1`
453
+ - **v2.9.0 是团队成员(子会话)可读版**:手机端的团队面板此前只能看到成员与子会话的名字,点开读不到
454
+ 任何内容 —— 因为 `sessions.list` 会把 `origin === 'subagent'` 的行整批滤掉(桥自己的
455
+ `dsh-0.2-bridge.ts:2327`,注释写明「暴露出来只会得到点开就崩的行」),子会话 id 打 `/messages`
456
+ 必然 404;即使绕过可见性,内核 `validateAddress()` 也要求用
457
+ `{ kind:'subagent', parentSessionId, childSessionId, mode }` 判别联合寻址,而非普通会话地址。
458
+ 这一版新增**受成员关系校验的只读窄口**
459
+ `GET /api/v1/chat/sessions/{leadSessionId}/subagents/{subagentId}/messages`:授权两步 ——
460
+ ① Lead 会话必须对设备可见(沿用 `requireSession`,规则一行不放宽);② 成员关系必须由内核自己发布的
461
+ 投影背书(`subagentCatalog[].id` 或 `agentTeam.members[].id`)。两步都过才直读历史,刻意不再调
462
+ `requireSession(subagentId)` —— 拆 `origin` 过滤会把「点开就崩」的行放回列表,放宽 `requireSession`
463
+ 等于给所有会话开后门,窄口的边界最清晰。只读(非 GET 一律 405),错误码 `SUBAGENT_NOT_FOUND`
464
+ 刻意不区分「不存在」与「不是成员」否则 id 是否存在就成了探测面。新增能力位 `subagent-history-v1`
465
+ (必须独立于 `history-v1`,后者对子会话永远 404 探测不到)。同版复核了 DSH 0.2.1-rc.2 → rc.5:
466
+ 桥依赖的 19 个包中 18 个零差异,唯一差异是一行 KDoc 注释。
437
467
  - **v2.7.1 是缺陷修复版**:内核 `sessionController.resolveAgent()` 返回的是 `{ agent }` / `{ error }`
438
468
  **包装对象**而非 Agent 本体(`dsh-api-session-controller/lib/index.js:365-368` 的 `liveAgent()` 与
439
469
  `:221-246` 的 `resolve()` 都是这个形状),桥此前把包装对象直接当 Agent 传给 `fileUploads.upload()`,
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  asyncapi: 3.0.0
2
2
  info:
3
3
  title: DeepSeek Harness Mobile Events
4
- version: 2.8.2
4
+ version: 2.9.1
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.
@@ -154,6 +154,13 @@ channels:
154
154
  > is missing from the set will start echoing a stale `requestId` from
155
155
  > whatever request happened to be in flight when it fired.
156
156
 
157
+ > **子会话历史不走 WS**(2026-10-30):团队成员 / 子会话的历史只能经
158
+ > REST `GET /chat/sessions/{leadSessionId}/subagents/{subagentId}/messages`
159
+ > 读取,本协议**没有**对应帧,也不打算加。原因是普通 `history` 帧对子会话
160
+ > id 必然 404(子会话被 `sessions.list` 刻意滤掉),而那条窄口的授权靠
161
+ > 「Lead 可见 + 内核投影背书成员关系」两步,与 WS 连接级的会话白名单不是
162
+ > 一回事;硬塞进 WS 只会让两套授权语义打架。详见 `docs/project/API.md`。
163
+
157
164
  Rules:
158
165
 
159
166
  - Absent or empty string ⇒ **no** `requestId` key in the response at all
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: DeepSeek Harness Mobile API
4
- version: 2.8.2
4
+ version: 2.9.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.8.2 }
27
+ pluginVersion: { type: string, const: 2.9.1 }
28
28
  /debug/host-services:
29
29
  get:
30
30
  operationId: hostServices
@@ -989,6 +989,80 @@ paths:
989
989
  responses:
990
990
  '202': { description: Prompt accepted by DSH. }
991
991
  '409': { $ref: '#/components/responses/Error' }
992
+ /chat/sessions/{leadSessionId}/subagents/{subagentId}/messages:
993
+ parameters:
994
+ - name: leadSessionId
995
+ in: path
996
+ required: true
997
+ description: |
998
+ The session that owns the team (the Lead). Its visibility is checked with the
999
+ ordinary session rules -- the narrow endpoint never relaxes them.
1000
+ schema: { type: string }
1001
+ - name: subagentId
1002
+ in: path
1003
+ required: true
1004
+ description: |
1005
+ A subagent / teammate session id. It is *not* required to be listed by
1006
+ `GET /chat/sessions` (subagent sessions are filtered out of that list);
1007
+ membership is verified against the Lead's own `subagentCatalog` /
1008
+ `agentTeam.members` projection instead.
1009
+ schema: { type: string }
1010
+ get:
1011
+ operationId: listSubagentMessages
1012
+ description: |
1013
+ Read the history of a subagent (teammate) session -- a read-only narrow endpoint.
1014
+
1015
+ Why it exists: `GET /chat/sessions/{sessionId}/messages` resolves visibility via
1016
+ the session list, and subagent sessions are deliberately filtered out of that list
1017
+ (the kernel refuses to activate them as ordinary sessions, so exposing them yields
1018
+ rows that crash on open). Every subagent id therefore 404s on the ordinary path.
1019
+
1020
+ Authorization has two steps, both required: (1) the Lead session must be visible to
1021
+ this device under the unchanged rules; (2) membership must be backed by the kernel's
1022
+ own projection for that Lead (`subagentCatalog[].id` or `agentTeam.members[].id`).
1023
+ Guessing a session id does not work -- a miss is a 404.
1024
+
1025
+ Read-only: any non-GET method is `405 METHOD_NOT_ALLOWED`. Writing to a teammate
1026
+ session would blur who is directing whom; the official Web UI likewise only writes
1027
+ through the Lead's model tools.
1028
+ parameters:
1029
+ - name: beforeSeq
1030
+ in: query
1031
+ schema: { type: integer, minimum: 0 }
1032
+ - name: maxMessages
1033
+ in: query
1034
+ schema: { type: integer, minimum: 1 }
1035
+ responses:
1036
+ '200':
1037
+ description: |
1038
+ Identical shape and byte budget as `listMessages`: the oldest events may be
1039
+ dropped (with `hasMore` forced to true) and oversized strings inside one event
1040
+ may be head/tail clamped. `truncated`/`bytes` appear only when something was
1041
+ actually clamped. `historyFormatVersion` is always present.
1042
+ content:
1043
+ application/json:
1044
+ schema:
1045
+ type: object
1046
+ required: [events, hasMore]
1047
+ properties:
1048
+ events:
1049
+ type: array
1050
+ items: { type: object, additionalProperties: true }
1051
+ hasMore: { type: boolean }
1052
+ projections: { type: object, additionalProperties: true }
1053
+ bytes: { type: integer, minimum: 0 }
1054
+ truncated: { type: boolean }
1055
+ historyFormatVersion: { type: integer, minimum: 1 }
1056
+ '404':
1057
+ description: |
1058
+ `SUBAGENT_NOT_FOUND` -- either the subagent is not a member of this team, or
1059
+ the Lead's projections were unavailable (membership unprovable). The two cases
1060
+ are deliberately indistinguishable so the existence of a session id is not
1061
+ turned into a probing surface.
1062
+ content:
1063
+ application/json:
1064
+ schema: { $ref: '#/components/schemas/Error' }
1065
+ '405': { $ref: '#/components/responses/Error' }
992
1066
  /chat/sessions/{sessionId}/attachments:
993
1067
  parameters:
994
1068
  - name: sessionId
@@ -541,6 +541,27 @@ class DshWorkspaceClient(
541
541
 
542
542
  fun listMessages(sessionId: String): JsonElement = executeJsonElement("chat/sessions/$sessionId/messages", "GET", null)
543
543
 
544
+ /**
545
+ * 读**子会话(团队成员)**的历史 —— 受成员关系校验的只读窄口。
546
+ *
547
+ * 不能复用 [listMessages]:那条路径靠会话列表判定可见性,而子会话被
548
+ * `sessions.list` 刻意滤掉(内核拒绝把它们当普通会话激活),所以任何子会话 id
549
+ * 打进去都是 404。这条窄口改成两步授权:Lead 会话必须可见 + 内核投影
550
+ * (`subagentCatalog` / `agentTeam.members`)必须背书成员关系。只读。
551
+ */
552
+ fun listSubagentMessages(
553
+ leadSessionId: String,
554
+ subagentId: String,
555
+ beforeSeq: Int? = null,
556
+ maxMessages: Int? = null,
557
+ ): JsonElement {
558
+ val query = buildString {
559
+ if (beforeSeq != null) append(if (isEmpty()) "?" else "&").append("beforeSeq=").append(beforeSeq)
560
+ if (maxMessages != null) append(if (isEmpty()) "?" else "&").append("maxMessages=").append(maxMessages)
561
+ }
562
+ return executeJsonElement("chat/sessions/$leadSessionId/subagents/$subagentId/messages$query", "GET", null)
563
+ }
564
+
544
565
  fun renameSession(sessionId: String, title: String) {
545
566
  val body = json.encodeToString(RenameRequest.serializer(), RenameRequest(title))
546
567
  executeUnit("chat/sessions/$sessionId", "PATCH", body)