Files
workbuddy_skills/session-mechanism/references/forensics.md
T
admin 19101acd65 init: workbuddy_skills 重建,仅收录 session-mechanism
- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件)
- 附 .gitignore(产物 + 本机凭据)
- 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库)
- 本提交为孤儿提交(父提交为空),历史自此重新开始
2026-10-05 14:13:24 +08:00

74 lines
5.9 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.
# 会话复盘 · 取证手册("某个会话当时到底干了啥 / 为什么卡住")
> 取证脚本:`scripts/forensics/proc-parent.py`(纯 ctypes 查进程父链,⛔ 不依赖 psutil)。
> 深度内容(每条的前因后果、实测读数)⇒ `references/pitfalls.md`。
## 1 先定位会话
- 会话表:`sessions`(`title` / `custom_title` / `created_at` / `last_activity_at` / `cwd` / `status`)。
- 用户说「找**最新的**那个」时,**两个指标都算、取更大者**:`createdAtMs`(创建)与 `lastActivityAtMs`(最后活动)。
⚠️ **两个指标打架 ⇒ 两条都列出来问用户,⛔ 别猜**。
- ⚠️ **同名多会话**在这一层很常见(标题会被复用)⇒ 必须靠 id / 时间 / cwd 三者交叉确认。
## 2 读转录(原文在哪、怎么抽)
- 转录是 JSON 行文件(`.jsonl`)。抽四类东西:**用户原话** / **AI 正文** / **reasoning 思考** / **工具调用**。
- ⚠️ **转录会被清理**(保留窗口有限)⇒ 过期后只能走 §6 的复原路径。
- 🔴 **别拿转录 mtime 当"有产出"**:agent 在跑时转录正文可能**滞后甚至不落盘**;反之活干完了也可能因推送失败而看起来没动。
## 3 🔴 四种"卡住"必须分开判(这是本节的核心)
| 型 | 症状 | 决定性判据 |
|---|---|---|
| **中断** | 那一轮从中间断了 | 转录尾部有**宿主注入的错误**(如 `## Model error retry: Tool Not Found`)⇒ 中断点;其后长时间零 `reasoning` / `function_call`。**瞬时工具不可用 ≠ 会话来死** |
| **工具循环不收敛** | 消息派发了、agent 真在跑,但**永不回话** | 工作区日志状态机:`finish_reason=` **反复是 `tool_calls`,从不出现 `stop`**;`busy=true` 恒不回 idle |
| **干完了推不出去** | 界面显示"一直运行",但看不到任何执行 | 日志里 `sendToClient: … SSE is closed` 大量堆积 + 该轮 `finish_reason="stop"` ⇒ **活干完了,结果送不出去** |
| **投递悬挂** | 消息收到了、也入队了,但**没有任何东西去排空队列** | 🔴 **`route=parkInQueue` + `hasWaiter=false`**(正常是 `route=resolveWaiter` + `hasWaiter=true`) |
🔴 **投递悬挂的定量指纹**(把"症状"变"确诊"):`hasWaiter=false` 只会在出事那段出现、`queueLen` **只进不出**(每再投一次就再压一条)、同一小时 `No state found for connectionId` **飙升**。
✅ **处置=让客户端重新挂上该会话**(切走再切回 / 重开该会话窗口);⛔ **别反复发消息试探**(只往队尾再堆一条)。
⚠️ **两条别混**:同一次事故里常常**同时**出现"日志层异常"与"投递异常" ⇒ **分别归因、分别取证**,⛔ 不要合成一个因。
⚠️ **别拿会话状态字段当判据**:客户端会把 `working` **基于错误假设主动写进库**,而宿主那边根本 idle;`last_activity_at` 的刷新也可能只是"UI 打开了这个会话"。
## 4 日志撞上限(10 MiB)⇒ 整批丢写
**三条同时成立即可确诊**:
1. 守护日志反复出现 `diagnostic log write failed … EPERM`,且 `droppedLines` **持续增长**;
2. 该会话的 `<sid>.log` **大小卡在 ≈10 MiB 且 mtime 冻结**,而同期**其他会话的同名日志正常在长**(对照排除"全盘坏了");
3. 同目录 `<sid>.log.1` 也是满的 ⇒ **轮转无空位**。
**修法(非破坏、可逆)**:把卡死的那个 `<sid>.log` 与 `<sid>.log.1` **改名挪开**(⛔ 不要删)。
🔴 用 Python `os.rename`,⛔ **不要用 `mv`**(MSYS 的 `mv` 在长路径/中文名下更易踩坑)。
**验收必做**:① 数十秒内被宿主**自动重建** ② 它**再次长大** ③ 告警**不再新增**。
⚠️ **它必然复发**(每个会话涨到上限都会重演)⇒ 需要**持续兜底**(本工作区有扫描器 + 钩子兜底)。
⚠️ 这两个文件**并没有被锁**(可正常打开)⇒ ⛔ 别再往"查谁占用文件"上耗时间。
## 5 进程定性("它是不是某个会话的后台任务")
```
netstat -ano | grep ":<端口>" # 只认 LISTENING 那行 ⇒ 拿 pid
python <本包>/scripts/forensics/proc-parent.py <pid> # pid ⇒ 父链
```
- 父链出现 `… ← sandbox-cli.exe ← WorkBuddy.exe` ⇒ ✅ **是会话/宿主托管的后台任务**。
- 父链是 `services.exe` / `svchost.exe` / `explorer.exe` ⇒ ⛔ 不是后台任务(独立进程)。
- 更硬的证据在宿主日志里搜 `executeInBackground` / `background task created` / `processExit`。
- ⚠️ 日志里的中文常是「UTF-8 字节被按 GBK 解」的乱码,还原一行:`s.encode("gbk","replace").decode("utf-8","replace")`。
## 6 转录已被清理时的复原路径(四条腿)
1. **全工作区 memory 日志批量搜"行为的独有名词"**(工具名 / 产物名 / 作品名)⇒ 一次命中就给出「工作区 + 日期」,比逐会话读 jsonl 快一个量级。
2. 读命中的那天日志(它通常写了工具链、命令要点、产出**绝对路径**)。
3. 按**独有产物名**跨盘搜文件系统(⚠️ 必须剪枝:跳过 `node_modules/.git/Windows/Program Files/AppData`,限制下钻深度;⛔ 别用全盘递归 `glob`,实测直接超时被杀)。
4. 与规范化文档互证(文档里常写「基于 <日期> 实测沉淀」)。
⚠️ **交付时必须同时说明「原产出目录是否已失效」与「现存可核验的替代物」**,别让用户去翻一个不存在的路径。
## 7 ⛔ 别做
- ⛔ 别因为 `status=working` 就断定卡死(当前正在跑的会话也是它)。
- ⛔ 别因为"抢不到锁"就去**删锁 / 接管**(红线,只能持有者释放)。
- ⛔ 别"再发一条试试"救投递悬挂的会话(会重开一轮同样的循环 / 只往队尾堆一条)。
- ⛔ 别在自动化会话里手动续聊当正式驱动。
- ⛔ 别为"恢复它"随手重启应用(会打断其他线正在跑的棒)。