/** * Checkpoints API Routes * * 提供 Checkpoint 管理的 REST API */ import { Hono } from 'hono'; import { getConfig } from './config.js'; import type { CheckpointMetadata, CheckpointConfig, CheckpointTrigger, FileChange, FileChangeType, DiffInfo, FileDiff, RollbackOptions, RollbackResult, RollbackRecord, SafetyCheckResult, UnrevertResult, } from '@ai-assistant/core'; import { CheckpointManager, getCheckpointManager, initCheckpointManager, RestoreMode, } from '@ai-assistant/core'; interface CheckpointStats { count: number; oldestTimestamp: number | null; newestTimestamp: number | null; } export const checkpointsRouter = new Hono(); // Manager 初始化状态 let managerInitialized = false; /** * 初始化 Checkpoint 模块 */ async function ensureCheckpointManager(): Promise { if (managerInitialized) { return getCheckpointManager(); } try { const config = getConfig(); await initCheckpointManager(config.workdir); managerInitialized = true; console.log('[Checkpoints] Checkpoint module initialized'); return getCheckpointManager(); } catch (error) { console.warn('[Checkpoints] Failed to initialize Checkpoint module:', error); return null; } } /** * GET /checkpoints - 获取所有检查点列表 */ checkpointsRouter.get('/', async (c) => { const manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { const body = await c.req.json<{ name?: string; description?: string }>(); 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { 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 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 manager = await ensureCheckpointManager(); if (!manager) { 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; }>(); // 转换 mode 字符串为枚举值 let mode: RestoreMode | undefined; if (body.mode) { switch (body.mode) { case 'ai_changes_only': mode = RestoreMode.AI_CHANGES_ONLY; break; case 'workspace_only': mode = RestoreMode.WORKSPACE_ONLY; break; case 'full': mode = 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { // 转换 mode 字符串为枚举值 let mode: RestoreMode | undefined; if (modeParam) { switch (modeParam) { case 'ai_changes_only': mode = RestoreMode.AI_CHANGES_ONLY; break; case 'workspace_only': mode = RestoreMode.WORKSPACE_ONLY; break; case 'full': mode = 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 manager = await ensureCheckpointManager(); if (!manager) { return c.json( { success: false, error: 'Checkpoint module not available', }, 503 ); } try { 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 ); } });