dsh-zen-remote 2.4.2 → 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/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@
5
5
  [GitHub Releases](https://github.com/KyoMio/dsh-zen-remote/releases)(tag 驱动自动生成)。
6
6
  界面半边的细节叙事另见 [`docs/interface.md`](docs/interface.md) 的「更新日志」一节。
7
7
 
8
+ ## 2.4.3(2026-11-12)
9
+
10
+ ### 兼容
11
+
12
+ - **适配 DSH 0.2.2-rc.2**:远程会话转发官方工作区文件搜索(`workspaceFiles/search`)、办公文档预览(`officeKit/render`,同时保留 0.2.2-rc.1 / npm `latest` 的 `officeToPdf/render`)、子智能体中断(`subagents/interruptByUser`,同时保留 `interruptByParent`),以及 Agent 终端侧栏的会话终端监视流(`terminal/watch`)。两端版本混连时会话 / 文件 / 子智能体等指纹组会显示为「不同」,不会硬拒绝。
13
+
8
14
  ## 2.4.2(2026-11-11)
9
15
 
10
16
  ### 修复
package/README.md CHANGED
@@ -99,7 +99,7 @@ dsh-zen-remote client unpair <hostUrl|entryId|--all> [--yes] # remove one or a
99
99
  // ~/.dsh/profiles/desktop/package.json
100
100
  {
101
101
  "dependencies": {
102
- "dsh-zen-remote": "^2.4.2" // for local development: "link:/path/to/dsh-zen-remote"
102
+ "dsh-zen-remote": "^2.4.3" // for local development: "link:/path/to/dsh-zen-remote"
103
103
  }
104
104
  }
105
105
  ```
package/README.zh-CN.md CHANGED
@@ -99,7 +99,7 @@ dsh-zen-remote client unpair <hostUrl|entryId|--all> [--yes] # 移除单台或
99
99
  // ~/.dsh/profiles/desktop/package.json
100
100
  {
101
101
  "dependencies": {
102
- "dsh-zen-remote": "^2.4.2" // 本地开发换成 "link:/path/to/dsh-zen-remote"
102
+ "dsh-zen-remote": "^2.4.3" // 本地开发换成 "link:/path/to/dsh-zen-remote"
103
103
  }
104
104
  }
105
105
  ```
@@ -322,7 +322,7 @@ A desktop client has no DSH login cookie, and the relay deliberately does not fo
322
322
  | `/v1/upload` | POST | The **non-image attachment** upload of a remote session: the request body is a raw byte stream (not JSON); the query string is normalized and rebuilt along an allowlist — only `sessionId` (exactly once, share-checked, 403 when not shared) and `name` (at most once) survive; the cap is 500 MiB, enforced twice (a `Content-Length` pre-check plus a counting pump over the stream), over-limit answers 413 (the refusal is written before the tail bytes are drained); at most 8 concurrent uploads per device (429 past that); then, like `/v1/http`, dispatched **in-process** through the host's shared `/api` handler — the staged receipt lands under the same session the later `session/prompt` resolves the attachment against |
323
323
  | `/v1/unshare` | POST | The client closes a session's remote access itself (table members only) |
324
324
 
325
- **The allowlist** (everything else is `forbidden-method`): session reads and writes (`session/follow`, `session/page`, `session/prompt`, `session/cancel`, `session/rename`, `session/selectModel`, `session/updateQueue`, `session/attachment`, `session/projections`), creation and forking (`session/create`, `session/fork` — results auto-share), subagents (`subagents/prompt`, `subagents/interruptByParent`, judged by the parent), attachments and @ references (`fileUploads/upload`, `fileReferences/list`, `sessionReferenceResolver/candidates`), the panel long tail (`goals/*` five verbs, `commands/list|execute`, `agentPresets/select`, `sessionFeedback/record`), file tree and previews (`workspaceFiles/list|changes|read|readBytes|stat`), terminals (`terminal/*`), jobs (`job/list|follow|kill`), `skills/list`, message feedback (`messageFeedback/*`), `schedule/list`, workspace session ops (`workspace/pinSession` and friends), plus four global reads: the `session/control` stream, `session/list`, the `workspace/follow` stream and `session/modelCatalog`. The first three carry no session argument — their safety is **output filtering**: every frame / every result row is narrowed by the share table before it is written, so an unshared session's row never reaches the client; in `session/list` result rows, a `parentSessionId` naming an inaccessible session is deleted as well (the rest of the row passes through unchanged). `session/modelCatalog` is likewise parameter-less and globally read-only — it is the server's model-provider directory, group and model ids plus display names only, no session data; the server empties the `failures` array in the result (the host's per-group error texts may carry endpoint or credential details the client never shows anyway). `sessionReferenceResolver/candidates` (the @-mention resolver) answers with EVERY server session's title, cwd and a ready-made mention, so it travels through the same row-level filtering — only shared sessions' rows leave the box. Approval/question events ride a `$zr/events` subscription, each forwarded event judged by its `agentId`.
325
+ **The allowlist** (everything else is `forbidden-method`): session reads and writes (`session/follow`, `session/page`, `session/prompt`, `session/cancel`, `session/rename`, `session/selectModel`, `session/updateQueue`, `session/attachment`, `session/projections`), creation and forking (`session/create`, `session/fork` — results auto-share), subagents (`subagents/prompt`, `subagents/interruptByParent` (0.2.2-rc.1, judged by parent + child), `subagents/interruptByUser` (0.2.2-rc.2 onward, judged by the child alone)), attachments and @ references (`fileUploads/upload`, `fileReferences/list`, `sessionReferenceResolver/candidates`), the panel long tail (`goals/*` five verbs, `commands/list|execute`, `agentPresets/select`, `sessionFeedback/record`), file tree and previews (`workspaceFiles/list|changes|read|readBytes|stat|search`, plus `officeToPdf/render` and 0.2.2-rc.2's `officeKit/render`), terminals (`terminal/*`, including 0.2.2-rc.2's `terminal/watch`), jobs (`job/list|follow|kill`), `skills/list`, message feedback (`messageFeedback/*`), `schedule/list`, workspace session ops (`workspace/pinSession` and friends), plus four global reads: the `session/control` stream, `session/list`, the `workspace/follow` stream and `session/modelCatalog`. The first three carry no session argument — their safety is **output filtering**: every frame / every result row is narrowed by the share table before it is written, so an unshared session's row never reaches the client; in `session/list` result rows, a `parentSessionId` naming an inaccessible session is deleted as well (the rest of the row passes through unchanged). `session/modelCatalog` is likewise parameter-less and globally read-only — it is the server's model-provider directory, group and model ids plus display names only, no session data; the server empties the `failures` array in the result (the host's per-group error texts may carry endpoint or credential details the client never shows anyway). `sessionReferenceResolver/candidates` (the @-mention resolver) answers with EVERY server session's title, cwd and a ready-made mention, so it travels through the same row-level filtering — only shared sessions' rows leave the box. Approval/question events ride a `$zr/events` subscription, each forwarded event judged by its `agentId`.
326
326
 
327
327
  The `/v1/http` route is a second allowlist, keyed by **route name** (not part of the method table), under the same discipline: an unregistered route is 404 before any id is read, and a registered one is judged only along its declared session parameter. The client-side counterpart is `GET /_dsh/zen-remote/client/http/<route>` — the browser fetch wrapper redirects the `/api/changes.*` calls it intercepted (virtual session ids) here, the original id is restored before the relay round-trip, and the upstream Content-Type is held to JSON — anything else downgrades to `text/plain` + `nosniff`.
328
328
 
@@ -359,7 +359,7 @@ sudo cloudflared service install # 通了再装成常驻服务
359
359
  | `/v1/upload` | POST | 远程会话的**非图片附件**上传:请求体是原始字节流(不是 JSON);查询串按允许清单规范化重建——只保留 `sessionId`(恰好一次、过共享表,未共享 403)与 `name`(至多一次);上限 500 MiB,`Content-Length` 预检加流式计数双保险,超限 413(先回拒绝再排空尾部字节);每设备并发 8(超出 429);随后与 `/v1/http` 一样**进程内**交给宿主 `/api` 共享 handler——上传回执落进的会话与之后 `session/prompt` 解析附件引用的会话是同一个 |
360
360
  | `/v1/unshare` | POST | 子客户端主动关闭某会话的远程(只认共享表内的会话) |
361
361
 
362
- **允许清单里的方法**(其余一律 `forbidden-method`):会话读写(`session/follow`、`session/page`、`session/prompt`、`session/cancel`、`session/rename`、`session/selectModel`、`session/updateQueue`、`session/attachment`、`session/projections`)、新建与分叉(`session/create`、`session/fork`,结果自动共享)、子智能体(`subagents/prompt`、`subagents/interruptByParent`,按父会话判定)、附件与 @ 引用(`fileUploads/upload`、`fileReferences/list`、`sessionReferenceResolver/candidates`)、面板长尾(`goals/*` 五个、`commands/list|execute`、`agentPresets/select`、`sessionFeedback/record`)、文件树与预览(`workspaceFiles/list|changes|read|readBytes|stat`)、终端(`terminal/*`)、任务(`job/list|follow|kill`)、`skills/list`、消息反馈(`messageFeedback/*`)、`schedule/list`、工作区会话操作(`workspace/pinSession` 等)、以及四个全局读:`session/control` 流、`session/list`、`workspace/follow` 流与 `session/modelCatalog`。前三个不带会话参数,安全靠**输出过滤**:每一帧/每一行结果先按共享表过滤,未共享会话的行到不了客户端;`session/list` 结果行里指向不可访问会话的 `parentSessionId` 字段也会被删掉(行的其余部分照常透传)。`session/modelCatalog` 同样无参数、全局只读——它就是服务端的模型提供方目录,只有分组与模型 id、显示名,不带会话数据;服务端会把结果里的 `failures` 数组清空(宿主的逐组错误文本可能带端点或凭据细节,客户端本来也不展示它)。`sessionReferenceResolver/candidates`(@ 引用候选)的答案带**每一个**服务端会话的标题、目录与现成 mention,同样走行级输出过滤,只放行已共享会话的行。审批/提问事件经 `$zr/events` 订阅转发,逐事件按 `agentId` 判定可达性;已共享会话的运行状态事件(`api-session/status` emit)也经同一条订阅转发(T75),按 `args[0]` 里的会话 id 判定可达性——这是唯一一种被放行的 emit。
362
+ **允许清单里的方法**(其余一律 `forbidden-method`):会话读写(`session/follow`、`session/page`、`session/prompt`、`session/cancel`、`session/rename`、`session/selectModel`、`session/updateQueue`、`session/attachment`、`session/projections`)、新建与分叉(`session/create`、`session/fork`,结果自动共享)、子智能体(`subagents/prompt`、`subagents/interruptByParent`(0.2.2-rc.1,按父+子会话判定)、`subagents/interruptByUser`(0.2.2-rc.2 起,仅按子会话判定))、附件与 @ 引用(`fileUploads/upload`、`fileReferences/list`、`sessionReferenceResolver/candidates`)、面板长尾(`goals/*` 五个、`commands/list|execute`、`agentPresets/select`、`sessionFeedback/record`)、文件树与预览(`workspaceFiles/list|changes|read|readBytes|stat|search`,以及 `officeToPdf/render` 与 0.2.2-rc.2 的 `officeKit/render`)、终端(`terminal/*`,含 0.2.2-rc.2 的 `terminal/watch`)、任务(`job/list|follow|kill`)、`skills/list`、消息反馈(`messageFeedback/*`)、`schedule/list`、工作区会话操作(`workspace/pinSession` 等)、以及四个全局读:`session/control` 流、`session/list`、`workspace/follow` 流与 `session/modelCatalog`。前三个不带会话参数,安全靠**输出过滤**:每一帧/每一行结果先按共享表过滤,未共享会话的行到不了客户端;`session/list` 结果行里指向不可访问会话的 `parentSessionId` 字段也会被删掉(行的其余部分照常透传)。`session/modelCatalog` 同样无参数、全局只读——它就是服务端的模型提供方目录,只有分组与模型 id、显示名,不带会话数据;服务端会把结果里的 `failures` 数组清空(宿主的逐组错误文本可能带端点或凭据细节,客户端本来也不展示它)。`sessionReferenceResolver/candidates`(@ 引用候选)的答案带**每一个**服务端会话的标题、目录与现成 mention,同样走行级输出过滤,只放行已共享会话的行。审批/提问事件经 `$zr/events` 订阅转发,逐事件按 `agentId` 判定可达性;已共享会话的运行状态事件(`api-session/status` emit)也经同一条订阅转发(T75),按 `args[0]` 里的会话 id 判定可达性——这是唯一一种被放行的 emit。
363
363
 
364
364
  `/v1/http` 那条路由是另一张按**路由名**登记的允许清单(不是方法表的一部分),纪律相同:表外路由一律 404,登记的路由只按声明的会话参数判定;子客户端一侧的对应入口是 `GET /_dsh/zen-remote/client/http/<route>`——浏览器 fetch 包装拦下的 `/api/changes.*` 虚拟 id 请求改道到这里,还原原始 id 后经中继转发,上游 Content-Type 只认 JSON,其余一律降级为 `text/plain` + `nosniff`。
365
365
 
package/lib/intercept.js CHANGED
@@ -120,6 +120,9 @@ export const CLIENT_METHOD_FIELDS = {
120
120
  // it is restored like the parent before forwarding
121
121
  'subagents/prompt': ['request.parentSessionId', 'request.childSessionId'],
122
122
  'subagents/interruptByParent': ['parentSessionId', 'childSessionId'],
123
+ // 0.2.2-rc.2 rename: only the child id travels (no parent, no mode).
124
+ // interruptByParent stays for 0.2.2-rc.1 (npm latest).
125
+ 'subagents/interruptByUser': ['childSessionId'],
123
126
  // attachments and @ references (T31) — the top-level agentId IS the session
124
127
  // id (the gateway's agent lookup resolves it through the session-keyed
125
128
  // agent registry).
@@ -151,6 +154,8 @@ export const CLIENT_METHOD_FIELDS = {
151
154
  'workspaceFiles/read': ['workspaceFileScopeId'],
152
155
  'workspaceFiles/readBytes': ['workspaceFileScopeId'],
153
156
  'workspaceFiles/stat': ['workspaceFileScopeId'],
157
+ // 0.2.2-rc.2: official sidebar-files search bar. Same scope id.
158
+ 'workspaceFiles/search': ['workspaceFileScopeId'],
154
159
  // Office document preview (2.2.6) — client side of relay-access.ts's entry:
155
160
  // `render` is located by the SAME `workspaceFileScopeId` lookup (the session
156
161
  // id). It MUST stay identical to the server table
@@ -159,6 +164,9 @@ export const CLIENT_METHOD_FIELDS = {
159
164
  // the relay is ever asked. `generation` stays unregistered: no virtual id
160
165
  // rides it, so it answers locally (see the server table's note).
161
166
  'officeToPdf/render': ['workspaceFileScopeId'],
167
+ // 0.2.2-rc.2 successor of officeToPdf/render (same scope id, same
168
+ // PDF-bytes result). officeToPdf/render stays for 0.2.2-rc.1.
169
+ 'officeKit/render': ['workspaceFileScopeId'],
162
170
  // terminal (T41a) — opens SERVER-side by explicit user request (SPEC story
163
171
  // 45); the boundary is the standing one (shared session + paired desktop
164
172
  // client), no extra switch. The terminal and attachment ids in these calls
@@ -179,6 +187,8 @@ export const CLIENT_METHOD_FIELDS = {
179
187
  // the retain/list half locates by a plain top-level json sessionId
180
188
  'terminal/list': ['sessionId'],
181
189
  'terminal/retain': ['sessionId'],
190
+ // 0.2.2-rc.2 live Agent-terminal roster stream (same sessionId as list).
191
+ 'terminal/watch': ['sessionId'],
182
192
  // session/* — the global control stream and the unscoped list: merged with
183
193
  // the relay's filtered answer by the T23b-2 routes below (never refused on
184
194
  // arguments — a stray id in them is unowned data).
@@ -242,7 +252,7 @@ export const CLIENT_METHOD_FIELDS = {
242
252
  * Everything else — `session/prompt|cancel|rename|selectModel|updateQueue`
243
253
  * (send, cancel, rename, model switch, inbox mutation), `session/create` /
244
254
  * `session/fork` (T31: new sessions on the server), `subagents/prompt` /
245
- * `subagents/interruptByParent` (T31: prompt/interrupt a remote subagent),
255
+ * `subagents/interruptByParent` / `subagents/interruptByUser` (T31: prompt/interrupt a remote subagent),
246
256
  * `fileUploads/upload` (T31: attachments into a remote session), `job/kill`,
247
257
  * `messageFeedback/put|delete`, the workspace session-list mutations, and
248
258
  * the T41a mutations (`goals/edit|pause|resume|clear`, `commands/execute`,
@@ -289,15 +299,20 @@ export const REMOTE_READ_METHODS = new Set([
289
299
  'workspaceFiles/read',
290
300
  'workspaceFiles/readBytes',
291
301
  'workspaceFiles/stat',
302
+ // 0.2.2-rc.2: official sidebar-files search — a pure Host-side walk.
303
+ 'workspaceFiles/search',
292
304
  // officeToPdf (2.2.6): a pure read — it converts one already-readable file
293
305
  // into PDF bytes on the server (writing only its own scratch space, like
294
306
  // any preview). Offline it answers `remote-offline`, never the write
295
- // message.
307
+ // message. officeKit/render is the 0.2.2-rc.2 successor (same shape).
296
308
  'officeToPdf/render',
309
+ 'officeKit/render',
297
310
  'terminal/environment',
298
311
  'terminal/shells',
299
312
  'terminal/list',
300
313
  'terminal/retain',
314
+ // 0.2.2-rc.2: live Agent-terminal roster (WebTerminalInfo[]); read-only.
315
+ 'terminal/watch',
301
316
  ]);
302
317
  /** Whether `endpoint` (a client-table method) is a remote WRITE: anything
303
318
  * the read whitelist does not name (T34-fix). */
@@ -727,13 +742,13 @@ function restoreRegisteredFields(fields, args) {
727
742
  * `rewriteFrame` untouched.
728
743
  * - everything else in the table (`session/prompt|cancel|rename|updateQueue|
729
744
  * attachment`, `job/kill`, `skills/list`, `schedule/list`, `subagents/prompt`
730
- * (`{messageId}`), `subagents/interruptByParent` (`{accepted}`),
745
+ * (`{messageId}`), `subagents/interruptByParent` / `subagents/interruptByUser` (`{accepted}`),
731
746
  * `fileUploads/upload` (receipt + attachment ids, not session ids),
732
747
  * `fileReferences/list` (`{path,kind}` rows), `goals/get|edit|pause|resume|
733
748
  * clear` (GoalView/GoalRef ids are GOAL ids), `commands/list|execute`
734
749
  * (`commandId` is a command id), `agentPresets/select` (a preset id
735
- * string), `workspaceFiles/list|read|readBytes|stat` (paths, versions,
736
- * text/bytes), `terminal/*` (terminal ids, attachment ids, controller ids
750
+ * string), `workspaceFiles/list|read|readBytes|stat|search` (paths, versions,
751
+ * text/bytes, search matches), `terminal/*` (terminal ids, attachment ids, controller ids
737
752
  * — client-side identity, never session ids)) has NO
738
753
  * session id in its result — verified. THREE of them still get a
739
754
  * T52 rewrite of the model-catalog PROVIDER ids their results carry (never