用户令逐字:「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/`)。
13 KiB
name, description, version, updated_at, last_change, agent_created
| name | description | version | updated_at | last_change | agent_created |
|---|---|---|---|---|---|
| dsh-diagnose | DSH 多租户平台(ai1net.com / 47.77.182.89)**故障诊断总入口** —— 覆盖三层:① **服务器上单个用户实例**(内存 / OOM / 崩溃重启 / 打不开 / 起不来 / 挂了 / 一直重启 / 503 / 502 / 会话突然中断 / 响应慢 / 怀疑内存不够)② **业务插件**(插件没 UI / 预览不出现 / 卡片不渲染 / 装了插件但界面没变化·跟没启用一样 / 导入导出报 GLIBC 版本 / 网关崩了但数据其实写进去了 / 改了包上传了还是老行为)③ **跨机状态回流**(某字段跨机恒为 false / 永远是旧值 / admin 看到的启停状态与用户实际不一致 / 命令下去了但状态回不来 / 刷新后状态回跳 / 要不要让 worker 主动上报)。⚠️ **边界**:本技能管**服务器上的实例与插件**;「**本机** Windows 上把官方 dsh 跑起来(web 实例 / 桌面壳)及其取证」属 `dsh-local-env`。核心 = **先按症状定层**(⛔ 别先猜代码)+ 三层各自的第一原则(实例=先读 journal 再分服务/实例/会话三级;插件=**静默失败当默认假设**;跨机=**先判"缺失能力 vs 代码缺陷"**)。 | 1.0.0 | 2026-09-28 | 【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 分诊表统一入口。⛔ 未删任何判据、命令、事故事实。 | 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 那种"随时查状态")常缺的就是这条。
⇒ 判据:"操作后立刻返回的值是对的、刷新后变错" ⇒ 缺的只有读路径;设计目标应写成「让读路径也拿到真值」,⛔ 不是"加个缓存"(缓存=第二个真相)。
第三步 · 三候选用「物理可行性」筛(⛔ 不是用"哪个更好"筛),照这个顺序问:
- worker 有入站口吗? DSH 硬口径是「worker 只拨出、无入站口」⇒ 候选 C(worker 主动上报)物理上走不通,要新开出站通道/入站端点 ⇒ 撞「扩大可见面」红线门禁。
- reconcile 谁驱动? DSH 由 worker 本机定时器驱动、Manager 不在场 ⇒ 候选 B(共用台账)不能单独成立,只能当缓存。
- 已有一条 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)⇒ 正文或别处若出现这三个名字,按本技能对应章节读。