fix(versioning): 修复浅备份文件修改归属错误及列表/详情不一致

This commit is contained in:
JOJO 2026-07-09 18:45:40 +08:00
parent 765196ea3c
commit 6a2cb820bc
4 changed files with 51 additions and 12 deletions

View File

@ -636,9 +636,12 @@ class MainTerminalToolsExecutionMixin:
def _track_shallow_versioning(self, file_path: Any) -> None:
"""Track a file edit for shallow versioning (conversation-scoped undo)."""
conversation_id = getattr(self.context_manager, "current_conversation_id", None)
# 优先使用当前用户消息的 message_id让文件修改归属到正确的输入节点。
message_id = getattr(self.context_manager, "current_shallow_message_id", None) or conversation_id
from utils.perf_log import perf_log
perf_log("_track_shallow_versioning called", extra={
"conversation_id": conversation_id,
"message_id": message_id,
"project_path": str(self.project_path),
"data_dir": str(self.data_dir),
"file_path": str(file_path),
@ -652,9 +655,10 @@ class MainTerminalToolsExecutionMixin:
data_dir=self.data_dir,
conversation_id=conversation_id,
)
manager.track_edit(file_path, conversation_id)
manager.track_edit(file_path, message_id)
perf_log("_track_shallow_versioning done", extra={
"conversation_id": conversation_id,
"message_id": message_id,
"file_path": str(file_path),
"tracked_files_count": len(getattr(manager, "_tracked_files", set())),
})

View File

@ -189,6 +189,14 @@ class ShallowVersioningManager:
# ----------------------------
# public API
# ----------------------------
def _find_latest_snapshot_by_message_id(self, message_id: str) -> Optional[ShallowSnapshot]:
"""Return the most recent snapshot whose message_id matches."""
target = str(message_id or "")
for snapshot in reversed(self._snapshots):
if str(snapshot.message_id or "") == target:
return snapshot
return None
def track_edit(self, file_path: Path | str, message_id: str) -> None:
"""Track a file before it is edited; backup its current content if not already tracked."""
tracking_path = self._normalize_path(file_path)
@ -206,12 +214,13 @@ class ShallowVersioningManager:
backup = self._create_backup(resolved, 1)
self._tracked_files.add(tracking_path)
# Add to the most recent snapshot if one exists, otherwise create a pre-edit snapshot.
# 归属到当前 message_id 对应的最新 snapshot不存在则新建。
# 避免把本次输入的修改追加到上一个输入的 snapshot。
now = datetime.now().isoformat()
if self._snapshots:
latest = self._snapshots[-1]
latest.tracked_file_backups[tracking_path] = backup
self._persist_snapshot(latest)
target_snapshot = self._find_latest_snapshot_by_message_id(message_id)
if target_snapshot is not None:
target_snapshot.tracked_file_backups[tracking_path] = backup
self._persist_snapshot(target_snapshot)
else:
snapshot = ShallowSnapshot(
message_id=message_id,
@ -256,8 +265,17 @@ class ShallowVersioningManager:
tracked_file_backups=tracked_file_backups,
timestamp=now,
)
self._snapshots.append(snapshot)
self._snapshot_sequence += 1
# 如果当前 message_id 已存在 snapshot由 track_edit 预创建),则替换它,
# 避免同一个输入节点留下多个 snapshot 导致 diff 只计算最后两次之间的变化。
existing_index = -1
for idx, s in enumerate(self._snapshots):
if str(s.message_id or "") == str(message_id or ""):
existing_index = idx
if existing_index >= 0:
self._snapshots[existing_index] = snapshot
else:
self._snapshots.append(snapshot)
self._snapshot_sequence += 1
self._persist_snapshot(snapshot)
# Evict old snapshots while keeping the most recent MAX_SNAPSHOTS.

View File

@ -431,6 +431,14 @@ def _record_hidden_versioning_checkpoint_after_run(
debug_log(f"[Versioning] 记录快照失败: {exc}")
except Exception as exc:
debug_log(f"[Versioning] 记录快照异常: {exc}")
finally:
# 当前输入处理结束,清除浅备份消息 ID避免后续无消息上下文时的错误归属。
try:
ctx = getattr(web_terminal, "context_manager", None)
if ctx is not None and hasattr(ctx, "current_shallow_message_id"):
ctx.current_shallow_message_id = None
except Exception:
pass
def _persist_and_echo_preceding_notice(
@ -1010,6 +1018,13 @@ async def handle_task_with_sender(
videos=videos,
metadata=user_message_metadata
)
# 为浅备份 track_edit 提供当前用户消息 ID避免文件修改被归到上一个输入。
try:
web_terminal.context_manager.current_shallow_message_id = (
saved_user_message.get("message_id") if isinstance(saved_user_message, dict) else None
)
except Exception:
web_terminal.context_manager.current_shallow_message_id = None
try:
user_message_index = len(getattr(web_terminal.context_manager, "conversation_history", []) or []) - 1
except Exception:

View File

@ -1074,14 +1074,16 @@ def get_conversation_versioning_checkpoint_detail(conversation_id, seq: int, ter
shallow_message_id = snapshot_data.get("shallow_message_id")
except Exception:
pass
files = row.get("files") or []
debug_log(f"[Versioning][Detail] resolved shallow_message_id={shallow_message_id} files={files}")
if shallow_message_id and isinstance(files, list):
debug_log(f"[Versioning][Detail] resolved shallow_message_id={shallow_message_id}")
if shallow_message_id:
shallow_manager = ShallowVersioningManager(
project_path=workspace.project_path,
data_dir=workspace.data_dir,
conversation_id=normalized_id,
)
# 与列表接口保持一致:从 shallow manager 重新计算文件变更,
# 而不是只给 inputs.jsonl 中原有的 files 补 patch_lines。
files = shallow_manager.get_file_diff_stats(str(shallow_message_id)) or []
stats = shallow_manager.get_diff_stats(str(shallow_message_id)) or {}
normalized_files: List[Dict[str, Any]] = []
for file_item in files:
@ -1091,7 +1093,7 @@ def get_conversation_versioning_checkpoint_detail(conversation_id, seq: int, ter
path = str(item.get("path") or "")
if path:
patch = shallow_manager.get_file_patch_lines(path, str(shallow_message_id))
debug_log(f"[Versioning][Detail] path={path} patch={patch}")
debug_log(f"[Versioning][Detail] path={path} patch_lines={len(patch.get('lines') or [])}")
item["patch_lines"] = patch.get("lines") or []
item["patch_truncated"] = bool(patch.get("truncated"))
normalized_files.append(item)