dsh-code-server-app 0.3.55 → 0.3.56

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
@@ -311,6 +311,17 @@ of passing stale data off as fresh).
311
311
  request/response round trip every 600 ms. Polling also buys two useful properties: it is idempotent (a dropped event
312
312
  only costs one notification — the data always lives in the editor) and the cached state is inherently fresh.
313
313
 
314
+ **Event delivery contract (fixed in 0.3.56)**: `agent-edit` notifications live in a ring buffer and are fetched with
315
+ `since=<cursor>`; **the sequence is monotonic for the lifetime of one bridge endpoint (host process)**, the client's
316
+ cursor only moves forward, and a new endpoint (the pipe name carries the host pid) re-aligns with `since=0`.
317
+ From 0.3.9 through 0.3.55 the host called `reset()` after **every** `/sync`, and that call rewound the sequence
318
+ counter — so once the extension had seen `seq=1`, every later event was numbered 1 again and `seq > since` never
319
+ held: **at most one event per IDE session was ever delivered** (exactly the "a diff rarely shows up, and when it does
320
+ its old side is empty" the user reported). Now `reset()` only clears the buffer, `/sync` also returns `lastSeq` (the
321
+ high-water mark), and the extension uses it to notice a cursor that ran ahead and re-align to 0 on its own.
322
+ Regression: the "push → take → clear, twice" case in `scripts/test-bridge-routes.mjs`. This remains a
323
+ **notification channel**, not a reliable queue: 64 entries, oldest dropped, a lost event costs one notification.
324
+
314
325
  ### Security model (five invariants; read before touching `lib/bridge.mjs`)
315
326
 
316
327
  The token lives in `<extensionsDir>/.dshcs-bridge/bridge.json`, **readable by any process of the same local
package/README.md CHANGED
@@ -296,6 +296,15 @@ host 反向请求不到它。所以编辑器状态只能在扩展主动发起的
296
296
  **为什么不用 SSE/WebSocket**:扩展宿主里没有 HTTP 服务器,而桥的形态是"每 600ms 一趟请求/响应"。
297
297
  轮询给了两条好性质:幂等(丢一次事件只是少一次提示,数据本身永远在编辑器里),以及状态天然最新(每趟都刷新)。
298
298
 
299
+ **事件的送达契约(0.3.56 修正)**:`agent-edit` 这类提示走环形缓冲,靠 `since=<游标>` 取"比我新的那些";
300
+ **seq 在一次桥端点(宿主进程)生命周期内单调递增**,客户端游标只增不减,端点换了(管道名里带宿主 pid)
301
+ 才用 `since=0` 重新对齐。0.3.9–0.3.55 的宿主在**每趟** `/sync` 后都调用 `reset()`,而它当时会把 seq 归零
302
+ ⇒ 扩展第一次收到 `seq=1` 后,新事件又从 1 开始编号,`seq > since` 永远不成立 ⇒ **每个 IDE 会话最多只送达
303
+ 第一条事件**(用户看到的现象就是"偶尔才有一条 diff,而那条还是空 old")。现在:`reset()` 只清缓冲、不回退
304
+ seq,并且 `/sync` 回应里带 `lastSeq`(高水位),扩展据此自查游标是否超前(超前就退回 0 重新对齐,自愈)。
305
+ 回归:`scripts/test-bridge-routes.mjs` 里有一条按真实调用顺序复刻"推送→取→清空"两次的用例。
306
+ 注意这仍是**提示通道**,不是可靠队列:缓冲 64 条,超出丢最旧,漏一次只是少一次提示。
307
+
299
308
  ### 安全模型(五条不变量,改 `lib/bridge.mjs` 之前先读)
300
309
 
301
310
  桥的令牌写在 `<extensionsDir>/.dshcs-bridge/bridge.json`(**对本机同用户进程可读**),所以:
@@ -488,6 +497,8 @@ pnpm test:vendored # 重打包表 ↔ 插件依赖表一致(无 npm:
488
497
  pnpm test:dsh-resolve # 部署位置表:各平台全局装布局(npm --prefix / nvm / pnpm global / %APPDATA%)都能找到 DSH 部署
