agent-Specialization/static/src/app/watchers.ts
JOJO 478d24c4dd feat(versioning): 重构版本控制为对话级浅备份 + 可选完全备份
本次变更是对版本控制功能的大范围重构,核心目标:
- 默认使用浅备份(只追踪 write_file/edit_file 实际修改的文件),避免大工作区首次初始化耗时十几秒;
- 保留可选的完全备份(git write-tree),不污染 git log;
- 回溯时统一选择范围(仅对话 / 对话+工作区)和模式(覆盖 / 复制);
- 修复复制对话、侧边栏同步、新建对话延迟、diff 统计与渲染等连锁问题。

后端改动:
- modules/shallow_versioning.py(新增)
  - 实现 ShallowVersioningManager:track_edit、make_snapshot、rewind、list_snapshots。
  - 基于 difflib.SequenceMatcher 计算真实 insertions/deletions,replace 场景正确显示 +N/-M。
  - diff 详情使用结构化 add/remove/context 行,不再解析 unified_diff 文本。
- modules/versioning_manager.py
  - 完全备份从 git commit 改为 git write-tree,字段 commit/parent_commit 改为 tree_hash/parent_tree_hash。
  - 恢复改用 read-tree + checkout-index;create_checkpoint 支持 diff_summary 参数。
  - get_checkpoint_detail 保留 API 字段名 last_commit/restored_commit 以兼容前端。
- core/main_terminal_parts/tools_execution.py
  - write_file/edit_file 在执行前调用浅备份 track_edit,保留编辑前状态。
- server/chat_flow_task_main.py
  - 消息轮次结束时根据 backup_mode 选择完全备份 create_checkpoint 或浅备份 make_snapshot。
  - 浅备份模式下同时写入 ConversationVersioningManager 检查点行,让前端能统一展示。
- server/conversation.py
  - restore/detail/list API 支持 shallow 模式;list/detail 对浅备份实时重新计算统计。
  - 创建对话时读取 personalization 的 versioning_backup_mode,shallow 模式固定 tracking_mode 为 conversation_only。
- core/web_terminal.py
  - _ensure_conversation_versioning_enabled 读取默认备份模式并写入 versioning meta。
- modules/file_manager/crud_mixin.py / replace_mixin.py
  - write_file / edit_file 返回 original_file / new_file / replacement_details(真实旧行/新行)。
- modules/personalization_manager.py
  - 新增 versioning_enabled_by_default、versioning_backup_mode(shallow/full)。
- utils/perf_log.py(新增)
  - 创建新对话全链路性能日志,用于定位延迟瓶颈。
- utils/conversation_manager/{index,list_search,crud}_mixin.py
  - 加性能日志;index_mixin 主动检查新增对话文件,修复侧边栏不同步。

前端改动:
- static/src/components/overlay/VersioningDialog.vue
  - 去掉顶部管理范围下拉;底部新增回溯范围与回溯模式选择。
  - diff 详情改为 div.diff-line 网格布局(marker + content),支持横向滚动,状态显示缩写 A/M/D。
  - 修复 pre 标签空白黑行、容器被挤窄导致折行等问题。
- static/src/app/methods/versioning.ts
  - 移除 mismatch 弹窗逻辑;支持 copy 模式;selectVersioningCheckpoint 增强错误捕获。
- static/src/components/personalization/PersonalizationDrawer.vue
  - 「工作区与权限」新增版本控制默认开关与备份方式选择。
- static/src/stores/personalization.ts
  - 新增 versioning_enabled_by_default、versioning_backup_mode 字段。
- static/src/components/chat/actions/ToolAction.vue / toolRenderers.ts
  - edit_file / write_file 工具展示基于 result.details / result.new_file 的真实替换结果。
- static/src/app/state.ts / watchers.ts / App.vue
  - 新增 versioningInitializingBackupToastId,完全备份初始化时显示 toast。
