Files
ai-terminal-assistant/packages/server/src/routes/checkpoints.ts
T
kurihada 1b7d55848d refactor(server): 消除与 Core 的重复类型定义
- 删除 Server 中 60+ 个与 Core 重复的类型定义
- 将动态导入 (await import) 改为静态类型导入 (import type)
- 保留必要的运行时静态导入
- 修复测试文件中的 mock 初始化问题
- 净删除约 960 行重复代码

重构文件:
- routes/checkpoints.ts: 删除 155 行重复类型
- routes/agents.ts: 删除 93 行重复类型
- routes/commands.ts: 删除 83 行重复类型
- routes/mcp.ts: 修复类型窄化
- routes/hooks.ts: 已使用静态导入
- routes/providers.ts: 删除 63 行重复类型
- session/manager.ts: 删除 41 行重复类型
- routes/sessions.ts: 添加类型导入
- permission/handler.ts: 添加类型导入
2025-12-16 20:19:24 +08:00

733 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<CheckpointManager | null> {
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
);
}
});