489
498
  pnpm test:child-node # Electron 宿主(桌面版)下给 IDE 子进程挑真 Node:候选顺序 / 剥离 ELECTRON_RUN_AS_NODE / 找不到时如实退回
490
499
  pnpm test:package-files # 发布物白名单守卫:files 里的模块**递归**import 到的本地文件也必须在 files 里(0.3.53 漏 lib/child-node.mjs 的事故)
500
+ # 以及反向:files 每条都得存在 —— **打包期生成物除外**(vendor/VENDOR.json 在
501
+ # .gitignore 里,由 prepack 的 vendor:vscode 生成;ci.yml 不下树,干净克隆上它必然不存在)
491
502
  pnpm test:installed # 安装冒烟:对**已装进 profile 的产物**做断言(默认 <DSH_HOME>/profiles/web)
492
503
  # files 白名单每条都在 / 重打包包在当前平台齐全 / 原生模块无缺失 /
493
504
  # 已安装副本能 import / 树在位 —— 仓库回归看不出这一类
@@ -279,6 +279,12 @@ function createClient(options) {
279
279
  for (const event of events) {
280
280
  if (Number.isSafeInteger(event.seq) && event.seq > since) since = event.seq;
281
281
  }
282
+ // 自查:宿主报的 seq 高水位若**低于**我的游标,说明两边序号错位了(宿主重启过,或我手里这份
283
+ // 游标是上一任宿主留下的 —— adopt 场景里 IDE 的 pid 没变,restore() 就会把旧游标装回来)。
284
+ // 不对齐的话 `seq > since` 永远为假 ⇒ 事件永久收不到(0.3.55 及以前的宿主还每趟把 seq 归零,
285
+ // 症状就是"每个 IDE 会话只收到第一条 diff")。退回 0 重新对齐即可自愈。
286
+ const lastSeq = body !== null && Number.isSafeInteger(body.lastSeq) ? body.lastSeq : null;
287
+ if (lastSeq !== null && lastSeq < since) since = 0;
282
288
  // 0.2.3 起面板像 DSH 对话一样显示内容,数据走这三个字段(见 host 侧 lib/bridge-thread.mjs
283
289
  // 与 lib/bridge-approval.mjs):
284
290
  // - thread:被面板观看的会话的**新内容**条目(旧版宿主没有这个字段 → 面板明确报错);
@@ -2,7 +2,7 @@
2
2
  "name": "dshcs-editor-bridge",
3
3
  "displayName": "DSH Editor Bridge",
4
4
  "description": "只读地把编辑器状态(未保存缓冲区、诊断、活动选区)提供给 DSH;右键提问打开 DSH 页面里的悬浮对话对话框(0.2.5;老宿主退回编辑器面板),正文/思考/上下文都用 DSH 官方渲染器,并可就地回答授权请求。由 dsh-code-server-app 插件安装,可禁用。",
5
- "version": "0.2.16",
5
+ "version": "0.2.17",
6
6
  "publisher": "dsh-code-server-app",
7
7
  "license": "MIT",
8
8
  "private": true,
@@ -12,7 +12,7 @@
12
12
  | react | 18.3.1 | MIT |
13
13
  | react-dom | 18.3.1 | MIT |
14
14
 
15
- 生成时间:2026-09-19T09:11:10.701Z
15
+ 生成时间:2026-09-19T09:45:51.309Z
16
16
  渲染器:@deepseek-ai/dsh-client-ui-primitives@0.1.6-alpha.1(DSH 部署的界面版本:未知)
17
17
  设计令牌:@deepseek-ai/dsh-client-ui-theme@0.1.6-alpha.1(11 段)
18
18
  KaTeX:已打包(公式排版与 DSH 界面一致)
package/lib/bridge.mjs CHANGED
@@ -252,7 +252,13 @@ export async function callBridge(target, route, options = {}) {
252
252
  * 事件环形缓冲。host 侧唯一的事件源是 `ctx.on('tools/result')`。
253
253
  *
254
254
  * 语义:**不是可靠队列**。60/64 条只是让扩展在下一次轮询时"追上",超出即丢最旧的;
255
- * host 重启后 seq 归零,扩展用 `since=0` 重新对齐(不承诺断点续传)。
255
+ * **seq 在一次桥端点(宿主进程)生命周期内必须单调**,客户端(扩展)的游标只增不减 ——
256
+ * 端点换了(管道名含宿主 pid)客户端用 `since=0` 重新对齐,不承诺断点续传。
257
+ *
258
+ * **0.3.56 修的坑(别再退回去)**:`reset()` 以前把 `nextSeq` 也归零,而 `/sync` 每趟都调用它 ⇒
259
+ * 扩展第一次收到 `seq=1` 后游标就是 1,之后每条新事件又从 1 开始编号,`since(1)` 的
260
+ * `e.seq > 1` 永远为假 ⇒ **每个 IDE 会话最多只送达第一条事件**(0.3.9 起就带着这个缺陷,
261
+ * 用户"只偶尔看到 diff、且那条还是空 old"就是这个现象)。清空缓冲时**不许动 seq**。
256
262
  */
257
263
  export function createEventRing(max = EVENT_RING_MAX) {
258
264
  /** @type {{seq: number, kind: string, time: number}[]} */
@@ -280,10 +286,15 @@ export function createEventRing(max = EVENT_RING_MAX) {
280
286
  size() {
281
287
  return items.length;
282
288
  },
283
- /** 清空(IDE 进程换掉时调用)。 */
289
+ /**
290
+ * 清空缓冲(**不重置 seq**)。
291
+ *
292
+ * 调用点:`/sync` 每趟取完事件后清空(事件是提示,取过就不必留着 —— 否则客户端重新对齐时
293
+ * 会把一堆陈旧提示重放成一片 diff 窗口),以及 IDE 实例换掉时的 `clearBridgeRuntime`。
294
+ * 两类调用都**不能**让 seq 回退,理由见文件头。
295
+ */
284
296
  reset() {
285
297
  items = [];
286
- nextSeq = 1;
287
298
  },
288
299
  };
289
300
  }
package/lib/index.js CHANGED
@@ -2014,7 +2014,13 @@ export async function apply(ctx, config) {
2014
2014
  since = 0;
2015
2015
  }
2016
2016
  const events = bridgeEvents.since(since);
2017
+ // 取完就清空(事件是提示,取过不必留着 —— 否则客户端重新对齐时会把陈旧提示重放成一片 diff 窗口)。
2018
+ // **注意 `reset()` 不再回退 seq**(0.3.56 修):以前它把 seq 归零,而这里每趟都调 ⇒ 扩展的游标
2019
+ // (单调递增)从此永远大于新 seq,`seq > since` 全被过滤 ⇒ 每个 IDE 会话只送达第一条事件。
2017
2020
  bridgeEvents.reset();
2021
+ // 把当前最大 seq 一并回去:客户端据此自查"我的游标是不是跑到宿主前面去了"(跨版本升级、
2022
+ // 接管了别的宿主留下的游标文件等),发现后退回 since=0 重新对齐,免得永久失聪。
2023
+ const lastSeq = bridgeEvents.lastSeq();
2018
2024
  // 面板声明它要看哪些会话(数组;空 = 关掉了)。据此对齐订阅 + 判断"面板在不在看"
2019
2025
  // (授权拦截只在面板看着的时候才抢答,否则一律交给官方链路)。
2020
2026
  const watch = Array.isArray(body.watch) ? body.watch.filter((id) => typeof id === 'string' && id !== '') : [];
@@ -2062,6 +2068,8 @@ export async function apply(ctx, config) {
2062
2068
  return jsonResponse({
2063
2069
  ok: true,
2064
2070
  events,
2071
+ // 事件序号的高水位:扩展用它自查游标是否超前(超前 ⇒ 退回 since=0 重新对齐,避免永久失聪)。
2072
+ lastSeq,
2065
2073
  // thread 缺省 = 没变化(扩展沿用上一份);threadRev 用来区分"没变化"与"旧宿主没有这个字段"。
2066
2074
  threadRev,
2067
2075
  thread: threadChanged ? snapshot : undefined,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.55",
4
- "description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Since 0.3.0 the bundle also ships an editor bridge (assets/extensions/dshcs-editor-bridge): a read-only channel between the in-tree VS Code extension host and DSH, giving the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and letting editor gestures drive the session. Since 0.3.22 the ask panel renders that session's new content with DSH's own Markdown renderer (pinned to the deployed DSH UI version); since 0.3.23 it is a full-width dialog with collapsible thinking rows and can answer approval requests in place — the bridge's only non-read-only route (POST /approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; panel-first for 5 minutes, handed back to the official card the moment the panel closes). The tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so the product's own produced-file chips, delivered-file previews and inline prose mentions open in the workbench. The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads it. Opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen). Following a workspace switch is lightweight: the workbench re-navigates with the new ?folder= and the IDE process is not restarted (since 0.2.12). Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions get a single upgrade notice on the settings page and no other UI. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). Since 0.3.55 the diff the bridge opens always has the real pre-write text on the left, even for files that are not open in the editor: the host reads the execution-local result.value.before in tools/post-execute (the durable tools/result projection deliberately omits value) into a bounded snapshot cache and the extension fetches it by opaque key over the read-only GET /old route; paths are resolved against the session cwd first, so relative file_path arguments are no longer dropped by the editor's workspace check. No code-server Node layer, no argon2, no C++ toolchain.",
3
+ "version": "0.3.56",
4
+ "description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Since 0.3.0 the bundle also ships an editor bridge (assets/extensions/dshcs-editor-bridge): a read-only channel between the in-tree VS Code extension host and DSH, giving the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and letting editor gestures drive the session. Since 0.3.22 the ask panel renders that session's new content with DSH's own Markdown renderer (pinned to the deployed DSH UI version); since 0.3.23 it is a full-width dialog with collapsible thinking rows and can answer approval requests in place — the bridge's only non-read-only route (POST /approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; panel-first for 5 minutes, handed back to the official card the moment the panel closes). The tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so the product's own produced-file chips, delivered-file previews and inline prose mentions open in the workbench. The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads it. Opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen). Following a workspace switch is lightweight: the workbench re-navigates with the new ?folder= and the IDE process is not restarted (since 0.2.12). Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions get a single upgrade notice on the settings page and no other UI. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). Since 0.3.55 the diff the bridge opens always has the real pre-write text on the left, even for files that are not open in the editor: the host reads the execution-local result.value.before in tools/post-execute (the durable tools/result projection deliberately omits value) into a bounded snapshot cache and the extension fetches it by opaque key over the read-only GET /old route; paths are resolved against the session cwd first, so relative file_path arguments are no longer dropped by the editor's workspace check. Since 0.3.56 the bridge actually delivers every diff: the event ring's sequence is monotonic for the lifetime of a host process (0.3.9-0.3.55 rewound it after every /sync, so at most one agent-edit per IDE session was ever delivered), /sync returns lastSeq, and the extension re-aligns its cursor by itself when it notices it ran ahead. No code-server Node layer, no argon2, no C++ toolchain.",
5
5
  "homepage": "https://github.com/jinsiyu/dsh-code-server-app",
6
6
  "repository": {
7
7
  "type": "git",
@@ -3,7 +3,7 @@
3
3
  "vscodeVersion": "1.137.0",
4
4
  "productPath": "stable-b11dabdaca0d3369986975be285db92c8795cea5",
5
5
  "layout": "vscode-only",
6
- "preparedAt": "2026-09-19T09:11:10.218Z",
6
+ "preparedAt": "2026-09-19T09:45:50.744Z",
7
7
  "source": "registry",
8
8
  "node": "v24.20.0",
9
9
  "platform": "linux",