Files
GenericAgent/frontends
nianyucatfish 14d8df3ddd feat(worldline): persistent checkpoint-tree rewind for tui_v2 (/rewind + /worldline) (#667)
* feat(worldline): integrate checkpoint-tree rewind into tui_v2

- tuiapp_v2.py: /rewind durable inline picker + /worldline three-pane
  checkpoint tree (tree UI inlined; colors follow the v2 theme) with a
  per-node diff viewport; conv-only /rewind restores via the persistent store.
- worldline.py (new): UI-agnostic backend — RewindStore (persistent
  checkpoint tree + content-addressed blobs), reconcile, restore_plan,
  node_diff, native-log projection.
- continue_cmd.py: gated worldline support — list_sessions(rewind_root=),
  continue_inplace/copy(allow_empty=); default-off, other UIs byte-identical.
- tui_v3.py: status line shows concrete model id (unrelated minor tweak).

* feat(worldline): /rewind reuses the restore-mode picker (conv/code/both)

After picking a turn, /rewind now opens the same RestoreModeScreen as
/worldline to choose conversation / code / both, then restores via the
persistent store — making /rewind a true lite view of /worldline (linear
list, no tree/diff) over the identical restore backend.

* feat(worldline): sync working memory (history_info/key_info) on rewind

树节点除 conv 外,再存 history_info 增量(与 conv 同构)+ key_info 快照。
restore_plan 重建二者,_rw_sync_working_memory 在 rewind 时就地写回
handler.history_info / working['key_info'],使注入的 WORKING MEMORY 与
回退后的对话一致,消除旧 rewind「历史回退、纪要串味」缺陷。

向后兼容:老树无 WM 记录 → path_has_wm=False → 恢复时跳过 WM 同步,
不动现场纪要。单测 21/21 + 真机(363580)端到端验证(call_013→014
v2 痕迹 ×3→×0)通过。

* fix(worldline): reconcile against the log, only on /continue; rebuild working memory on absorb

- reconcile now takes the log-parsed full history, never the compressed
  backend.history. Compressed memory is a lossy derivative; reconciling the
  full tree against it made the tree adopt a front-trimmed suffix as a new
  trunk (false divergence). Contract pinned in the docstring.
- drop in-session reconcile from /rewind and /worldline: the tree is kept
  current every turn by _rw_commit, and live history there is compressed.
  reconcile now runs only on /continue, where backend.history is the freshly
  loaded full log.
- reconcile rebuilds history_info/key_info from the absorbed log so resumed
  and external-UI sessions also carry working memory on rewind.

* fix(worldline): feed commit the full log; derive deltas from the tree's real length

_rw_commit fed the compressed backend.history, so once a long session front-trims,
commit's absolute hist_len delta drops turns. Now:
- _rw_commit feeds the log-parsed full history (fallback to backend.history).
- commit derives the delta start from len(rebuild_history(parent)) /
  len(rebuild_hist_info(parent)) instead of the stored hist_len, so a poisoned
  count can't drop turns and a previously-lost turn self-heals on the next commit
  (the log is append-only, always a superset of the tree).

* chore(worldline): mark in-memory _do_rewind as the non-persistent degraded fallback

It chops the compressed live history (can't reach front-trimmed turns, keeps
truncated remnants, restores neither files nor working memory). Only used when
there is no checkpoint tree; the durable tree-rebuild path is always preferred.

* fix(worldline): derive tree working memory from the log, not live handler state

/continue resets agent.history (handler.history_info) to empty. _rw_commit
previously fed that live list as hist_info, so after a continue the per-turn WM
delta misaligned with the tree's log-derived WM and new turns' summaries were
dropped. commit now derives hist_info/key_info from the history it stores (the
full log) when not given, matching reconcile; _rw_commit stops passing the live
list. Tree WM is uniformly log-derived and survives the continue reset.

* feat(worldline): restore working memory on /continue (TUI-only, derived from the log)

continue_cmd clears handler.history_info on restore; the TUI's continue path now
derives history_info from the loaded log (same _derive_hist_info as the tree) and
writes it back to agent.history, so a continued session keeps its working memory,
consistent with the tree and live sessions. Scoped to tuiapp_v2 only — shared
continue_cmd is untouched, so telegram/discord/streamlit frontends are unaffected.

* refactor(continue): restore working memory on /continue via opt-in restore_wm

Move the WM restore from the TUI into continue_cmd as an opt-in. _load_history_into
derives history_info from the loaded log and writes it back to agent.history when
restore_wm=True; continue_inplace/continue_copy thread the flag and the worldline
TUI passes True. Default False keeps every other frontend (telegram/discord/
streamlit/shared chat) byte-for-byte unchanged. _derive_hist_info is self-contained
(no worldline dependency), same extraction口径 as the tree.

* perf(worldline): only re-parse the log in _rw_commit when front-trim is detected

_rw_commit re-parsed the whole log every segment to feed commit a full history;
for very long sessions (multi-MB logs) that adds a multi-second lag per turn. Now
it uses the fast in-memory backend.history by default and only falls back to
parsing the log when front-trim is detected — RewindStore.first_question() vs the
live history's first question (front-trim pops the first question, so they diverge).
Common case (no trim) pays nothing; correctness under trim is preserved.

* feat(worldline): conv/code bridge nodes + align _rw_commit to native log

- worldline: commit_bridge() + restore_plan() three-way (both/conv/code) with a new rw_tag field. conv bridges fork at parent(target); code bridges mount at parent(old_head) as a sibling of the current conversation node (same conversation, older code).

- tuiapp_v2: _rw_commit now reads the native log (parse_native_log) instead of the compressible live backend.history; stat-cached _rw_log_history; title from the real log delta; skip commit when there is no new complete pair. Cursor points at the bridge for conv/code; rw_tag rendered after the title.

- continue_cmd: add parse_native_log() public wrapper (complete Prompt->Response pairs only; [] for native-but-empty, None for non-native).

* fix(worldline): realpath 去重 key + 节点行数增删 + 剥离 project-mode

- key()/cwd 改用 realpath 解析 junction/symlink,同一物理文件只产生一个 tracked
  key(修 workspace 下 @提及绝对路径与相对写入产生双 key、回退误覆写当前内容)
- 新增 node_line_delta:按 node_diff 计每节点相对父节点的 +新增/-删除 行数,
  供 /rewind、worldline 显示代码变动量
- 重建历史时剥离 project-mode 注入块与自动注入标记(worldline._msg_user_text +
  continue_cmd._derive_hist_info),标题/提问只留用户原话

* feat(worldline/rewind): 面板与 rewind UI 精简重设计

worldline 面板:
- 右栏改用原生 OptionList(整项高亮/滑窗跟踪/点击映射,取代手写 ClickableList)
- 左右栏比例 3:2;详情面板精简(删掉与状态条/diff 重复的操作提示、时间、回退说明)
- 状态条重写为完整动宾短语,Enter 随聚焦切文案;右栏聚焦高亮改用更亮的 alt_bg
  (对齐左树,修此前聚焦态反而更淡的问题)

/rewind:
- 三列:用户 prompt · 上次时间 · 代码 +新增/-删除(绿/红,无改动显示「(无改动)」);
  倒序、聚焦底部、超长 prompt 省略号截断
- 延迟折叠(defer_collapse):选中先弹模式窗,确认才回退并清卡片;Esc 取消保留列表、
  焦点回到列表,不再留误导性「✓ 已选中」

其他:
- Ctrl+D 关会话时释放日志锁(abort + release_current),之后可原地 /continue

* fix(worldline): 回填输入框的 prefill 剥离 project-mode 注入

restore_plan 的 prefill/_prompt_of 走未清洗的 user_msg_text(树里的 user 消息取自
含注入的 native 日志),把 --- [PROJECT MODE:...] --- 一起塞进输入框。统一过
_strip_project_mode(幂等)。回归:temp/test_rewind_prefill_no_projectmode.py 9/9。

* refactor(tui): 删除被取代的旧 rewind 面板与死代码

- 删旧 /rewind 整簇:RewindScreen 类 + _on_rewind_pick + _rw_* 9 个 helper
  (已被内联 choice 卡 + RestoreModeScreen / RewindTreeScreen 取代,无 live 入口)
- 删未用 import next_same_depth/CheckpointTree、只写不读的 _diff_for/assistant_len
- 净 -276 行;import tuiapp_v2 通过、worldline 回归全绿

* fix(worldline): apply_code 回退失败不再静默,回传错误由 UI 提示

apply_code 直接写用户工作区文件,中途失败(权限/磁盘)会留下半回退状态。原先
restore_plan 吞成 changed=[],UI 显示「无变更」,用户毫不知情——全 PR 里失败半径
最大却唯一被静默的点。改为 _apply_code_safe 回传 (changed, code_error),
_rw_restore_node 据此追加「⚠️ 代码回退未完成…请检查」告警。
2026-07-07 08:36:28 +08:00
..
…
2026-05-21 17:26:59 +08:00
…
…