Files
figmar 63bcb47ed0
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
docs(wiki): 建立项目百科(需求分析 → 架构 → 功能 → 部署 → 运维 → 排障 → 开发与测试)
Gitea 的 wiki 此前是空的。本次把百科的**源文件版本化进仓库**(docs/wiki/),
再用脚本发布到 wiki——文档因此与代码同源、可 review、可回溯,而不是只存在于网页上。

八页(Home 作为导航与项目速览):

  Home          项目速览 / 两条版本线的宿主兼容性差异 / 文档地图 / 谱系说明
  01-需求分析    五条核心需求(现象·根因·验收标准·状态)+ 非目标与约束 + 需求→实现→锚点
  02-系统架构    host/client 分层与产物、三层接口面、面板宿主层叠契约与 isolate 陷阱、关键不变量
  03-功能说明    面板/预览器总表、信息层三 tab 数据来源、HTML 预览交付方式、i18n 与令牌、配置面
  04-部署指南    宿主版本矩阵与选线决策、profile 接线(file: + bundles)、构建、验收清单、卸载与切换
  05-运维手册    分支/tag 策略与回滚剧本、发布纪律(版本三元组/非确定性产物)、巡检、备份铁律、诊断
  06-故障排查    8 类症状 → 根因 → 处置(含本会话实机定位过的全部案例)+ 定位手法
  07-开发与测试  工程约定、目录、门禁基线、测试分层与契约 spec、扩展点、测量式 re-base 方法

脚本 scripts/push-wiki.sh:幂等发布(POST 新建 / PATCH 覆盖),凭据走 git credential helper
(临时 netrc,600,退出即删),不落盘、不进 argv。已实测:8 页全部发布成功,复推为更新。

内容纪律:只写**本次会话实证过**的事实(提交号、行数、层级取值、产物大小、宿主版本矩阵、
静默禁用机制、re-base 步骤),未验证的部分明确标注为待验证或已知限制;并如实说明本仓库
历代承载过 v1 / v2 / 当前 fork 三代实现,本百科描述的是 main 上的 fork 线。
2026-10-04 01:19:31 +08:00

10 KiB
Raw Permalink Blame History

06 · 故障排查

每条都给出症状 → 根因(机制)→ 处置 → 验证。标注 ✅ 的条目是在真机上定位并验证过的案例。

速查表

症状 最可能根因 跳到
装完「像没装」,界面无变化且无任何日志 peer 范围与宿主版本不匹配 → 静默禁用 §1
页面出现两个侧边栏 / 两套开合按钮 双挂载(两条线同装,或残留挂载行) §2
HTML 预览只显示 forbidden 桌面壳渲染器围栏 + 不透明源 iframe §3
插件面板盖住宿主/第三方弹窗(如插件市场) 面板宿主挂在层叠上下文之外 §4
终端打不开 / node-pty 加载失败 pnpm 拦截原生构建(仅 v0.19.1 线) §5
改了代码,界面没变 产物未重建 / profile 副本未刷新 / 浏览器缓存 §6
DSH 拒绝加载插件、entry pending 插件声明(dsh.plugin.json / bundle patch)不合法 §7
DSH 启动进恢复模式 profile 配置被写成错误类型(如 YAML 布尔) §8

§1 装完「像没装」:静默禁用 ✅

症状:profile 里依赖装了、文件也在,但界面没有任何变化;日志里也没有插件相关报错。

根因:宿主启动时做兼容性预检,判定 peerDependencies 与自身版本是否满足:

semver.satisfies(hostVersion, peerRange, { includePrerelease: true })

caret 范围跨 minor 必然为 false(例如 ^0.1.5-rc.1 对 0.2.0)。判定失败后宿主的处理是 跳过这一行——不抛错、不打日志、不加载。

处置:核对宿主版本,改用匹配的版本线(见 04-部署指南 §1.1)。

grep -m1 '"version"' "/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai/dsh/package.json"
grep -n 'dsh-client-ui-sidebar-right' <插件仓库>/package.json     # 看 peer 范围

验证:重启后右列出现插件 tab;lib/client.js 里能 grep 到 apes:agents。

