dsh-code-server-app 0.3.14 → 0.3.16

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
@@ -221,8 +221,10 @@ extension → host POST /code-server-bridge/ask push an editor question in
221
221
  extension → host GET /code-server-bridge/health unauthenticated liveness probe
222
222
  extension → host POST /code-server-bridge/event extension reports open/close etc. (host log tail)
223
223
  host → extension <extensionsDir>/.dshcs-bridge/bridge.json endpoint + token, re-read 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)
224
+ (the same content is also written **next to the built-in extension** in
225
+ `<tree>/lib/vscode/extensions/.dshcs-bridge/` the env var is only injected when the host
226
+ spawns the IDE, and an **adopted** IDE is a process from an earlier start that never saw it,
227
+ so the extension must be able to find the config from its own location alone)
226
228
  ```
227
229
 
228
230
  Requests use `http.request({ socketPath })` (`fetch` has no socket support) and **no port is ever opened**.
package/README.md CHANGED
@@ -216,7 +216,9 @@ DSH 用**资源地址**命名文件,`openFile` 只负责把地址交给右侧栏
216
216
  扩展 → host GET /code-server-bridge/health 无鉴权探活(便于重启后一眼确认)
217
217
  扩展 → host POST /code-server-bridge/event 扩展上报打开/关闭文件等(进 host 日志尾)
218
218
  host → 扩展 <extensionsDir>/.dshcs-bridge/bridge.json 端点 + 令牌(扩展每 5s 重读)
219
- (目录由 host 注入的 `DSHCS_EXTENSIONS_DIR` 告知 —— 扩展装在内置目录里,自己推不出来)
219
+ (同一份内容还会写到**内置扩展旁边** `<树>/lib/vscode/extensions/.dshcs-bridge/` ——
220
+ 环境变量只在 host spawn IDE 时注入,而被**接管**的 IDE 是上一次启动的进程、拿不到它,
221
+ 扩展得能只靠自身位置读到配置)
220
222
  ```
221
223
 
222
224
  请求走 `http.request({ socketPath })`(`fetch` 不支持 socket),**不开任何端口**。
package/lib/index.js CHANGED
@@ -354,6 +354,36 @@ function openFileSignalPath(userDataDir) {
354
354
  return path.join(userDataDir, 'User', 'dshcs-open.json');
355
355
  }
356
356
 
357
+ /** 已安装的内置扩展里最新的文件 mtime(ms);取不到返回 null。
358
+ *
359
+ * 用途:**adopt(接管正在运行的 IDE)时判断"里面跑的是不是升级前的旧代码"**。
360
+ * 扩展代码只在扩展宿主进程启动时被加载一次 —— 文件是新的不代表跑着的进程里是新的。
361
+ * `mtime > IDE 进程启动时间` 说明这些文件是在该进程起来之后写的,那个进程不可能加载过它们。
362
+ * (可能偏保守:之后重载过窗口就没事了 —— 所以提示写成"可能/重载即可"。) */
363
+ export function newestBundledExtensionMtime(extensionsDir, options = {}) {
364
+ const treeRoot = options.treeRoot === undefined ? vsRoot() : options.treeRoot;
365
+ let newest = null;
366
+ for (const ext of BUNDLED_EXTENSIONS) {
367
+ const target = extensionTarget(extensionsDir, ext.name, ext.placement, treeRoot);
368
+ if (!fs.existsSync(target.dst)) continue;
369
+ let files;
370
+ try {
371
+ files = listExtensionFiles(target.dst);
372
+ } catch {
373
+ continue;
374
+ }
375
+ for (const rel of files) {
376
+ try {
377
+ const at = fs.statSync(path.join(target.dst, rel)).mtimeMs;
378
+ if (newest === null || at > newest) newest = at;
379
+ } catch {
380
+ // 单个文件 stat 失败不影响结论
381
+ }
382
+ }
383
+ }
384
+ return newest;
385
+ }
386
+
357
387
  /** 插件 package.json 里声明的内部依赖(纯 JS 直装集;排除 VS Code 树包本身)。 */
