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:
2025-12-12 22:52:27 +08:00
parent a225e66ad7
commit cb554c65b4
23 changed files with 4970 additions and 116 deletions
+216
View File
@@ -28,6 +28,16 @@ import type {
AgentDetail,
AgentInput,
AgentDefaults,
CheckpointListItem,
CheckpointDetail,
CheckpointStats,
DiffInfo,
FileDiffDetail,
RestoreOptions,
RestoreResult,
SafetyCheckResult,
UnrevertStatus,
UnrevertResult,
} from './types.js';
// Re-export types
@@ -71,6 +81,21 @@ export type {
AgentDetail,
AgentInput,
AgentDefaults,
// Checkpoint types
CheckpointTrigger,
FileChangeType,
CheckpointListItem,
CheckpointDetail,
FileChange,
DiffInfo,
FileDiffDetail,
RestoreMode,
RestoreOptions,
RestoreResult,
SafetyCheckResult,
CheckpointStats,
UnrevertStatus,
UnrevertResult,
} from './types.js';
// API Configuration
@@ -588,3 +613,194 @@ export async function updateAgentDefaults(defaults: AgentDefaults): Promise<{
}> {
return request('PUT', '/agents/defaults', defaults);
}
// ============ Checkpoints API ============
/**
* 获取所有检查点列表
*/
export async function listCheckpoints(): Promise<{
success: boolean;
data: CheckpointListItem[];
error?: string;
}> {
return request('GET', '/checkpoints');
}
/**
* 获取检查点统计信息
*/
export async function getCheckpointStats(): Promise<{
success: boolean;
data: CheckpointStats;
error?: string;
}> {
return request('GET', '/checkpoints/stats');
}
/**
* 获取最新检查点
*/
export async function getLatestCheckpoint(): Promise<{
success: boolean;
data: CheckpointDetail | null;
error?: string;
}> {
return request('GET', '/checkpoints/latest');
}
/**
* 获取单个检查点详情
*/
export async function getCheckpoint(id: string): Promise<{
success: boolean;
data?: CheckpointDetail;
error?: string;
}> {
return request('GET', `/checkpoints/${encodeURIComponent(id)}`);
}
/**
* 创建手动检查点
*/
export async function createCheckpoint(options?: {
name?: string;
description?: string;
}): Promise<{
success: boolean;
data?: CheckpointDetail;
error?: string;
}> {
return request('POST', '/checkpoints', options || {});
}
/**
* 删除检查点
*/
export async function deleteCheckpoint(id: string): Promise<{
success: boolean;
data?: { deleted: boolean };
error?: string;
}> {
return request('DELETE', `/checkpoints/${encodeURIComponent(id)}`);
}
/**
* 获取检查点与当前工作区的差异
*/
export async function getCheckpointDiff(id: string): Promise<{
success: boolean;
data?: DiffInfo;
error?: string;
}> {
return request('GET', `/checkpoints/${encodeURIComponent(id)}/diff`);
}
/**
* 获取单个文件的详细差异
*/
export async function getFileDiff(checkpointId: string, filePath: string): Promise<{
success: boolean;
data?: FileDiffDetail;
error?: string;
}> {
const params = new URLSearchParams({ path: filePath });
return request('GET', `/checkpoints/${encodeURIComponent(checkpointId)}/file-diff?${params}`);
}
/**
* 回滚到检查点
*/
export async function restoreCheckpoint(
id: string,
options?: RestoreOptions
): Promise<{
success: boolean;
data?: RestoreResult;
error?: string;
}> {
return request('POST', `/checkpoints/${encodeURIComponent(id)}/restore`, options || {});
}
/**
* 预览回滚(dry run
*/
export async function previewRestore(
id: string,
options?: { mode?: RestoreOptions['mode']; files?: string[] }
): Promise<{
success: boolean;
data?: RestoreResult;
error?: string;
}> {
const params = new URLSearchParams();
if (options?.mode) params.set('mode', options.mode);
if (options?.files) params.set('files', options.files.join(','));
return request('GET', `/checkpoints/${encodeURIComponent(id)}/restore/preview?${params}`);
}
/**
* 撤销最近一次回滚
*/
export async function unrevert(): Promise<{
success: boolean;
data?: UnrevertResult;
error?: string;
}> {
return request('POST', '/checkpoints/unrevert');
}
/**
* 检查是否可撤销回滚
*/
export async function getUnrevertStatus(): Promise<{
success: boolean;
data?: UnrevertStatus;
error?: string;
}> {
return request('GET', '/checkpoints/unrevert/status');
}
/**
* 执行安全检查
*/
export async function checkSafety(id: string): Promise<{
success: boolean;
data?: SafetyCheckResult;
error?: string;
}> {
return request('GET', `/checkpoints/${encodeURIComponent(id)}/safety-check`);
}
/**
* 清理过期检查点
*/
export async function cleanupCheckpoints(): Promise<{
success: boolean;
data?: { deleted: number };
error?: string;
}> {
return request('POST', '/checkpoints/cleanup');
}
/**
* 获取会话的所有检查点
*/
export async function getSessionCheckpoints(sessionId: string): Promise<{
success: boolean;
data: CheckpointListItem[];
error?: string;
}> {
return request('GET', `/checkpoints/sessions/${encodeURIComponent(sessionId)}`);
}
/**
* 获取消息关联的检查点
*/
export async function getMessageCheckpoints(messageId: string): Promise<{
success: boolean;
data: CheckpointListItem[];
error?: string;
}> {
return request('GET', `/checkpoints/messages/${encodeURIComponent(messageId)}`);
}
+166
View File
@@ -424,3 +424,169 @@ export interface AgentDefaults {
/** 权限配置 */
permission?: AgentPermission;
}
// ============ Checkpoint 相关 ============
/** 检查点触发类型 */
export type CheckpointTrigger =
| 'auto'
| 'manual'
| 'tool:write_file'
| 'tool:edit_file'
| 'tool:delete_file'
| 'tool:move_file'
| 'tool:copy_file'
| 'tool:bash'
| 'task_start'
| 'task_complete'
| 'pre_rollback'
| 'session_start'
| 'session_end';
/** 文件变更类型 */
export type FileChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
/** 检查点列表项 */
export interface CheckpointListItem {
/** 唯一标识 */
id: string;
/** 用户可读名称 */
name?: string;
/** 描述信息 */
description?: string;
/** 创建时间戳 */
timestamp: number;
/** 触发类型 */
trigger: CheckpointTrigger;
/** 受影响的文件数 */
filesChanged: number;
/** Git commit hash */
commitHash: string;
/** 关联的消息 ID */
messageId?: string;
/** 会话 ID */
sessionId?: string;
}
/** 检查点详情 */
export interface CheckpointDetail extends CheckpointListItem {
/** 关联的工具调用 */
toolCall?: {
tool: string;
params: Record<string, unknown>;
};
/** 对话轮次 */
turnIndex?: number;
}
/** 文件变更信息 */
export interface FileChange {
/** 文件路径 */
path: string;
/** 变更类型 */
type: FileChangeType;
/** 旧路径 (重命名时) */
oldPath?: string;
/** 添加的行数 */
insertions?: number;
/** 删除的行数 */
deletions?: number;
}
/** 差异信息 */
export interface DiffInfo {
/** 源检查点/commit */
from: string;
/** 目标检查点/commit */
to: string;
/** 变更的文件列表 */
files: FileChange[];
/** 总添加行数 */
totalInsertions: number;
/** 总删除行数 */
totalDeletions: number;
}
/** 文件差异详情 */
export interface FileDiffDetail {
/** 文件路径 */
path: string;
/** 变更类型 */
type: FileChangeType;
/** 旧内容 */
oldContent?: string;
/** 新内容 */
newContent?: string;
/** 差异补丁 (unified diff 格式) */
patch?: string;
}
/** 恢复模式 */
export type RestoreMode = 'ai_changes_only' | 'workspace_only' | 'full';
/** 恢复选项 */
export interface RestoreOptions {
/** 恢复模式 */
mode?: RestoreMode;
/** 只恢复指定文件 */
files?: string[];
/** 跳过安全检查 */
skipSafetyCheck?: boolean;
}
/** 恢复结果 */
export interface RestoreResult {
/** 是否成功 */
success: boolean;
/** 恢复的文件列表 */
restoredFiles: string[];
/** 错误列表 */
errors: Array<{ file: string; error: string }>;
/** 回滚前的 commit hash */
previousCommit?: string;
}
/** 安全检查结果 */
export interface SafetyCheckResult {
/** 是否安全 */
safe: boolean;
/** 警告列表 */
warnings: string[];
/** 错误列表 */
errors: string[];
}
/** 检查点统计信息 */
export interface CheckpointStats {
/** 检查点数量 */
count: number;
/** 最早时间戳 */
oldestTimestamp: number | null;
/** 最新时间戳 */
newestTimestamp: number | null;
}
/** Unrevert 状态 */
export interface UnrevertStatus {
/** 是否可撤销回滚 */
canUnrevert: boolean;
/** 最后一次回滚记录 */
lastRollback?: {
id: string;
timestamp: number;
targetCheckpoint: string;
restoredFiles: string[];
};
}
/** Unrevert 结果 */
export interface UnrevertResult {
/** 是否成功 */
success: boolean;
/** 恢复的 commit */
restoredCommit: string;
/** 恢复的文件数 */
filesRestored: number;
/** 错误信息 */
error?: string;
}