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 线。
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
# 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-部署指南](04-部署指南) §1.1)。
|
||||
|
||||
```bash
|
||||
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 冲突,第二个被丢弃)。
|
||||
|
||||
**处置**:
|
||||
|
||||
```bash
|
||||
# 确认产物里已经没有自绘开关
|
||||
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 在沙箱态下缺资源属**已知限制**,点「临时解锁」后应完整显示。
|
||||
|
||||
**排查命令**(确认宿主围栏在起作用):
|
||||
|
||||
```bash
|
||||
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-系统架构](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` 已移除该依赖,终端由宿主内置承担。)
|
||||
|
||||
**处置**:
|
||||
|
||||
```bash
|
||||
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 半没有)。
|
||||
|
||||
**处置**:
|
||||
|
||||
```bash
|
||||
cd <插件仓库> && pnpm build
|
||||
cd ~/.dsh/profiles/<profile> && pnpm install # 刷新 file: 副本
|
||||
# ⌘Q 完整退出 DSH → 重启
|
||||
```
|
||||
|
||||
**验证**:比对两边产物 sha:
|
||||
|
||||
```bash
|
||||
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`)。
|
||||
|
||||
**处置**:
|
||||
|
||||
```bash
|
||||
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` —— 宿主按类型校验失败即进恢复模式。
|
||||
|
||||
**处置与验证**:
|
||||
|
||||
```bash
|
||||
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-运维手册](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 剖面,再下结论。
|
||||
Reference in New Issue
Block a user