Skip to content

feat(continue): 将 /continue 默认改为原地恢复,并引入会话锁与占用确认#617

Merged
lsdefine merged 1 commit into
lsdefine:mainfrom
nianyucatfish:feat/continue-inplace
Jun 15, 2026
Merged

feat(continue): 将 /continue 默认改为原地恢复,并引入会话锁与占用确认#617
lsdefine merged 1 commit into
lsdefine:mainfrom
nianyucatfish:feat/continue-inplace

Conversation

@nianyucatfish

@nianyucatfish nianyucatfish commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

背景与动机

现有 /continue N 采用镜像复制策略(见 #612):恢复历史会话时,将源会话日志复制进当前进程日志,后续轮次写入该副本。此策略保证多个会话可并发恢复同一来源而互不冲突,但每次恢复都会派生一份新的会话副本,导致 temp/model_responses/ 下的日志文件持续增殖。

本 PR 将 /continue N 的默认行为改为原地恢复(in-place restore):直接接管所选会话的日志文件,后续轮次写回该文件本身。如此在最常见的场景(恢复一个已结束的会话)下不再产生副本。

方案

恢复时按目标会话的占用状态分流:

  • 目标空闲:直接原地接管,写回原文件。
  • 目标正被活动进程占用:弹出确认对话框「会话被占用,是否从原会话拷贝一份继续?」,用户确认后再复制一份继续。

并发安全由每会话一把文件锁保证「同一时刻至多一个写者」。原镜像策略「多会话并发恢复同一来源」的能力予以保留,仅在目标确实被占用时需要一次确认。

实现

会话锁与存活判定

  • 每个会话在创建时持有 temp/model_responses/.locks/<logid>.lock
  • 进程内共用单个心跳线程,每约 5 秒更新锁文件 mtime(不调用 fsync)。
  • 存活判定基于 mtime 新鲜度:30 秒内有心跳视为存活,超过则视为持有者已退出,锁可被接管。
  • 抢锁使用原子的 O_EXCL 创建。atexit 在正常退出时即时释放;进程被强制终止或崩溃时,由 30 秒超时兜底回收。
  • 锁相关操作的任何故障均降级为「假定空闲、放行恢复」,不阻断 /continue

移除快照机制

  • 切换会话、新建对话、清空时,不再执行「快照 + 清空当前日志」。原 _snapshot_current_log 依据 PID 拼接文件名,而会话日志实际以随机 logid 命名,二者永不匹配,该函数从未实际生成过快照(遗留死代码)。
  • 现在切换会话时,旧日志原样保留为一个空闲会话,新对话分配新的 logid。

修正 list_sessions

  • 新增 exclude_log 参数,按文件名排除调用方自身的当前会话。原 exclude_pid 因相同的 PID 与 logid 不匹配问题而失效,导致当前会话出现在自己的恢复列表中。

影响范围与兼容性

  • 改动仅涉及 frontends/continue_cmd.pyfrontends/tui_v3.pyfrontends/tuiapp_v2.py 三个文件。
  • continue_cmd.pyreset_conversationrestorehandlehandle_frontend_command 等既有接口未作改动,新增逻辑均以新函数形式提供。dcapp、tgapp、stapp、qtapp 等其他前端行为不变。
  • 已知限制:会话锁仅在上述两个 TUI 前端中持有。若以 TUI 原地恢复一个由非-TUI 前端持有的活动会话,将无法检测到占用。本 PR 暂不处理该情形。

测试

  • 三个改动文件及相关模块均通过 py_compile 与导入检查。
  • continue_cmd 的锁获取、存活判定、原地恢复、复制恢复、新建会话、过期接管、心跳刷新、抢锁竞态等场景均通过离线单元测试。
  • 真实终端下的交互测试(占用确认对话框、原地接管完整流程、双进程争用同一会话、清空后分配新 logid)尚在进行中。

…conflict)

/continue N 默认改为「原地续」:接管原会话日志、后续轮次写回同一文件,取代旧的
镜像复制(supersedes b4356c7 的 mirror 方案),不再每次续接都 fork 新会话、日志增殖。

- 空闲会话 → 直接原地接管;被活进程占用 → 弹窗确认后才复制一份续。
- 每会话出生持锁(temp/model_responses/.locks/<logid>.lock),整进程共用一个心跳
  线程每 5s touch mtime、30s 无心跳判死可接管;atexit 干净释放,崩溃/强杀靠超时兜底。
- 切走/新对话不再「快照+清空」(原 _snapshot_current_log 因 logid≠pid 实为死代码,
  从未生成过快照),旧日志原样留作空闲会话,新对话铸新 logid。
- list_sessions 新增 exclude_log,修正 exclude_pid=getpid 失效导致当前会话出现在
  自己列表里的问题。
- continue_cmd 仅新增函数,reset_conversation/restore/handle 未改动 → 其他前端
  (IM/qt/streamlit 等)行为完全不变。
- 改动仅限 continue_cmd.py + tui_v3.py + tuiapp_v2.py;rewind/worldline 逻辑不在本 PR。
@nianyucatfish nianyucatfish changed the title feat(continue): /continue 默认原地续 + 会话锁(占用则弹窗拷贝) feat(continue): 将 /continue 默认改为原地恢复,并引入会话锁与占用确认 Jun 15, 2026
@lsdefine
lsdefine merged commit ae7b028 into lsdefine:main Jun 15, 2026
lsdefine pushed a commit that referenced this pull request Jul 23, 2026
…conflict) (#617)

/continue N 默认改为「原地续」:接管原会话日志、后续轮次写回同一文件,取代旧的
镜像复制(supersedes b4356c7 的 mirror 方案),不再每次续接都 fork 新会话、日志增殖。

- 空闲会话 → 直接原地接管;被活进程占用 → 弹窗确认后才复制一份续。
- 每会话出生持锁(temp/model_responses/.locks/<logid>.lock),整进程共用一个心跳
  线程每 5s touch mtime、30s 无心跳判死可接管;atexit 干净释放,崩溃/强杀靠超时兜底。
- 切走/新对话不再「快照+清空」(原 _snapshot_current_log 因 logid≠pid 实为死代码,
  从未生成过快照),旧日志原样留作空闲会话,新对话铸新 logid。
- list_sessions 新增 exclude_log,修正 exclude_pid=getpid 失效导致当前会话出现在
  自己列表里的问题。
- continue_cmd 仅新增函数,reset_conversation/restore/handle 未改动 → 其他前端
  (IM/qt/streamlit 等)行为完全不变。
- 改动仅限 continue_cmd.py + tui_v3.py + tuiapp_v2.py;rewind/worldline 逻辑不在本 PR。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants