dsh-code-server-app 0.3.10 → 0.3.12

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
@@ -206,6 +206,12 @@ only the editor knows, and lets editor gestures drive the current session.
206
206
  - **Everything is read-only**: the bridge never writes files, applies edits, or runs commands. The agent's writes
207
207
  still go through its own `fs` tools; the bridge only *knows about* them.
208
208
  - Status bar shows `$(plug) DSH` while connected (click it for the log in the "DSH Editor Bridge" output channel).
209
+ - **The extension ships as a built-in** (fixed in 0.3.12): `dshcs-editor-bridge` is installed into
210
+ `<tree>/lib/vscode/extensions/` next to `dshcs-open-file`. 0.3.0–0.3.11 installed it as a *user* extension
211
+ instead, and the VS Code server marks any extension that sits in the user extensions folder but in no profile
212
+ manifest as removed (`.obsolete`, log line `Marked extension as removed`) and then skips it forever — re-marked on
213
+ every start, so **the bridge never reported any state**. To turn the bridge off use the plugin setting
214
+ `editorBridge=false` (no mount, no tools) rather than uninstalling the extension from the Extensions view.
209
215
 
210
216
  ### The channels (since 0.3.9 they live on DSH's webServer under `/code-server-bridge`)
211
217
 
@@ -215,6 +221,8 @@ extension → host POST /code-server-bridge/ask push an editor question in
215
221
  extension → host GET /code-server-bridge/health unauthenticated liveness probe
216
222
  extension → host POST /code-server-bridge/event extension reports open/close etc. (host log tail)
217
223
  host → extension <extensionsDir>/.dshcs-bridge/bridge.json base URL + token, re-read by the extension every 5s
