fix(tui): close resume handoff races

This commit is contained in:
NI0317
2026-07-24 12:58:53 +08:00
committed by ZiyaZhang
parent 2ae9f4fdf3
commit 54d986ed87
20 changed files with 375 additions and 76 deletions
@@ -14,9 +14,9 @@ Status: implemented
`session-query.readSession()` 提供一份脱离运行时的完整日志,并通过恢复流程所用的同一核心回放边界完成验证。TUI 从该日志中折叠出标题和目标状态。候选项加载失败时只影响该行;选择候选项后会再次检查日志加载、cwd、占用情况和路由,避免陈旧列表绕过预检。适配器缺失时会报告会话完整但路由不可用。系统绝不会隐式切换或取消处于运行状态的 agent。
第一方持久化后端通过共享协调器实现跨进程的活跃会话租约。JSONL 使用所有者专属的锁记录;SQLite 使用一条 `live_session_leases` 记录。两者都保存 PID 以及进程替换前后保持稳定的随机标记,拒绝其他活跃进程领取租约,回收已终止 PID 的租约,并且仅在对应会话生命周期完全停稳后释放租约。`AgentLoop.resume()` 在加载前领取租约,消除预检与启动之间的竞态。
第一方持久化后端通过共享协调器实现跨进程的活跃会话租约。JSONL 使用所有者专属的锁记录;SQLite 使用一条 `live_session_leases` 记录。两者都保存 PID 以及进程替换前后保持稳定的随机标记,拒绝其他活跃进程领取租约,回收已终止 PID 或 PID 相同但进程代际不同的租约,并且仅在对应会话生命周期完全停稳后释放租约。进程内最后一个引用开始释放后,新的领取操作必须等待物理租约完成释放再重新获取。`AgentLoop.resume()` 在加载前领取租约,消除预检与启动之间的竞态。
预检通过后,TUI 先刷写当前会话并停止终端,再调用 `TuiRuntime.handoffResume`。已交付的 `dsh` 宿主会释放根应用,并使用带有规范化 `--resume` 参数的 `process.execve` 原子替换当前进程,而不会创建第二个终端所有者。恢复后的应用发布相同的 `SessionId`;常规回放会还原 transcript(文本记录)、标题、待办事项和持久化目标状态。系统会有意解除目标的激活状态,TUI 则要求用户确认继续或执行 `/goal resume`。
预检通过后,TUI 会先领取目标会话在进程替换前后保持稳定的活跃租约,再刷写当前会话。如果目标在预检后被其他进程抢占,当前 TUI 会继续运行;之后任何可恢复失败也会释放该预留租约。随后 TUI 停止终端并调用 `TuiRuntime.handoffResume`。已交付的 `dsh` 宿主会释放根应用,并使用带有规范化 `--resume` 参数的 `process.execve` 原子替换当前进程,同时保留目标预留租约,而不会创建第二个终端所有者。恢复后的应用发布相同的 `SessionId`;常规回放会还原 transcript(文本记录)、标题、待办事项和持久化目标状态。系统会有意解除目标的激活状态,TUI 则要求用户确认继续或执行 `/goal resume`。
`resumeCommand` 保留为退出及无宿主时的回退方案。TUI 仅为显示目的替换 `{session}`,绝不执行任意 shell 文本。只有当前会话已经持久化时,退出提示才会出现。