- static/src/app/methods/conversation/action.ts / message/send.ts
  - host + 完全备份 + 默认开启时,创建新对话/从 /new 发送首条消息显示「正在初始化备份」toast。

验证:
- /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/bin/python3.9 -m unittest test.test_server_refactor_smoke 通过。
- npm run build 通过。
- 宿主机与 docker 模式新建对话、编辑文件、回溯、复制对话均验证通过。
2026-07-07 12:07:44 +08:00

119 lines
3.6 KiB
TypeScript

// @ts-nocheck
import { debugLog, traceLog } from './methods/common';
export const watchers = {
inputMessage() {
this.autoResizeInput();
if (typeof this.scheduleComposerDraftPersist === 'function') {
this.scheduleComposerDraftPersist('watch-input-message');
}
},
messages: {
deep: true,
handler() {
this.refreshBlankHeroState();
}
},
composerBusy(newValue, oldValue) {
if (oldValue && !newValue && typeof this.tryAutoSendRuntimeQueuedMessages === 'function') {
this.tryAutoSendRuntimeQueuedMessages('watch-composer-idle');
}
},
runtimeQueuedMessages: {
deep: true,
handler(list) {
if (
Array.isArray(list) &&
list.length > 0 &&
!this.composerBusy &&
typeof this.tryAutoSendRuntimeQueuedMessages === 'function'
) {
this.tryAutoSendRuntimeQueuedMessages('watch-runtime-queue');
}
}
},
runtimeGuidanceFallbackQueue: {
deep: true,
handler(list) {
if (
Array.isArray(list) &&
list.length > 0 &&
!this.composerBusy &&
typeof this.tryAutoSendRuntimeQueuedMessages === 'function'
) {
this.tryAutoSendRuntimeQueuedMessages('watch-runtime-guidance-fallback');
}
}
},
currentConversationTitle(newVal, oldVal) {
const target = (newVal && newVal.trim()) || '';
if (this.suppressTitleTyping) {
this.titleTypingText = target;
this.titleTypingTarget = target;
return;
}
const previous =
(oldVal && oldVal.trim()) || (this.titleTypingText && this.titleTypingText.trim()) || '';
const placeholderPrev = !previous || previous === '新对话';
const placeholderTarget = !target || target === '新对话';
const animate = placeholderPrev && !placeholderTarget; // 仅从空/占位切换到真实标题时动画
this.startTitleTyping(target, { animate });
},
currentConversationId: {
immediate: false,
handler(newValue, oldValue) {
debugLog('currentConversationId 变化', {
oldValue,
newValue,
skipConversationHistoryReload: this.skipConversationHistoryReload
});
traceLog('watch:currentConversationId', {
oldValue,
newValue,
skipConversationHistoryReload: this.skipConversationHistoryReload,
historyLoading: this.historyLoading,
historyLoadingFor: this.historyLoadingFor,
historyLoadSeq: this.historyLoadSeq
});
this.refreshBlankHeroState();
this.logMessageState('watch:currentConversationId', {
oldValue,
newValue,
skipConversationHistoryReload: this.skipConversationHistoryReload
});
if (
oldValue !== newValue &&
!this.taskInProgress &&
!this.composerBusy &&
typeof this.restoreComposerDraftState === 'function'
) {
this.restoreComposerDraftState(
`watch-conversation-id:${oldValue || 'none'}->${newValue || 'none'}`
);
}
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
this.versioningEnabled = false;
this.versioningTrackingMode = 'conversation_only';
return;
}
this.fetchVersioningStatus(newValue, { silent: true });
if (this.skipConversationHistoryReload) {
this.skipConversationHistoryReload = false;
return;
}
if (oldValue && newValue === oldValue) {
return;
}
this.fetchAndDisplayHistory();
this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens();
}
},
fileTree: {
immediate: true,
handler(newValue) {
this.monitorSyncDesktop(newValue);
}
}
};