用户令逐字:「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/`)。
11 KiB
运维锚点 · 会话记录取证 · 功能插件启用
归属:技能
dsh-change-workflow的详情档(按需读,不是每次都要读)。 本档覆盖:通用运维锚点(重启用) · 功能插件启用:探活 · 快照回滚 · 逐插件隔离 · 会话记录取证(原行 L505–L525 + L935–L998)。 主文件 / 判据与流程主干 =../SKILL.md(§1 六阶段流程 · §2 红线 R1–R8 原文速查 · §3 本机 Git Bash 环境坑 · §4 并行调度结论)。 来源:2026-09-22「技能重组线」把../SKILL.md的 L505–L525 + L935–L998 段逐行原样下沉到本文件,未改一字。 跨档引用:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用../SKILL.md末节「详情索引」的原章节列定位。 维护:本文件与../SKILL.md的指针行成对;改内容时同时核对主文件的指针描述是否仍准确。
通用运维锚点(重启用)
- 门户重启后实例需重新 launch:
curl -b "sid=..." -X POST http://127.0.0.1:3080/api/dsh/enter→ 返回实例 port + token url - 实例检测:
pgrep -af "^node /usr/local/bin/dsh --profile web";监听:ss -tlnp | grep :<port> - 测试 session 生成:
node /opt/dshs/mksess.cjs(PG 直插,连接串取自 env /dshs.env/dshs.service.d;10 分钟;user_agent=poc-curl2);清理:DELETE WHERE user_agent='poc-curl2' - 存量会话档位体检(档案 36 P0-1,只读,建议定期跑):权限档位会话级播种(建会话时写
permission/preset+sandbox/mode+approval/policy,resume 不重播)→ 改默认档位只惠及新会话。查全平台还有多少会话是旧档位:2026-09-11 实测:总会话 10,for f in /var/lib/dshs/users/*/home/sessions/*/*/session.jsonl.zstd; do [ -f "$f" ] || continue printf "%-12s %s\n" \ "$(zstd -dc "$f" 2>/dev/null | head -c 4000 | grep -o '"preset":"[a-z-]*"' | head -1)" \ "$(echo $f | sed 's#.*/users/##')" doneworkspace-write10,danger-full-access0 —— 档案 33 之后一条都没迁移。 - 技能投放体检:
for u in /var/lib/dshs/users/*/; do echo "$(basename $u): $(ls -A $u/home/skills 2>/dev/null|wc -l)"; done; ls -A /var/lib/dshs/bundled-skills | wc -l—— 2026-09-11 实测全为 0(机制就绪但零投放,档案 36 P0-2)。 - 文档备份目录要先建:
mkdir -p /opt/dsh/backups/docs否则cp ... .bak-<ts>报No such file or directory(本次踩坑)。 - 服务器 git:
git -C /opt/dshs(user.name/email = [email protected]) - ⚠️ 服务器 git 提交前先
git status --short:工作树常滞留未提交文件(曾有一次git add -A把/logout路由、ensure-role-profile-patch.cjs、mksess.cjs、*.bak一并卷进fa718d2);应定向git add只暂存本次改动文件。 - 登录直达冷启动竞态(档案 13):
enter返回打开 URL 前必须等 launch token(waitForLaunchTokenForUser);实例 spawn 后status即 running 但 HTTP 路由未就绪,浏览器撞启动窗口会 404(proxy.ts的 404 只对应 unknown_user/not_running,此 404 来自实例自身透传)。验证用「enter 并发 +--resolve回源 curl」:URL 必须含?token=,子域应 303→200。
功能插件启用:探活 · 快照回滚 · 逐插件隔离(档案 34/35,2026-09-11)
原缺陷:/api/plugins/mine/apply 在 pnpm add → reconcile → restartMain 之后直接标 success,全程无探活 → 插件搞崩实例(崩溃循环)时任务仍报「已应用」。
正确流程:快照 → 禁用项直接 remove(不引入新代码,永远安全)→ 启用项先整批试一次 → 探活 → 失败才回滚 + 逐插件隔离(能起来的保留、起不来的单独摘掉并记 plugin_incident 审计)。
探活的两个必踩坑(都靠实测发现):
- 不能只判
restartMain的返回值 —— 编排器里没有该用户实例时(portal 重启清了内存态、或实例被空闲回收)它返回undefined,会把无辜插件误判为「不兼容」。→ 无实例时先按/api/dsh/enter同路径launch一个再探。 - 固定短等待会把好插件判死 —— 初版「固定 2.5s 稳定窗口」实测好插件也报「未产出 launch token」。→ 改为轮询(500ms 间隔 / 总预算 60s):拿到
launchToken且status==='running'后,再过 2.5s 确认没闪崩才算通过。 - 探活失败要回传 dsh 的真实错误(如
duplicate loader entry id/ERR_MODULE_NOT_FOUND),不要泛化 —— 这是 admin 判断"这个插件为什么坏"的唯一线索。
坑:平台 bundle 包不能被 ws 清理删掉(档案 35 P0)。ws-cleanup 的 T1 会无条件删 ws 顶层 .tgz,而三个平台 bundle 是以 file:<ws>/*.tgz 安装的 → 删掉后 profile 的 dependencies 指向不存在文件 → 任何 pnpm 操作 ENOENT 失败(启用功能插件必然失败)。且极隐蔽:node_modules 已装好,实例照常运行,只在下次 pnpm 操作时爆发。 修法:清理前读每个用户所有 profile 的 file: 依赖,被引用的文件一律豁免(别硬编码文件名)。平台 bundle 的可重建源码在 /opt/dsh/docs/04-调整方案/poc/,产物存 /opt/dsh/artifacts/。
造测试 fixture 的坑:bundle 的 package.json.name 必须与 cordis.patch.yml 里 insert 的 name: 一致,否则 ERR_MODULE_NOT_FOUND;且打包要用 package/ 前缀的 npm-pack 布局(tar czf x.tgz package),平铺布局 pnpm 可能不接受。
平台 bundle 的铺装(档案 36):07-scripts/ensure-biz-plugins.cjs —— 幂等,判定看 dsh.profile.bundles 不看 dependencies(包内 cordis.patch.yml 只对 bundles 成员生效;有 dep 没 bundles = 等于没装)。流程:复制产物 tgz 到用户 ws(chown)→ pnpm add file: → 对齐 dsh plugin add 的 reconcile。新用户自动铺 = 审批时 detached spawn(fire-and-forget)+ cron 巡检兜底。
坑:pnpm-workspace.yaml 会让 pnpm 拒绝 add。dsh 会给某些 profile 生成 pnpm-workspace.yaml(packages: [.])→ pnpm 视为 workspace root → ERR_PNPM_ADDING_TO_ROOT。→ 所有 pnpm add / pnpm remove 都要带 --ignore-workspace-root-check(有无该文件都工作)。
坑:判断「装没装」永远看 dsh.profile.bundles,不要看 dependencies —— 两者可以不一致,而不一致时组件是没生效的。
坑:Node 22 的 execFile 类型:execFile(file, args, { stdio:'ignore' }, cb) 在本项目 TS 版本报「'stdio' does not exist in type ExecFileOptions」。要做 fire-and-forget 用 spawn(..., { stdio:'ignore', detached:true }).unref()。
会话记录取证(分析某工作区/用户的真实对话,2026-09-11 定型)
用户常提「看看 XX 工作空间下的对话记录,能发现哪些问题」。这是只读任务,可与其他只读任务并行,且适合丢给子代理(大文件长分析,别吃主上下文)。
路径规律(<UID> = 用户 id,admin = cce6d1cd-b376-4304-80f0-0e1c58c9ffde):
U=/var/lib/dshs/users/<UID>
$U/home/sessions/--<cwd 路径把 / 换成 ->--/session-<uuid>/session.jsonl.zstd
$U/home/storages/workspace.json # 工作区注册表:id → { path, title, sessionIds }
例:--var-lib-dshs-users-<UID>-ws-mcntimo-- 对应工作区 $U/ws/mcntimo。
坑:文件是「追加式多帧 zstd」,不是普通 zstd。
zlib.zstdDecompressSync/createZstdDecompress只解第一帧 → 只得 240 字节的 session 头,然后报Unknown frame descriptor。- 必须用 zstd CLI(服务器默认没装):
yum install -y zstd(al8 官方源zstd-1.5.1-2.0.2.al8,安全,已获用户授权)。然后zstd -dc <src> > /tmp/s.jsonl—— 1.6 MB 明文正常解出。 - 用户点过
/export(command/done→Session log download requested.)拿到的是这种多帧 zstd,用户根本打不开 —— 属平台缺陷(档案 36 P1-6)。
分析脚本写法(2026-09-11 踩坑后定型)
# ❌ 别用:ssh 'node -e "..."' —— 内层引号转义在单引号 ssh 命令里必坏
# ✅ 一律:本地写 → scp → 远端执行
P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0
export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH"
S="C:/Users/Administrator/AppData/Local/Temp/wb-scratch"
scp -i ~/.ssh/id_ed25519 -P 22 "$S/an.cjs" [email protected]:/tmp/an.cjs
ssh -i ~/.ssh/id_ed25519 -p 22 [email protected] 'node /tmp/an.cjs /tmp/g.jsonl users'
- ⚠️
.cjs后缀是必须的:服务器/tmp/package.json含"type":"module"→/tmp/*.js被当 ESM,require is not defined in ES module scope(本次踩坑)。 - 脚本按
MODE分支输出(users/errors/tools/assistant/seq),一次写好复用,避免反复 scp。 type分布里request/header每条含完整 system prompt + 工具表(占明文相当大比例)→ 务必按type过滤,别全量 dump。
JSONL 结构:每行一条 JSON。第 1 行 {"type":"session", cwd, agentPreset, ...};其余 type ∈ request/header(含完整 system prompt + 工具表)、assistant/chunk、assistant/message(含 reasoning + tool-call)、tool/result、turn/end 等。工具结果里能直接看到真报错原文。
分析要点(产出「问题清单」,P0/P1/P2 + 行号 + ≤60 字原文片段):
① 工具报错/重试/超时/权限被拒(沙箱限制是高频根因)② 用户挫败信号(反复纠正、重复要求、放弃)③ 平台侧缺陷(菜单无反应、报错不可读、路径写死、工作区为空、会话中断)④ 卡点与无谓的工具调用 ⑤ 安全:凭据明文(只报「第 N 行疑似凭据,类型 X」,绝不抄值)、越权尝试。
额外可用信号:报错在会话中的位置分布(grep -n)能区分「已修」还是「一直在发生」。
必须先查「是不是已知问题」再报(2026-09-11 定型):动手前先 ls /opt/dsh/docs/04-调整方案/ + 读同类旧档案(如 21 卡顿 / 23 环境限制 / 32 上次取证),新发现要明确标注「新增」还是「修正/补充旧档案」,否则会把已归档的结论当新问题重复报。
产出后的闭环(别只输出清单):① 落 04-调整方案/<NN>-<主题>.md(含「修正旧档案的哪条结论」小节);② INDEX.md §二 追加 04-<NN> 行并更新状态摘要(README 的档案清单已定格为历史对照,别只改 README);③ INDEX.md §六.1 的「下一号」+1、01-规范/03-路线图与待办.md 补登记;④ 收尾四件套:python 07-scripts/docs-audit.py(退出码 0)→ python 07-scripts/docs-manifest.py(刷新机读清单)→ bash 07-scripts/docs-sync-check.sh(全绿)→ MINE="<我改的文件>" PUSH=1 bash 07-scripts/handoff-guard.sh(幽灵文件硬判定)。