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:
@@ -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)}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user