224
+ (the directory is announced via the host-injected `DSHCS_EXTENSIONS_DIR` — the extension lives in
225
+ the built-in tree now, so it cannot derive it from its own path)
218
226
  ```
219
227
 
220
228
  > **Why not under `/api` (fixed in 0.3.9)**: Connection puts a Host/Origin/cookie fence on `/api`
@@ -670,6 +678,10 @@ What remains on the plugin side:
670
678
  line explains it). **File opening is unaffected**: it uses the signal file and works regardless of mode.
671
679
  Up to 0.3.7 the bridge was registered under `/api/code-server/bridge/*` and was killed by Connection's cookie
672
680
  fence (401) — that was a bug.
681
+ - **`/code-server-bridge/health`'s `bridge` field does not mean the extension is running** (clarified in 0.3.12):
682
+ it only says the bridge *target* is configured. Whether the extension actually runs shows up in the exthost log
683
+ or by simply calling `editor_context` — 0.3.0–0.3.11 sat in the state "health says bridge:true, extension never
684
+ loaded" (cause above: the user-level install was marked `.obsolete`).
673
685
  - **Bridged state can lag by up to 600 ms**, and the tools say "stale" rather than serving data older than 10 s.
674
686
  - **Unsaved buffers are reported, not taken over.** The agent still edits via its own `fs` tools, i.e. against
675
687
  disk. What the bridge adds is a notice *before* writing a dirty file, a diff *after*, and a warning instead of
package/README.md CHANGED
@@ -202,6 +202,11 @@ DSH 用**资源地址**命名文件,`openFile` 只负责把地址交给右侧栏
202
202
  桥只是"知道它写了什么"。
203
203
  - 编辑器侧的入口还有状态栏的 `$(plug) DSH`(连通时显示,点击打开日志),日志在输出面板
204
204
  「DSH Editor Bridge」里 —— 出问题时先看它。
205
+ - **扩展装在内置目录**(0.3.12 修正):`dshcs-editor-bridge` 与 `dshcs-open-file` 一样装进
206
+ `<树>/lib/vscode/extensions/`。0.3.0–0.3.11 装的是用户级目录,而 VS Code 服务端会把
207
+ "在用户扩展目录里、不在任何 profile 清单里"的扩展标进 `.obsolete`(日志 `Marked extension as removed`)
208
+ 并永远跳过它 —— 每一轮启动都再标一次,**桥因此从来没有上报过状态**。
209
+ 要关掉桥请用插件设置 `editorBridge=false`(不挂桥、不注册工具),不要再指望在扩展视图里卸载它。
205
210
 
206
211
  ### 三条通道(0.3.9 起走 DSH webServer 的 `/code-server-bridge`)
207
212
 
@@ -211,6 +216,7 @@ DSH 用**资源地址**命名文件,`openFile` 只负责把地址交给右侧栏
211
216
  扩展 → host GET /code-server-bridge/health 无鉴权探活(便于重启后一眼确认)
212
217
  扩展 → host POST /code-server-bridge/event 扩展上报打开/关闭文件等(进 host 日志尾)
213
218
  host → 扩展 <extensionsDir>/.dshcs-bridge/bridge.json base URL + 令牌(扩展每 5s 重读)
219
+ (目录由 host 注入的 `DSHCS_EXTENSIONS_DIR` 告知 —— 扩展装在内置目录里,自己推不出来)
214
220
  ```
215
221
 
216
222
  > **为什么不在 `/api` 下(0.3.9 修正)**:Connection 给 `/api` 装了 Host/Origin/cookie fence
@@ -651,6 +657,9 @@ desktop profile 由 `apps/desktop-host` 把 `/api/*` 交给同一个 `createShar
651
657
  (status 的 `bridge.supported=false`,host 不写 `bridge.json`,日志里说明一次)。
652
658
  **文件打开不受影响**:它走信号文件,与 serve 模式和 webServer 都无关。
653
659
  0.3.7 及以前把桥挂在 `/api/code-server/bridge/*`,被 Connection 的 cookie fence 401 挡死 —— 那是个 bug。
660
+ - **`/code-server-bridge/health` 的 `bridge` 字段不代表扩展在跑**(0.3.12 澄清):它只表示"桥的目标已就绪"。
661
+ 扩展是否真的在跑,看 exthost 日志里有没有它的激活记录,或直接用 `editor_context` 试一次 ——
662
+ 0.3.0–0.3.11 就是"health 说 bridge:true、扩展却从没被加载"的状态(原因见上:用户级安装被标 `.obsolete`)。
654
663
  - **桥的状态有最多 600ms 滞后**:扩展每 600ms 推一次;超过 10s 没更新时工具会明说"状态已过期"
655
664
  而不是拿旧数据当新数据(例如用户在 IDE 里关掉面板之后)。
656
665
  - **未保存缓冲区是"上报"而不是"接管"**:agent 仍然通过它自己的 `fs` 工具按磁盘内容编辑。
@@ -74,12 +74,6 @@ function log(message) {
74
74
  if (output !== null) output.appendLine(`[${new Date().toISOString()}] ${message}`);
75
75
  }
76
76
 
77
- /** 取扩展所在目录(桥配置与它同级:extensionsDir/.dshcs-bridge/)。 */
78
- function extensionDir() {
79
- // __dirname = <extensionsDir>/dshcs-editor-bridge/lib → 上溯两级即 extensionsDir
80
- return path.resolve(__dirname, '..', '..');
81
- }
82
-
83
77
  /** 是否落在某个工作区文件夹内(不能把 workspaceFolder 之外的路径喂给 DSH)。 */
