jinsiyu/dsh-code-server-app

Desktop桌面端 Native原生 ⭐ 2 MIT Dev Workflow开发与代码工作流

静态 profile 插件(npm 包形态,host + client bundle),把 code-server 发行版里的 VS Code server 树 作为平台无关依赖随插件安装(打包期产物 vendor/vscode → @jinsiyu/dshcs-vscode-server, 无安装脚本、无 postinstall);code-server 的 Node 服务层已由插件自带的 lib/launcher.mjs 取代 (它直接驱动 /lib/vscode/out/server-main.js 的 loadCodeWithNls() / createServer() / handleRequest() /

catalog descriptioncatalog 简介 / catalog description:将code-server(VSCode网页版)打包安装到dsh内的插件,快速实现专业的文件编辑。Package and install code-server (the web version of VSCode) as a plugin within dsh to quickly achieve professional file editing.

Project Overview项目介绍

DSH static plugin that bundles code-server 4.134 as a dependency, embedding it with no global install required. A draggable floating ball opens an iframe window that follows the active workspace, with green/yellow/red status indicator, maximize, 8-direction resize, and Esc close. Use it to bring a full VS Code editor into DSH. Caveat: extensions come from Open VSX, so proprietary Microsoft extensions like GitHub Copilot need manual .vsix install; Windows folder paths require forward slashes like /C:/...; reverse-proxy subpath is unsupported.

DSH 静态插件,把 code-server 4.134 作为依赖随装、零全局配置,浮窗 iframe 直连并跟随活动工作区自动重启;悬浮球可拖动记忆位置,状态点显示绿/黄/红,支持最大化、8 向缩放、Esc 关闭。注意:扩展市场为 Open VSX,GitHub Copilot 等微软专有扩展需手动下载 .vsix 安装;Windows 路径需正斜杠形如 /C:/...;子路径反向代理不支持,必须独立端口。

Or use CLI install (for developers)或使用命令行安装(适合开发者)

CLI Install命令行安装

dsh plugin --profile web add dsh-code-server-app@0.2.1

jinsiyu/dsh-code-server-app 加入你的 DSH 配置(web profile)即可启用。

READMEREADME

dsh-code-server-app — 在 DSH 中集成 code-server(VS Code 网页版)

源码仓库地址见 package.jsonrepository / homepage 字段。

⚠️ 扩展市场说明(重要)

  • code-server 的扩展商店是 Open VSX,不是微软 Visual Studio Marketplace;
  • 微软 Marketplace 的条款禁止第三方产品(含 code-server)使用其 API,所以 code-server 无法查询微软市场的扩展列表;
  • 因此微软商业/专有扩展(如 GitHub Copilot、Remote-SSH 等 Remote 系列、Azure 系列、IntelliCode)在商店里找不到——这是微软发行策略,不是缺失;
  • 微软开源系扩展(Python、TypeScript 调试、ESLint 等)在 Open VSX 有镜像,搜索正常可装;
  • 需要微软专有扩展时:从 Marketplace 网页下载 .vsix,用 code-server --install-extension <文件>(或放入 --extensions-dir)手动安装,即可在插件列表使用。

静态 profile 插件(npm 包形态,host + client bundle),把 code-server 发行版里的 VS Code server 树 作为平台无关依赖随插件安装(打包期产物 vendor/vscode@jinsiyu/dshcs-vscode-server, 无安装脚本、无 postinstall);code-server 的 Node 服务层已由插件自带的 lib/launcher.mjs 取代 (它直接驱动 <树>/lib/vscode/out/server-main.jsloadCodeWithNls() / createServer() / handleRequest() / handleUpgrade(),并补上 /healthz/manifest.json/_static/*/proxy/:port 这几条 code-server 原本提供的 HTTP 面); 原生模块(node-pty / @vscode/sqlite3 / spdlog …)由 @jinsiyu/dshcs-* 子包按真名直接挂在插件依赖上、按 os/cpu 自动选中 —— 无需全局 npm 安装、无需配置 bin、无需改 profile 配置、无需第二条安装命令、无需 argon2/C++ 工具链

0.2.0 起:argon2 与 code-server 的 136 个运行时依赖(express / proxy-agent / js-yaml / pem / limiter …) 全部不再随包分发(减少 ~34.5MB + 一条原生构建链);IDE 提供方式见下方「服务方式(serve)」。 依据与实测证据见 docs/analysis-code-server-as-dsh-plugin.md(含子路径挂载、WS 路径、命名管道、fence 的逐项验证)。

0.3.22 起:「问 DSH」面板直接渲染 DSH 官方的 markdown 结果(与 DSH 界面同一份渲染器 + 同一套设计令牌, 只渲染该会话的新内容),并且就在面板里处理授权(工作区外写入 / 执行命令)—— 详见 「与 DSH 的协同:编辑器桥」与 docs/analysis-code-server-as-dsh-plugin.md 第 21 节。

UI 载体与 DSH 版本要求(0.2.3 起只支持带右侧栏的 DSH)

DSH 版本 载体 入口
≥ 0.1.5-alpha.1(有 sidebarRight / sidebarRightTabs 服务) 右侧栏标签(kind=code-server,标签名 Code Server),并认领文件地址(见下) DSH 官方的产物 chip / 「交付」卡片预览 / 正文里的文件名(0.2.5 起,走官方 openFile → 文件地址 → 本 tab);② 右侧栏「开始」页的 Code Server 入口框;③ 设置 → 插件 → Code Server → 「在右侧栏打开」
更早(无右侧栏服务) 不受支持:除设置页的一条提示外不提供任何入口 无(设置 → 插件 → Code Server 显示升级提示)
  • 检测方式:先 ctx.get('sidebarRightTabs') / ctx.get('sidebarRight') 同步探测; 服务可能晚于本插件就绪,则 ctx.inject(['sidebarRightTabs','sidebarRight'], …) 等待, 2.5 s 内仍未就绪即判定旧版 DSH(不按版本号硬判,也不影响插件激活)。 0.2.4 起判定可逆、且注册不再依赖 ctx 属性访问(desktop 上曾因此静默不注册、表现为"设置卡正常但侧栏没有入口"):
    • 服务查找先读 ctx.<name>,再回退 ctx.get(name)——两种上下文形态都能注册;
    • 同步已能看到服务、但 inject 迟迟不回调时,1.5 s 后用同步服务兜底注册;
    • 2.5 s 只给设置页提示,10 s 仍无服务才通知 host 回收/停止预启动(避免误杀慢启动的宿主);
    • 服务晚到 → 自动撤销旧版判定、补注册侧栏,并上报 {sidebar:true} 让 host 恢复;
    • 注册失败不再静默:控制台报错,设置卡入口行显示"已探测到右侧栏服务,但标签注册失败"。
  • 0.2.3 起不再兼容旧版 DSH:悬浮球与内部浮动窗口回退已删除。判定为旧版时:
    • 只注册设置卡片,内容是一条升级提示(见下),不注册悬浮球/浮窗/文件地址认领,也不预热 IDE;
    • 客户端向 host 上报 /api/code-server/ui-mode { sidebar:false }(在上面的 10 s 宽限之后),host 据此回收自动预启动的实例 并停止预启动(用户手动启动的实例不受影响);服务随后才出现时会再上报 {sidebar:true} 撤销;
    • 升级 DSH 后无需重装插件,刷新页面即可,本页会恢复为完整设置卡片。
  • 侧栏标签内即 code-server 页面(iframe),跟随当前会话工作区;面板可折叠/分屏/浮动/全屏(由 DSH 右侧栏提供)。
  • 打开即全屏(0.2.9 起,默认开):打开 Code Server 标签(含点开产物 chip / 交付卡片 / 正文文件名)时, 自动把右侧栏从"与对话并排"切到全屏(铺满窗口)——IDE 在窄栏里太挤。 只影响"打开那一刻":随时点右侧栏的「退出全屏」不会被抢回去;切走再切回、再次打开文件 tab 会重新切全屏。 不想要就在设置卡片里关掉(fullscreenOnOpen=false)。
    • 实现说明:DSH 没有把"模式"开放给插件 —— ctx.sidebarRight 只有 isExpanded/toggleExpanded(展开/收起), push ⟷ fullscreen 记在 ui-sidebar-right 自己的 store 里(actions.setMode,只发给它的 seat 内部组件); ctx.layout.openRightbar(track, fullscreen) 也不是控制面,而是 seat 用来汇报 presentation 的通道 (上游源码注释:the occupant reports it; nothing else writes it)。 所以本插件做的是"用户那个动作本身":closest('[data-sidebar-right-panel]') 定位自己所在面板, 再点面板 chrome 上的 [data-sidebar-right-mode="fullscreen"] 按钮(与手点完全同一条路径, 含窄视口下的连带处理)。按钮找不到时保持原模式并 console.warn 一条,绝不影响面板渲染。
  • IDE 常驻(0.2.2 起,默认开):切到别的标签/收起侧栏再回来不再重载 code-server—— 未保存的编辑缓冲区、终端、调试会话都留在原处(见下方「为什么切标签不再重载」)。
  • 设置卡片只有三个设置:「认领类型」「打开即全屏」与「后台常驻(切标签不重载)」——没有其它行(0.2.7 起移除「入口」「依赖安装」「环境检测」)。 打开 IDE 用右侧栏「开始」页的 Code Server 入口框,或直接点官方的产物 chip / 交付卡片 / 正文文件名; 诊断看 DSH host 日志里的 [code-server] 输出(/api/code-server/status 仍返回 env 供脚本排查)。 旧版的 windowedOpen(窗口化打开)、reserveComposer 已在 0.2.6 移除:旧设置文档里残留的这两个键不会报错,只是被忽略(不再出现在 schema 里)。 需要在新标签页用 IDE 时,从设置卡/空态提示里复制完整地址(含路径令牌,http://127.0.0.1:<port>/<token>/; serve: dsh 时为 DSH 的 /code-server/)—— 少了令牌那一段会 404。

文件打开(0.2.5 起走官方入口)

DSH 用资源地址命名文件,openFile 只负责把地址交给右侧栏去认领:

官方产物 chip / 「交付」卡片预览 / 正文内联提及
      → openFile(path, { line? })                     (ui-chat 提供)
      → dsh-resource://file/session/<sessionId>/<path> (或 …/file/absolute/<path>)
      → ctx.sidebarRight.openResource(address)
      → 由注册了匹配 patterns 的 tab 类型认领(优先级 extension(3) > builtin(2) > fallback(1),
        同带内按 pattern 长度、再按注册顺序)

本插件注册时带上:

字段 作用
patterns ['dsh-resource://file/**'] 认领文件地址(含 : 的 pattern 按整址 glob 匹配)
priority 'extension' 高于官方纯文本预览的 fallback——DSH 源码注释写明后者是"VS Code 文本编辑器在编辑器中的位次,任何更具体的类型都应当击败它"
canOpen 见下 按「认领类型」设置否决,未认领的地址由官方预览兜底
title 地址末段(=文件名) tab chip 显示文件名;页面 tab(sidebar://code-server)仍是 Code Server
  • 认领类型(设置卡片里的文本框,claimExtensions,0.2.11 起): 不再区分作用域 —— dsh-resource://file/session/……/file/absolute/… 一视同仁,只看扩展名。 文本框语法(分号分隔,,/空白/换行也认;写 py.py*.py 等价;大小写不敏感):
    • * = 其余类型也认领(兜底);
    • py = 认领 .py;
    • !md = 不认领 .md(排除优先于认领与 *);
    • 默认 *;!md;!markdown;!html;!htm;!png;!jpg;!jpeg;!gif;!webp;!bmp;!ico;!svg;!pdf = DSH 自带预览渲染得好的四类(markdown / html / 图片 / PDF)留给它,其余(代码、json/yaml、txt、日志、 无扩展名如 Makefile、未知扩展名)都进 IDE;清空文本框 = 不认领任何文件(只保留页面 tab)。
    • 三种实际形态:纯白名单(py;ts,无 * → 其余不认领)、兜底(*)、兜底加排除(默认)。
    • 语法、默认值与解析都在 lib/claim-types.js(host 的 Config 默认值与客户端 canOpen 共用同一份, 随包发布,不会两边漂移);单测 scripts/test-claim-types.mjs
  • tab body 怎么定位文件:从 useTabInfo().tab.navigation.address 解析出会话与路径 (src/address.js,与 DSH parseFileAddress 同语义),相对路径按该会话 cwd 展开成绝对路径, 再把绝对路径 + 可选 line 交给 host 的 /api/code-server/open-file;内建扩展 (dshcs-open-file)在 workbench 里 showTextDocument(带行号时定位到该行)。
  • 一个地址 = 一个 tab(官方语义,contentId 就是地址):打开三个文件会有三个 chip, 但它们共用同一个常驻 workbench(我们的 IDE 是单实例),切换时只是让 workbench 定位到对应文件。
  • 为什么还留着那个内建扩展:VS Code Web 没有"从外部打开文件"的官方 API(唯一入口是 ?folder= 指定工作区),所以"让 workbench 定位到某个文件"只能由树内的扩展完成; host 写信号文件、扩展轮询并 showTextDocument,失败保留重试(实例尚未就绪时也不会丢)。

为什么切标签不再重载(IDE 常驻)

过去的坑:DSH 的右侧栏(ui-dockkit)TabPanel 只渲染当前激活标签的 body (TabPanel.tsx:412renderTab(active))——切到别的标签 = React 卸载该 body = iframe 被移出文档 = 浏览上下文销毁,切回来就是一次完整的 VS Code 重载(未保存的缓冲区丢失)。把标签浮动成独立面板只是绕开它, 并没有解决。

现在的做法(客户端 src/surface.js,0.2.2):插件把 iframe 从 React 手里接管,做成单例常驻面:

场景 动作 结果
标签激活 host.moveBefore(frame, null) 移进当前可见的停靠位 状态保持型原子移动,不重载
标签失活 / 收起侧栏 移回文档级 park 容器(离屏、保留最后停靠尺寸、inert + aria-hidden) 面不销毁,后台继续跑
工作区 / 端口变化 显式设置 src 这是唯一正常的"重载"入口
  • 为什么是 moveBefore:浏览器实测(Edge/Chromium 151)普通 appendChild 移动 iframe 会让内部计时器归零 (等价重载),而 Element.moveBefore()(Chromium ≥133)保持状态(计时器 1→2 连续)。
  • 降级不静默:moveBefore 缺失、或宿主已被 React 摘除而抛 HierarchyRequestError: invalid hierarchy (passive effect cleanup 晚于 DOM 卸载)时,退回 appendChild——会重载一次,但绝不丢帧; 状态里 degraded/lastMoveError 明示,界面据此提示"常驻不可用"。
  • 重绘兜底(实测坑):真实 GUI 里观测到一次"元素在、画面不重绘"——iframe 尺寸、命中测试、visibility 全部正常,面板却一片白(连续两张截图哈希相同,确认没有新帧);translateZ(0)opacity 微调无效, display:none → 强制重排 → 还原(同一个 JS 任务内)可恢复,且 iframe 文档不重载、内部状态不变、无可见闪烁。 触发条件未能复现:探针页里 moveBefore 停放 337 s(超过 Chrome 对不可见跨源 iframe 的 ~5 min 节流窗口) 后移回、且关掉修复,仍正常绘制。因此把它当兜底保留:每次「停放 → 停靠」补一次 nudgeRepaint()(surfaceSnapshot().nudgeCount 计数,setNudgeEnabled(false) 可现场 A/B)。
  • 后台预热:配置 keepResident(默认 true)时,宿主在插件启动后就把面建好并停在停放区, 首次点开标签无需冷启动等待;preload 不会把正在使用的面拽走。
  • 排障句柄:控制台可用 window.__dshcsSurface(snapshot() / setParkStrategy('offscreen'|'behind') / dock() / park() / nudge() / setNudgeEnabled(false) / destroy())。

实测记录(DSH web GUI,sidebar 标签间真实鼠标切换):切走 → docked:false、iframe 仍为同一节点、内部探针存活、 degraded:false;切回 → docked:truesrc 不变、IDE 画面与编辑状态保持(无整页重载)。 完整证据与探针脚本见 docs/analysis-code-server-as-dsh-plugin.md

服务方式(serve)

方式 说明 需要
loopback(默认) 插件自己起一个回环端口(默认 port: 0 = 每次启动由系统分配随机端口),右侧栏 iframe 跨源直连;URL 带随机路径令牌(http://127.0.0.1:<port>/<token>/,见下「回环端口的安全模型」);进程可被 adopt(DSH host 重启后接管)
dsh IDE 挂到 DSH 自己的 HTTP 端口上的 /code-server/*(HTTP prefix 路由)+ /code-server/<quality>-<commit>(WS 精确路由),转发到 launcher 的命名管道;没有额外端口;每条请求(含 WS 握手)先过 ctx.connection.requestRejection() —— 与 /api 同一套 Host/Origin fence + 浏览器 cookie 认证 DSH 提供 webServer 服务(web profile);desktop 无此服务 → 自动回退 loopback

回环端口的安全模型(0.2.14 起)

loopback 是 desktop 端唯一的通路(无 webServer、无同源挂载),所以它单独加固了一层:

  • 随机端口:port 默认 0 → 由系统分配空闲端口,launcher 把实际端口写进 $DSH_HOME/code-server/endpoint.json, host 读回(因此端口每次都变、也不存在"8090 被占用"这类冲突)。要固定地址就显式配 port
  • 路径令牌:每次新启动生成 32 位随机令牌([0-9A-Za-z_-],24 字节随机),写在 $DSH_HOME/code-server/path-token(用户 profile 下,默认 ACL 仅本人可读),成为 URL 的路径前缀。 没有这个前缀的请求一律 404(不泄露"这里跑着 IDE"),前缀不带结尾斜杠会 302 补上。
  • 为什么不用 VS Code 自带的 connection-token:它靠 ?tkn= → 302 + Set-Cookie: vscode-tkn; SameSite=Lax; 而 desktop 的 iframe 是跨源的(dsh-app://127.0.0.1),Lax cookie 在跨站子框架里不会被带上 → 会让 IDE 直接打不开。路径前缀不需要 cookie:workbench 的资源与 WS 全部由 location.pathname 派生 (serve: dsh 挂在 /code-server/ 下已验证同一机制),前缀天然跟随每个子请求与 WS 握手。 (已用真实 Edge + CDP 在跨源 iframe 里验证:随机端口 + 令牌下 workbench 正常渲染并建立 WS。)
  • Host 白名单:回环模式只接受 127.0.0.1 | localhost | [::1] : <实际端口>。挡的是 DNS rebinding —— 这类攻击构造的请求可以不带 Origin,只靠 Origin == Host 那条检查拦不住。
  • Referrer-Policy: no-referrer:令牌在路径里,不能让它在加载站外资源时经 Referer 漏出去。
  • 令牌不落 argv、不进日志:命令行对本机任意进程可见,所以走文件传递;日志里只打印"已启用"。

边界(说清楚,不夸大):这一层挡的是"本机其它应用/端口扫描器/浏览器页面"顺手访问你的 IDE; 同用户的本地恶意程序本来就能直接读你的文件、也能读那个令牌文件 —— 那不在本插件的威胁模型内。

  • cordis.patch.ymlconfig.serve(或设置文档里的 code-server.serve)切换,下次启动生效 —— 设置卡片不提供这一行(卡片只有认领类型/打开即全屏/后台常驻三个设置)。

  • dsh 模式的实际收益:单一 URL/单一端口(远程访问 DSH 即可用 IDE)、不再暴露额外回环端口、认证与 DSH 同级。

  • dsh 模式的两点已知取舍:

    1. iframe 与 DSH 同源 → 该模式下不再挂 sandbox(同源 + allow-same-origin 可被 frame 自行摘除,属"看起来有防护"); loopback 模式跨源,sandbox 保持原样作为真防护。剪贴板仍由 allow="clipboard-read; clipboard-write" 提供。
    2. 转发端口(Ports 面板)的 WebSocket 无法用精确升级路由覆盖(端口号在路径里)→ 该功能在 dsh 模式下不可用; HTTP 转发端口正常;需要端口转发 WS 时请用 loopback 模式。
  • loopback 模式下 upgrade 会做 code-server 同款 Origin 校验(0.2.1 起):带 Origin 时其 host 必须等于 Host (含 Forwarded: host= / X-Forwarded-Host 的反代语义),否则回 403;缺 Origin 的非浏览器请求放行。 没有这道检查时,本机任意浏览器页面都能对 ws://127.0.0.1:<port>/stable-<commit> 完成握手并驱动 IDE。

与 DSH 的协同:编辑器桥(0.3.0 起,默认开)

"IDE 就在旁边"和"agent 真的知道编辑器里发生了什么"是两件事。编辑器桥补的是后一半:只读地把 只有编辑器才知道的信息交给 agent,并让用户在编辑器里的动作能反过来驱动当前会话。

双向能力

方向 能力 落地方式
编辑器 → agent 未保存缓冲区(磁盘内容 ≠ 用户所见)、活动文件与选区、语言服务器诊断(含 file:line、来源、code) agent 工具 editor_context / editor_diagnostics;写脏文件前额外附一条提醒
编辑器 → DSH 选中代码 → 右键「DSH: 针对选中内容提问」→ 打开提问面板(带 文件:行 与选中内容);提问以用户输入进当前会话,该会话的新内容用 DSH 官方 markdown 渲染器显示在面板里 命令 dsh-code-server.askAboutSelection(编辑器右键菜单最上面两条之一)+ webview 面板 + POST /ask + /syncthread 字段
DSH → 编辑器(授权) agent 要写工作区外的文件 / 执行命令时的授权请求 → 面板里就地弹卡片(工具名 + 原因 + 倒计时),点「允许一次 / 拒绝」立刻生效 /syncapprovals 字段 + POST /approve(桥里唯一的非只读路由,约束见「安全模型」)
agent → 编辑器 agent 改了哪个文件 → 开原生 diff 审阅;缓冲区有未保存改动时告警而不覆盖 host 观察 tools/result,扩展轮询后开 diff + 非模态告警
  • 工具只在桥就绪时注册(IDE 没起来时模型看不到"有个用不了的工具");提示词段落也只在桥存活时渲染。
  • 提问面板(扩展 0.2.0 起;0.2.3 起正文走官方渲染器;0.2.5 起是浮在 DSH 界面上的对话框): 右键命令不再开编辑器里的面板(那是编辑器的一个 tab/一列,怎么开都不像对话框),而是让 DSH 页面里的 插件客户端弹出一个可拖动、可缩放的浮动对话窗(右下角,✕ 关闭),不占编辑器版面; 面板开着的同时还能改选区再问。老宿主(探测不到对话框能力)才退回编辑器里的 webview 面板。 两个命令的意图分开记(0.3.21):「针对选中内容提问」只有真的选了内容才带行区间 + 选区正文; 「针对当前文件提问」永远不带行号、不带选区 —— 光标停在哪一行跟问题无关,行号只会误导 agent; 没选区时用选中命令提问也会退化成纯文件。
  • 注入的上下文是折叠的(0.3.24):桥拼进消息的位置行 + 选区代码块会被拆出来,显示成一行默认收起的 「上下文」(点开才看得到那段代码),气泡里只留你的原话 —— 与 DSH 界面处理注入上下文的方式一致。
  • 面板里的正文就是 DSH 的渲染结果(0.3.22):面板打包了 DSH 官方的 markdown 渲染器 (@deepseek-ai/dsh-client-ui-primitivesMarkdownText)与官方设计令牌 —— 同一套 micromark/mdast 管线、 同一个增量流式解析器、同一个 shiki 高亮(启动集 typescript / shellscript / json)、KaTeX 公式、同样的标题与表格排版。 只渲染新内容(从面板订阅那一刻起),不重放历史、没有"加载更早"。
  • 思考过程也照官方显示(0.3.23):助手的 reasoning 以「思考」行出现 —— 默认收起、收起时显示首行 (流式时显示最新一行)、点整行展开全文,用的就是官方 DisclosureRow + 官方的思考图标与排版语言。
  • 授权就在面板里处理(0.3.22;0.3.23 修好窗口):面板打开着的时候,该会话的授权请求先问面板(默认 5 分钟), 点「允许一次」/「拒绝」立刻生效;关掉面板或等满窗口就把请求原样交回官方链路(DSH 界面照旧弹卡)。 0.3.22 的 8 秒窗口对人来说太短 —— 卡片还没读完按钮就灰了(实测反馈「授权框失效了」),现在窗口是 5 分钟, 而且面板一关就立刻交回、不干等。 永不自动放行 —— allowed-once 只能来自你的一次点击,面板里没有"以后都允许"这种入口。
  • 提问进 DSH 会话时是普通用户消息(source: { kind: 'user' },host 0.3.19 修正):早期版本用 {kind:'plugin'} 会被 DSH 渲染成"上下文更新",看起来不像自己说的话;来源信息靠正文首行 From the editor: <file>[:<行>] 保留。
  • 只读 + 一个受限例外:桥不写文件、不改文档、不执行命令;唯一的非只读路由是 POST /approve, 它只能回答已经存在的授权请求(见「安全模型」第 2 条)。agent 的写操作仍然全部走它自己的 fs 工具, 桥只是"知道它写了什么"、并把你对授权的答复带回去。
  • 编辑器侧的入口还有状态栏的 $(plug) DSH(连通时显示,点击打开日志),日志在输出面板 「DSH Editor Bridge」里 —— 出问题时先看它。
  • 扩展装在内置目录(0.3.12 修正):dshcs-editor-bridgedshcs-open-file 一样装进 <树>/lib/vscode/extensions/。0.3.0–0.3.11 装的是用户级目录,而 VS Code 服务端会把 "在用户扩展目录里、不在任何 profile 清单里"的扩展标进 .obsolete(日志 Marked extension as removed) 并永远跳过它 —— 每一轮启动都再标一次,桥因此从来没有上报过状态。 要关掉桥请用插件设置 editorBridge=false(不挂桥、不注册工具),不要再指望在扩展视图里卸载它。

三条通道(0.3.13 起走本机 IPC:Windows 命名管道 / unix socket)

扩展 → host     POST /code-server-bridge/sync    一趟来回:上报编辑器状态(+ 面板在看哪个会话)+ 取回待处理事件与对话流
扩展 → host     POST /code-server-bridge/ask     把编辑器里的提问投进当前会话
扩展 → host     POST /code-server-bridge/approve 回答一条**已经存在**的授权请求(唯一的非只读路由)
扩展 → host     GET  /code-server-bridge/health  无鉴权探活(便于重启后一眼确认)
扩展 → host     POST /code-server-bridge/event   扩展上报打开/关闭文件等(进 host 日志尾)
host  → 扩展    <extensionsDir>/.dshcs-bridge/bridge.json  端点 + 令牌(扩展每 5s 重读)
                (同一份内容还会写到**内置扩展旁边** `<树>/lib/vscode/extensions/.dshcs-bridge/` ——
                 环境变量只在 host spawn IDE 时注入,而被**接管**的 IDE 是上一次启动的进程、拿不到它,
                 扩展得能只靠自身位置读到配置)

请求走 http.request({ socketPath })(fetch 不支持 socket),不开任何端口

/sync 的响应里,面板真正用到的三段(0.3.22):

字段 内容 面板怎么用
thread 被面板 watch 的会话的新内容条目(user / assistant / tool / approval),有界:每会话 ≤120 条、单条正文 ≤8000 字符、同时 watch ≤4 个会话 助手正文交给官方渲染器;工具与授权是紧凑摘要行
approvals 待决授权请求 [{id, toolName, reason, at}](≤4 条) 弹卡片 + 倒计时;点按后 POST /approve
approvalHoldMs / uiVersion 授权窗口长度(默认 300000ms = 5 分钟)/ DSH 界面版本 倒计时基准;渲染器版本不一致时提示

为什么不是 HTTP(0.3.13 定论,三条都实测过)

  1. desktop 根本没有 HTTP 面:渲染进程经 Electron IPC 调 host.fetch() (apps/desktop-host/src/index.ts:308createSharedFetchHandler('/api'))—— 那是进程内函数调用, 进程外不可达;插件能挂 HTTP 的只有 web profile 的 webServer
  2. /api 也不行:Connection 给 /api 装了 Host/Origin/cookie fence (packages/client/connection/src/index.tsrequestRejection → 无 cookie 即 401),而桥的客户端 是扩展宿主里的 Node 进程 —— 它永远拿不到浏览器 cookie。实测(0.3.7):扩展按 /api/code-server/bridge/sync 轮询,要么 405(打到 launcher/VS Code)、要么 401(打到 /api fence), 桥从来没有真正同步过
  3. 桥的两端本来就是同一台机器上的两个进程(扩展宿主 ← 插件 spawn 的 IDE ← 插件)。 本机 IPC 比开端口更小:没有网络面、没有 Host/Origin 混淆代理问题,web 与 desktop 走同一条路。 令牌校验照旧保留(见下),Windows 管道名带随机后缀、POSIX socket 文件 chmod 0600

历史:0.3.9–0.3.12 挂在 DSH 的 webServer 前缀下 —— 于是 desktop 永远休眠(没有 webServer)。

为什么状态是"推"而不是"拉":扩展宿主是 VS Code server 的一个子进程,不监听任何端口 —— host 反向请求不到它。所以编辑器状态只能在扩展主动发起的那趟轮询里带上来,host 缓存后给工具读 (缓存滞后最多一个轮询周期 600ms,超过 10s 没更新就判为过期,工具会明说"状态已过期");

为什么不用 SSE/WebSocket:扩展宿主里没有 HTTP 服务器,而桥的形态是"每 600ms 一趟请求/响应"。 轮询给了两条好性质:幂等(丢一次事件只是少一次提示,数据本身永远在编辑器里),以及状态天然最新(每趟都刷新)。

安全模型(五条不变量,改 lib/bridge.mjs 之前先读)

桥的令牌写在 <extensionsDir>/.dshcs-bridge/bridge.json(对本机同用户进程可读),所以:

  1. /code-server-bridge/* 只读,只有 /approve 一个例外。 没有写文件、改文档、执行命令、 拉起进程的路由。令牌泄露的爆炸半径被封在"看到编辑器里的信息",不会变成任意文件写/任意命令执行。 scripts/test-bridge-routes.mjs 里有一条白名单断言盯着这件事(未知后缀一律 404)。
  2. /approve 的四条约束(缺一条就等于开了任意命令执行的后门,不许放宽): (a) 只能回答已经存在的授权请求,请求体只有 {id, outcome},不接受任何自由文本 / 路径 / 命令参数 —— 它只能"回答问题",不能"发起动作";(b) id 必须是本进程自己发起、且仍未决的请求(用后即废); (c) outcome 只接受 allowed-once / rejected,没有"永久允许"; (d) 没有面板在看 / 面板关掉 / 窗口超时(默认 5 分钟)→ 交回官方链路,绝不自动放行 (DSH 的 approval/request 本身 fail closed,这里只能把"没人答"保持成"没人答")。 pnpm test:webview-bundle 里有针对这四条与宿主侧白名单的断言。
  3. Origin 的请求一律 403。 浏览器发起必带 Origin(含沙箱 iframe 的 Origin: null), 扩展宿主是 Node 进程、不带。判定顺序上 Origin 先于令牌 —— 否则等于给浏览器一个 "令牌猜对没有"的 oracle。 实现细节:Node 路由 → Fetch 适配器把原始 headers 挂在 request 上(dshcsRawHeaders), 因为 undici 的 Request 构造器会把 origin 当 forbidden header 归一化掉 —— 读 request.headers 会让这道 403 静默失效(测试里有这条实测记录)。
  4. 路径收敛在编辑器当前工作区(workspaceFolder 之外的诊断直接丢弃)。
  5. 有界:诊断默认 200 条 / 单条截断 500 字符 / 上报体上限 256KB / 事件环形缓冲 64 条 / 对话流每会话 ≤120 条(单条正文 ≤8000 字符、同时 watch ≤4 个会话)/ 待决授权 ≤4 条。

这一层挡的是"本机其它应用或浏览器页面拿到那个文件后乱调桥";同用户的本地恶意程序 本来就能直接读你的文件与令牌文件 —— 那不在本插件的威胁模型内(与「回环端口的安全模型」同一句话)。

开关与诊断

怎么关 效果
cordis.patch.ymlconfig.editorBridge: false 下次启动不写 bridge.json、不注册工具
设置文档里的 code-server.editorBridge: false 即时生效:删配置 + 注销工具,扩展随即休眠
在 IDE 里禁用扩展 dshcs-editor-bridge 桥自然不可用(工具会注册但立刻报"状态未上报";IDE 侧无任何动作)

诊断:GET /api/code-server/statusbridge 字段返回 { enabled, live, toolsRegistered, supported, url, file } —— 不含令牌(令牌只在那个文件里)。

旧版 DSH(0.2.3 起不再支持)

行为:探测不到 sidebarRightTabs / sidebarRight 时,插件只注册一张设置卡片,内容是:

Code Server — 当前 DSH 版本不受支持(缺少右侧栏服务) 本插件自 0.2.3 起不再兼容旧版 DSH。 未检测到右侧栏插件服务 sidebarRightTabs / sidebarRight,因此插件不提供任何入口(旧版的悬浮球与浮动窗口已移除), 也不会后台启动 IDE。升级 DSH 到带右侧栏的版本(≥ 0.1.5-alpha.1)后,Code Server 会出现在右侧栏标签里, 本页同时显示完整设置项;升级后无需重装本插件,刷新页面即可。

  • 没有任何其他 UI:不注册 shell.overlay(悬浮球)、不认领文件地址、不做常驻预热。
  • host 侧:客户端会 POST /api/code-server/ui-mode { sidebar:false };host 收到后 ① 不再自动预启动 IDE(maybePrestart 直接返回),② 若 IDE 是本插件刚自动预启动且尚未被 adopt,则回收该进程, 避免留下一个用不上的 IDE 与端口。用户手动启动的实例(adopted)不会被停。
  • 为什么删除而不是保留:内部浮动窗口是 2026 年早期 DSH(无右侧栏服务)时代的临时载体, 常驻面、剪贴板、快捷键、面板折叠等能力都建立在 DSH 右侧栏之上;维护两套载体的成本高于其残余价值。 旧版用户继续用 0.2.2 即可(dsh plugin --profile web add dsh-code-server-app@0.2.2)。
  • 回滚:任何版本都能降级到旧版实现,例如 dsh plugin --profile web add dsh-code-server-app@0.2.2

code-server 服务目录与进程生命周期

  • code-server 服务目录跟随活动工作区/会话:打开期间切换 DSH 会话/工作区,code-server 自动切到新目录 (解析优先级:当前会话 cwd → 会话所属 workspace.path → recentWorkspace.path → 首个 workspace.path); 打开目录显示在 code-server 页面内(?folder=<cwd>,跟随切换时页面自动重新加载); 实现要点:iframe src 必须带 ?folder=<cwd>——code-server 前端会记住“最近工作区”并自行恢复, 仅用裸根 URL 只会显示上一次打开的目录、不会跟随切换(本机实测确认)。 Windows 路径格式(实测):folder 参数必须以 / 开头且全部正斜杠,形如 /C:/Users/User/Desktop/biss; 裸 Windows 路径(C:\...)会被前端当 URI scheme 而剥掉盘符(页面显示 \Users\User\... 且文件树为空), file:///C:/... 形式则报 “Workspace does not exist”。
  • 切换是"轻量"的(0.2.12 起):运行中切工作区不重启 IDE 进程,host 只把 state.cwd 改成新目录, 由 workbench 拿新的 ?folder= 重新导航(工作区目录本来就由客户端 URL 决定,进程 cwd 只影响它自己 spawn 时的相对路径解析)。因此切换不再丢扩展宿主/后台任务/服务端状态,也快得多。
    • statuscwd = 当前 workbench 目录;launchCwd = 进程启动时的目录(诊断用,不随切换变化)。
    • 代价(要说清楚):旧目录里由 IDE 拉起的后台进程/终端不再被自动杀掉(以前靠"整进程重启"顺带收走)—— 需要时手动收;这也是"不丢状态"的同一枚硬币。
    • 触发时机与本标签是否可见无关:侧栏收起时标签 body 并不卸载,所以后台也会跟随(0.2.12 明确保留此行为)。
    • 回归:scripts/test-workspace-switch.mjs(5 项:接管实例、切目录不改 pid/不换状态、进程存活、 同目录幂等、无 cwd 不切换;改回旧行为必挂)。
  • process 生命周期由 host 插件管理:启动写 $DSH_HOME/code-server/pid.json,停止树级终止(taskkill /T 或进程组 SIGKILL), 崩溃/退出实时更新状态;DSH host 重启后自动 adopt 仍在运行的实例(校验 pid + /healthz),不重复启动、不误杀别的进程;
  • node_modulesvendor/repack/ 已被 .gitignore 排除,推送/克隆仓库后按下方 "打包(如何出包)"执行 pnpm installpnpm run build:clientpnpm run vendor:vscode → (发布预编译原生包)→ pnpm pack + dsh plugin --profile web add 即可。

本机(BM: Windows 11 ARM64)实测:树/依赖全链路是"平台子包直挂插件依赖"供给 —— 树包 @jinsiyu/dshcs-vscode-server(当前 4.137.0,50.8 MB tgz)、纯 JS 内部依赖与 8 个平台无关 重打包包直接进插件 dependencies、8 个平台专属重打包包按 win32-arm64/x64 进 optionalDependencies(包自带 os/cpu 自动选),原始名字由 lib/native.js 补 junction 还原 → healthz 200 → 停止 → 回收全链路验证。 (0.1.37 时代的主包形态已废弃,见下方"升级 VS Code 树"。)

打包(如何出包)

cd C:\Users\User\Desktop\dsh-code-server-app
pnpm install             # 开发依赖(esbuild + 官方渲染器打包依赖);allowBuilds 已显式声明 → 不执行任何 postinstall
pnpm run build:client    # src/factory.js → lib/client.js(不入库,必须先构建)
pnpm run build:webview   # 「问 DSH」面板:官方 markdown 渲染器 + 面板外壳 → webview/thread.{js,css}(不入库,必须先构建)
pnpm run vendor:check    # 可选:查看内置 VS Code 树版本 vs code-server 最新版
pnpm run vendor:vscode                            # ① 生成 vendor/vscode(精简 VS Code 树,≈197MB)
pnpm run repack:build -- --target win32-arm64,win32-x64 --pack   # ② 统一脚本产出全部子包(见下表)
pnpm run publish:repacks                         # ③ 发布全部 @jinsiyu/* 子包(默认 dist-tag = next)
pnpm pack                                        # ④ → dsh-code-server-app-<version>.tgz(约 750KB,含面板渲染器产物)
pnpm run publish:plugin                          # ⑤ 发布插件本体(默认 dist-tag = next)
# 用户重启 dsh web 确认无误后,再把 latest 推进到该版本:
pnpm run promote -- <version>

build:webview 会把 DSH 官方的 markdown 渲染器与设计令牌打进面板产物(≈1.34MB:JS 996KB + CSS 87KB + KaTeX 字体 254KB),所以它要求本机有 DSH 部署:脚本读部署里 @deepseek-ai/dsh-web-frontend 的版本,与 devDependency 钉住的渲染器版本比对,不一致就报错退出(--allow-version-mismatch 才放行)。 它与 lib/client.js 同一约定:产物不入 git,prepack 里会自动重跑。 细节(为什么不是 iframe、令牌从哪来、体积取舍)见 docs/analysis-code-server-as-dsh-plugin.md 第 21 节。

dist-tag 政策(必须遵守):发布一律发到 next,不动 latest; latest 只保留「最近一个确认无 bug 的版本」,由 pnpm run promote -- <version> (= npm dist-tag add dsh-code-server-app@<version> latest)在用户重启 dsh web 确认无误后才推进。 这样 dsh plugin add dsh-code-server-app(不带版本)和任何按 latest 安装的流程都不会拿到未验证的版本。 子包(@jinsiyu/dshcs-*)被依赖以精确版本引用(平台专属的按目标各钉一份),dist-tag 不影响解析,但同样默认发 next。 查看当前标签:npm dist-tag ls dsh-code-server-app

desktop profile 不走命令行安装(2026-09-13 起的约定):对 desktop 只做 pnpm pack + publish:plugin(发 next), 由用户在 DSH Desktop 里用官方安装方式自行安装;不要再把 tarball 文件级覆盖进 ~/.dsh/profiles/desktop —— 那条路会绕过 desktop 应用自己的依赖闭包检查,把真实的解析问题掩盖成"装上了但行为怪"。 web profile 仍可照旧安装验证。

0.3.45 起没有平台聚合包,requires missing @microsoft/mxc-sdk@npm:… 那类报错不会再出现。根因(实测): pnpm 的增量 hoisted 安装会漏链「可选子树里的 npm: 别名包」,16 个里漏 9 个(第一个就是 mxc-sdk),而 dsh-desktop 在 pnpm add 之后立刻校验依赖图 ⇒ 首次安装必失败;重启后应用走「删 node_modules + 完整安装」 才补齐 ⇒ 就是你看到的"重启自己装好了"。复现命令与两条修法见 docs/desktop-first-install-root-cause.md

repack:build(scripts/vendor-repacks.mjs)是唯一的子包产出脚本,一次生成:

子包 内容 os/cpu
@jinsiyu/dshcs-vscode-server@<code-server 版本> 精简 VS Code 树(lib/vscode + out/browser + src/browser,不含 code-server 的 out/node 与 136 个运行时依赖) 平台无关
@jinsiyu/dshcs-<名字>[-win32-<arch>] ×16 VS Code 内部依赖里需要构建的原生包(node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / spdlog / …) 平台专属带 os/cpu
lib/vendored.json(不是包) 「原名 → 重打包子包」表,随插件发布;运行时由 lib/native.js 据此补 junction。0.3.45 起不再产出平台聚合包

argon2 已随 code-server 服务层一起移除(0.2.0):auth 固定 none,需要对外访问请用 serve: dsh

目标 命令
打最新版(上游 code-server 发行版) pnpm run vendor:latest(= --force):从 registry 取 code-server@latest 的树到 vendor/vscode;之后必须重跑 repack:build 并重发全部子包
指定版本 pnpm run vendor:vscode -- --version 4.137.0
从已装好的树快照 pnpm run vendor:vscode -- --from <code-server 目录>(秒级)
开发期让树可直接跑 pnpm run vendor:vscode -- --dev-links(额外把 lib/vscode/node_modules 用 junction 补上)
完整重打子包 pnpm run repack:build -- --target win32-arm64,win32-x64 --pack(不给 --from 会自动 npm install 解包 + 编译,耗时)
只重打树包 + 依赖表 node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack(复用 repack/build 里已有的原生包,不重新分析源树;顺带重写 lib/vendored.json 与插件依赖表)
发布子包 pnpm run publish:repacks(--dry-run 预览;--only <子串> 过滤;--otp <code> / --limit N 应对 2FA)
发布插件本体 pnpm run publish:plugin(发布已验证过的那份 tarball,不会重新打包;默认 dist-tag = next)
推进 latest pnpm run promote -- <version>(用户重启确认无误后;--dry-run 先看当前标签)
只报告版本 pnpm run vendor:check

pnpm packprepack 会自动跑一次 vendor-vscode-server 脚本;vendor/vscode 已存在时它是 秒级 no-op,所以日常只改插件代码的话直接 pnpm pack 即可(不会偷偷升级 VS Code)。 升级树必须显式 pnpm run vendor:latest(或 --force/--version),并重发子包。

回归脚本(改完跑一遍)

pnpm test:apply              # 桩 ctx 下跑通 apply(回归:apply 期的 ReferenceError)
pnpm test:claim-types        # 认领类型语法与默认值
pnpm test:bridge-routes      # 编辑器桥:路由表白名单(只读 + /approve)/ Origin 与令牌的判定顺序 / 令牌头三处一致
pnpm test:bridge-extension   # 编辑器桥扩展侧纯逻辑:未保存缓冲区上报、诊断排序截断、diff 判据、投递降级、面板状态机
pnpm test:webview            # 面板 webview 产物:官方渲染器与令牌打包、版本一致、/approve 的四条约束(先跑 build:webview)
pnpm test:launcher-routes    # launcher 的 HTTP 面(起真进程,较慢)
pnpm test:workspace-switch   # 切工作区不重启进程
pnpm test:fullscreen         # 打开标签即全屏
pnpm test:vendored           # 重打包表 ↔ 插件依赖表一致(无 npm: 别名 / 无聚合包 / vendored.json 进了 files)

test:bridge-routes 会把 DSH_HOME 指向临时目录(否则它会 adopt 开发机上正在跑的那个实例, 并改写真实的 bridge.json);脚本最后有一条"隔离自检"断言真实配置一字未动。

安装插件(一条命令;依赖全部由包管理器装好)

# 包内无 postinstall → 无需 pnpm approve-builds / allowBuilds;一条命令装完
dsh plugin --profile web add dsh-code-server-app@0.2.1
# 本地 tarball 同理:
dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-server-app-0.2.1.tgz

装完即用,没有第二步、没有「安装环境」、不弹安装指引。主包约 110KB(插件自身代码 + launcher), 其余全部是依赖:

  • VS Code 树(lib/vscode 196.9MB + out/browser + src/browser)是一个平台无关的包 @jinsiyu/dshcs-vscode-server@<code-server 版本>(0.2.0 起),写进插件 dependencies,运行根在 <profile>\node_modules\@jinsiyu\dshcs-vscode-server\vscode;
  • VS Code 内部依赖里纯 JS 的部分(35 个:xterm / katex / typescript / ws / tar …)也写在插件 dependencies,由 pnpm 装到 profile 的 node_modules(hoisted);
  • 二进制部分全部由 @jinsiyu/dshcs-* 子包提供,且直接挂在插件依赖上(0.3.45 起): 平台无关的 8 个(node-pty / koffi / ssh2 / cpu-features / @parcel/watcher / @vscode/fs-copyfile / @vscode/proxy-agent / @microsoft/mxc-sdk)写进插件 dependencies(真名); 平台专属的 8 个(@vscode/sqlite3 / spdlog / kerberos / deviceid / native-watchdog / windows-registry / windows-process-tree / windows-ca-certs)按 win32-arm64 与 win32-x64 各一份写进 optionalDependencies(真名 + 包自带 os/cpu)→ 一条命令自动选对架构; 原始名字lib/native.js 运行时补 junction 还原(见下「运行时布局自愈」);
  • 因此依赖图里没有任何带 pre/install/postinstall 或 binding.gyp 的包 → 不需要 profile 的 allowBuilds、不执行任何构建、使用者机器不需要 C++ 工具链;
  • 升级插件不再重下树:树包版本按上游 code-server 版本缓存,pnpm 直接复用(约 60MB,解包 ≈197MB)。

安装机制(为什么这样设计)

  • pnpm 11 的硬约束:依赖树里任何带 preinstall|install|postinstall(或包内含 binding.gyp/.hooks) 的包都被判定"需要构建",必须由宿主 profilepnpm-workspace.yamlallowBuilds 批准, 否则 dsh plugin add 直接 [ERR_PNPM_IGNORED_BUILDS] exit 1。依赖包自己的 pnpm.allowBuilds.npmrcpatch: 协议、optionalDependencies 全都不起作用(实测 2026-09,pnpm 11.25);
  • 打包期用 npm install code-server@<版本> --ignore-scripts(跳过官方 sh ./postinstall.sh: Windows 无 sh,且它只认 npm/yarn 的 user-agent)拿到上游发行版,然后只保留 VS Code 树: scripts/vendor-vscode-server.mjs 复制 lib/vscode/**out/browser/**src/browser/** 与许可文件到 vendor/vscode/,生成树根 package.json(版本 = 上游 code-server 版本,便于版本比对); code-server 自己的 out/node/** 与 136 个运行时依赖不再进包(由 lib/launcher.mjs 取代);
  • 需要编译的包scripts/vendor-repacks.mjs 重打包成 @jinsiyu/dshcs-*: 复制已编译的包目录 → 删除 scripts / files / binding.gyp / .hooks / .npmignore (保留编译好的 .node 与全部运行时文件)→ 依赖里的同集包改成 npm: 别名 → 平台专属的加 os/cpu-<platform>-<arch> 后缀;win32 目标还会校验 .node 的 PE machine (0x8664=x64 / 0xaa64=arm64),防交叉编译产物装错架构;
  • 原始名字怎么还原(0.3.45 起):重打包包的真名是 @<scope>/dshcs-<名字>,而 VS Code import 的是 node-pty / @vscode/sqlite3 这类原名;打包期把「原名 → 真名」写进 lib/vendored.json(随插件发布), 运行时由 lib/native.js<树>/node_modules/<原名> 补 junction 指向真名包(幂等、可自愈)。 为什么不再用「平台聚合包 + npm: 别名」:pnpm 的增量 hoisted 安装会漏链可选子树里的别名包 (实测 16 个漏 9 个),而 dsh-desktop 安装后立刻校验依赖图 ⇒ 首次安装必报 requires missing; 改成真名直接依赖后,同一条安装命令 + 校验器判据实测全部通过(复现见 docs/desktop-first-install-root-cause.md);
  • 解析路径:host 用 require.resolve('@jinsiyu/dshcs-vscode-server/package.json') 找到运行根 (包内子目录 vscode/),入口 vscode/lib/vscode/out/server-main.js;VS Code 内部依赖从该运行根向上查找 (vscode/lib/vscode/node_modules → 包 node_modules<profile>/node_modules)。 (旧全量树 @jinsiyu/dshcs-code-server/code-server 仍作为回退被识别。)
  • 运行时布局自愈(lib/native.jsensureRuntimeLayout(),激活时(先于 envCheck)与每次启动前幂等执行): host 会在 VS Code 树里补两类 junction(Windows junction / POSIX 目录软链):
    1. ensureAliasLinks():按 lib/vendored.json 把 16 个原始名字补到 <树>/node_modules —— 真名子包装在插件依赖图里,而 VS Code 的 lib/vscode/out/server-main.jsESM import (ESM 不认 NODE_PATH),缺了就直接 500;
    2. ensureInnerModuleLinks():把 VS Code 的内部依赖目录 lib/vscode/node_moduleslib/vscode/extensions/node_modules 按两个 package.jsondependencies 补回老布局 —— 精简树里没有这两个目录,用显式路径拼依赖的代码 (如内置 TS 扩展找 <ext>/../node_modules/typescript/lib/tsserver.js)否则会报 「VS Code's tsserver was deleted by another application…」(1.136.1 实测)。 链接都指向包管理器装出来的真实包,树被重装后的断链会被自动清理重建;envCheck 按双锚点解析, 并用 NODE_PATH 兜底 CJS。

体积提示:插件 tarball 约 110KB;@jinsiyu/dshcs-vscode-server60MB(解包 ≈197MB); 16 个原生包合计约 250MB。全部合计安装下载约 310MB。vendor/repack/ 都不入 git(见 .gitignore)。

从 ≤ 0.1.43 升级:树包由 @jinsiyu/dshcs-code-server(全量 code-server,含 out/node 与 136 个依赖) 换成 @jinsiyu/dshcs-vscode-server(精简树);新代码默认 serve: loopback,行为与 0.1.43 等价, 需要同源挂载再切 serve: dsh。升级命令不变(一条 dsh plugin --profile web add dsh-code-server-app@<版本>),旧的 dshcs-code-server 子包会被 pnpm 清掉。

从 ≤ 0.1.35 升级:旧版的安装根 <profile>\.code-server-app(含约 1.4GB 内部依赖)与 「安装环境」步骤都不再需要 —— 新版本会检测到它并打一条日志提示可安全删除: Remove-Item -Recurse -Force <profile>\.code-server-app。profile 的 pnpm-workspace.yaml 里 若还留着 dsh-code-server-app: false 之类的旧条目,也可以删掉(新版不再需要任何构建许可)。

卸载:dsh plugin --profile web remove dsh-code-server-app 即可;树包与原生包 是独立依赖,若要彻底清干净可再 dsh plugin --profile web remove @jinsiyu/dshcs-code-server (或直接在 profile 里 pnpm remove);若还残留旧安装根,再手动删除 <profile>\.code-server-app

安装/依赖变化后请重启 dsh web(静态插件行与 host 探测路径在启动时加载)。

开发期:源码目录安装(改动即时生效)

dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app

源码路径以 link: 安装。开发机上没有 vendor/vscode 时先 pnpm run vendor:vscode -- --dev-links; 没有平台子包时 host 会回退到包内 vendor/code-server(两种布局都支持)。 依赖(内部 JS 依赖 + 重打包子包)同样由 pnpm 安装 —— 本地未发布的 @jinsiyu/* 需先发布, 或把 repack/tgz/*.tgzfile: 依赖临时装进 profile(见 .tmp-verify.mjs)。

改动 client bundle:编辑 src/factory.js 后执行 pnpm run build:client 重新生成 lib/client.js(仓库不跟踪该产物;浏览器刷新即生效,host 无需重启)。 改动提问面板:编辑 assets/extensions/dshcs-editor-bridge/webview/src/* 后执行 pnpm run build:webview(同一约定:产物不入库;IDE 需重启一次才会加载新产物,扩展宿主会缓存 webview 资源)。

打包机环境要求(使用者机器什么都不需要)

工具链只在打包期需要;使用者机器不需要 C++ 工具链,也不需要联网装依赖之外的任何东西。

环境 版本/要求 使用者机器 打包机
Node.js v24.x(code-server 最新要求;本机 v24.13.1) 必需 必需
npm / pnpm npm 跟随 Node;pnpm 由 DSH 提供 必需(装依赖) 必需
MSVC 构建工具 VS Community 2026 + C++ 桌面负载 ❌ 不需要 打包期需要(编译 16 个原生包)
VS Spectre 缓解库 ARM64 与 x86/x64 各一份("MSVC v14x Spectre-mitigated libs") ❌ 不需要 打包期需要(否则 MSB8040)
Python 3.13.x ❌ 不需要 打包期需要(node-gyp)
node-gyp 13.x(旧版不识别 VS 2026) ❌ 不需要 打包期需要

缺 Spectre 库也能出包:vendor-repacks.mjs 在某个包编译失败时会自动把该架构 *.gyp 里的 SpectreMitigation 降级为 false 并重试(只是少了 Spectre 加固,功能不受影响),日志会明确提示。

Windows 原生构建要点(打包期,本机实测 ARM64)

  • VS 需 Spectre 缓解库组件(MSB8040):Visual Studio Installer → 单个组件 → "MSVC v14x Spectre-mitigated libs",ARM64 与 x86/x64 要分别安装(只装一个架构会缺另一个)。
  • node-gyp 13.x(旧版 9.x 不识别 VS 2026):npm install -g node-gyp@latest
  • x64 交叉编译:vendor-repacks.mjsnpm install --os=win32 --cpu=x64 --ignore-scripts 取包,再 npm rebuild --arch=x64 逐个编译,已验证产物 PE 架构正确(kerberos / sqlite3 / spdlog …)。
  • code-server 最新版要求 Node v24
  • 若不需要插件自足(例如已有全局 code-server),可跳过安装: 插件会回退到 PATH/配置的 bin(见"配置"表)。

升级 VS Code 树(上游 = code-server 发行版)

  • 打包期决定版本:pnpm run vendor:latest(= --force)取 npm 最新版 code-server 的树并重建 vendor/vscode; 也可 pnpm run vendor:vscode -- --version 4.137.0 或设 DSHCS_CODE_SERVER_VERSION。 已有 vendor/vscode 时,不带 --force/--version 不会升级(日常 pnpm pack 是 no-op)。 源树优先级(0.2.13 修正):显式 --from 用给定树;显式 --force/--version 一定走 registry —— 修正前它们会被"本机已有源树"抢走(defaultSourceTree() 先命中 vendor/code-server 或 profile 里的旧树), 于是 README 写的"vendor:latest 从 registry 取 latest"实际拿不到新版本(0.2.13 升级时踩到:内置树一直停在 4.136.2); 只有既没 --force 也没 --version 时才复用本机源树省一次下载。
  • 换版本时同步树内依赖 pin:--reuse 模式下纯 JS 直装集是从插件 package.json 现读的, 所以要先按新树的 lib/vscode/package.json 更新 pin(本次 10 个 @xterm/* 的 beta 跳号; 判定规则是"现有 pin 不满足新范围才动",避免把 cookie/ws/tar/node-addon-api 这类已有更高版本降级)。
  • 先查再升:pnpm run vendor:check 打印「内置版本 / 上游 latest」。
  • 换版本后重新出子包并发布(全部由同一个脚本):
    1. pnpm run repack:build -- --target win32-arm64,win32-x64 --pack → 新的树包 (@jinsiyu/dshcs-vscode-server@<新版本>)以及按新内部依赖重建的原生包 (脚本会把新的「纯 JS 直装集」写进插件 dependencies,并把 16 个重打包包按真名写进 dependencies / optionalDependencies、重写 lib/vendored.json);
    2. pnpm run publish:repacks → 发布;然后 bump 插件版本 → pnpm pack → 发布插件。
  • productPath(<quality>-<commit>,客户端 WS 路径的组成)lib/vscode/product.json 现算, 升级树后无需改代码 —— 但也意味着切版本后必须重启 dsh web(路由在激活期注册)。
  • 不再有运行期自动升级:不会在启动时联网取 latest;版本完全由内置产物决定。
  • 本机当前内置:code-server@4.137.0 的树(VS Code 1.137.0,productPath=stable-b11dabda…)。

兼容旧安装位

host 探测顺序:@jinsiyu/dshcs-vscode-server/vscode(0.2.0+ 正式布局)> @jinsiyu/dshcs-code-server/code-server (0.1.40–0.1.43 全量树)> @jinsiyu/dshcs-code-server-<平台>-<架构>/code-server(0.1.37 平台专属子包)> 插件包内 vendor/vscode > 插件包内 vendor/code-server(开发期)。旧安装根 <profile>\.code-server-app 只在启动日志里提示可删除,不再被使用。

设置卡片(设置 → 插件 → Code Server)

参照 dsh-auto-open-web 的自绘卡片模式,注册在 settings.plugin.item 插槽, 数据经官方 settings 域(settingsScope,命名空间 code-server)持久化到官方 settings 文档:

默认 说明
claimExtensions *;!md;!markdown;!html;!htm;!png;!jpg;!jpeg;!gif;!webp;!bmp;!ico;!svg;!pdf 认领类型(0.2.11,取代 0.2.5 的 fileOpenScope):按扩展名决定哪些文件交给 VS Code,分号分隔;* = 其余类型也认领,!ext = 不认领(排除优先)。默认把 DSH 预览渲染得好的四类(markdown/html/图片/PDF)留给它,其余全进 IDE;清空 = 不认领任何文件。不再区分 session/absolute 作用域
fullscreenOnOpen true 打开即全屏(0.2.9):打开 Code Server 标签(含点开文件)时自动把右侧栏切到全屏(铺满窗口);关闭则保持 DSH 默认的 push(与对话并排)。只影响打开那一刻,用户点「退出全屏」不会被抢回去
keepResident true 后台常驻:开启后宿主启动即把 IDE 预加载到"停放区",切标签/收起侧栏不重载、首次打开免等待;关闭则只在打开面板时加载(省内存)

(0.2.9 起卡片只留上面三个设置;windowedOpenreserveComposer 已移除 —— 旧设置文档里残留的键既不报错也不生效。 serve 仍是设置命名空间里的键(便于用设置文档切换),但没有卡片行,见「服务方式」。)

0.2.7 起卡片没有「入口」「依赖安装」「环境检测」三行:入口在右侧栏「开始」页的 Code Server 入口框(或官方的文件点击), 诊断信息不再进 UI —— /api/code-server/statusenv 字段仍返回 树版本 / productPath / server 入口、VS Code 内部依赖、预编译原生包(重打包子包名 + 已解析模块数),需要时用脚本查或看 host 日志。

卡片改动经 scope.watch 实时生效(host 端 status API 同步返回 keepResidentclaimExtensionsfullscreenOnOpen,客户端立即生效);无需重启 dsh。新增设置键后首次使用前需重启 dsh web, 让 host 重新注册设置命名空间(schema 含新键),否则新键的保存与校验不生效。

配置(cordis.patch.yml 的 config,均有默认值)

默认 说明
serve loopback 服务方式:loopback(独立回环端口,iframe 跨源)→ dsh(挂到 DSH 自身端口的 /code-server/*,转发到命名管道,复用 DSH 的 Host/Origin + cookie 防护)。需 DSH 提供 webServer,缺失时自动回退 loopback
bin ''(空 = 用自带 launcher) 逃生舱:显式指定外部 code-server 可执行文件 / out/node/entry.js 时退回旧模型(不经 lib/launcher.mjs)
host 127.0.0.1 loopback 模式的绑定地址(仅允许回环)
port 0 loopback 模式的端口;0 = 每次启动由系统分配随机端口(实际端口写在 endpoint.json,host 读回)。显式给端口则固定使用;该端口被占用且无有效 pid.json 时报错并给诊断(拒绝误杀)
auth none 固定 none(0.2.0 起 argon2 已移除);回环模式的访问控制由随机端口 + 路径令牌 + Host 白名单承担(见「回环端口的安全模型」),对外访问请用 serve: dsh
userDataDir $DSH_HOME/code-server/user-data 用户数据隔离目录
extensionsDir $DSH_HOME/code-server/extensions 扩展目录
locale '' 界面语言(空 = 跟随浏览器),如 zh-cn
readyTimeoutMs 60000 /healthz 就绪探测超时(TCP 或命名管道)
editorBridge true 编辑器桥(0.3.0 起):树内扩展 dshcs-editor-bridge 与 host 之间的只读通道(见「与 DSH 的协同」)。关掉 = 不写 bridge.json、不注册 editor_context/editor_diagnostics、扩展休眠。设置文档里的 code-server.editorBridge即时开关

用户级覆盖示例(写在 $DSH_HOME/profiles/web/cordis.patch.yml,应使用 - id: code-server 行覆盖):

- id: code-server
  config:
    serve: dsh        # 同源挂载:/code-server/*(无额外端口,复用 DSH 防护)
    # port: 8091      # serve: loopback 时才生效

JSON API(同源 fetch;web 与 desktop 同一套路径)

不依赖 webServer:host 半部经 ctx.connection.fetch.register 把路由挂在 DSH Connection 的共享 /api 通道上—— web profile 由 Connection 自己把 /api 前缀挂到 webServer(带 Host/Origin 校验 + 浏览器鉴权), desktop profile 由 apps/desktop-host/api/* 交给同一个 createSharedFetchHandler('/api')(IPC 帧管道,无 HTTP 服务器)。 客户端只写相对路径 fetch('/api/code-server/<op>'),两端行为一致。

方法 路径 说明
GET /api/code-server/status { ok, running, status, host, port, pid, cwd, launchCwd, url, version, error, logTail, adopted }(另含 env 环境检测与 setup 兼容字段;cwd = 当前 workbench 目录,launchCwd = 进程启动目录,loopback 下 port = 实际端口、url = 含路径令牌的完整地址)
POST /api/code-server/start body { cwd? }(省略 cwd 不切换工作目录);幂等;运行中切 cwd = 只换目录不重启进程(0.2.12);loopback 新启动会轮换端口与路径令牌(0.2.14)
POST /api/code-server/stop 停止并回收进程树
POST /api/code-server/setup 兼容空操作:0.1.36 起依赖由包管理器安装,调用只重新自检 env 并返回
POST /api/code-server/open-file body { file } — 写信号文件,由内置扩展 dshcs-open-file 在 code-server 中打开
GET /code-server-bridge/health 编辑器桥探活(无鉴权;只回答"桥活着吗",不含任何编辑器数据)。走本机 IPC(命名管道 / unix socket),不在 /api 下、也不需要 webServer
POST /code-server-bridge/sync 编辑器桥:扩展上报状态({context, diagnostics, workspace, at})并取回事件;?since=<seq> 是事件游标。需 x-dshcs-bridge-token,带 Origin 一律 403
POST /code-server-bridge/ask 编辑器桥:把编辑器里的提问投进当前会话({text, file?, lineStart?, lineEnd?, selection?, languageId?});没有可投递的会话时回 409
POST /code-server-bridge/event 编辑器桥:扩展上报打开/关闭文件等(进 host 日志尾)。需令牌

桥的四条路由都自带令牌鉴权(它们不依赖 DSH 的 cookie fence —— 扩展宿主拿不到浏览器 cookie), 且永远只读。传输是本机 IPC(0.3.13 起),所以 web 与 desktop 同一套: 端点由 host 写在 bridge.jsonpipe 字段里,扩展用 http.request({ socketPath }) 访问。

除桥之外,插件不再注册任何插件自有 HTTP 路由;code-server 图标已内联为 data URI(client bundle 内), 因此客户端不请求任何插件自有 HTTP 资源。

DSH Desktop(无 webServer)

  • host 半部 inject = ['connection', 'settings'](不含 webServer)——desktop profile 关掉了 webserver/web-runtime, 本插件照常工作;/api/* 请求由 Electron dsh-app:// 协议处理器 → IPC 帧管道 → createSharedFetchHandler('/api')
  • 右侧栏标签、guide 入口框、文件地址认领、设置卡片在 desktop 下与 web 相同(code-server 仍是本机 http://127.0.0.1:<port> 的 iframe; 桌面端 webSecurity: true 且页面无 CSP 限制,跨源 iframe 正常加载)。 桌面端同样自带 dsh-client-ui-sidebar-right(见 desktop 构建 seed 包列表),因此 0.2.3 的 "只支持带右侧栏的 DSH" 对 desktop 不构成降级;serve: dsh 会自动回退 loopback(那条路确实需要 webServer)。
  • 编辑器桥在 desktop 下可用(0.3.13 起):桥走本机 IPC(命名管道),与 webServer 无关 —— 扩展宿主是插件自己 spawn 的 IDE 的子进程,两端都在同一台机器上。host 注入 DSHCS_EXTENSIONS_DIR 后 扩展即可找到 bridge.json;/statusbridge.supported/endpoint 在 desktop 下同样是 true/管道名。
  • 安装到 desktop profile:桌面端插件管理窗(不是 CLI,见下)。
  • 桌面端安装的 24 小时供应链策略(实测,2026-09-10,已用它装上 0.2.4):
    • CLI 路径不可用:dsh plugin --profile desktop … 会被拒绝("profile "desktop" is managed exclusively by the Electron application"), 桌面端只能走应用内的包事务(pnpm add <spec> --save-exact,在 ~/.dsh/desktop/staging/<uuid>/profile 里执行后激活)。
    • 该事务用的 pnpm(应用自带 11.7.0,DeepSeek 打过补丁)在 add 前先做锁文件供应链校验: "Verifying lockfile against supply-chain policies (717 entries)",默认要求发布满 24 小时, 否则 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION
    • 关键区别(两者行为不同,实测):
      • 校验已有锁文件时:不接受 minimumReleaseAgeExclude(精确版本、裸包名都试过,不放行);
      • 解析(没有锁文件可校验时,如 pnpm clean --lockfile 之后):这份名单,而且 pnpm 自己会往 pnpm-workspace.yaml 追加条目(安装日志会打印 "Added N entries to minimumReleaseAgeExclude…")。
    • 因此桌面端装刚发布(<24h)版本的可行路径是 从"没有锁文件"的干净起点安装:
      1. pnpm clean --lockfile(注意:它会连 node_modules 一起删,profile 变成待重装状态);
      2. 用应用自带的 runtime 在 profile 目录里 add <spec> --save-exact --trust-lockfile (运行时/仓库/配置目录都在 ~/.dsh/desktop/pnpm/{store,cache,state,config,home},--config.userconfig=…/config/npmrc, 否则会出现 ERR_PNPM_UNEXPECTED_STORE / …UNEXPECTED_VIRTUAL_STORE);
      3. 安装后应用启动用的 install --offline --frozen-lockfile --trust-lockfile 可正常通过(锁文件与 package.json 一致、 包已在 store);dsh.profile.bundles 里已有插件名时不需要再改。
    • 不要用 minimumReleaseAge: 0 绕过:它确实能让校验通过,但那个键不在应用容忍的 policy 段里 (project-manager.ts 只忽略 minimumReleaseAgeExclude: / trustPolicyExclude:,且每次 mutate() 前都会校验), 写进去会让应用报 "core package mapping does not match desktop-packages.json"
    • 也可以选择 等满 24 小时再在插件管理里正常安装;web profile 不受影响(它的 pnpm-workspace.yamlminimumReleaseAge: false)。
    • 本插件的依赖闭包里含平台原生子包(@jinsiyu/dsh-code-server-runtime-win32-*),它们与插件本身同批发布, 因此每次新版本在桌面端都会受这条策略约束。
  • 桌面端的客户端 bundle 会被 Electron 缓存,重启应用不保证换新(2026-09 实测,0.2.5 踩到):
    • 现象:profile 里 lib/client.js 已是新版本,但渲染器仍跑旧代码——%APPDATA%\@deepseek-ai\dsh-desktop\Code Cache\js 里只有旧版本独有的字符串(如 dshcs-artifacts),新版本独有的字符串(如 claimExtensions / fullscreenOnOpen)一个都没有; Cache\ 里也存着一份引用 dsh-code-server-app 的旧响应体。官方插件同理(缓存里那份 ui-deliverables 连当前的 data-presented-files-row 都没有)。
    • 诊断手法(字节级,注意别用按控制台编码读文件的 Select-String,中文标记会假阴性): 在 Code Cache\js 里搜本版本独有的ASCII 标记(0.2.11 起用 claimExtensions),命中即说明新 bundle 真的被编译过。
    • 修法:完全关闭应用后删 CacheCode CacheGPUCache 三个目录再启动(只清缓存,不动 profile/会话/设置):
      Remove-Item -Recurse -Force "$env:APPDATA\@deepseek-ai\dsh-desktop\Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\Code Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\GPUCache"
      
    • 影响面:不只本插件——任何客户端插件升级后都可能继续跑旧代码;发布后请按上面的标记法确认渲染器真的换了 bundle。

已知限制

  • 编辑器桥需要 DSH 提供 webServer 已不成立(0.3.13 修正):桥改走本机 IPC (Windows 命名管道 / unix socket,http.request({ socketPath })),web 与 desktop 同一套, 不需要 webServer、也不开端口。历史:0.3.9–0.3.12 挂在 DSH 的 webServer 前缀下 ⇒ desktop 永远休眠; 0.3.7 及以前挂在 /api/code-server/bridge/* ⇒ 被 Connection 的 cookie fence 401 挡死。 文件打开从来不受影响(它走信号文件)。

  • /code-server-bridge/healthbridge 字段不代表扩展在跑(0.3.12 澄清):它只表示"桥的目标已就绪"。 扩展是否真的在跑,看 exthost 日志里有没有它的激活记录,或直接用 editor_context 试一次 —— 0.3.0–0.3.11 就是"health 说 bridge:true、扩展却从没被加载"的状态(原因见上:用户级安装被标 .obsolete)。

  • 桥的状态有最多 600ms 滞后:扩展每 600ms 推一次;超过 10s 没更新时工具会明说"状态已过期" 而不是拿旧数据当新数据(例如用户在 IDE 里关掉面板之后)。

  • 未保存缓冲区是"上报"而不是"接管":agent 仍然通过它自己的 fs 工具按磁盘内容编辑。 桥能做的是在写之前提醒写之后给 diff冲突时告警而不覆盖 —— 它不能替用户决定保存与否(那需要改动 agent 的读路径,不在本版本范围内)。

  • 提问面板只渲染"新内容"(0.3.22):订阅从面板建立那一刻开始,follow 开帧里的历史 records 被丢弃, 面板里没有"加载更早"(历史分页 API sessionController.page() 在这个版本里刻意不调用)。 想看更早的内容请回 DSH 界面。

  • 面板的高亮只带 DSH 启动集的三套语法(typescript / shellscript / json):官方其余语法走懒加载 (按需 import(),合计约 1.6MB),面板是单文件 IIFE、没有按需加载,所以那些语言的代码块纯文本显示 (与 DSH 首次渲染时的样子一致,不报错)。要全量:node scripts/build-webview.mjs --all-grammars

  • 面板产物与 DSH 版本绑定:渲染器按构建时 DSH 部署的界面版本打包,DSH 升级后要重打面板 (pnpm run build:webview;构建脚本会在版本不一致时直接报错)。运行期面板顶部也会提示版本不一致, 不会悄悄用错版本的渲染器。

  • 面板里的授权窗口是 5 分钟:面板打开着的时候授权先问面板(卡片上有倒计时);关掉面板或等满 5 分钟 就交回 DSH 界面 —— 交回之后这一条只能在 DSH 界面里处理(卡片从面板消失,对话流里留一行授权审计)。

  • 子路径不支持 已不成立(0.2.0 实测更正):VS Code 渲染出的 workbench HTML 里 资源引用全是相对路径(实测 9 条引用中绝对路径 0 条,serverBasePath="."rootEndpoint="."), 客户端 WebSocket 路径由 location.pathname + join(serverBasePath ?? '/', <quality>-<commit>) 拼成, 因此可以直接挂在 DSH 自身的 /code-server/* 下(serve: dsh),不需要独立端口、 也不需要改写 HTML。逐项证据见 docs/analysis-code-server-as-dsh-plugin.md

  • serve: dsh 的端口转发 WS 不可用:registerUpgrade 是精确路径匹配,而 /proxy/:port 的端口号在路径里 → 该模式下 Ports 面板的 WebSocket 转发不可用(HTTP 转发正常);需要时用 serve: loopback

  • serve: dsh 的 iframe 与 DSH 同源 → 该模式不挂 sandbox(同源 + allow-same-origin 可被 frame 自行摘除); loopback 模式跨源,sandbox 作为真防护保留。

  • 跨会话单实例:host 级共享一份 IDE;切换 cwd 只换 workbench 目录(0.2.12 起不重启进程,旧目录的后台终端不会被收走)。

  • 旧版 DSH 不受支持(0.2.3 起):没有 sidebarRightTabs/sidebarRight 的 DSH 上,除设置页一条升级提示外无任何入口; 旧版用户请留在 0.2.2(dsh plugin --profile web add dsh-code-server-app@0.2.2)。

  • 侧栏标签切换(0.2.2 起不再重载):DSH 右侧栏只渲染当前激活标签的 body,React 卸载会移走 iframe; 插件把 iframe 收成单例常驻面,用 Element.moveBefore()(状态保持型原子移动)在停靠位与文档级停放区之间搬, 切标签/收起侧栏再回来不重载。不支持 moveBefore 的浏览器退回旧行为(appendChild → 整页重载), 状态里以 degraded 明示;详见下方「为什么切标签不再重载」。

  • 远程访问:serve: dsh 下浏览器只需能到达 DSH 本身(单一端口,认证与 /api 同级); serve: loopback 仅回环绑定(随机端口 + 路径令牌 + Host 白名单,见「回环端口的安全模型」), 跨机访问请改用 serve: dsh(0.2.0 起不再支持 auth: password)。

  • 回环模式的令牌会随实例轮换:每次新启动端口与令牌都变;adopt(host 重启后接管存活实例)靠 endpoint.json + path-token 两个文件对上,所以别手动删这两个文件(删了 host 认不出旧实例, 会当成陌生端口占用处理)。

上一个 Prev harbor-self-evolving 下一个 Next dsh-llmasking