教训:这一条没有日志可查,因此部署前先看版本矩阵是唯一可靠的预防手段。

§2 两个侧边栏 / 两套开合按钮 ✅

症状:窗口里出现两套右侧面板与两组开合控件。

根因:两个方向都可能:

  1. 旧版遗留:v0.18.0 基线时插件自绘右侧面板与开关(toggleCluster)。升级后自绘部分应已移除。
  2. 双挂载:同一条线被挂两次(profile 自己的 cordis.patch.yml 里还留着旧挂载行, 与 bundle patch 同时生效),或者本 fork 线与上游线同时安装(两者都注册 ctx.betterSidebar, entry id 冲突,第二个被丢弃)。

处置:

# 确认产物里已经没有自绘开关
grep -c toggleCluster <插件仓库>/lib/client.js        # 期望 0

# 检查是否双挂载:profile 里是否同时存在 bundle 行 与 手写挂载行
grep -n "dsh-apes-sidebar\|dsh-better-sidebar" ~/.dsh/profiles/<profile>/cordis.patch.yml ~/.dsh/profiles/<profile>/package.json
  • 有残留手写挂载行 → 删除它,只保留一条通道。
  • 两条线同装 → bash scripts/switch.sh aps(或 upstream)二选一。
  • 装了聚合包(如 @linxin666/dsh-web-ui-all)→ 确认聚合包在本包之前,让包内守卫生效。

验证:右列只剩一套面板与一个开合控件。

§3 HTML 预览显示 forbidden ✅

症状:预览 HTML 文件时 iframe 里只有一行裸文本 forbidden;而「临时解锁」后又能看。

根因(三层):

  1. DSH Desktop 在扩展模式下给所有路由套了一层请求围栏:ordinaryBrowserEnabled 恒为 false, 不带 Electron 注入的 x-dsh-desktop-renderer 头的请求一律 403 + "forbidden";
  2. 该头只注入给 mainFrame 与同源子框架;
  3. 预览 iframe 的 sandbox 令牌不含 allow-same-origin,请求时其源为 null → 拿不到头 → 403。

与「是否升级了插件基线」无关(v0.18.0 起行为一致)。

处置:插件已在 1.2.0+ 用 srcDoc 交付修复(主框架取文档 → 注入 <base> → 注入 iframe), main 上对应提交 4c242ac。不要通过给 iframe 加 allow-same-origin 来"修"——那是安全降级。

验证:

  • 沙箱态下自包含 HTML(含内联脚本)能正常渲染;
  • 有外链资源的 HTML 在沙箱态下缺资源属已知限制,点「临时解锁」后应完整显示。

排查命令(确认宿主围栏在起作用):

lsof -nP -iTCP -sTCP:LISTEN | grep -i dsh          # 找宿主端口(每次启动可能不同)
curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:<端口>/"   # 无渲染器头 → 403

§4 插件面板盖住宿主/第三方弹窗 ✅

症状:打开「社区插件市场」(宿主自带 dsh-community-market)时,弹窗左下角被本插件的 文件面板整齐切掉一块。

根因:桌面壳的有框模式会给上游 #root 打标 data-dsh-desktop-content-viewport 并施加 isolation: isolate,使 #root 成为层叠上下文;宿主的 overlay 层(.dshDesktopOverlay, z-index: 1000)位于其中。插件面板宿主若挂在 document.body(该上下文之外)且是定位元素, 就会画在整个上下文之上,把 overlay 里的弹窗盖住。

处置:已修(4422001)——面板宿主改为挂进 #root[data-dsh-desktop-content-viewport], 无标记时回退 document.body。不要通过调大 z-index 解决(25 的取义见 02-系统架构 §四;乱调会破坏与宿主其他层级的关系)。

验证:插件市场弹窗完整盖住本插件面板,同时本插件面板仍在对话内容之上。

§5 终端不可用 / node-pty 加载失败(仅 v0.19.1 线)✅

症状:终端面板报 shell 启动失败或提示 node-pty 加载失败;pnpm install 阶段报 "ignored build scripts"。