358
388
  function declaredInnerDeps() {
359
389
  try {
@@ -820,6 +850,30 @@ export async function apply(ctx, config) {
820
850
  isLive: () => bridgeMeta !== null && !bridgeContext.isStale(),
821
851
  });
822
852
 
853
+ /** 桥配置要写的目录(可能有**两个**):
854
+ *
855
+ * ① `<extensionsDir>/.dshcs-bridge/` —— 权威位置;host 侧 /status 与诊断都指向它,
856
+ * 扩展在有 `DSHCS_EXTENSIONS_DIR` 时也读它;
857
+ * ② `<树>/lib/vscode/extensions/.dshcs-bridge/` —— **内置扩展自己会去找的位置**。
858
+ * 为什么需要 ②:`bridge-client` 的兜底是"从自身位置反推"(`<ext>/lib/` 上溯两级),
859
+ * 对内置扩展就是 `<树>/lib/vscode/extensions`。而环境变量只在 host **spawn** IDE 时才注入 ——
860
+ * **adopt(接管正在运行的 IDE)拿不到**:那个进程是上一次启动的,env 早已定死。
861
+ * 没有 ② 的话,被接管的 IDE 即使重载窗口也读不到配置,桥只能等 IDE 重启。
862
+ * 两份内容完全一致(同一个 write/remove 一起写),不存在优先级问题。 */
863
+ function bridgeConfigDirs() {
864
+ const dirs = [bridgeExtensionsDir];
865
+ try {
866
+ const target = extensionTarget(bridgeExtensionsDir, 'dshcs-editor-bridge', 'builtin');
867
+ if (target.builtin) {
868
+ const sibling = path.join(path.dirname(target.dst), BRIDGE_DIRNAME);
869
+ if (!dirs.includes(sibling)) dirs.push(sibling);
870
+ }
871
+ } catch {
872
+ // 树不可用 → 只有权威位置
873
+ }
874
+ return dirs;
875
+ }
876
+
823
877
  /** 同步桥运行时:写入/更新 bridge.json(端点 = 本机 IPC 路径 + 令牌 + pid)。
824
878
  * 端点、令牌、pid 三者任一变化都重写 —— 扩展每 5s 重读,故不需要任何推送。
825
879
  * 监听口起不来时**不写配置**(宁可休眠,不可指向死端点),并说明一次。 */
@@ -829,18 +883,22 @@ export async function apply(ctx, config) {
829
883
  if (handle === null) {
830
884
  if (bridgeMeta !== null) {
831
885
  bridgeMeta = null;
832
- try { removeBridgeConfig(bridgeExtensionsDir); } catch { /* 删不掉也不影响:扩展会因端点在而连不上 */ }
886
+ for (const dir of bridgeConfigDirs()) {
887
+ try { removeBridgeConfig(dir); } catch { /* 删不掉也不影响:扩展会因端点在而连不上 */ }
888
+ }
833
889
  }
834
890
  return;
835
891
  }
836
892
  bridgeToken ??= mintBridgeToken();
837
893
  const changed = bridgeMeta === null || bridgeMeta.pipe !== handle.path || bridgeMeta.token !== bridgeToken || bridgeMeta.pid !== pid;
838
894
  bridgeMeta = { pipe: handle.path, token: bridgeToken, pid, startedAt: startedAt ?? null };
839
- try {
840
- writeBridgeConfig(bridgeExtensionsDir, { pipe: handle.path, token: bridgeToken, pid, startedAt: startedAt ?? null });
841
- } catch (err) {
842
- console.warn(`[code-server] 编辑器桥配置写入失败(${bridgeExtensionsDir}):${err && err.message ? err.message : err}`);
843
- return;
895
+ const value = { pipe: handle.path, token: bridgeToken, pid, startedAt: startedAt ?? null };
896
+ for (const dir of bridgeConfigDirs()) {
897
+ try {
898
+ writeBridgeConfig(dir, value);
899
+ } catch (err) {
900
+ console.warn(`[code-server] 编辑器桥配置写入失败(${dir}):${err && err.message ? err.message : err}`);
901
+ }
844
902
  }
845
903
  if (changed) {
846
904
  // 只打印端点与配置文件位置,不打印令牌本身(与 path-token 同一决策)。
@@ -870,10 +928,12 @@ export async function apply(ctx, config) {
870
928
  bridgeEvents.reset();
871
929
  if (bridgeMeta === null) return;
872
930
  bridgeMeta = null;
873
- try {
874
- removeBridgeConfig(bridgeExtensionsDir);
875
- } catch {
876
- // 配置删不掉不影响正确性(扩展会因 IDE 不可达而休眠)
931
+ for (const dir of bridgeConfigDirs()) {
932
+ try {
933
+ removeBridgeConfig(dir);
934
+ } catch {
935
+ // 配置删不掉不影响正确性(扩展会因 IDE 不可达而休眠)
936
+ }
877
937
  }
878
938
  }
879
939
 
@@ -1219,10 +1279,15 @@ export async function apply(ctx, config) {
1219
1279
  // 桥的传输/协议变更(0.3.13 的 url → pipe)会让旧代码读到 v2 配置后休眠 —— 必须明说,否则
1220
1280
  // 表现又是那句无法自查的"编辑器还没有上报状态"。
1221
1281
  const synced = installBundledExtensions(extensionsDir, userDataDir);
1222
- if (synced.updated.length > 0) {
1223
- console.warn(`[code-server] 内置扩展文件已更新(${synced.updated.join(',')}),`
1224
- + '但正在运行的 IDE 里跑的是旧代码:请在 Code Server 标签里重载一次窗口(或重启 IDE),'
1225
- + '否则编辑器桥不会连上');
1282
+ const newest = newestBundledExtensionMtime(extensionsDir);
1283
+ const staleCode = synced.updated.length > 0
1284
+ || (newest !== null && record.startedAt !== null && newest > record.startedAt);
1285
+ if (staleCode) {
1286
+ console.warn('[code-server] 内置扩展文件比当前 IDE 进程新'
1287
+ + `${synced.updated.length > 0 ? `(本次更新:${synced.updated.join(',')})` : ''}:`
1288
+ + '被接管的 IDE 不会重新加载扩展,里面跑的**可能仍是旧代码**。'
1289
+ + '若编辑器桥没有状态(editor_context 报"编辑器还没有上报状态"),'
1290
+ + '请在 Code Server 标签里重载一次窗口(或重启 IDE)让扩展宿主读到新代码');
1226
1291
  }
1227
1292
  adoptBridgeRuntime(record.pid, state.startedAt);
1228
1293
  return snapshot();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
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": {
@@ -3,7 +3,7 @@
3
3
  "vscodeVersion": "1.137.0",
4
4
  "productPath": "stable-b11dabdaca0d3369986975be285db92c8795cea5",
5
5
  "layout": "vscode-only",
6
- "preparedAt": "2026-09-13T05:11:48.032Z",
6
+ "preparedAt": "2026-09-13T05:19:14.035Z",
7
7
  "source": "registry",
8
8
  "node": "v24.21.0",
9
9
  "platform": "win32",