Files
workbuddy_skills/session-mechanism/references/forensics.md
T

73 lines
5.9 KiB
Markdown
Raw Normal View History

# 会话复盘 · 取证手册("某个会话当时到底干了啥 / 为什么卡住")
> 取证脚本:`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` 就断定卡死(当前正在跑的会话也是它)。
- ⛔ 别因为"抢不到锁"就去**删锁 / 接管**(红线,只能持有者释放)。
- ⛔ 别"再发一条试试"救投递悬挂的会话(会重开一轮同样的循环 / 只往队尾堆一条)。
- ⛔ 别在自动化会话里手动续聊当正式驱动。
- ⛔ 别为"恢复它"随手重启应用(会打断其他线正在跑的棒)。