dsh-sidebar-vscode
已验证dsh-sidebar-vscode · v0.3.3 · MIT · Web 界面
DSH web plugin: an official right-Sidebar tab type ('vscode') that embeds the VS Code web workbench opened at the current session workspace — same-origin by default through the plugin's built-in reverse proxy (/sidebar/vscode), or via a gateway subpath /
安装
dsh plugin add dsh-sidebar-vscode 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
标签
说明文档
dsh-sidebar-vscode
为 DeepSeek Harness(DSH)官方右侧边栏注册一个内嵌 VS Code 网页版 的标签类型(ctx.sidebarRightTabs + @deepseek-ai/dsh-client-ui-sidebar-right 的 sidebar.right.pane.tab 席位),并把编辑器选区 / 资源管理器文件变成对话输入框里的原子引用 chip——提交时由 host 半展开为紧随引用消息之后的模型上下文。用户设置由插件入口的 volatile Config schema 派生,在官方插件页编辑:设置 → 插件 → dsh-sidebar-vscode 的配置区。
界面截图

编辑器选区 资源管理器
右键 / Ctrl+Alt+C 右键文件 / 文件夹
│ │
└──────────► 原子 chip ◄───────────┘
@src/main.ts L10-L12 @src/main.ts @src
│ │
▼ 提交时 ▼
<text-selection path line …> <file-selection path/> / <folder-selection path/>
(携带捕获快照与时效标记) (仅路径,无内容)
- 包名:dsh-sidebar-vscode(npm)
- 源码:chendefine/dsh-sidebar-vscode(GitHub)
- 版本:0.3.3
- 许可证:MIT
- 平台:web(DSH Web GUI)
- 测试:522 例全部通过(28 个规格文件)
功能简介
标签页
- 在官方右侧边栏注册页面标签类型
vscode(两阶段注册:静态定义进ctx.sidebarRightTabs(含引导页入口框),正文进 keyed 的sidebar.right.pane.tab席位),以同源 iframe 内嵌code serve-web工作台,并自动定位到当前会话的工作区(<base>/?folder=<映射后路径>)。官方 pane 只渲染活动标签的正文,而 iframe 一旦离开父节点浏览上下文即被销毁(HTML 规范:removing steps 销毁 child navigable;Chromium 151 实测同 document 内搬家也会整帧重载),因此 workbench 并不住在标签正文里:workbenchRuntime.ts把它放进document.body下的常驻宿主、永不离开 DOM,正文只渲染占位符,由投影器把宿主盒子钉在占位矩形上(见技术架构「常驻工作台」一节)。同 pane 切走再切回零成本——帧、WebSocket、编辑器状态全程在线;工作区 /serverUrl/pathMap变化原地重载;关闭标签(记录移除,即 signal abort)销毁工作台,插件卸载亦然; - 工具栏显示工作区路径,提供「刷新」「在新窗口打开」;外观跟随 DSH 亮 / 暗 / 系统主题;界面文案中英双语。
引用注入
选中代码引用:在嵌入的 VS Code 里选中代码,右键「DSH: 发送选中代码到会话」(英文界面:DSH: Send Selection to Session)或按 Ctrl/Cmd+Alt+C,选区落为对话输入框当前光标处的一条不可拆分的原子 chip(形如
@src/main.ts L10-L12,退格一次删整条;输入框如有选区则替换该选区);多光标选区按编辑器顺序逐条成 chip。提交时 host 半在agent/pre-step把它展开为紧跟该消息的独立 context 消息:<!-- User-captured VS Code selection (capture-time snapshot); re-read the file before editing. --> <text-selection path="src/main.ts" line="L10-L12" lang="typescript"> const a = 1 const b = 2 const c = 3 </text-selection>资源管理器文件 / 文件夹引用:资源管理器右键「DSH: 发送文件到会话」「DSH: 发送文件夹到会话」(英文界面:DSH: Send File/Folder to Session;支持多选与混选,按右键对象是否为文件夹二选一显示),每个选中项落为输入框当前光标处的一条原子 chip:文件
@src/main.ts、文件夹@src。资源引用不携带任何内容——提交时展开为只有路径属性、无正文、无提示注释的标记,tag 名本身表达文件 / 文件夹类型,模型需要时自行读取:<file-selection path="src/main.ts"/> <folder-selection path="src"/>引用管理:输入框上方渲染引用 tag 栏——同一引用的多条 chip 归并为一枚 tag(显示截断 / 文件夹徽标与出现次数),点 × 经一次草稿写入移除该引用的全部 chip;chip 的序列化形态是自包含的规范 mention,草稿文本即唯一存储,删光即不再注入,无残留状态;
降级与恢复:直连跨域
serverUrl时同源桥不可用,信封落入真实剪贴板,粘贴进输入框仍被识别为 chip 并落在粘贴光标处;chip 插入被输入机拒绝(提交中等瞬态)时退化为追加纯文本 mention(host 解析路径相同,仅失去 chip 外观);从对话气泡 / 外部编辑器复制渲染出的引用再粘回——即使是 sigil 被空白撑开的散架文本(@ [ label ]( dsh-vscode: … ))或丢失闭合括号的截断复制体——经 canonical 校验后在光标处重建为原子 chip,前后散文保持原样(fail-soft,绝不报错);openAsDefault开关只控制下面三个文件打开接管(better-sidebar 时代的「新会话种子交换」随该体系一同移除——官方侧栏种子是引导页、布局仅内存态、openTab必定展开列,新会话的发现路径就是引导页入口框)。右侧边栏按钮接管(同一开关):会话头部的「打开右侧边栏」按钮(折叠态的展开控制,官方
ExpandButton,唯一带data-sidebar-right-expand标记的节点)原本把列展开到上次停留的标签——新表面的种子是引导页(多条引导入口并存时官方种子规则永远解析到 guide)。开关开启时,本插件在 document 捕获阶段截获该按钮的点击并改发openTab('vscode'):揭示/新建会话唯一的 workbench 页标签并同一步展开列(打开自带的展开恰好覆盖按钮原要执行的setExpanded(true);只有打开成功后才stopPropagation+preventDefault,失败则点击原样落回官方展开)。按钮直连每会话 store、无公开服务接缝,DOM 捕获是唯一稳定切入点;开关每次点击现读,关闭即完全恢复官方行为。对话文件点击接管(同一开关控制):对话里点击变更文件标签(每轮结束的 produced-files chips)、工具行路径链接或正文文件引用时,改在内嵌 VS Code 里直接打开该文件——官方运行时把这些打开全部路由到
ctx.sidebarRight.openResource公共漏斗(ui-chat 注入的openFile、deliverables 行的 chips、正文文件链接),本插件在 controller 实例上包装这一个接缝(原型方法之上的自有属性 shadow;卸载时仅当阴影仍属自己才还原)。包装器解析dsh-resource://file/session/<id>/<rel>/…/absolute/<path>地址、解析会话工作区根,把打开翻译为openTab('vscode', { params: { path, line? } })—— 侧栏同一步展开并揭示唯一的 workbench 标签,文件经tab.navigation(带单调revision的 params——天然的单发命令载体)落到其内部。文件类型拦截(openBlocklist):命中黑名单后缀(默认 pdf/docx/xlsx/pptx/png/jpeg/jpg,可在设置卡增删)的文件不进 VSCode——调用原样落回官方路由,由官方注册表的查看器认领(今天是内置文本预览;未来的查看器插件会注册自己的官方类型)。拒绝路径同理:开关关闭、非文件地址、解析失败、会话根未知都原样放行,且每次点击现读设置。认领后的链路:正文侧的 navigation 消费者 → 扩展 spool(/tmp/dsh-sidebar-vscode/<slug(workspace)>/cmd.json,500ms 轮询消费、showTextDocument)——cap.json活性标记 + 能力探测把门,任一失手降级为 URLpayload参数整页重载一次。启动标签投递(扩展 ≥ 0.1.3):命令等本次启动的 nonce 落盘后才发送,并把 nonce 作为启动标签写进命令(cmd.json {boot});只有以同一 nonce 启动的扩展宿主才会消费带标签的命令(残留宿主不得把命令吃进垂死窗口)。开关关闭 = 完全不启用(chat 行为零变化)。设置页「打开配置文件」接管(同一开关):按钮原本把
$DSH_HOME/settings.yaml交给系统原生打开器——headless 容器上直接失败(xdg-open缺失);当前运行时上点击走remote.settings.openSettingsDocument宿主 Remote(SettingsDocumentStore.open 是唯一生产调用方;包装器重定义该命名空间方法的 getter-only 自有属性,经嵌套可选 inject 安装——子 fiber 等到remote.settings服务出现才运行),gateway 之前的运行时则走旧版/api/settings.openDocument成员——两者恰好只会拦截到一个。开关开启时,本插件改走自有的受信围栏路由(POST /sidebar-vscode/api/settings.document→prepareDocument())取到文档绝对路径,再以openTab('vscode', { params: { path } })改道——与对话点击同一条导航通道,配置文件在内嵌 VS Code 里打开(绝对路径无需命中pathMap规则,mapPathForOpen对未匹配路径原样透传)。改道落地后「设置」弹框也会自动关闭:弹框开启状态是组件本地 state(没有服务暴露关闭方法),关闭走弹框自身挂在 document 上的 Escape 监听(生命周期恰好等于弹框开启期)——合成一次 Escape 键事件即可,视野留给工作台。全程 fail-soft:settings 服务缺失、host 半未重载、任何传输错误都回退到原生打开(弹框不关),按钮不会因本插件而坏。
安装方法
前提
- Web 应用带官方右侧边栏的 DSH 宿主(
@deepseek-ai/dsh-client-ui-sidebar-right;sidebarRightTabs/sidebarRight服务自 0.1.5-alpha.1 起存在,configForms服务自 0.1.7 起存在——会话头部的「展开侧栏」按钮即标志)。缺席时客户端 fiber 静默等待、什么都不注册; - 一个浏览器可达的
code serve-web实例。接入形态按部署环境任选:- 内置反代(默认,Windows / 局域网直跑
dsh web首选)——serverUrl留空即默认http://127.0.0.1:8000(本机裸启动code serve-web的完整地址),或直填 serve-web 输出的任意完整地址(可含基路径与?tkn=令牌)。地址经/sidebar-vscode/api/proxy.config推给宿主半,在dsh web自己监听的端口上挂载为同源/sidebar/vscode/(HTTP 透传 + WebSocket 管道 + 令牌自动附加),零 nginx、零启动参数:
路由以首页 HTML 烘焙的真实code serve-web # CLI 默认:8000 端口、根路径serverBasePath自动校正——serve-web 加不加--server-base-path都行(非挂载点基路径注册恒等镜像,根路径注册<quality>-<commit>补片)。宿主暂时探不到该地址时自动回退直连 iframe 并提示,代理就绪后自动切回挂载点。免配置预启用可设DSH_SIDEBAR_VSCODE_UPSTREAM(同样接受完整地址;默认http://127.0.0.1:8000;off关闭)。/sidebar/vscode被其他插件占用时仅告警退出,不影响插件其余功能; - 网关同源反代(参考拓扑)——serve-web 与 dsh-runtime 同容器,经网关子路径
/vscode反代(见部署拓扑);同机部署留空 serverUrl 也可(内置反代接管)。仅当 serve-web 不在 DSH 宿主可达范围内时才需显式配置(serverUrl填/vscode或预配置环境变量指向真实地址); - 跨域直连(自动降级形态)——仅当宿主半无法反代时自动出现:同源剪贴板桥不可用(粘贴兜底仍在),选区发送走「复制 → 粘贴」链路;
- 内置反代(默认,Windows / 局域网直跑
- 配套 VS Code 扩展
dsh.selection-reference已装入该 serve-web 实例(提供右键命令与快捷键;对话文件点击打开通道需 ≥ 0.1.2,见下)。
插件本体
通道 A —— bundle 通道(标准,干净 profile 推荐)
包内声明了 dsh.bundle.patch(cordis.patch.yml:一条 insert 行,挂载 host 半 entry)。从 npm registry 安装(预构建产物,无需构建许可):
dsh plugin --profile web add dsh-sidebar-vscode
从 GitHub 仓库安装(源码——pnpm 会执行 prepare 构建;仓库同时带有已提交的 lib/ 产物兜底):
dsh plugin --profile web add github:chendefine/dsh-sidebar-vscode
或经 DSH 插件市场(设置 → DSH插件市场)——给仓库打上 dsh-plugin topic 即被自动收录。
bundle 插件加入 profile 层叠后需重启 dsh web 才加载;卸载用 dsh plugin --profile web remove dsh-sidebar-vscode,再重启一次。
通道 B —— link + 手动挂载行(本部署的热通道,免重启)
# 1. 把插件以 link: 依赖装进 web profile
# (<repo-checkout> 为仓库检出路径,如 /opt/dsh/plugins/dsh-sidebar-vscode;
# 本部署 profile 目录为 /data/dsh-home/profiles/web)
pnpm -C <profile-dir> add link:<repo-checkout>
# 2. 在 profile 自己的 cordis.patch.yml 追加挂载行
# - insert:
# - id: dsh-sidebar-vscode
# name: dsh-sidebar-vscode
watchUserPatches 热挂载 node 半,client boot graph 实时重算,/plugins/dsh-sidebar-vscode/client.js 立即可服务——浏览器硬刷新(Cmd/Ctrl+Shift+R)即可看到新标签。
⚠️ 两个通道互斥:包内 bundle 行与 profile 手动行使用同一 entry id
dsh-sidebar-vscode,同时存在会在启动时报 duplicate entry。切换通道前先删掉另一通道的行(bundle 通道 ↔ 手动行 + link 依赖)。包内 patch 中预留的双挂载守卫(disabled: !!js …)默认注释停用。
提示:client 半改动硬刷新即生效;host 半(
src/index.ts/src/mention.ts)改动需dsh web重启(或经 profile 热通道重挂载该 entry)后才加载新 bundle。
VS Code 扩展
选中 / 文件发送命令与对话文件点击的轮询通道由扩展 dsh.selection-reference(源码在 extension/)提供,需装入 serve-web 实例。文件打开通道需要 ≥ 0.1.2(0.1.1 会在每次 workbench 重启时重放已消费的命令——见下方重放防护;其能力标记过不了版本探测,打开点击会安全降级为 URL payload 重载):
scripts/install-extension.sh # 打包 VSIX → 装入 serve-web → 注册清单 → 重启 → 健康检查
scripts/install-extension.sh --skip-build # 复用已构建的 VSIX
scripts/install-extension.sh --vsix <path> # 使用指定 VSIX
本机的 code 是 standalone CLI(无桌面安装),code --install-extension 不可用,脚本以「vsce 打包 → 落文件 → 注册 extensions.json 清单 → 按原参数重启 serve-web」四步完成安装。分步流程与排障见 scripts/install-extension.md。
重放防护(≥ 0.1.2) —— 关闭侧边栏 VSCode 标签会把 workbench iframe 整个从 DOM 撕掉,重新打开时扩展宿主全新启动;一条在消费后仍留在 spool 里的命令会在每次这样的重启时重新打开它的文件(即「文件已关闭、下次启动 VS Code 又自动打开」bug)。三重独立防护:扩展消费即删除 cmd.json(垃圾/超期命令同样当场删除——超过 10 分钟的命令永不打开)、已消费 nonce 水位线持久化在 last.json(删除失败的兜底)、**带版本的 cap.json 标记({v:2,at})**让客户端能力探测直接拒绝会重放的 0.1.1。
内嵌启动干净开场:台账 + 对账 + 隐藏揭幕(≥ 0.1.2) —— iframe 的销毁同样跳过 VS Code 的 unload 生命周期,其编辑器状态恢复可能重放用户在关标签前几秒刚关闭的文件(VS Code 仅周期性落盘工作区编辑器状态)。本模型精确复原关闭前仍打开的文件,且中间过程不可见:
- 编辑器台账(
editors.json)——扩展在每次标签变动时把窗口当前打开的文件标签(顺序 + 活动编辑器)同步写进 spool,销毁竞态无法弄丢它;下次激活时磁盘上的内容就是上一会话的最终状态。 - 启动对账——激活时先等 VS Code 自身的恢复落定,再让窗口对齐台账:台账里没有的恢复标签(关标签前已关闭的文件)被关闭(脏标签保留,数据优先)、恢复丢失的台账文件被补开、活动编辑器复原。无台账的启动(工作区首次启动,或降级 URL-payload 打开)完全不动 VS Code 自身行为。
- 隐藏揭幕(
boot.begin/boot.status)——挂载 iframe 之前,标签页先在 spool 里停放一枚启动 nonce(bootreq.json);扩展对账完成后在boot.json回执里回显它,客户端在此之前让 iframe 保持 opacity 0、以加载遮罩示人。用户看到的第一帧就是对账完的编辑器区——绝不会先看见某文件被打开又被关掉。揭幕是一场竞速而非单一等待:回执轮询与 DOM 静默观察器(同源读取 workbench 的编辑器标签条)并联,先到先赢——原地重载后的扩展宿主可能不再重新激活(回执永远不来),而已经画完的工作台本身就是「布景完成」的证明。但仅静默构不成这个证明:幽灵启动的整个对账 settle 窗口内,标签条是「静默但错误」的(静默恰恰是随后那次关闭的前置条件,绝不是关闭已发生的证据),因此竞速者是账本感知的——boot.begin应答会一并带回停靠时启动账本的期望开档集合,静默揭幕(以及 4 秒轮询预算耗尽后的兜底)还额外要求采样到的标签条与该集合按基名多重集一致(标签条 DOM 不暴露全路径);回执匹配仍无条件揭幕,无账本(首次启动 / 较旧 host 半)照旧仅凭静默,而账本不吻合的标签条会让 iframe 一直藏到对账关闭落地、或观察器自身 8 秒上限兜底。提前揭幕还有第二重危险,仅靠握手看不见:用户在「已揭幕、对账未跑」窗口里打开的文件晚于对账所依据的账本,会被当幽灵关掉——因此揭幕后帧内的首个用户手势会落盘interact.json(nonce 作用域,路由boot.interact),对账的关闭循环与幽灵补刀都会为本 boot 的交互标记让步(回执报告让步数;陈旧标记绝不会解除后续 boot 的武装)。全链路 fail-soft:较旧的 host 半(boot 路由尚未重载)由 DOM 静默观察器单独裁决,跨源直连 iframe 则不加门控、按原生行为可见启动。 - 跨标签启动锁——两个同源 DSH 页面并发启动工作台时会竞争创建 VS Code 的 IndexedDB 存储(
vscode-web-db),败者可能在自身启动过程中挂死数分钟(实测:一次 294 秒的库打开;两个全新 profile 各自卡到另一标签页关闭为止)。因此 iframe 挂载前必须先持有 Web Lockdsh-sidebar-vscode:workbench-boot,工作台绘制完成即释放(那一刻创建竞争已经结束);排队超过半秒会在加载遮罩中说明缘由。设计上处处 fail-open:没有 Web Locks API、锁被卡死的持有者(60 秒等待上限)、或不可读的框架(30 秒持有上限)都选择放行而不是阻塞。 - 台账归属围栏 + 启动轮换(扩展 ≥ 0.1.3)——serve-web 在渲染器消失后也会让扩展宿主存活一段时间,而这类残留宿主的台账处理器仍然 armed:它会把自己那个不可见窗口的标签集写进共享的
editors.json,对账的 reopen 循环还会把台账文件开进该窗口、其标签事件又反过来重写台账——台账就此被可见 workbench 从未展示过的文件毒化,之后每次启动都如实复活(「已关闭的文件又回来了」)。台账现在归属本次启动:每次台账写入、对账本身、以及幽灵补刀都会复核停放的启动 nonce 是否仍是本宿主激活时的那枚。客户端在 iframe 原地重载时(工作区 /serverUrl/pathMap变化、降级通道 payload、手动刷新——运行时的换键路径;新渲染器的宿主若沿用旧 nonce 就会错配。单纯切换标签已不再触发任何重载,见「常驻工作台」一节)以及工作台销毁时轮换该 nonce,把所有仍持旧 nonce 的宿主一并退役。 - 迟到幽灵补刀(扩展 ≥ 0.1.3)——VS Code 自身的恢复可能在 对账的 settle 预算耗尽之后还在陆续落标签(重工作区的慢启动),而这些迟到者恰恰是没人会再去关的「已关闭文件幽灵」。武装之后会间隔着跑几轮只关不开的差分:每轮关掉台账未列出且处于后台的标签,豁免脏标签与活动编辑器(重新挂载后数秒内,用户主动打开的文件必然成为活动编辑器,而恢复幽灵落在后台);任何一轮都不会打开文件。
使用方法
打开标签页
展开右侧边栏,在引导页(「开始」页)选择 VSCode 工作台 入口框——它在引导标签的位置打开。布局仅内存态:刷新页面后每个会话回到折叠默认态。工具栏显示当前工作区路径,「⧉ 在新窗口打开」可弹出独立窗口。
发送选区
- 在嵌入的编辑器里选中代码(多光标 = 多条 chip);
- 右键菜单「DSH: 发送选中代码到会话」,或按 Ctrl/Cmd+Alt+C;
- 输入框出现原子 chip
@src/main.ts L10-L12——注入成功静默无提示,chip 出现即反馈;仅降级 / 失败时工具栏闪琥珀色提示; - 照常输入并提交。chip 被改写为可读
@path L10-L12,<text-selection>context 紧跟该消息注入。
选区引用行为细则:
- 去重:同一 step 内按
(路径, 起始行, 结束行)去重——重复发送同一选区只注入一条 context;同范围不同内容(文件已改)保留最新捕获; - 时效:提交时在会话 cwd 约束下重读磁盘行区间比对哈希——不一致标
stale="true";截断快照改为校验保留的首尾两半(截断本身绝不导致 stale);未保存缓冲区标dirty="true";快照内容始终注入(不依赖文件系统),tag 首部注释提示模型修改前先重新读取; - 截断:超过
maxLines(默认 200)/maxBytes(默认 20000,防单行压缩大文件)时保留首尾两半、省略中间,正文内嵌... (N lines omitted, L51-L150) ...标记,标签带truncated="true";行号保留真实范围; - 注入的 context 消息 source 为
{ kind: 'vscode-mention', form: 'notice', version: 1, path, startLine, endLine, language?, contentHash, bytes, truncated, dirty, stale }。
发送文件 / 文件夹
资源管理器选中文件 / 文件夹(可多选混选),右键「发送文件 / 发送文件夹到会话」(命令位于「复制路径」附近)。每项一条 chip:文件 @src/main.ts(文件图标)、文件夹 @src(文件夹图标)。类型由扩展侧 workspace.fs.stat 判定(symlink 按目标归类)。提交时展开为 <file-selection path/> / <folder-selection path/>,source 为 { kind: 'vscode-resource', form: 'notice', version: 1, path, type }。资源引用不做新鲜度检查、不受截断上限约束;同一 step 内按 (路径, 类型) 去重,同一路径的选区引用与资源引用互为独立引用。
管理引用
- 输入框上方 tag 栏归并显示全部 VS Code 引用(截断徽标
…、文件夹图标、出现次数 ×N);点 tag 的 × 一次移除该引用的全部 chip; - chip 本身退格一次删整条;草稿里该引用的 mention 全部消失后,提交即不再注入;
- 复制 chip(在对话里渲染后)再粘回输入框,会重建为原子 chip。
设置
设置由插件入口的 volatile Config schema(src/config.ts)派生,在官方插件页编辑——设置 → 插件 → dsh-sidebar-vscode 的配置区(插件包页面里描述与行之间的配置表单)或该行的配置入口:设置服务按组合入口 id dsh-sidebar-vscode 键派生配置节,浏览器半把同一张卡片注册进插件页提供的两个席位(plugins.bundle.config 按包名键、plugins.row.config 按 <包名>#<行 id> 键,均在宿主伺服该节期间挂载),每行经共享配置表单(ctx.configForms)提交(set/unset;底部的「恢复默认」逐字段清除用户层,各自回到组合基线——出厂基线随 cordis.patch.yml 携带)。无需手工注册任何命名空间(下表的 pathMap 由同一节伺服,但刻意不设卡片行):
| 键 | 默认 | 说明 |
|---|---|---|
openAsDefault |
false |
控制三个文件打开接管(对话文件点击、设置页「打开配置文件」、右侧边栏展开按钮)。关闭后处处恢复官方默认行为 |
openBlocklist |
(未设 = pdf, docx, xlsx, pptx, png, jpeg, jpg) |
「不由 VSCode 打开的文件类型」:后缀字符串数组,面板行为 tag 编辑器(tag 带 × 删除;输入框自由输入后缀,回车/逗号添加,下拉建议常用二进制类型)。命中后缀的文件在对话中点击时不被接管——原样落回官方路由,由官方侧栏的查看器认领(今天是内置文本预览;未来查看器插件会注册自己的官方类型),绝不落宿主 OS 打开器。未设置 = 默认七项;清空 = 存 [],即全部由 VSCode 打开(两者语义不同)。匹配大小写不敏感,按「基名以 .后缀 结尾」判定(a.notpdf 不命中 pdf;条目可含内部点,如 tar.gz;无后缀文件永不命中);每次点击时现读设置,改动即时生效。设置页「打开配置文件」接管不受此表影响 |
serverUrl |
(空 = http://127.0.0.1:8000) |
code serve-web 输出的完整地址(可含基路径与 ?tkn= 令牌);留空 = 默认 http://127.0.0.1:8000(本机裸启动)。代理可达时一律挂载为同源 /sidebar/vscode/ 打开;宿主不可达时完整地址回退直连(同源桥降级);显式相对子路径(如 /vscode)在代理关闭时按网关语义使用 |
pathMap |
(空 = 不映射) | 仅配置文件——无设置面板行(少见,分容器部署才需要)。DSH 路径前缀 → VSCode 容器路径前缀,源=目标 对用 ; 分隔;最长源前缀优先;某前缀已是映射目标时原样透传。留空时不做任何映射,会话 cwd 与文件均按原始绝对路径直接打开(同容器部署即用此默认)。规则只做前缀改写、不是白名单:未命中任何规则的绝对路径原样透传(文件真不存在时由 VS Code 报错兜底) |
maxLines |
200(范围 1–2000) |
单次引用注入的代码行数上限,超出保留首尾两半、省略中间并标注省略区间 |
maxBytes |
20000(范围 1000–200000) |
单次引用注入的 UTF-8 字节上限(防压缩成一行的超大文件) |
(选中代码注入功能常开,无开关。)数值行在输入时即校验范围(越界红框 + 行内提示,确认时吸附到最近边界);文本行为上下布局(说明在上、输入框独占一行);openBlocklist 行为 tag + 内联输入(非法后缀红框提示,重复添加为静默无操作)。
排障
| 症状 | 处理 |
|---|---|
| 标签页长时间空白 / 加载提示不消失 | 检查「功能设置」里的 serverUrl 是否可达;用「在新窗口打开」直连排查;跨域地址下同源桥不可用属预期(粘贴兜底仍可用)。用内置反代时确认 serve-web 已在配置的上游应答(默认 http://127.0.0.1:8000,任意基路径均可) |
命中 openBlocklist 的文件改在自带「文件」标签打开(或点击报错) |
黑名单命中 = 改道侧边栏自带「文件」标签(其查看器负责渲染该类型);仅当「文件」标签类型在侧边卡片设置里被禁用时才回落宿主 OS 打开器,无头容器上该打开器可能缺失。若希望该类型仍由 VSCode 打开,从列表移除对应后缀即可 |
| 工具栏提示「当前工作区路径不是绝对路径…」 | 仅在会话 cwd 不是绝对路径时出现(workbench 已按默认界面打开);正常部署不会触发,无需配置映射 |
| 右键没有 DSH 命令 / 命令面板搜不到 | 扩展未装或 serve-web 未重启(清单仅启动时扫描),或工作区处于受限模式未信任——见 scripts/install-extension.md 常见问题表 |
| 发送后 chip 未出现,剪贴板出现代码片段 | 注入降级为可读回退文本(无可用输入框 / 跨域);直接粘贴进输入框即可恢复为 chip |
| 提示「已注入为文本引用…」 | 输入框处于提交中等瞬态,chip 化被拒,已退化为纯文本 mention——提交效果相同 |
| 新建会话后光标闪两下就消失,或从其他工作区会话切回后焦点跳进侧边栏打开的文件里(旧版本症状) | workbench 开机会聚焦自身(欢迎页渲染即抢、工作区恢复时聚焦恢复的编辑器):收起面板后的隐形启动会无声夺走光标;跨工作区切回触发的重插重载又恰好抢在输入框自动聚焦之后。现版本通用防护——首次加载延迟到标签真正可见,且帧的每次开机都带焦点围栏,把未经邀请的抢焦点弹回原处,用户真实点进 workbench 则立即放行(见下文「焦点防护」);展开侧边栏时的首次加载会稍晚出现工作台,属预期 |
技术架构
双端结构
DSH 插件分 host(node)半与 browser 半,本插件各自职责:
┌─ 共享协议平面 ────────────────────────────────────────────────┐
│ src/shared/protocol.ts 跨进程边界的全部常量,单一事实来源; │
│ 扩展侧 CJS 镜像由锁步测试钉死不漂移 │
└───────────────────────────────────────────────────────────────┘
┌─ host 半 (node) ──────────────────────────────────────────────┐
│ src/index.ts agent/created → 在每个 agent 作用域挂 pre-step │
│ src/mention.ts 引用边界核心:解析改写 / 去重 / 新鲜度 / 注入 │
└───────────────────────────────────────────────────────────────┘
┌─ browser 半 (web) ────────────────────────────────────────────┐
│ src/client/index.tsx 注册 tab + dock + @ 触发源 + 词典 │
│ src/client/VscodeView.tsx 标签视图:投影锚点 + 外壳 │
│ src/client/*Controller 启动门控/焦点围栏/基址/打开请求/打开 │
│ 编排器(均可注入依赖单测) │
│ src/client/references.ts 载荷→chip、插入、tag 栏、粘贴恢复 │
│ src/client/composer.tsx dock 组件:引用 tag 栏 + 粘贴兜底 │
│ …(完整清单见下文目录结构) │
└───────────────────────────────────────────────────────────────┘
- host 半是模型 facing 边界:对每个存活 agent 在
agent/pre-step监听,解析被认领用户消息中的规范 mention(markdown 与裸 URI,两种 scheme,严格 canonical 校验),改写为可读标签(freezeMessage保留消息 id),按引用身份去重后逐条注入 context(createUserMessage,紧跟首次引用它的消息)。文件系统只用于新鲜度标记——快照内容随 mention 携带,注入不依赖磁盘状态; - browser 半负责全部 UI:官方
vscode标签类型与正文、chip、tag 栏、设置卡片、接管、词典;官方侧栏服务缺席时客户端 fiber 静默等待、什么都不注册。
常驻工作台(切换标签为何零成本)
官方 pane 只渲染活动标签的正文,而 iframe 元素一旦离开父节点,其嵌套浏览上下文即被销毁——HTML 规范的 removing steps 会 destroy child navigable,且 Chromium 151 实测:即使同 document 内移动 iframe(或移动其祖先容器)也会整帧重载。VS Code 没有「快照重建」路径(ui-sidebar-terminal 用控制器里的屏幕修订重建 xterm——本设计借用的模式,换成唯一一种「状态即文档」的元素),因此工作台要活过标签切换,只有一条路:元素永不离开 DOM。
workbenchRuntime.ts持有页面级单例——一个一次性挂到document.body的宿主div,iframe 在首次获准加载时创建于其中,销毁时才移除。全部启动门控控制器(boot lock、boot gate、焦点围栏、打开通道 opener、剪贴板桥)都住进运行时,状态因此活过标签正文的挂载周期。视图(VscodeView.tsx)只渲染占位符与外壳,向运行时喂解析后的输入,并按${sessionId}:${tabId}采纳它;projection.ts把宿主盒子粘在占位矩形上——可见期间每动画帧一次getBoundingClientRect读 + 一次样式写,投影帧因此能跟上面板滑动、拖宽手柄、全屏切换与浮窗拖动而无需移动 iframe。层叠策略:停靠 / 全屏 45(高于全屏面板的 40、低于浮窗宿主的 60),占位符浮出时 61(高于浮层、低于 70 的菜单)。隐藏时以visibility保留最后矩形——宿主归零会逼 VS Code 在每次隐藏/显示周期里重排布局;- 防泄漏纪律:每页最多一个活工作台(basis 变化——换工作区、改设置——原地重载,绝不出现第二个实例;boot lock 存在的意义正是防止两个同源并发启动把 VS Code 的 IndexedDB 死锁);运行活过正文卸载,在最后一个采纳标签记录消失时销毁(框架在记录移除时 abort 的 tab signal),插件卸载时也销毁(client 入口 teardown 里的
destroyWorkbenchRuntime())——经 signal abort 自毁的运行时会自行让出单例槽位,下一次 adopt(重新打开的标签)铸造全新工作台,而不是拿到方法全部 no-op、宿主永不回归的死句柄。两个 pane 可同时持有同 kind——最后挂载者拥有投影,其释放时回退到前一个 pane 的占位符而非留白。
对旧防护所针对的重载路径的影响:同 pane 切标签、面板收起/展开、同工作区切换会话,现在都不再重载(帧在后台继续运行——连启动都会在后台完成);原地重载只剩:工作区 / serverUrl / pathMap 变化、降级通道 payload、手动刷新按钮——恰好是启动门 nonce 轮换与账本对账仍然发挥作用的场合。重挂载后的瞬时 base 重解析也绝不拆掉活帧——运行时持有上一个已解析 base,直到出现不同的解析结果。
四级链路
选区与资源引用共用一条链路:
- VS Code 扩展(
extension/):选区命令打包{ path, relative?, language?, dirty?, spans[] };资源命令对每个 URIworkspace.fs.stat判型打包{ kind: 'resource', resources: [{ path, relative?, type }] }(无内容),经vscode.env.clipboard.writeText写入信封@@DSH_REF::<base64url(json)>::\n<可读回退文本>; - 剪贴板信号桥(
src/client/clipboardBridge.ts):同源 iframe 特权——父页面在 workbenchwindow.navigator.clipboard.writeText上打补丁,拦截扩展宿主的剪贴板写入链(ext host → MainThreadClipboard → BrowserClipboardService → 晚绑定的navigator.clipboard.writeText);注入成功时完全不触碰真实剪贴板,仅失败时才把可读回退写入供手动粘贴;跨域 URL 下桥直接 no-op; - composer chip(
src/client/references.ts+composer.tsx):载荷经pathMap反向映射回 DSH 路径(cwd 之下相对化)、截断(首尾两半)、crypto.subtle计算 sha-256 前缀,编成规范 mention,经conversation.input服务的insertReference落为原子 occurrence chip——落点是输入框当前光标:优先经输入解析器的 keyboard face(caretSpan(),Lexical 时代 composer 自身的光标投影)取目标会话的实时选区,退回显示中 composer 的 DOM 选区经 detect 投影映射(composerDom.ts——chip 在其中只算一个原子字符);只要该表面属于目标会话即可用(有选区则替换、多条 chip 按序连排);光标不可得(会话不匹配 / 无可用输入框)时保持历史的草稿末尾零宽 span CAS。span 坐标在 Lexical 宿主(DSH ≥ 0.1.2-alpha.2:草稿是 clipboard 投影,chip 在其中展开为完整clipboardText,而在 span 坐标里只是一个)取 detect 投影,在旧 textarea 时代机器取草稿坐标——一个结构探测(输入门面上的editor)选定坐标平面;纯文本降级走会话级'slash/input-insert-text'事件,草稿里其余 chip 在落地后完好无损;本插件注册名为vscode-reference的@触发源(候选恒空,仅为提交序列化路由 codec);conversation.input.dock组件渲染引用 tag 栏(关闭按钮经会话级'slash/input-consume-token'事件移除该引用的全部 chip——Lexical 宿主下保 chip:整稿setDraft写入会把其余 chip 全部压成裸 mention 文本)并在 document 捕获相拦截粘贴,同时适配 contenteditable composer 与旧 textarea(信封走注入 lander 并落在粘贴光标处;mention 复制体经解析后落 chip——preventDefault之外还要stopPropagation,仅 preventDefault 拦不住 composer 自身的粘贴处理); - host 边界(
src/mention.ts):严格解析之外再追加一层 fail-soft 恢复扫描兜住散架复制体;闭合标签碰撞用内容哈希盐化防伪造。
mention 编解码
- 规范形态:
@[<转义标签>](dsh-vscode:<base64url(json)>)(选区)/dsh-vscode-res:(资源);载荷自包含(路径 / 行号 / 快照 / 哈希 / 标志),草稿文本即唯一存储;两个 scheme 前缀互斥,互不误配; - 解码必须重新编码为完全相同的 URI(canonical 纪律,与
dsh-session:引用一致):显式 markdown mention 遇到畸形 URI 严格报错;裸文本仅在 base64url 形态跟随 scheme 时才算引用,且仍须通过 canonical 校验; - 恢复层(
scanRecoveredMentions)识别 sigil 被空白撑开的复制体与丢失闭合括号的截断复制体;投影只从完整通过校验的载荷重建——复制文本里的 label 一律不信任(展示残留),全部由载荷重新推导; - 共享纯逻辑模块
src/mentionCodec.ts无 Node 内建、无@deepseek-ai/*值导入,host 与 browser 两个 bundle 原样复用(client 纯度门通过)。
截断与新鲜度
- 捕获时(
truncateSnapshot):LF 归一化 → 行数上限(保留首尾两半整行)→ 字节上限(首段从尾部缩、尾段从头部缩,多字节安全);载荷记录headLen/omitLines/omitBytes,host 渲染内嵌省略标记,标记本身不计入计数; - 提交时(
freshnessOf):在会话 cwd 约束下重读磁盘行区间(路径越界 / 文件超 8 MiB / 读取失败一律unknown),哈希比对得fresh/stale;截断快照校验磁盘区间以保留首段开头、以保留尾段结尾且中间至少一字符(被省略中间的改动不可检测,截断本身不致 stale)。
打开导航(单发打开命令)
官方侧栏把一次打开送到标签正文的形式是 tab.navigation —— { address, params, revision },每次导航 revision 递增——这正是 better-sidebar 时代要靠 openRequest meta + 墙钟 nonce + 持久化 meta 卫生手工搭建的「单发命令载体」。src/client/openRequests.ts 只保留仍然要紧的纪律:revision 0(种入的引导、撤销恢复的记录)不是任何人的点击;页级水位表(${sessionId}:${tabId} → 已执行的最高 revision)让重挂载跳过已执行过的导航、而挂载批次的点击仍然执行;帧的启动门未落定时新导航延迟执行(其打开命令须携带启动 nonce)。navigation 消费者在执行时铸造扩展命令 nonce,把打开交给双通道 opener(workbenchLink.ts)。
焦点防护(workbench 必须自己赢得焦点)
VS Code workbench 开机后会编程性聚焦自身内容——Getting Started 欢迎页渲染即 focus() 自身,恢复的工作区会聚焦它恢复的编辑器——大约在 iframe 加载后 0.5–4 秒发生,与用户是否交互无关。只要这次开机落在用户并非奔着 workbench 去的时刻,光标就会被从脚下夺走:新会话的默认标签在收起面板后隐形启动会无声夺走光标(闪两下);而跨工作区切换会话时 workbench 会原地重载(basis 变了;同工作区切换则原样复用活帧、零重载)——恢复的文件恰好抢在输入框自动聚焦之后夺走焦点。src/client/VscodeView.tsx + workbenchRuntime.ts 用两个机制防护:
- 首次加载延迟:iframe 一直挂起,直到本标签真正可见过至少一次(官方停靠正文可见性:活动标签且面板展开;浮窗恒可见)。接管打开若落在面板收起时,workbench 等待观众——隐藏启动用户什么都看不到,还白抢焦点;延迟到首次揭示后再加载,启动期的任何焦点抓取都发生在用户正看着 workbench 的时候。延迟条件是运行时的
visibleOnce门,因此活过标签正文的卸载周期——而一旦帧已存在,切到同 pane 其他标签就再无任何成本(正文卸载、帧转入后台运行,切回即时重新投影)。 - 焦点围栏(
src/client/focusGuard.ts):焦点一进入帧内,立即还给帧外最近持有它的元素——归还次数按滑动窗口限额(默认 10 秒 5 次),病态循环抢焦点时围栏让位以免焦点乒乓——仅在两种情况下武装:- 隐藏(显式
visible === false):帧收不到用户点击,任何进入都是偷取。面板收起或标签非当前时,抢焦点无从合法化。 - 开机:帧每次加载后的一个窗口期(默认 6 秒),因为每次加载都是一次 workbench 开机、每次开机都会自聚焦——页面刷新与工作区切回的重插都算。窗口期内进入会被弹回,除非用户在帧内做了手势(同源
pointerdown/keydown追踪,每次加载重挂——刚开机的 workbench 一点即入),或父页面 Tab 键把焦点交了过来。唯一被认可的开机是上面的延迟首载:那是用户展开标签释放的加载,抢焦点正中下怀。跨域 iframe(直连回退模式)看不到帧内手势,开机围栏直接解除,绝不弹掉用户的真实点击。 - 识别走
focusout而非focusin(焦点穿入 iframe 时父文档只发focusout,focusin落在帧内文档)。
- 隐藏(显式
部署拓扑(默认值的依据)
VS Code server(code serve-web)直接跑在 dsh-runtime 容器里:
code serve-web --host 0.0.0.0 --port 8000 --server-base-path /vscode \
--server-data-dir /data/workspace/.vscode --without-connection-token \
--default-folder /data/workspace
nginx: location /vscode/ → 127.0.0.1:8000(含 WebSocket upgrade)
网关只做用户 → 实例透传,/vscode 无特例,增删用户零同步
没有网关层的部署(Windows / 局域网直跑 dsh web)不需要自建 nginx:插件宿主半在 dsh web
自己监听的端口上注册同等的反代(src/vscodeProxy.ts,挂载点 /sidebar/vscode)。HTTP 前缀路由
透传且保留浏览器 Host(serve-web 因此把 remoteAuthority 烘焙成 DSH 端口,一切回程 URL 走同源);
WebSocket upgrade 以精确路径 <上游基路径>/<quality>-<commit> 注册——浏览器 socket 工厂只连这一
路径,serve-web 的 handleUpgrade 不校验路径。路由基路径以首页 HTML 烘焙的 serverBasePath
为准(探测入口 URL 说了不算):非挂载点基路径额外注册恒等镜像,根路径上游注册 <quality>-<commit>
补片。首页探测最多跟随三次重定向并采纳最终 origin,因此上游即便挂在会重定向的反代后面(如强制
http→https 跳转)也能正确发现并转发。上游优先取 serverUrl 的完整地址(经 proxy.config 推送),
其次 DSH_SIDEBAR_VSCODE_UPSTREAM(默认 http://127.0.0.1:8000,off 可关)。
DSH 会话与嵌入 workbench 看到同一文件系统、同一路径,因此 pathMap 默认留空 = 不映射:会话 cwd 与对话点击的文件都按原始绝对路径直接交给 workbench,不做任何改写(此前默认是两条恒等规则 /data/workspace=/data/workspace;/opt=/opt,只覆盖这两个根,其余目录一律提示无法映射)。规则是前缀改写器而非白名单:即便配置了规则,未命中的路径仍原样透传(存在性由 VS Code 判定),不会再出现阻断性提示。若把 workbench 挪去别的容器 / 挂载,在设置文档里用 pathMap 配置真实的前缀改写即可(该键无设置面板行)。
目录结构
代码按领域分层 —— 一个所有运行时共用常量的共享协议平面、host 半的服务、browser 半的控制器 + 视图、按同样缝隙分解的扩展 —— 任何契约的改动都只落在一处:
src/shared/protocol.ts # 协议平面:跨进程边界的全部常量(信封标记、代理挂载路径、spool 文件名、能力版本、TTL、工作区 slug)——纯模块,host+client 原样引用
src/index.ts # host 半入口:agent/created → pre-step 边界挂载 + /sidebar-vscode/api 围栏路由(一张方法表分发)+ 静态 Config 导出(inject: agents, webServer, webRuntime)
src/config.ts # 插件的 volatile Config schema——设置服务由此派生 `dsh-sidebar-vscode` 配置节(7 测试)
src/shared/settings.ts # 两半共享的设置模型:入口 id 命名空间、类型、组合基线
src/vscodeProxy.ts # host 半:/sidebar/vscode 同源反代(HTTP 透传 + WS upgrade 管道 + 路径/令牌改写 + configure 通道)(41 测试)
src/mention.ts # host 半核心:解析改写/去重/新鲜度/<text-selection> 等注入(38 测试)
src/mentionCodec.ts # 共享纯逻辑:两种 scheme 规范 URI 编解码/截断/哈希归一(42 测试)
src/openChannel.ts # host 半:/tmp 命令通道 spool —— 全部持久化走唯一 SpoolStore(原子写 + 容错读)(13 测试)
src/trust-fence.ts # host 半:本插件路由的浏览器信任围栏(回环/trustedHosts + 同源标记)
src/client/index.tsx # browser 半入口:薄组合根(tab + dock + @ 触发源 + 接管装配),机制全部在下列模块
src/client/VscodeView.tsx # 标签视图:投影锚点 + 外壳(工具栏/提示/加载遮罩),向常驻运行时喂输入
src/client/workbenchRuntime.ts # 工作台守护者:每页一个常驻宿主 + 帧,活过标签正文卸载;lock→gate→src 协调器;采纳者 signal 回收(13 测试)
src/client/projection.ts # 矩形投影器:rAF 追踪常驻宿主对准标签正文占位符 + 层叠策略(10 测试)
src/client/bootGate.ts # BootGateController:nonce 停靠 → 回执 × DOM 静默揭幕竞速 → 原地重载轮换;+ DOM 静默观察器(13+5 测试)
src/client/bootLock.ts # WorkbenchBootLock:跨标签 Web Lock 串行化首次绘制(vscode-web-db 创建竞争)+ acquireWebLock(11 测试)
src/client/focusFence.ts # FocusFenceController:每次 load 的手势追踪 + 文档监听,包着 focusGuard 的纯规则
src/client/focusGuard.ts # 焦点围栏纯决策 + 滑动窗口归还限额(7 测试)
src/client/workbenchBase.ts # WorkbenchBaseController + useWorkbenchBase:mount/直连解析、上游推送/重置、毕业轮询(6 测试)
src/client/openRequests.ts # OpenRequestConsumer:单发导航纪律 —— revision 0 静默、页级水位、门控延迟(10 测试)
src/client/workbenchLink.ts # createWorkbenchOpener:优先扩展 spool(cap v4 起带启动标签),降级 URL payload 重载(8 测试)
src/client/clipboardBridge.ts # 同源 iframe navigator.clipboard.writeText 信号补丁(10 测试;跨域读取抛 SecurityError 时 no-op)
src/client/composer.tsx # dock 组件:引用 tag 栏 + 粘贴兜底(样式走 styles.ts)
src/client/composerDom.ts # Lexical composer DOM 的 detect 投影遍历(DOM 选区 ⇄ detect 偏移映射)(11 测试)
src/client/references.ts # 载荷→chip(选区/资源)/光标处插入/tag 栏投影/粘贴恢复(69 测试)
src/client/referencePipeline.ts # 插件体、标签页、dock 三方共享的 lander/选项句柄表
src/client/selection.ts # 剪贴板信封编解码(选区 + 资源两种 payload)(16 测试)
src/client/paths.ts # pathMap 解析/映射/反向映射、URL 构建(34 测试)
src/client/settings.ts # `dsh-sidebar-vscode` 配置表单读取(快照 + 基线回退)+ 截断上限契约(默认/边界/提交助手)(18 测试)
src/client/settingsCard.tsx # 插件页配置表单(plugins.bundle.config / plugins.row.config):表单体 + 恢复默认 + 开关行 + 黑名单 tag 行 + 文本行 + 数值行(样式走 styles.ts)
src/client/openBlocklist.ts # 「不由 VSCode 打开」后缀表:默认值 / 归一化 / 基名后缀匹配(24 测试)
src/client/settingsTakeover.ts # 设置页「打开配置文件」接管:两个时代包装器共用一个决策核 + 设置弹框关闭(17 测试)
src/client/openIntercept.ts # 官方 openResource 接管:本地 file 地址解析器 + wrapSidebarRightOpenResource(门/黑名单放行/params 翻译)(22 测试)
src/client/takeovers.ts # 接管家族装配:一张门控接到两条缝(openResource 漏斗 + 两条设置漏斗)
src/client/openChannelApi.ts # 打开通道 client 半:围栏 /sidebar-vscode/api 探测与命令(11 测试)
src/client/definition.ts # 官方 tab 类型定义:kind/id/params 面 + 引导页入口框
src/client/styles.ts # 样式注册表:全部注入 CSS 块 + 一个幂等 adopter
src/client/i18n.ts # locale 服务挂接 + t()
src/client/locales.ts # zh/en 词典
src/client/icons.tsx # VS Code 标志 + 引用 chip 文件/文件夹/关闭图标(currentColor SVG)
extension/ # VS Code 扩展 dsh.selection-reference,按同样缝隙分解:
├ extension.js # ~90 行激活根(命令 → 读账本 → 对账 → 武装 → 轮询 → 幽灵补刀)
├ lib/protocol.js # 共享协议平面的 CJS 镜像 —— 由 tests/protocolLockstep.spec.ts 与 src/shared/protocol.ts 锁步钉死
├ lib/fsutil.js # 原子标记写 + spool 目录
├ lib/envelope.js # 三个发送命令 + 它们乘坐的剪贴板信封
├ lib/channel.js # spool 轮询:能力标记 + 一次性命令消费(TTL/nonce/启动标签三重防护)
├ lib/boot.js # 编辑器账本 + 启动对账 + 迟到幽灵补刀 + 拥有它们的 nonce 围栏
├ harness.js / package.json / package.nls*.json / .vscodeignore / vsix/*.vsix
scripts/install-extension.sh # 扩展一键安装(vsce 打包 → 落文件 → 注册清单 → 重启 → 健康检查)
scripts/install-extension.md # 安装分步文档 + 排障表
README.md / README.zh-CN.md # 英文文档 / 本文档(中文)
screenshot.png # 产品使用截图(见上方「界面截图」)
tests/*.spec.ts # vitest 单测,共 511 例 / 24 文件(如上括注分文件计数)
cordis.patch.yml # bundle 通道的 host 半插入行(挂载声明)
tsdown.config.ts # 双 bundle 构建(host ESM + client ModuleLoader 格式 + 纯度门)
vitest.config.ts # 测试期 dsh-llm 别名(优先 harness 检出,回退已装包)
lib/ # 构建产物(随仓库提交:link: 部署直接服务 lib/client.js)
.github/workflows/ci.yml # CI:Node 22 & 24 上 typecheck / test / build / 包内容校验
构建产物交付:host 半为普通 ESM bundle(@deepseek-ai/dsh-llm 保持外部导入,由 DSH host loader 解析);browser 半为 window.__ModuleLoader__.load({ id, factory }) 注册格式(官方外部 client 插件交付格式),React / cordis 走 external,并带纯度门——拒绝 Node 内建与 @deepseek-ai/* 值导入。
开发相关
构建与测试
git clone https://github.com/chendefine/dsh-sidebar-vscode && cd dsh-sidebar-vscode
pnpm build # tsc 声明 + tsdown 双 bundle → lib/
pnpm typecheck # tsc --noEmit
pnpm test # vitest run(511 例)
重建后硬刷新浏览器即可(link: 依赖 + 内容 rev 查询参数自动破缓存);host 半改动需重启 dsh web。
环境要点
- pnpm ≥ 11:pnpm 专属设置只从
pnpm-workspace.yaml读取(.npmrc中的同名键会被静默忽略)。本仓库在pnpm-workspace.yaml固定autoInstallPeers: false(@deepseek-ai/*内部包不在公网 registry)与verifyDepsBeforeRun: false(node_modules+ lockfile 为冻结基线,跳过 run 前预检),以及allowBuilds.node-pty: false(仅类型引用,不运行其原生构建); - 类型与运行时映射:
@deepseek-ai/*构建期包(dsh-llm、dsh-agent及dsh-llm的运行时 peer)均为从 npm registry 解析的 devDependencies,普通 clone 与 CI 开箱即用;tsconfigpaths与 vitest alias 在存在相邻 harness 检出(/app/dsh)时优先使用它(其构建产物比已发布 rc 更新),否则回退到已安装的包; - CI:GitHub Actions(
.github/workflows/ci.yml)在 Node 22 与 24 上运行 typecheck / test / build / 包内容校验——矩阵对齐 DSH 自身的支持范围(^22.19.0 || >=24.0.0,与发布的engines字段一致); - devDependencies 基线:各
@deepseek-ai/*devDependencies(外加宿主侧设置 schema 用的@deepseek-ai/schemastery)仅为类型、测试与开发期对齐——运行时它们都是可选 peer,由 DSH 宿主解析; - 扩展手工测试:
node extension/harness.js extension/extension.js(stub 掉注入的vscode模块,跑三条命令并打印信封与解码载荷)。
发布
npm 包名为 dsh-sidebar-vscode(仓库:chendefine/dsh-sidebar-vscode):
# 1. 提升 package.json 版本(extension/ 有改动时同步提升 extension/package.json)
# 2. 构建 + 测试,然后发布(prepublishOnly 会再跑一次构建)
pnpm test && pnpm publish --access public
# 3. 打 tag 并推送发布
git tag v<version> && git push origin main --tags
extension/(VS Code 扩展)虽随 npm 包一起分发,但 DSH 本身从不加载它——它经 scripts/install-extension.sh 装入 serve-web 实例(见安装方法)。改动它之后,提升 extension/package.json 的版本并重跑脚本,保持随仓库提交的 VSIX 同步。
已知限制
- 宿主反代不可达时的直连回退形态下,同源剪贴板桥不可用(浏览器同源限制),仅剩粘贴兜底;若只是启动时序问题(serve-web 比
dsh web晚就绪),客户端每 5s 轮询proxy.status的serving,代理就绪后自动切回挂载点,无需手动刷新; - 内置反代面向单上游(最后写入的
serverUrl生效——多会话推送不同地址时全局共享一个);上游须http(s)直达(自签 TLS 需系统信任,URL 内嵌凭证不支持),代理不做鉴权剥离,令牌按原样附加。挂载路径继承 dsh web 端口的暴露面:能访问该端口的客户端即可使用被代理的工作台(?tkn=令牌只保护上游、不保护挂载——代理会透明追加),请让端口与 GUI 本体处于同一信任边界(回环/受信网关); - 选中注入常开,无开关;
- host 半代码改动需
dsh web重启后生效; - 构建时 tsdown 对
external/noExternal报弃用警告(构建产物正确,迁移到deps.*待后续)。
许可证
MIT(见 LICENSE)。