Files
workbuddy_skills/dsh-diagnose/SKILL.md
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

105 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: dsh-diagnose
description: DSH 多租户平台(ai1net.com / 47.77.182.89)**故障诊断总入口** —— 覆盖三层:① **服务器上单个用户实例**(内存 / OOM / 崩溃重启 / 打不开 / 起不来 / 挂了 / 一直重启 / 503 / 502 / 会话突然中断 / 响应慢 / 怀疑内存不够)② **业务插件**(插件没 UI / 预览不出现 / 卡片不渲染 / 装了插件但界面没变化·跟没启用一样 / 导入导出报 GLIBC 版本 / 网关崩了但数据其实写进去了 / 改了包上传了还是老行为)③ **跨机状态回流**(某字段跨机恒为 false / 永远是旧值 / admin 看到的启停状态与用户实际不一致 / 命令下去了但状态回不来 / 刷新后状态回跳 / 要不要让 worker 主动上报)。⚠️ **边界**:本技能管**服务器上的实例与插件**;「**本机** Windows 上把官方 dsh 跑起来(web 实例 / 桌面壳)及其取证」属 `dsh-local-env`。核心 = **先按症状定层**(⛔ 别先猜代码)+ 三层各自的第一原则(实例=先读 journal 再分服务/实例/会话三级;插件=**静默失败当默认假设**;跨机=**先判"缺失能力 vs 代码缺陷"**)。
version: 1.0.0
updated_at: 2026-09-28
last_change: 【2026-09-28】由三个同域技能**合并**而成:`dsh-instance-diagnose`(v1.0.0)+ `dsh-plugin-diagnose`(v1.0.0)+ `dsh-distributed-state-readback`(首版)。按 `dsh-knowledge-upkeep §10`「主干 + 详情档」形态:判据实体留在本主干,全文下沉到 `references/`(**内容守恒,逐行未改**)。合并理由:三者都在回答同一问题「**坏了 / 值不对,怎么定位**」,且前两者正文**本就互相声明边界**(同一领域的分层)⇒ 合并后由 §0 分诊表统一入口。⛔ 未删任何判据、命令、事故事实。
agent_created: true
---
# dsh-diagnose — DSH 故障诊断(三层入口)
> ## 🔴 第 0 步:按**症状**定层(⛔ 别先读代码猜)
>
> | 用户说的 | 层 | 去哪 |
> |---|---|---|
> | 实例打不开 / 起不来 / 挂了 / 一直重启 / **503** / **502** / 会话突然中断 / 很慢 / 内存不够 | **① 服务器实例** | §1 |
> | 插件没 UI / 预览不出现 / 卡片不渲染 / **装了插件但界面没变化** / GLIBC / **改了包还是老行为** / 网关崩了但数据进去了 | **② 业务插件** | §2 |
> | **某字段跨机恒为 `false`** / 永远是旧值 / admin 看到的状态与实际不一致 / 命令下去了状态回不来 / 刷新后状态回跳 | **③ 跨机状态回流** | §3 |
> | 「**本机** Windows 上把官方 dsh 跑起来」/ 桌面壳 / 本机取证 | ⛔ **不属本技能** | 技能 `dsh-local-env` |
>
> ⚠️ **②③ 最容易走错方向**:两者的症状都**静默**(不报错、不崩,只是"什么都没有"或"值不对")⇒ ⛔ 别当代码缺陷去 debug,先按各自的**第一原则**判性质。
---
## 1. 服务器实例(①)
**第 0 步永远是「先读 journal 拿真实失败请求」,不要先猜**(报障第一步)。
**定位顺序(每层可单独结案)**:
- **第 0 层 · 跨机日志取证(先做)** —— `dshlog` 把 47/106 的 journald 拉回本机再判(跨机、跨单元、带毫秒时间线)。🔴 两条硬前提:`ssh -C` **必加**(不加约 20 KB/s,**看起来像卡死**);**实例层(`dsh --profile`)的 stdout 不进 journald** ⇒ `dshlog` 拿不到实例自己的日志,实例层证据仍须上机取 cgroup / proc。
- **第 1 层 · 服务级** —— `systemctl status dshs` + journal 里 `crash-restart|instance-restart`。🔴 `exitCode 137` = 内核 SIGKILL(128+9),**九成是 OOM**。
- **第 2 层 · 实例级(内存三件套)** —— 先现查 scope 名(**每次重启 hash 会变**)⇒ `memory.usage_in_bytes` / `max_usage_in_bytes`(**字节,不是 KB**)/ `memory.stat`(**分 rss 与 cache**,rss 占绝对多数 ⇒ 内核 OOM killer 无路可退)+ `/proc/<pid>/smaps_rollup`。⚠️ **本机是 cgroup v1**(`/sys/fs/cgroup/memory/system.slice/…`),写 v2 路径**静默读到空**。
- **第 3 层 · 会话级(谁在吃内存)** —— 按 `session.jsonl.zstd` 的 mtime/size 排序;⚠️ 会话目录中间还有一层 workspace 转义目录,⛔ 别按 `home/sessions/<id>` 找。
**四条死亡路径(症状不同,别混)**:A · V8 堆限(`Reached heap limit` / `status=ABRT`)|B · cgroup 限(`oom-kill:constraint=CONSTRAINT_MEMCG` / `status=9` → 平台 137)|C · bwrap 挂载点被遮蔽(`Can't chdir to <正常路径>` —— **路径看起来完全正常**,极易误判成"目录没建")|D · cwd 为空(`Can't chdir to :` —— **路径是空的**,一眼可辨)。
⇒ **见到 `Can't chdir` 直接分诊**:路径空 ⇒ D(查 `folder` 是不是 NULL);路径正常却报不存在 ⇒ C(看 bwrap 的**中间目录是不是"就近创建"的**;✅ 正解=所有中间目录**统一前置 + 去重 + 由外到内**)。
**快速分诊(用户看到的状态码决定查哪层)**:**502** ⇒ 先看平台 journal 那条请求有没有 `request completed`(没有 ⇒ **平台接了却没回响应**,⛔ 别先查 nginx/域名/实例死活)|**431** ⇒ Cookie 超限(陈旧 `dsh-auth-*` 堆积;⚠️ 平台 journal 查不到该请求)|白屏 / `Failed to load plugins` ⇒ 先问"**无痕窗口是否同样报错**"。
**两条铁律**:🔴 **压测走隔离 cgroup**(`systemd-run … -p MemoryHigh/`MemoryMax`),⛔ 不在生产实例上压满配额(会触发 crash-loop / 熔断);⚠️ 复现路径 B 必须用**纯堆外分配**(`Buffer.allocUnsafe(...).fill(1)`),用 JS 对象数组会**先撞 V8 堆限(路径 A)**、永远复现不出 137。
**账实判据**:成本由「**装了什么**」决定,不是「装了几个」—— 一个重型插件(如 `dsh-univer-office` **+65 MiB**)≈ 无穷多个轻量插件;实测**整份会话数据只 0.9 MB** ⇒ 「减会话长度 / 少用 web_fetch」对内存**几乎无用**。
📂 **全文 ⇒ `references/00-服务器实例故障.md`**(含平台事实表、内存去向 smaps 拆解、插件内存逐个实测、注入层真机验证配方、9 条踩坑清单)
---
## 2. 业务插件(②)
> **第一原则:静默失败要当默认假设。** 排查顺序永远是「**先证明某一层到底有没有活着**」,而不是先读代码猜。
**三层归属(先定层)**:**host 半边**(工具能调通?死了=报"未知工具")|**client 半边**(页面 `__DSH_BOOT__` 里**有没有**该插件行?死了=**有无都没痕迹**:无卡片/无 dock、无报错)|**网关 / 原生绑定**(进程在、socket 能连、健康路径 200?死了=进程崩 / reset / `.node` 加载报错)。
**最隐蔽的一类 · client 半边静默挂死**:机理 = 加载器解析 `package.json` 的 `dsh.client.inject`(**包名**列表),**只要有一条 inject 永远不可满足,`apply()` 永不执行**(连注册都没有,静默)。
🔑 **通用尺子 = `inject` 差集**:取实例页面实际下发的客户端插件集合(**权威清单,⛔ 别用 manifest 正则——会漏行**)→ 取目标插件的 `inject` → **求差集**。**差集非空 = 确诊**。
⚠️ **别混两个 inject**:`package.json` 的 `dsh.client.inject` 是**包名**(不可满足 ⇒ **整体不 apply**);源码里的 `export const inject = [...]` 是**服务名**(只影响个别 `ctx.inject` 块)。
**第二类 · 组件挂上了、点击却没反应(外部契约失效)**:dsh 升级会改**外部契约**(槽位名 `conversation` → `main.conversation`;`ctx.locale.getLocale()` 由返回 id 变成返回**快照对象**)⇒ `querySelector` / 取值**静默失效**。
**桥接类(host 半边连不上 / 静默无限重连)**:⚠️ **先破一个假象** —— 内核 cordis 的默认 exporter **只写内存环形缓冲、不落 stdout** ⇒ 内核插件报错在实例日志里**看不见**,必须先挂 `ctx.logger.exporter(...)`。四层诊断配方(上游直连 → 实例内 fetch → **出站抓包**(能看出"从哪一代请求开始丢凭据")→ 硬编码对照)**逐层排除,⛔ 别跳步**。
**「注册成功」≠「模型可见」**:`register()` 返回 disposer 只说明"调用没抛错",能不能被模型看见要**另用 `tools.schemas()` 对账**。⚠️ 读法陷阱:⛔ 别用 `getOwnPropertyNames(...tools.data)` 数工具 —— `Map` 属性名是**空数组**,会把"有 N 个工具"误读成 `size=0`;**必须先判 `instanceof Map`**。
**原生绑定 / glibc**:第一步永远**先直测**(`ldd --version` + `node -e "require('<.node>')"`),⛔ 别读代码猜。两类对策不同:**有 JS 回退开关** ⇒ 资产侧垫片(可用则原样交上游 ⇒ 宿主升级后自动恢复);**无回退** ⇒ 只能宿主/镜像层解决(换 glibc ≥ 2.35)⇒ **上报用户决策,别硬凑**。
**「改了却没生效」四类**(按可能性):打包顺序把垫片废掉(模块级 `const` 被降级为 `var` ⇒ 静默回退原实现)|插件没真正换上新包(`mine/apply` 对**已启用**插件是 **noop** ⇒ 必须**停用→启用**两步;终态是 `success` 不是 `done`)|客户端包按 `rev` 缓存(**必须硬刷新**)|🔴 **同 `version` 号不会被重装** ⇒ **改代码必升 version** + 装完做**内容门禁**(`grep -q "<新符号>"`)。
**验证纪律**:每条结论都要有**可复现的命令**;`grep` 会骗人(esbuild 的 CJS 导出用 getter ⇒ 假阴性);业务插件 stdout **不落 journald** ⇒ 找不到"日志"时**别下结论**。
📂 **全文 ⇒ `references/01-业务插件故障.md`**(含注入差集完整命令、桥接类四层配方、可见性对账脚本、实测真因、平台侧事实)
---
## 3. 跨机状态回流(③)
> **第一原则:先判「缺失能力」还是「代码缺陷」** —— 判错方向会白烧一整轮。
**第一步 · 三条排除**(在**实例所在那台机**上取证,⛔ 不是 Manager):① 操作本身失败了吗(看**目标机文件系统**:软链/声明实际存在吗)② 执行环境没配好吗(看**目标机进程命令行** `/proc/<pid>/cmdline`)③ 调用链断了吗(**下发是否真走到远端**)。
🔑 **判据一句话:原机上成了 + Manager 侧读不到 = 缺失能力(拓扑必然),⛔ 不是缺陷。**
**第二步 · 分开看读/写两条路径**(⚠️ 最省时间):写路径(Manager→worker 下发)多**已有**|响应路径(任务返回值)多**已有**|**读路径(`GET` 那种"随时查状态")常缺的就是这条**。
⇒ 判据:**"操作后立刻返回的值是对的、刷新后变错" ⇒ 缺的只有读路径**;设计目标应写成「让读路径也拿到真值」,⛔ **不是**"加个缓存"(缓存=第二个真相)。
**第三步 · 三候选用「物理可行性」筛**(⛔ 不是用"哪个更好"筛),照这个顺序问:
1. **worker 有入站口吗?** DSH 硬口径是「worker 只拨出、无入站口」⇒ **候选 C(worker 主动上报)物理上走不通**,要新开出站通道/入站端点 ⇒ 撞「扩大可见面」红线门禁。
2. **reconcile 谁驱动?** DSH 由 **worker 本机定时器**驱动、**Manager 不在场** ⇒ **候选 B(共用台账)不能单独成立**,只能当缓存。
3. **已有一条 Manager→worker 的周期调用吗?** 有(`reportHost()` 每次心跳 `GET /healthz`)⇒ **候选 A 的增量 = 加宽返回值**,成本最低。
⇒ 结论通常是 **A 为骨架 + B 降级为可选缓存**;C 需先授权。
**语义细节(⛔ 不写这三条,回流做出来照样是错的)**:`asOf` 必带且 `stale` **现算不落库**(落库的 `stale` 自己也会陈旧);阈值 = **3 × 心跳周期**且**与心跳周期同源计算**(⛔ 不写两个独立常量);**三层语义别混**(意图=控制面审计/**事实=对端 profile 的 bundles**/不可用=数据面台账)⇒ **`GET` 取「事实」不取「意图」**;🔴 降级时 **⛔ 不许把"未知"显示成"未启用"**(这正是原缺陷的成因)。
📂 **全文 ⇒ `references/02-跨机状态回流.md`**(含本判据的 DSH 实证、三候选完整优缺点、失败降级场景表、与既有台账的粒度对比、交付件 10 节骨架、9 条反模式、关键文件地图)
---
## 4. 详情档索引(跨档引用按此表定位)
| 档 | 覆盖的原技能 | 原章节 |
|---|---|---|
| `references/00-服务器实例故障.md` | `dsh-instance-diagnose`(v1.0.0,全文) | 何时用 / 平台事实 / 三层定位 / 快速分诊 / 四条死亡路径 / 隔离复现 / 踩坑清单 / 定量归因 / 内存去向 / 插件内存成本 / 不兼容插件 / 注入层验证配方 / 相关 |
| `references/01-业务插件故障.md` | `dsh-plugin-diagnose`(v1.0.0,全文) | §0 第一原则 / §1 三层归属 / §2 静默挂死 / §2.5 桥接类 / §3 glibc / §4 改了没生效 / §5 验证纪律 / §6 平台侧事实 |
| `references/02-跨机状态回流.md` | `dsh-distributed-state-readback`(首版,全文) | §0 治什么 / §1 三条排除 / §2 读写路径 / §3 三候选 / §4 语义细节 / §5 与台账关系 / §6 交付件骨架 / §7 反模式 / §8 文件地图 |
🔴 **合并前的三个技能名已退役**(`dsh-instance-diagnose` / `dsh-plugin-diagnose` / `dsh-distributed-state-readback`)⇒ 正文或别处若出现这三个名字,**按本技能对应章节读**。