84
78
  function isInWorkspace(fsPath) {
85
79
  try {
@@ -462,7 +456,10 @@ function activate(context) {
462
456
  vscode.workspace.registerTextDocumentContentProvider('dshcs-old', oldSideProvider),
463
457
  );
464
458
 
465
- client = createClient({ extensionsDir: extensionDir() });
459
+ // 配置目录由 bridge-client 统一解析(host 注入的 DSHCS_EXTENSIONS_DIR → 自身位置反推)
460
+ // **不要在这里自己算**:0.3.0–0.3.11 那段 `resolve(__dirname,'..','..')` 比 <extensionsDir>
461
+ // 还高一级,任何布局都读不到 bridge.json ⇒ 桥一直休眠(见 docs 第 18 节)。
462
+ client = createClient();
466
463
  if (client.refresh() === null) {
467
464
  log('未找到桥配置(休眠)。DSH 插件启用编辑器桥并启动 IDE 后,这里会自动连上。');
468
465
  } else {
@@ -36,15 +36,32 @@ const CONFIG_REREAD_MS = 5000;
36
36
 
37
37
  const TOKEN_RE = /^[0-9A-Za-z_-]{16,128}$/;
38
38
 
39
- /** 桥配置路径;`extensionsDir` 可由调用方给(测试用),默认从 __dirname 反推。 */
39
+ /** 桥配置所在目录(=`<extensionsDir>/.dshcs-bridge`)
40
+ *
41
+ * 取值顺序(0.3.12 修正 —— 这里以前写错了,是"桥永远休眠"的另一半原因):
42
+ * 1. **host 注入的 `DSHCS_EXTENSIONS_DIR`**(launcher 的 env 会被扩展宿主继承,与 dshcs-open-file
43
+ * 用 `DSHCS_OPEN_FILE_SIGNAL` 是同一套做法)。0.3.12 起桥扩展装在**内置**目录(树里),
44
+ * 与 <extensionsDir> 不再同级,只能靠 host 告诉它;
45
+ * 2. 调用方显式给的 `extensionsDir`(测试用);
46
+ * 3. 从本文件反推:`<extensionsDir>/dshcs-editor-bridge/lib/` 上溯两级。
47
+ * —— 注意 `extension.js` 曾自己算过一次,算成了三级(比 <extensionsDir> 还高一级),
48
+ * 即使按老的用户级布局也读不到配置。计算只留在这里一处。
49
+ */
50
+ function defaultExtensionsDir() {
51
+ const fromEnv = process.env.DSHCS_EXTENSIONS_DIR;
52
+ if (typeof fromEnv === 'string' && fromEnv.trim() !== '') return fromEnv;
53
+ return path.resolve(__dirname, '..', '..');
54
+ }
55
+
56
+ /** 桥配置路径;`extensionsDir` 可由调用方给(测试用),默认见 `defaultExtensionsDir`。 */
40
57
  function bridgeFile(extensionsDir) {
41
- const dir = extensionsDir ?? path.resolve(__dirname, '..', '..');
58
+ const dir = extensionsDir ?? defaultExtensionsDir();
42
59
  return path.join(dir, BRIDGE_DIRNAME, BRIDGE_FILENAME);
43
60
  }
44
61
 
45
62
  /** 扩展自己的小状态文件(since 游标),与桥配置同目录。 */
46
63
  function stateFile(extensionsDir) {
47
- const dir = extensionsDir ?? path.resolve(__dirname, '..', '..');
64
+ const dir = extensionsDir ?? defaultExtensionsDir();
48
65
  return path.join(dir, BRIDGE_DIRNAME, STATE_FILENAME);
49
66
  }
50
67
 
@@ -236,6 +253,7 @@ module.exports = {
236
253
  CONFIG_REREAD_MS,
237
254
  REQUEST_TIMEOUT_MS,
238
255
  BridgeError,
256
+ defaultExtensionsDir,
239
257
  bridgeFile,
240
258
  stateFile,
241
259
  readBridgeConfig,
package/lib/index.js CHANGED
@@ -185,13 +185,13 @@ function launcherPath() {
185
185
  /** 扩展安装目标:`placement: 'builtin'` 时优先 VS Code「内置扩展」目录 = <树>/lib/vscode/extensions
186
186
  * (位于程序内置目录的扩展被 VS Code 视为内置——用户视图显示为"内置",**不能卸载**);
187
187
  * `placement: 'user'` 时只用 --extensions-dir(用户级扩展,可被用户禁用/卸载)。
188
- * 返回 { dst, builtin }——builtin=true 时是核心路径;找不到树时回退用户级。 */
189
- function extensionTarget(extensionsDir, name, placement = 'builtin') {
188
+ * 返回 { dst, builtin }——builtin=true 时是核心路径;找不到树时回退用户级。
189
+ * `treeRoot` 可注入(测试用):默认 `vsRoot()`,注入后测试不会碰真实树。 */
190
+ function extensionTarget(extensionsDir, name, placement = 'builtin', treeRoot = vsRoot()) {
190
191
  if (placement === 'builtin') {
191
192
  try {
192
- const root = vsRoot();
193
- if (root !== null) {
194
- const vscodeExt = path.join(root, 'lib', 'vscode', 'extensions');
193
+ if (treeRoot !== null) {
194
+ const vscodeExt = path.join(treeRoot, 'lib', 'vscode', 'extensions');
195
195
  if (fs.existsSync(vscodeExt)) {
196
196
  return { dst: path.join(vscodeExt, name), builtin: true };
197
197
  }
@@ -203,12 +203,19 @@ function extensionTarget(extensionsDir, name, placement = 'builtin') {
203
203
 
204
204
  /** 树内自带扩展清单。
205
205
  * - dshcs-open-file:host 信号文件 → VS Code 打开文件(可带行号)。**内置**(用户不需要看到它)。
206
- * - dshcs-editor-bridge(0.3.0):编辑器桥的扩展侧。**必须用户级** —— 它是可选能力,
207
- * 用户得有办法一键禁用;内置扩展按 VS Code 语义不能禁用。代价是用户可能卸载它,
208
- * 这没关系:host 侧本来就按"桥不可用"降级。 */
206
+ * - dshcs-editor-bridge(0.3.0):编辑器桥的扩展侧。**必须内置**(0.3.12 修正;0.3.0–0.3.11 是用户级)。
207
+ * 为什么"直接往 <extensions-dir> 拷目录"装不进用户级:VS Code 服务端启动时
208
+ * `ExtensionsWatcher.initialize()` `deleteExtensionsNotInProfiles()` 会把
209
+ * 「在用户扩展目录里、但不在任何 profile 的 extensions.json 里」的扩展写进 `<extensions-dir>/.obsolete`
210
+ * (服务端日志 `Marked extension as removed dsh-code-server-app.dshcs-editor-bridge-0.1.0`),
211
+ * 扫描器从此不再看它;下一轮它自然又不在 profile 里 ⇒ **每启动一次就再标一次,自锁**。
212
+ * 实测(本机 profile,10 次启动 10 条标记)与源码位置:`out/server-main.js` 的
213
+ * `ExtensionsWatcher#initialize` / `ExtensionsScannerService#setExtensionsForRemoval`。
214
+ * 内置目录(<树>/lib/vscode/extensions,与 dshcs-open-file 同处)不参与 profile 机制。
215
+ * 用户的"关掉它"由插件设置 `editorBridge=false`(不挂桥、不注册工具)承担,不再依赖 VS Code 的卸载。 */
209
216
  const BUNDLED_EXTENSIONS = [
210
217
  { name: 'dshcs-open-file', placement: 'builtin' },
211
- { name: 'dshcs-editor-bridge', placement: 'user' },
218
+ { name: 'dshcs-editor-bridge', placement: 'builtin' },
212
219
  ];
213
220
 
214
221
  /** 递归列出扩展源目录里的文件(相对路径,posix 分隔)。
@@ -233,19 +240,56 @@ export function listExtensionFiles(srcDir) {
233
240
  return out;
234
241
  }
235
242
 
243
+ /** 清掉 VS Code 写给本插件扩展的"已移除"标记(`<extensions-dir>/.obsolete`)。
244
+ * 机制:`ExtensionsWatcher.initialize()` 把"在用户扩展目录里、不在任何 profile 里"的扩展标成 removed,
245
+ * 扫描器随后永远跳过它 ⇒ **自锁**(见 BUNDLED_EXTENSIONS 的注释)。0.3.0–0.3.11 的桥扩展就卡在这里:
246
+ * 文件装全了、路由通了,扩展却一次都没被加载(`editor_context` 永远报"扩展还没有上报状态")。
247
+ * 别的扩展的条目原样保留;清空后直接删掉文件(VS Code 需要时会自己重建)。
248
+ * 导出供测试直接验。 */
249
+ export function clearObsoleteMarkers(extensionsDir, ids) {
250
+ const file = path.join(extensionsDir, '.obsolete');
251
+ if (ids.length === 0 || !fs.existsSync(file)) return [];
252
+ let map;
253
+ try {
254
+ map = JSON.parse(fs.readFileSync(file, 'utf8'));
255
+ } catch {
256
+ return [];
257
+ }
258
+ if (map === null || typeof map !== 'object' || Array.isArray(map)) return [];
259
+ const cleared = [];
260
+ for (const key of Object.keys(map)) {
261
+ if (ids.some((id) => key === id || key.startsWith(`${id}-`))) {
262
+ delete map[key];
263
+ cleared.push(key);
264
+ }
265
+ }
266
+ if (cleared.length === 0) return [];
267
+ try {
268
+ if (Object.keys(map).length === 0) fs.rmSync(file, { force: true });
269
+ else fs.writeFileSync(file, JSON.stringify(map), 'utf8');
270
+ } catch {
271
+ // 清标记失败不该拦住安装:扩展仍会被扫描器跳过,但文件是新的(下次启动还会再试)
272
+ }
273
+ return cleared;
274
+ }
275
+
236
276
  /** 内置扩展安装:每次启动调用 —— 缺失**或内容有变化**即同步(自愈,且插件升级后能更新已装的旧副本)。
237
277
  * 源目录里已不存在的文件会从目标里删掉(否则旧版本的 `lib/` 会一直留着)。
238
- * 同时清理"放错位置"的旧副本(用户级/内置两份同时存在会让 VS Code 打架)
239
- * 导出供测试直接调(apply 期不装扩展,只有 start 才装) */
240
- export function installBundledExtensions(extensionsDir, userDataDir) {
278
+ * 同时清理"放错位置"的旧副本(用户级/内置两份同时存在会让 VS Code 打架),并清掉 `.obsolete` 自锁标记。
279
+ * 导出供测试直接调(apply 期不装扩展,只有 start 才装);`options.treeRoot` 供测试注入假树。 */
280
+ export function installBundledExtensions(extensionsDir, userDataDir, options = {}) {
241
281
  const here = path.dirname(fileURLToPath(import.meta.url));
282
+ const treeRoot = options.treeRoot === undefined ? vsRoot() : options.treeRoot;
283
+ const managedIds = [];
242
284
  for (const ext of BUNDLED_EXTENSIONS) {
243
285
  try {
244
286
  const src = path.join(here, '..', 'assets', 'extensions', ext.name);
245
287
  const manifest = path.join(src, 'package.json');
246
288
  if (!fs.existsSync(manifest)) continue;
289
+ const manifestJson = JSON.parse(fs.readFileSync(manifest, 'utf8'));
290
+ managedIds.push(`${manifestJson.publisher ?? 'dsh-code-server-app'}.${manifestJson.name ?? ext.name}`);
247
291
  const files = listExtensionFiles(src);
248
- const target = extensionTarget(extensionsDir, ext.name, ext.placement);
292
+ const target = extensionTarget(extensionsDir, ext.name, ext.placement, treeRoot);
249
293
  const dst = target.dst;
250
294
  const stale = files.filter((name) => {
251
295
  const to = path.join(dst, name);
@@ -280,8 +324,8 @@ export function installBundledExtensions(extensionsDir, userDataDir) {
280
324
  };
281
325
  walkDst('');
282
326
  }
283
- // 清理放错位置的副本:dshcs-open-file 的旧用户级副本 / 编辑器桥的错误内置副本。
284
- const wrong = extensionTarget(extensionsDir, ext.name, ext.placement === 'builtin' ? 'user' : 'builtin');
327
+ // 清理放错位置的副本:编辑器桥的旧用户级副本(0.3.11 及更早)/ dshcs-open-file 的旧用户级副本。
328
+ const wrong = extensionTarget(extensionsDir, ext.name, ext.placement === 'builtin' ? 'user' : 'builtin', treeRoot);
285
329
  if (wrong.dst !== dst && fs.existsSync(wrong.dst)) {
286
330
  fs.rmSync(wrong.dst, { recursive: true, force: true });
287
331
  console.log(`[code-server] 清理放错位置的扩展副本 ${wrong.dst}(${ext.name} 应装在${target.builtin ? '内置' : '用户级'}目录)`);
@@ -292,6 +336,11 @@ export function installBundledExtensions(extensionsDir, userDataDir) {
292
336
  console.warn(`[code-server] bundled extension ${ext.name} install failed:`, err && err.message ? err.message : String(err));
293
337
  }
294
338
  }
339
+ const cleared = clearObsoleteMarkers(extensionsDir, managedIds);
340
+ if (cleared.length > 0) {
341
+ console.log(`[code-server] 清掉 VS Code 的"已移除"标记(.obsolete):${cleared.join(',')}`
342
+ + '(留着的话扫描器会永远跳过这些扩展,编辑器桥就永远没有状态)');
343
+ }
295
344
  void userDataDir;
296
345
  }
297
346
 
@@ -1209,6 +1258,11 @@ export async function apply(ctx, config) {
1209
1258
  const env = { ...process.env };
1210
1259
  // 内置扩展信号文件路径(host → 扩展 打开文件)
1211
1260
  env.DSHCS_OPEN_FILE_SIGNAL = openFileSignalPath(userDataDir);
1261
+ // 扩展目录(host → 扩展 找桥配置 `/.dshcs-bridge/bridge.json`)。
1262
+ // **必须注入**:0.3.12 起桥扩展装在**内置**目录(树里),与 <extensionsDir> 不同级,
1263
+ // 扩展再也不能靠"自己的路径上溯两级"找到配置(0.3.0–0.3.11 那个上溯是错的,多上溯了一级,
1264
+ // 就算装在用户级也读不到配置 ⇒ 桥一直是休眠态)。
1265
+ env.DSHCS_EXTENSIONS_DIR = extensionsDir;
1212
1266
  // ?v= 缓存击穿标记:按"插件版本 + VS Code 树"生成 —— 只在真升级时让渲染器丢掉旧 bundle,
1213
1267
  // 平时正常命中缓存(workbench.js 有 18MB,每次启动都重拉不划算)。
1214
1268
  env.DSHCS_HTML_TAG = `${pluginVersion()}-${productPath() ?? 'dev'}`.replace(/[^A-Za-z0-9._-]/g, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
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. 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). No code-server Node layer, no argon2, no C++ toolchain.",
5
5
  "homepage": "https://github.com/jinsiyu/dsh-code-server-app",
6
6
  "repository": {
@@ -34,9 +34,6 @@
34
34
  "engines": {
35
35
  "node": ">=24"
36
36
  },
37
- "peerDependencies": {
38
- "@deepseek-ai/dsh": ">=0.1.2-rc.1"
39
- },
40
37
  "files": [
41
38
  "lib/index.js",
42
39
  "lib/client.js",
@@ -3,7 +3,7 @@
3
3
  "vscodeVersion": "1.137.0",
4
4
  "productPath": "stable-b11dabdaca0d3369986975be285db92c8795cea5",
5
5
  "layout": "vscode-only",
6
- "preparedAt": "2026-09-12T17:04:12.601Z",
6
+ "preparedAt": "2026-09-12T17:39:03.492Z",
7
7
  "source": "registry",
8
8
  "node": "v24.21.0",
9
9
  "platform": "win32",