根因:pnpm 11 默认拦截依赖的构建脚本,node-pty 的原生二进制没被编译。 (main 依赖 node-pty;sync/v0.24.1 已移除该依赖,终端由宿主内置承担。)

处置:

cd ~/.dsh/profiles/<profile>
pnpm approve-builds            # 选中 node-pty
pnpm rebuild node-pty          # 必要时
# ⌘Q 完整退出 DSH 再启动

验证:终端能开出 shell;node_modules/node-pty/build/Release/*.node 存在。

§6 改了代码,界面没变 ✅

症状:源码已改、pnpm build 也跑了,但界面行为仍是旧的。

根因(三个叠加因素):

  1. 产物非字节确定性,且 profile 的 file: 依赖是复制语义 → profile 里仍是旧副本;
  2. lib/client.js 是浏览器的 window.__ModuleLoader__.load({...}) bundle,不是 CJS/ESM, 在 Node 里 require 会因 window is not defined 报错——因此不能用「在 Node 里试跑」来验证前端改动;
  3. host 半的改动必须重启进程(client 半有热加载,host 半没有)。

处置:

cd <插件仓库> && pnpm build
cd ~/.dsh/profiles/<profile> && pnpm install      # 刷新 file: 副本
# ⌘Q 完整退出 DSH → 重启

验证:比对两边产物 sha:

shasum -a 256 <插件仓库>/lib/client.js \
  ~/.dsh/profiles/<profile>/node_modules/dsh-apes-sidebar/lib/client.js

§7 DSH 拒绝加载插件 / entry pending

症状:插件目录存在,但宿主不加载,或 loader 报 entry pending / 恢复模式。

根因:插件的声明面不合法或不被接受,常见于:

  • dsh.plugin.json 的 engines.dsh 与宿主不符;
  • bundle patch / 挂载行的 insert 结构不合法(例如把声明写成宿主无法解析的形态);
  • 同 id 重复挂载(duplicate loader entry id / duplicate prefix route)。

处置:

cat <插件仓库>/dsh.plugin.json                      # 检查 id / engines / main
cat <插件仓库>/cordis.patch.yml                     # 检查 insert 结构与守卫表达式
grep -n "cordis.patch\|dsh-apes-sidebar" ~/.dsh/profiles/<profile>/package.json

确保只有一条挂载通道、name 与实际包名一致(本 fork 为 dsh-apes-sidebar), engines.dsh 与宿主版本相容。改完 pnpm install + 完整重启。

验证:启动日志无 loader 报错;右列出现插件 tab。

§8 DSH 启动进「恢复模式」

症状:DSH 启动后进入恢复/安全模式,插件与侧边栏都不正常。

根因:profile 或应用配置被写成错误类型。典型是材质/透明度字段要求字符串 ("off" / "transparent"),但被 YAML 序列化成了布尔 false —— 宿主按类型校验失败即进恢复模式。

处置与验证:

python3 -c "import yaml;d=yaml.safe_load(open('$HOME/.dsh/settings.yaml'));v=d.get('macosMaterial');print(repr(v), type(v).__name__)"
# 期望:'off' str(或 'transparent' str);若为 False bool → 改回字符串

改配置前先备份,改完回读校验类型(见 05-运维手册 §五)。


附:定位手法(本次会话验证有效)

  1. 先按用户可见文案 grep 找归属:grep -rl "<界面上的中文短语>" <app>/lib <profile>/node_modules。 若两边都搜不到,说明它来自运行时下载/缓存(例如市场插件),再按 NSUserData 缓存或 desktop-market/state.json 之类线索去找提供方。
  2. 再读提供方自己的 CSS/DOM 结构:拿到它声明的 z-index、定位方式与所在槽位 (如 renderSlot("shell.overlay")),据此判断是「它太高」还是「我们太高」。
  3. 层叠问题先找 isolation / transform / filter / contain:这些才是层叠上下文与包含块的真正来源, 比 z-index 数值更关键。
  4. 像素证据要用原图量:交给视觉模型的截图会被缩放(1920→1280),裁剪坐标 ≠ 屏幕坐标; 用 PIL 在原图上量边界、比对 RGB 剖面,再下结论。