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:
@@ -0,0 +1,902 @@
|
||||
/**
|
||||
* Checkpoints API Routes
|
||||
*
|
||||
* 提供 Checkpoint 管理的 REST API
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Core Checkpoint 模块类型
|
||||
interface CheckpointModule {
|
||||
getCheckpointManager: () => CheckpointManager;
|
||||
initCheckpointManager: (
|
||||
workDir: string,
|
||||
config?: Partial<CheckpointConfig>
|
||||
) => Promise<CheckpointManager>;
|
||||
RestoreMode: typeof RestoreMode;
|
||||
}
|
||||
|
||||
interface CheckpointManager {
|
||||
initialize(): Promise<void>;
|
||||
isEnabled(): boolean;
|
||||
getConfig(): CheckpointConfig;
|
||||
listCheckpoints(): Promise<CheckpointMetadata[]>;
|
||||
getCheckpoint(idOrHash: string): Promise<CheckpointMetadata | null>;
|
||||
getLatestCheckpoint(): Promise<CheckpointMetadata | null>;
|
||||
createCheckpoint(options: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
trigger?: CheckpointTrigger;
|
||||
}): Promise<CheckpointMetadata>;
|
||||
deleteCheckpoint(id: string): Promise<boolean>;
|
||||
getDiff(checkpointId: string): Promise<DiffInfo>;
|
||||
getFileDiff(checkpointId: string, filePath: string): Promise<FileDiff>;
|
||||
rollback(options: RollbackOptions): Promise<RollbackResult>;
|
||||
checkSafety(checkpointId: string): Promise<SafetyCheckResult>;
|
||||
unrevert(): Promise<UnrevertResult>;
|
||||
canUnrevert(): boolean;
|
||||
getLastRollback(): RollbackRecord | null;
|
||||
cleanup(): Promise<number>;
|
||||
getStats(): Promise<CheckpointStats>;
|
||||
getSessionCheckpoints(sessionId: string): Promise<CheckpointMetadata[]>;
|
||||
getMessageCheckpoints(messageId: string): Promise<CheckpointMetadata[]>;
|
||||
}
|
||||
|
||||
interface CheckpointConfig {
|
||||
enabled: boolean;
|
||||
autoCheckpoint: {
|
||||
beforeWrite: boolean;
|
||||
beforeEdit: boolean;
|
||||
beforeDelete: boolean;
|
||||
beforeMove: boolean;
|
||||
beforeBash: boolean;
|
||||
};
|
||||
maxCheckpoints: number;
|
||||
maxAge: number;
|
||||
storageDir: string;
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
interface CheckpointMetadata {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
timestamp: number;
|
||||
trigger: CheckpointTrigger;
|
||||
toolCall?: {
|
||||
tool: string;
|
||||
params: Record<string, unknown>;
|
||||
};
|
||||
commitHash: string;
|
||||
filesChanged: number;
|
||||
messageId?: string;
|
||||
sessionId?: string;
|
||||
turnIndex?: number;
|
||||
}
|
||||
|
||||
type FileChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
|
||||
interface FileChange {
|
||||
path: string;
|
||||
type: FileChangeType;
|
||||
oldPath?: string;
|
||||
insertions?: number;
|
||||
deletions?: number;
|
||||
}
|
||||
|
||||
interface DiffInfo {
|
||||
from: string;
|
||||
to: string;
|
||||
files: FileChange[];
|
||||
totalInsertions: number;
|
||||
totalDeletions: number;
|
||||
}
|
||||
|
||||
interface FileDiff {
|
||||
path: string;
|
||||
type: FileChangeType;
|
||||
oldContent?: string;
|
||||
newContent?: string;
|
||||
patch?: string;
|
||||
}
|
||||
|
||||
enum RestoreMode {
|
||||
AI_CHANGES_ONLY = 'ai_changes_only',
|
||||
WORKSPACE_ONLY = 'workspace_only',
|
||||
FULL = 'full',
|
||||
}
|
||||
|
||||
interface RollbackOptions {
|
||||
target: string;
|
||||
files?: string[];
|
||||
dryRun?: boolean;
|
||||
mode?: RestoreMode;
|
||||
skipSafetyCheck?: boolean;
|
||||
}
|
||||
|
||||
interface RollbackResult {
|
||||
success: boolean;
|
||||
restoredFiles: string[];
|
||||
errors: Array<{ file: string; error: string }>;
|
||||
previousCommit?: string;
|
||||
}
|
||||
|
||||
interface SafetyCheckResult {
|
||||
safe: boolean;
|
||||
warnings: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
interface RollbackRecord {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
targetCheckpoint: string;
|
||||
previousCommit: string;
|
||||
restoredFiles: string[];
|
||||
canUnrevert: boolean;
|
||||
}
|
||||
|
||||
interface UnrevertResult {
|
||||
success: boolean;
|
||||
restoredCommit: string;
|
||||
filesRestored: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface CheckpointStats {
|
||||
count: number;
|
||||
oldestTimestamp: number | null;
|
||||
newestTimestamp: number | null;
|
||||
}
|
||||
|
||||
export const checkpointsRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let checkpointModule: CheckpointModule | null = null;
|
||||
let managerInitialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Checkpoint 模块
|
||||
*/
|
||||
async function initCheckpointModule(): Promise<CheckpointModule | null> {
|
||||
if (checkpointModule && managerInitialized) return checkpointModule;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
typeof core.getCheckpointManager !== 'function' ||
|
||||
typeof core.initCheckpointManager !== 'function'
|
||||
) {
|
||||
console.warn('[Checkpoints] Core module missing Checkpoint exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
checkpointModule = {
|
||||
getCheckpointManager: core.getCheckpointManager as () => CheckpointManager,
|
||||
initCheckpointManager: core.initCheckpointManager as (
|
||||
workDir: string,
|
||||
config?: Partial<CheckpointConfig>
|
||||
) => Promise<CheckpointManager>,
|
||||
RestoreMode: core.RestoreMode as typeof RestoreMode,
|
||||
};
|
||||
|
||||
// 初始化 Checkpoint Manager
|
||||
const config = getConfig();
|
||||
await checkpointModule.initCheckpointManager(config.workdir);
|
||||
managerInitialized = true;
|
||||
|
||||
console.log('[Checkpoints] Checkpoint module initialized');
|
||||
return checkpointModule;
|
||||
} catch (error) {
|
||||
console.warn('[Checkpoints] Failed to load Checkpoint module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /checkpoints - 获取所有检查点列表
|
||||
*/
|
||||
checkpointsRouter.get('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.listCheckpoints();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoints.map((cp) => ({
|
||||
id: cp.id,
|
||||
name: cp.name,
|
||||
description: cp.description,
|
||||
timestamp: cp.timestamp,
|
||||
trigger: cp.trigger,
|
||||
filesChanged: cp.filesChanged,
|
||||
commitHash: cp.commitHash,
|
||||
messageId: cp.messageId,
|
||||
sessionId: cp.sessionId,
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to list checkpoints',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/stats - 获取检查点统计信息
|
||||
*/
|
||||
checkpointsRouter.get('/stats', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const stats = await manager.getStats();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: stats,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get stats',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/latest - 获取最新检查点
|
||||
*/
|
||||
checkpointsRouter.get('/latest', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getLatestCheckpoint();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoint,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get latest checkpoint',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/unrevert/status - 检查是否可撤销回滚
|
||||
*/
|
||||
checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const canUnrevert = manager.canUnrevert();
|
||||
const lastRollback = manager.getLastRollback();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
canUnrevert,
|
||||
lastRollback: lastRollback
|
||||
? {
|
||||
id: lastRollback.id,
|
||||
timestamp: lastRollback.timestamp,
|
||||
targetCheckpoint: lastRollback.targetCheckpoint,
|
||||
restoredFiles: lastRollback.restoredFiles,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get unrevert status',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/:id - 获取单个检查点详情
|
||||
*/
|
||||
checkpointsRouter.get('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getCheckpoint(id);
|
||||
|
||||
if (!checkpoint) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Checkpoint not found: ${id}`,
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoint,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get checkpoint',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /checkpoints - 创建手动检查点
|
||||
*/
|
||||
checkpointsRouter.post('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{ name?: string; description?: string }>();
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
const checkpoint = await manager.createCheckpoint({
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
trigger: 'manual',
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoint,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create checkpoint',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /checkpoints/:id - 删除检查点
|
||||
*/
|
||||
checkpointsRouter.delete('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.deleteCheckpoint(id);
|
||||
|
||||
if (!deleted) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Checkpoint not found: ${id}`,
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: { deleted: true },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete checkpoint',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/:id/diff - 获取检查点与当前工作区的差异
|
||||
*/
|
||||
checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const diff = await manager.getDiff(id);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: diff,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get diff',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/:id/file-diff - 获取单个文件的详细差异
|
||||
*/
|
||||
checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const filePath = c.req.query('path');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'File path is required',
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const fileDiff = await manager.getFileDiff(id, filePath);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: fileDiff,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get file diff',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /checkpoints/:id/restore - 回滚到检查点
|
||||
*/
|
||||
checkpointsRouter.post('/:id/restore', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{
|
||||
mode?: 'ai_changes_only' | 'workspace_only' | 'full';
|
||||
files?: string[];
|
||||
skipSafetyCheck?: boolean;
|
||||
}>();
|
||||
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
// 转换 mode 字符串为枚举值
|
||||
let mode: RestoreMode | undefined;
|
||||
if (body.mode) {
|
||||
switch (body.mode) {
|
||||
case 'ai_changes_only':
|
||||
mode = module.RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await manager.rollback({
|
||||
target: id,
|
||||
mode,
|
||||
files: body.files,
|
||||
skipSafetyCheck: body.skipSafetyCheck,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to restore checkpoint',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/:id/restore/preview - 预览回滚(dry run)
|
||||
*/
|
||||
checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const modeParam = c.req.query('mode');
|
||||
const filesParam = c.req.query('files');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
// 转换 mode 字符串为枚举值
|
||||
let mode: RestoreMode | undefined;
|
||||
if (modeParam) {
|
||||
switch (modeParam) {
|
||||
case 'ai_changes_only':
|
||||
mode = module.RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const files = filesParam ? filesParam.split(',') : undefined;
|
||||
|
||||
const result = await manager.rollback({
|
||||
target: id,
|
||||
mode,
|
||||
files,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to preview restore',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /checkpoints/unrevert - 撤销最近一次回滚
|
||||
*/
|
||||
checkpointsRouter.post('/unrevert', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.unrevert();
|
||||
|
||||
if (!result.success) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: result.error || 'Failed to unrevert',
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to unrevert',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/:id/safety-check - 执行安全检查
|
||||
*/
|
||||
checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.checkSafety(id);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check safety',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /checkpoints/cleanup - 清理过期检查点
|
||||
*/
|
||||
checkpointsRouter.post('/cleanup', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.cleanup();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: { deleted },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to cleanup',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/sessions/:sessionId - 获取会话的所有检查点
|
||||
*/
|
||||
checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
const sessionId = c.req.param('sessionId');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getSessionCheckpoints(sessionId);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoints,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get session checkpoints',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /checkpoints/messages/:messageId - 获取消息关联的检查点
|
||||
*/
|
||||
checkpointsRouter.get('/messages/:messageId', async (c) => {
|
||||
const messageId = c.req.param('messageId');
|
||||
const module = await initCheckpointModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Checkpoint module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getMessageCheckpoints(messageId);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: checkpoints,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get message checkpoints',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user