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: 添加类型导入
This commit is contained in:
@@ -6,157 +6,26 @@
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
@@ -166,46 +35,25 @@ interface CheckpointStats {
|
||||
|
||||
export const checkpointsRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let checkpointModule: CheckpointModule | null = null;
|
||||
// Manager 初始化状态
|
||||
let managerInitialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Checkpoint 模块
|
||||
*/
|
||||
async function initCheckpointModule(): Promise<CheckpointModule | null> {
|
||||
if (checkpointModule && managerInitialized) return checkpointModule;
|
||||
async function ensureCheckpointManager(): Promise<CheckpointManager | null> {
|
||||
if (managerInitialized) {
|
||||
return getCheckpointManager();
|
||||
}
|
||||
|
||||
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);
|
||||
await initCheckpointManager(config.workdir);
|
||||
managerInitialized = true;
|
||||
|
||||
console.log('[Checkpoints] Checkpoint module initialized');
|
||||
return checkpointModule;
|
||||
return getCheckpointManager();
|
||||
} catch (error) {
|
||||
console.warn('[Checkpoints] Failed to load Checkpoint module:', error);
|
||||
console.warn('[Checkpoints] Failed to initialize Checkpoint module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -214,9 +62,9 @@ async function initCheckpointModule(): Promise<CheckpointModule | null> {
|
||||
* GET /checkpoints - 获取所有检查点列表
|
||||
*/
|
||||
checkpointsRouter.get('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -227,7 +75,6 @@ checkpointsRouter.get('/', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.listCheckpoints();
|
||||
|
||||
return c.json({
|
||||
@@ -259,9 +106,9 @@ checkpointsRouter.get('/', async (c) => {
|
||||
* GET /checkpoints/stats - 获取检查点统计信息
|
||||
*/
|
||||
checkpointsRouter.get('/stats', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -272,7 +119,6 @@ checkpointsRouter.get('/stats', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const stats = await manager.getStats();
|
||||
|
||||
return c.json({
|
||||
@@ -294,9 +140,9 @@ checkpointsRouter.get('/stats', async (c) => {
|
||||
* GET /checkpoints/latest - 获取最新检查点
|
||||
*/
|
||||
checkpointsRouter.get('/latest', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -307,7 +153,6 @@ checkpointsRouter.get('/latest', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getLatestCheckpoint();
|
||||
|
||||
return c.json({
|
||||
@@ -329,9 +174,9 @@ checkpointsRouter.get('/latest', async (c) => {
|
||||
* GET /checkpoints/unrevert/status - 检查是否可撤销回滚
|
||||
*/
|
||||
checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -342,7 +187,6 @@ checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const canUnrevert = manager.canUnrevert();
|
||||
const lastRollback = manager.getLastRollback();
|
||||
|
||||
@@ -376,9 +220,9 @@ checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -389,7 +233,6 @@ checkpointsRouter.get('/:id', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getCheckpoint(id);
|
||||
|
||||
if (!checkpoint) {
|
||||
@@ -421,9 +264,9 @@ checkpointsRouter.get('/:id', async (c) => {
|
||||
* POST /checkpoints - 创建手动检查点
|
||||
*/
|
||||
checkpointsRouter.post('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -435,7 +278,6 @@ checkpointsRouter.post('/', async (c) => {
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{ name?: string; description?: string }>();
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
const checkpoint = await manager.createCheckpoint({
|
||||
name: body.name,
|
||||
@@ -463,9 +305,9 @@ checkpointsRouter.post('/', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.delete('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -476,7 +318,6 @@ checkpointsRouter.delete('/:id', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.deleteCheckpoint(id);
|
||||
|
||||
if (!deleted) {
|
||||
@@ -509,9 +350,9 @@ checkpointsRouter.delete('/:id', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -522,7 +363,6 @@ checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const diff = await manager.getDiff(id);
|
||||
|
||||
return c.json({
|
||||
@@ -546,9 +386,9 @@ checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const filePath = c.req.query('path');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -569,7 +409,6 @@ checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const fileDiff = await manager.getFileDiff(id, filePath);
|
||||
|
||||
return c.json({
|
||||
@@ -592,9 +431,9 @@ checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.post('/:id/restore', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -611,20 +450,18 @@ checkpointsRouter.post('/:id/restore', async (c) => {
|
||||
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;
|
||||
mode = RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
mode = RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
mode = RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -658,9 +495,9 @@ 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();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -671,20 +508,18 @@ checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
// 转换 mode 字符串为枚举值
|
||||
let mode: RestoreMode | undefined;
|
||||
if (modeParam) {
|
||||
switch (modeParam) {
|
||||
case 'ai_changes_only':
|
||||
mode = module.RestoreMode.AI_CHANGES_ONLY;
|
||||
mode = RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
mode = RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
mode = RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -717,9 +552,9 @@ checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
* POST /checkpoints/unrevert - 撤销最近一次回滚
|
||||
*/
|
||||
checkpointsRouter.post('/unrevert', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -730,7 +565,6 @@ checkpointsRouter.post('/unrevert', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.unrevert();
|
||||
|
||||
if (!result.success) {
|
||||
@@ -763,9 +597,9 @@ checkpointsRouter.post('/unrevert', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -776,7 +610,6 @@ checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.checkSafety(id);
|
||||
|
||||
return c.json({
|
||||
@@ -798,9 +631,9 @@ checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
* POST /checkpoints/cleanup - 清理过期检查点
|
||||
*/
|
||||
checkpointsRouter.post('/cleanup', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -811,7 +644,6 @@ checkpointsRouter.post('/cleanup', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.cleanup();
|
||||
|
||||
return c.json({
|
||||
@@ -834,9 +666,9 @@ checkpointsRouter.post('/cleanup', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
const sessionId = c.req.param('sessionId');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -847,7 +679,6 @@ checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getSessionCheckpoints(sessionId);
|
||||
|
||||
return c.json({
|
||||
@@ -870,9 +701,9 @@ checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/messages/:messageId', async (c) => {
|
||||
const messageId = c.req.param('messageId');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -883,7 +714,6 @@ checkpointsRouter.get('/messages/:messageId', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getMessageCheckpoints(messageId);
|
||||
|
||||
return c.json({
|
||||
|
||||
Reference in New Issue
Block a user