feat(checkpoint): 添加 Checkpoint 可视化管理功能
Core 层增强:
- 添加 safety.ts: 7点安全检查机制
- 添加 session-tracker.ts: 会话级检查点跟踪
- 添加 lock.ts: 并发控制文件锁
- 添加 lfs.ts: Git LFS 大文件支持
- 添加 path-validator.ts: 路径验证
- 添加 commit-message.ts: 智能提交消息生成
- 增强 manager.ts: 支持三种恢复模式、unrevert 撤销回滚
Server 层:
- 添加 checkpoints.ts: 16个 REST API 端点
- GET/POST /checkpoints: 列表/创建检查点
- GET/DELETE /checkpoints/🆔 获取/删除检查点
- GET /checkpoints/:id/diff: 获取差异
- POST /checkpoints/:id/restore: 恢复到检查点
- POST /checkpoints/unrevert: 撤销回滚
- GET /checkpoints/:id/safety-check: 安全检查
UI 层:
- 添加 CheckpointPanel.tsx: 检查点列表面板
- 添加 CheckpointDiffViewer.tsx: 差异查看器
- 添加 RestoreDialog.tsx: 恢复确认对话框
- 添加 16 个 API 客户端函数
- 添加完整的 TypeScript 类型定义
Web/Desktop 集成:
- 添加 History 按钮到工具栏
- 集成 CheckpointPanel 组件
This commit is contained in:
@@ -3,22 +3,29 @@
|
||||
* 管理检查点的创建、回滚、清理等操作
|
||||
*/
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ShadowGit, createShadowGit } from './shadow-git.js';
|
||||
import type {
|
||||
CheckpointMetadata,
|
||||
CheckpointConfig,
|
||||
CheckpointTrigger,
|
||||
RollbackOptions,
|
||||
RollbackResult,
|
||||
DiffInfo,
|
||||
FileDiff,
|
||||
CheckpointEvent,
|
||||
CheckpointEventListener,
|
||||
DEFAULT_CHECKPOINT_CONFIG,
|
||||
import { CheckpointLock } from './lock.js';
|
||||
import { CheckpointSafetyChecker } from './safety.js';
|
||||
import { WorkspacePathValidator } from './path-validator.js';
|
||||
import { CommitMessageGenerator } from './commit-message.js';
|
||||
import { LFSPatternLoader } from './lfs.js';
|
||||
import {
|
||||
RestoreMode,
|
||||
type CheckpointMetadata,
|
||||
type CheckpointConfig,
|
||||
type CheckpointTrigger,
|
||||
type RollbackOptions,
|
||||
type RollbackResult,
|
||||
type DiffInfo,
|
||||
type FileDiff,
|
||||
type CheckpointEvent,
|
||||
type CheckpointEventListener,
|
||||
type RollbackRecord,
|
||||
type UnrevertResult,
|
||||
type SafetyCheckResult,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
@@ -38,6 +45,20 @@ export class CheckpointManager {
|
||||
private lastCheckpointTime = 0;
|
||||
private eventListeners: Set<CheckpointEventListener> = new Set();
|
||||
|
||||
// 新增:增强功能组件
|
||||
private lock: CheckpointLock;
|
||||
private safetyChecker: CheckpointSafetyChecker;
|
||||
private pathValidator: WorkspacePathValidator;
|
||||
private commitMessageGenerator: CommitMessageGenerator;
|
||||
private lfsLoader: LFSPatternLoader;
|
||||
|
||||
// 新增:Unrevert 支持
|
||||
private lastRollback: RollbackRecord | null = null;
|
||||
|
||||
// 新增:会话跟踪
|
||||
private currentSessionId: string | null = null;
|
||||
private sessionCheckpoints: Map<string, string[]> = new Map();
|
||||
|
||||
// 防止重复创建检查点的最小间隔 (毫秒)
|
||||
private static readonly MIN_CHECKPOINT_INTERVAL = 1000;
|
||||
|
||||
@@ -59,6 +80,13 @@ export class CheckpointManager {
|
||||
};
|
||||
|
||||
this.shadowGit = createShadowGit(this.workDir, this.config.storageDir);
|
||||
|
||||
// 初始化增强组件
|
||||
this.lock = new CheckpointLock(this.shadowGit.getShadowGitDir());
|
||||
this.safetyChecker = new CheckpointSafetyChecker(this.workDir);
|
||||
this.pathValidator = new WorkspacePathValidator();
|
||||
this.commitMessageGenerator = new CommitMessageGenerator();
|
||||
this.lfsLoader = new LFSPatternLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +100,15 @@ export class CheckpointManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证工作目录路径
|
||||
const pathValidation = this.pathValidator.validate(this.workDir);
|
||||
if (!pathValidation.valid) {
|
||||
throw new Error(`Invalid workspace path: ${pathValidation.reason}`);
|
||||
}
|
||||
|
||||
// 加载 LFS 模式
|
||||
await this.lfsLoader.loadPatterns(this.workDir);
|
||||
|
||||
// 初始化 Shadow Git
|
||||
await this.shadowGit.initialize();
|
||||
|
||||
@@ -195,6 +232,9 @@ export class CheckpointManager {
|
||||
description?: string;
|
||||
trigger?: CheckpointTrigger;
|
||||
toolCall?: { tool: string; params: Record<string, unknown> };
|
||||
messageId?: string;
|
||||
sessionId?: string;
|
||||
turnIndex?: number;
|
||||
}): Promise<CheckpointMetadata> {
|
||||
await this.initialize();
|
||||
|
||||
@@ -202,48 +242,75 @@ export class CheckpointManager {
|
||||
throw new Error('Checkpoint system is disabled');
|
||||
}
|
||||
|
||||
const id = nanoid(10);
|
||||
const timestamp = Date.now();
|
||||
// 使用锁保护检查点创建
|
||||
return this.lock.withLock(async () => {
|
||||
const id = nanoid(10);
|
||||
const timestamp = Date.now();
|
||||
const trigger = options.trigger || 'manual';
|
||||
|
||||
// 创建元数据
|
||||
const metadata: CheckpointMetadata = {
|
||||
id,
|
||||
name: options.name,
|
||||
description: options.description,
|
||||
timestamp,
|
||||
trigger: options.trigger || 'manual',
|
||||
toolCall: options.toolCall,
|
||||
commitHash: '', // 待填充
|
||||
filesChanged: 0, // 待填充
|
||||
};
|
||||
// 创建元数据
|
||||
const metadata: CheckpointMetadata = {
|
||||
id,
|
||||
name: options.name,
|
||||
description: options.description,
|
||||
timestamp,
|
||||
trigger,
|
||||
toolCall: options.toolCall,
|
||||
commitHash: '', // 待填充
|
||||
filesChanged: 0, // 待填充
|
||||
// 新增:消息和会话关联
|
||||
messageId: options.messageId,
|
||||
sessionId: options.sessionId || this.currentSessionId || undefined,
|
||||
turnIndex: options.turnIndex,
|
||||
};
|
||||
|
||||
// 获取变更文件数
|
||||
try {
|
||||
const diff = await this.shadowGit.getWorkingDirDiff();
|
||||
metadata.filesChanged = diff.files.length;
|
||||
} catch {
|
||||
// 忽略
|
||||
}
|
||||
// 获取变更文件数
|
||||
let filesChanged: Array<{ path: string; type: string }> = [];
|
||||
try {
|
||||
const diff = await this.shadowGit.getWorkingDirDiff();
|
||||
metadata.filesChanged = diff.files.length;
|
||||
filesChanged = diff.files;
|
||||
} catch {
|
||||
// 忽略
|
||||
}
|
||||
|
||||
// 创建 commit
|
||||
const commitMessage = CHECKPOINT_PREFIX + JSON.stringify(metadata);
|
||||
const commitHash = await this.shadowGit.createCommit(commitMessage);
|
||||
metadata.commitHash = commitHash;
|
||||
// 生成智能提交消息
|
||||
const humanReadableMessage = this.commitMessageGenerator.generateMessage(
|
||||
trigger,
|
||||
options.toolCall,
|
||||
filesChanged as any
|
||||
);
|
||||
|
||||
// 更新索引
|
||||
this.checkpointsIndex.set(id, metadata);
|
||||
// 创建 commit(使用 JSON 元数据作为 commit message,但包含可读描述)
|
||||
const commitMessage = CHECKPOINT_PREFIX + JSON.stringify({
|
||||
...metadata,
|
||||
_readableMessage: humanReadableMessage,
|
||||
});
|
||||
const commitHash = await this.shadowGit.createCommit(commitMessage);
|
||||
metadata.commitHash = commitHash;
|
||||
|
||||
// 触发事件
|
||||
this.emitEvent({
|
||||
type: 'created',
|
||||
checkpoint: metadata,
|
||||
timestamp,
|
||||
// 更新索引
|
||||
this.checkpointsIndex.set(id, metadata);
|
||||
|
||||
// 记录到当前会话
|
||||
if (this.currentSessionId) {
|
||||
const sessionCps = this.sessionCheckpoints.get(this.currentSessionId) || [];
|
||||
sessionCps.push(id);
|
||||
this.sessionCheckpoints.set(this.currentSessionId, sessionCps);
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
this.emitEvent({
|
||||
type: 'created',
|
||||
checkpoint: metadata,
|
||||
timestamp,
|
||||
});
|
||||
|
||||
// 异步清理
|
||||
this.cleanupAsync();
|
||||
|
||||
return metadata;
|
||||
});
|
||||
|
||||
// 异步清理
|
||||
this.cleanupAsync();
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,63 +426,253 @@ export class CheckpointManager {
|
||||
throw new Error(`Checkpoint not found: ${options.target}`);
|
||||
}
|
||||
|
||||
// 获取当前 HEAD 用于可能的撤销
|
||||
const previousCommit = await this.shadowGit.getHead();
|
||||
// 安全检查(除非明确跳过)
|
||||
if (!options.skipSafetyCheck) {
|
||||
const safetyResult = await this.safetyChecker.checkBeforeRollback(checkpoint, this);
|
||||
if (!safetyResult.safe) {
|
||||
const errorMsg = safetyResult.errors.join('; ');
|
||||
throw new Error(`Safety check failed: ${errorMsg}`);
|
||||
}
|
||||
// 警告仍然记录,但不阻止操作
|
||||
if (safetyResult.warnings.length > 0) {
|
||||
console.warn('Rollback warnings:', safetyResult.warnings.join('; '));
|
||||
}
|
||||
}
|
||||
|
||||
// 预览模式
|
||||
if (options.dryRun) {
|
||||
const diff = await this.getDiff(checkpoint.id);
|
||||
return {
|
||||
// 使用锁保护回滚操作
|
||||
return this.lock.withLock(async () => {
|
||||
// 获取当前 HEAD 用于可能的撤销
|
||||
const previousCommit = await this.shadowGit.getHead();
|
||||
|
||||
// 预览模式
|
||||
if (options.dryRun) {
|
||||
const diff = await this.getDiff(checkpoint.id);
|
||||
return {
|
||||
success: true,
|
||||
restoredFiles: diff.files.map((f) => f.path),
|
||||
errors: [],
|
||||
previousCommit,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建回滚前检查点(用于 unrevert)
|
||||
let preRollbackCheckpoint: CheckpointMetadata | null = null;
|
||||
try {
|
||||
preRollbackCheckpoint = await this.createCheckpointInternal({
|
||||
trigger: 'pre_rollback',
|
||||
description: `Before rollback to ${options.target}`,
|
||||
});
|
||||
} catch {
|
||||
// 忽略创建失败
|
||||
}
|
||||
|
||||
const result: RollbackResult = {
|
||||
success: true,
|
||||
restoredFiles: diff.files.map((f) => f.path),
|
||||
restoredFiles: [],
|
||||
errors: [],
|
||||
previousCommit,
|
||||
};
|
||||
}
|
||||
|
||||
const result: RollbackResult = {
|
||||
success: true,
|
||||
restoredFiles: [],
|
||||
errors: [],
|
||||
previousCommit,
|
||||
};
|
||||
try {
|
||||
const mode = options.mode || RestoreMode.FULL;
|
||||
|
||||
try {
|
||||
if (options.files && options.files.length > 0) {
|
||||
// 选择性回滚
|
||||
await this.shadowGit.checkoutFiles(checkpoint.commitHash, options.files);
|
||||
result.restoredFiles = options.files;
|
||||
} else {
|
||||
// 完整回滚
|
||||
await this.shadowGit.resetHard(checkpoint.commitHash);
|
||||
if (options.files && options.files.length > 0) {
|
||||
// 选择性回滚(指定文件)
|
||||
await this.shadowGit.checkoutFiles(checkpoint.commitHash, options.files);
|
||||
result.restoredFiles = options.files;
|
||||
} else if (mode === RestoreMode.AI_CHANGES_ONLY) {
|
||||
// 仅恢复 AI 修改的文件
|
||||
const aiFiles = await this.getAiModifiedFiles(checkpoint);
|
||||
if (aiFiles.length > 0) {
|
||||
await this.shadowGit.checkoutFiles(checkpoint.commitHash, aiFiles);
|
||||
result.restoredFiles = aiFiles;
|
||||
}
|
||||
} else if (mode === RestoreMode.WORKSPACE_ONLY) {
|
||||
// 仅恢复工作区变更(不包括 AI 修改)
|
||||
const workspaceFiles = await this.getWorkspaceOnlyFiles(checkpoint);
|
||||
if (workspaceFiles.length > 0) {
|
||||
await this.shadowGit.checkoutFiles(checkpoint.commitHash, workspaceFiles);
|
||||
result.restoredFiles = workspaceFiles;
|
||||
}
|
||||
} else {
|
||||
// 完整回滚
|
||||
await this.shadowGit.resetHard(checkpoint.commitHash);
|
||||
|
||||
// 获取恢复的文件列表
|
||||
const diff = await this.shadowGit.getDiffSummary(
|
||||
previousCommit,
|
||||
checkpoint.commitHash
|
||||
);
|
||||
result.restoredFiles = diff.files.map((f) => f.path);
|
||||
// 获取恢复的文件列表
|
||||
const diff = await this.shadowGit.getDiffSummary(
|
||||
previousCommit,
|
||||
checkpoint.commitHash
|
||||
);
|
||||
result.restoredFiles = diff.files.map((f) => f.path);
|
||||
}
|
||||
|
||||
// 记录回滚信息(用于 unrevert)
|
||||
this.lastRollback = {
|
||||
id: nanoid(10),
|
||||
timestamp: Date.now(),
|
||||
targetCheckpoint: checkpoint.id,
|
||||
previousCommit: preRollbackCheckpoint?.commitHash || previousCommit,
|
||||
restoredFiles: result.restoredFiles,
|
||||
canUnrevert: true,
|
||||
};
|
||||
|
||||
// 触发事件
|
||||
this.emitEvent({
|
||||
type: 'restored',
|
||||
checkpoint,
|
||||
timestamp: Date.now(),
|
||||
details: {
|
||||
files: result.restoredFiles,
|
||||
previousCommit,
|
||||
mode,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
result.success = false;
|
||||
result.errors.push({
|
||||
file: '*',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
this.emitEvent({
|
||||
type: 'restored',
|
||||
checkpoint,
|
||||
timestamp: Date.now(),
|
||||
details: {
|
||||
files: result.restoredFiles,
|
||||
previousCommit,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
result.success = false;
|
||||
result.errors.push({
|
||||
file: '*',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销最近一次回滚(Unrevert)
|
||||
*/
|
||||
async unrevert(): Promise<UnrevertResult> {
|
||||
await this.initialize();
|
||||
|
||||
if (!this.lastRollback || !this.lastRollback.canUnrevert) {
|
||||
return {
|
||||
success: false,
|
||||
restoredCommit: '',
|
||||
filesRestored: 0,
|
||||
error: 'No rollback to unrevert',
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
return this.lock.withLock(async () => {
|
||||
try {
|
||||
// 恢复到回滚前的状态
|
||||
await this.shadowGit.resetHard(this.lastRollback!.previousCommit);
|
||||
|
||||
const result: UnrevertResult = {
|
||||
success: true,
|
||||
restoredCommit: this.lastRollback!.previousCommit,
|
||||
filesRestored: this.lastRollback!.restoredFiles.length,
|
||||
};
|
||||
|
||||
// 清除 unrevert 记录
|
||||
this.lastRollback = null;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
restoredCommit: '',
|
||||
filesRestored: 0,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以执行 unrevert
|
||||
*/
|
||||
canUnrevert(): boolean {
|
||||
return this.lastRollback !== null && this.lastRollback.canUnrevert;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一次回滚记录
|
||||
*/
|
||||
getLastRollback(): RollbackRecord | null {
|
||||
return this.lastRollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行安全检查
|
||||
*/
|
||||
async checkSafety(checkpointId: string): Promise<SafetyCheckResult> {
|
||||
await this.initialize();
|
||||
|
||||
const checkpoint = await this.getCheckpoint(checkpointId);
|
||||
if (!checkpoint) {
|
||||
return {
|
||||
safe: false,
|
||||
warnings: [],
|
||||
errors: ['Checkpoint not found'],
|
||||
};
|
||||
}
|
||||
|
||||
return this.safetyChecker.checkBeforeRollback(checkpoint, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部创建检查点(不使用锁,供 rollback 内部调用)
|
||||
*/
|
||||
private async createCheckpointInternal(options: {
|
||||
trigger: CheckpointTrigger;
|
||||
description?: string;
|
||||
}): Promise<CheckpointMetadata> {
|
||||
const id = nanoid(10);
|
||||
const timestamp = Date.now();
|
||||
|
||||
const metadata: CheckpointMetadata = {
|
||||
id,
|
||||
description: options.description,
|
||||
timestamp,
|
||||
trigger: options.trigger,
|
||||
commitHash: '',
|
||||
filesChanged: 0,
|
||||
};
|
||||
|
||||
const commitMessage = CHECKPOINT_PREFIX + JSON.stringify(metadata);
|
||||
const commitHash = await this.shadowGit.createCommit(commitMessage);
|
||||
metadata.commitHash = commitHash;
|
||||
|
||||
this.checkpointsIndex.set(id, metadata);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 修改的文件列表
|
||||
*/
|
||||
private async getAiModifiedFiles(checkpoint: CheckpointMetadata): Promise<string[]> {
|
||||
const files: string[] = [];
|
||||
const checkpoints = await this.listCheckpoints();
|
||||
|
||||
// 找到该检查点之后的所有检查点
|
||||
for (const cp of checkpoints) {
|
||||
if (cp.timestamp > checkpoint.timestamp && cp.toolCall) {
|
||||
const filePath =
|
||||
(cp.toolCall.params.file_path as string) ||
|
||||
(cp.toolCall.params.path as string);
|
||||
if (filePath && !files.includes(filePath)) {
|
||||
files.push(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取仅工作区变更的文件(不包括 AI 修改)
|
||||
*/
|
||||
private async getWorkspaceOnlyFiles(checkpoint: CheckpointMetadata): Promise<string[]> {
|
||||
const diff = await this.getDiff(checkpoint.id);
|
||||
const aiFiles = await this.getAiModifiedFiles(checkpoint);
|
||||
|
||||
// 返回不在 AI 修改列表中的文件
|
||||
return diff.files
|
||||
.map((f) => f.path)
|
||||
.filter((path) => !aiFiles.includes(path));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,6 +835,151 @@ export class CheckpointManager {
|
||||
getConfig(): CheckpointConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
// ==================== 会话管理方法 ====================
|
||||
|
||||
/**
|
||||
* 开始新会话
|
||||
*/
|
||||
async startSession(sessionId?: string): Promise<string> {
|
||||
await this.initialize();
|
||||
|
||||
const id = sessionId || nanoid(10);
|
||||
this.currentSessionId = id;
|
||||
this.sessionCheckpoints.set(id, []);
|
||||
|
||||
// 创建会话开始检查点
|
||||
try {
|
||||
await this.createCheckpoint({
|
||||
trigger: 'session_start',
|
||||
description: `Session started: ${id}`,
|
||||
sessionId: id,
|
||||
});
|
||||
} catch {
|
||||
// 忽略创建失败
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束当前会话
|
||||
*/
|
||||
async endSession(): Promise<void> {
|
||||
if (!this.currentSessionId) return;
|
||||
|
||||
// 创建会话结束检查点
|
||||
try {
|
||||
await this.createCheckpoint({
|
||||
trigger: 'session_end',
|
||||
description: `Session ended: ${this.currentSessionId}`,
|
||||
sessionId: this.currentSessionId,
|
||||
});
|
||||
} catch {
|
||||
// 忽略创建失败
|
||||
}
|
||||
|
||||
this.currentSessionId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话 ID
|
||||
*/
|
||||
getCurrentSessionId(): string | null {
|
||||
return this.currentSessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话的所有检查点
|
||||
*/
|
||||
async getSessionCheckpoints(sessionId: string): Promise<CheckpointMetadata[]> {
|
||||
await this.initialize();
|
||||
|
||||
const checkpointIds = this.sessionCheckpoints.get(sessionId);
|
||||
if (!checkpointIds) return [];
|
||||
|
||||
const checkpoints: CheckpointMetadata[] = [];
|
||||
for (const id of checkpointIds) {
|
||||
const checkpoint = this.checkpointsIndex.get(id);
|
||||
if (checkpoint) {
|
||||
checkpoints.push(checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return checkpoints.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建与消息关联的检查点
|
||||
*/
|
||||
async createMessageCheckpoint(
|
||||
messageId: string,
|
||||
turnIndex?: number,
|
||||
options?: {
|
||||
trigger?: CheckpointTrigger;
|
||||
description?: string;
|
||||
}
|
||||
): Promise<CheckpointMetadata> {
|
||||
return this.createCheckpoint({
|
||||
trigger: options?.trigger || 'auto',
|
||||
description: options?.description || `Message checkpoint: ${messageId}`,
|
||||
messageId,
|
||||
sessionId: this.currentSessionId || undefined,
|
||||
turnIndex,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与消息关联的检查点
|
||||
*/
|
||||
async getMessageCheckpoints(messageId: string): Promise<CheckpointMetadata[]> {
|
||||
await this.initialize();
|
||||
|
||||
const checkpoints: CheckpointMetadata[] = [];
|
||||
for (const checkpoint of this.checkpointsIndex.values()) {
|
||||
if (checkpoint.messageId === messageId) {
|
||||
checkpoints.push(checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return checkpoints.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销整个会话的修改
|
||||
*/
|
||||
async undoSession(sessionId: string): Promise<RollbackResult> {
|
||||
const sessionCheckpoints = await this.getSessionCheckpoints(sessionId);
|
||||
if (sessionCheckpoints.length === 0) {
|
||||
throw new Error(`No checkpoints found for session: ${sessionId}`);
|
||||
}
|
||||
|
||||
// 找到会话开始的检查点
|
||||
const startCheckpoint = sessionCheckpoints.find(
|
||||
(cp) => cp.trigger === 'session_start'
|
||||
);
|
||||
|
||||
if (!startCheckpoint) {
|
||||
// 如果没有明确的开始检查点,使用第一个检查点
|
||||
return this.rollback({ target: sessionCheckpoints[0].id });
|
||||
}
|
||||
|
||||
return this.rollback({ target: startCheckpoint.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 LFS 模式加载器
|
||||
*/
|
||||
getLfsLoader(): LFSPatternLoader {
|
||||
return this.lfsLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否由 LFS 管理
|
||||
*/
|
||||
isLfsFile(filePath: string): boolean {
|
||||
return this.lfsLoader.isLfsFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局检查点管理器实例
|
||||
|
||||
Reference in New Issue
Block a user