- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件) - 附 .gitignore(产物 + 本机凭据) - 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库) - 本提交为孤儿提交(父提交为空),历史自此重新开始
5.9 KiB
5.9 KiB
会话复盘 · 取证手册("某个会话当时到底干了啥 / 为什么卡住")
取证脚本:
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)⇒ 整批丢写
三条同时成立即可确诊:
- 守护日志反复出现
diagnostic log write failed … EPERM,且droppedLines持续增长; - 该会话的
<sid>.log大小卡在 ≈10 MiB 且 mtime 冻结,而同期其他会话的同名日志正常在长(对照排除"全盘坏了"); - 同目录
<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 转录已被清理时的复原路径(四条腿)
- 全工作区 memory 日志批量搜"行为的独有名词"(工具名 / 产物名 / 作品名)⇒ 一次命中就给出「工作区 + 日期」,比逐会话读 jsonl 快一个量级。
- 读命中的那天日志(它通常写了工具链、命令要点、产出绝对路径)。
- 按独有产物名跨盘搜文件系统(⚠️ 必须剪枝:跳过
node_modules/.git/Windows/Program Files/AppData,限制下钻深度;⛔ 别用全盘递归glob,实测直接超时被杀)。 - 与规范化文档互证(文档里常写「基于 <日期> 实测沉淀」)。
⚠️ 交付时必须同时说明「原产出目录是否已失效」与「现存可核验的替代物」,别让用户去翻一个不存在的路径。
7 ⛔ 别做
- ⛔ 别因为
status=working就断定卡死(当前正在跑的会话也是它)。 - ⛔ 别因为"抢不到锁"就去删锁 / 接管(红线,只能持有者释放)。
- ⛔ 别"再发一条试试"救投递悬挂的会话(会重开一轮同样的循环 / 只往队尾堆一条)。
- ⛔ 别在自动化会话里手动续聊当正式驱动。
- ⛔ 别为"恢复它"随手重启应用(会打断其他线正在跑的棒)。