feat: 重构为 Monorepo 架构并实现 HTTP Server

架构变更:
- 采用 pnpm workspaces 实现 Monorepo 结构
- 将现有代码迁移到 packages/core
- 新增 packages/server HTTP 服务层

Server 功能:
- REST API: 会话管理、工具管理、配置管理
- WebSocket: 实时双向通信支持
- SSE: 服务端事件推送
- Hono + Bun 作为运行时

API 端点:
- GET/POST /api/sessions - 会话 CRUD
- GET/POST /api/sessions/:id/messages - 消息管理
- GET /api/sessions/:id/events - SSE 事件流
- WS /api/ws/:sessionId - WebSocket 连接
- GET/POST /api/tools - 工具管理
- GET/PUT /api/config - 配置管理
This commit is contained in:
2025-12-12 10:42:20 +08:00
parent 59dbed926e
commit 5e32375f0e
301 changed files with 3281 additions and 43 deletions
+613
View File
@@ -0,0 +1,613 @@
/**
* 检查点管理器
* 管理检查点的创建、回滚、清理等操作
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import { nanoid } from 'nanoid';
import { ShadowGit, createShadowGit } from './shadow-git.js';
import type {
CheckpointMetadata,
CheckpointConfig,
CheckpointTrigger,
RollbackOptions,
RollbackResult,
DiffInfo,
FileDiff,
CheckpointEvent,
CheckpointEventListener,
DEFAULT_CHECKPOINT_CONFIG,
} from './types.js';
/**
* 检查点提交消息前缀
*/
const CHECKPOINT_PREFIX = 'checkpoint:';
/**
* 检查点管理器
*/
export class CheckpointManager {
private shadowGit: ShadowGit;
private config: CheckpointConfig;
private workDir: string;
private checkpointsIndex: Map<string, CheckpointMetadata> = new Map();
private initialized = false;
private lastCheckpointTime = 0;
private eventListeners: Set<CheckpointEventListener> = new Set();
// 防止重复创建检查点的最小间隔 (毫秒)
private static readonly MIN_CHECKPOINT_INTERVAL = 1000;
constructor(workDir: string, config: Partial<CheckpointConfig> = {}) {
this.workDir = path.resolve(workDir);
this.config = {
enabled: true,
autoCheckpoint: {
beforeWrite: true,
beforeEdit: true,
beforeDelete: true,
beforeMove: true,
beforeBash: false,
},
maxCheckpoints: 100,
maxAge: 7 * 24 * 60 * 60 * 1000,
storageDir: path.join(os.homedir(), '.ai-assist', 'checkpoints'),
...config,
};
this.shadowGit = createShadowGit(this.workDir, this.config.storageDir);
}
/**
* 初始化检查点管理器
*/
async initialize(): Promise<void> {
if (this.initialized) return;
if (!this.config.enabled) {
this.initialized = true;
return;
}
// 初始化 Shadow Git
await this.shadowGit.initialize();
// 加载检查点索引
await this.loadCheckpointsIndex();
this.initialized = true;
}
/**
* 加载检查点索引
*/
private async loadCheckpointsIndex(): Promise<void> {
try {
const commits = await this.shadowGit.getCommits(this.config.maxCheckpoints);
for (const commit of commits) {
if (commit.message.startsWith(CHECKPOINT_PREFIX)) {
try {
const jsonStr = commit.message.slice(CHECKPOINT_PREFIX.length);
const metadata = JSON.parse(jsonStr) as CheckpointMetadata;
metadata.commitHash = commit.hash;
this.checkpointsIndex.set(metadata.id, metadata);
} catch {
// 解析失败,跳过
}
}
}
} catch {
// 仓库可能是空的
}
}
/**
* 判断是否应该为指定工具创建检查点
*/
shouldCreateCheckpoint(tool: string): boolean {
if (!this.config.enabled) return false;
const { autoCheckpoint } = this.config;
switch (tool) {
case 'write_file':
return autoCheckpoint.beforeWrite;
case 'edit_file':
return autoCheckpoint.beforeEdit;
case 'delete_file':
return autoCheckpoint.beforeDelete;
case 'move_file':
case 'copy_file':
return autoCheckpoint.beforeMove;
case 'bash':
return autoCheckpoint.beforeBash;
default:
return false;
}
}
/**
* 在工具执行前创建检查点
*/
async beforeToolExecution(
tool: string,
params: Record<string, unknown>
): Promise<string | null> {
if (!this.shouldCreateCheckpoint(tool)) {
return null;
}
// 防止过于频繁的检查点创建
const now = Date.now();
if (now - this.lastCheckpointTime < CheckpointManager.MIN_CHECKPOINT_INTERVAL) {
return null;
}
try {
const checkpoint = await this.createCheckpoint({
trigger: `tool:${tool}` as CheckpointTrigger,
toolCall: { tool, params },
description: this.generateDescription(tool, params),
});
this.lastCheckpointTime = now;
return checkpoint.id;
} catch (error) {
console.warn('Failed to create checkpoint:', error);
return null;
}
}
/**
* 生成检查点描述
*/
private generateDescription(
tool: string,
params: Record<string, unknown>
): string {
switch (tool) {
case 'write_file':
return `Write file: ${params.file_path || params.path}`;
case 'edit_file':
return `Edit file: ${params.file_path || params.path}`;
case 'delete_file':
return `Delete file: ${params.file_path || params.path}`;
case 'move_file':
return `Move: ${params.source} -> ${params.destination}`;
case 'copy_file':
return `Copy: ${params.source} -> ${params.destination}`;
case 'bash':
return `Bash: ${String(params.command).slice(0, 50)}`;
default:
return `Tool: ${tool}`;
}
}
/**
* 创建检查点
*/
async createCheckpoint(options: {
name?: string;
description?: string;
trigger?: CheckpointTrigger;
toolCall?: { tool: string; params: Record<string, unknown> };
}): Promise<CheckpointMetadata> {
await this.initialize();
if (!this.config.enabled) {
throw new Error('Checkpoint system is disabled');
}
const id = nanoid(10);
const timestamp = Date.now();
// 创建元数据
const metadata: CheckpointMetadata = {
id,
name: options.name,
description: options.description,
timestamp,
trigger: options.trigger || 'manual',
toolCall: options.toolCall,
commitHash: '', // 待填充
filesChanged: 0, // 待填充
};
// 获取变更文件数
try {
const diff = await this.shadowGit.getWorkingDirDiff();
metadata.filesChanged = diff.files.length;
} catch {
// 忽略
}
// 创建 commit
const commitMessage = CHECKPOINT_PREFIX + JSON.stringify(metadata);
const commitHash = await this.shadowGit.createCommit(commitMessage);
metadata.commitHash = commitHash;
// 更新索引
this.checkpointsIndex.set(id, metadata);
// 触发事件
this.emitEvent({
type: 'created',
checkpoint: metadata,
timestamp,
});
// 异步清理
this.cleanupAsync();
return metadata;
}
/**
* 创建命名检查点
*/
async createNamedCheckpoint(name: string, description?: string): Promise<CheckpointMetadata> {
return this.createCheckpoint({
name,
description,
trigger: 'manual',
});
}
/**
* 获取所有检查点
*/
async listCheckpoints(): Promise<CheckpointMetadata[]> {
await this.initialize();
return Array.from(this.checkpointsIndex.values()).sort(
(a, b) => b.timestamp - a.timestamp
);
}
/**
* 获取指定检查点
*/
async getCheckpoint(idOrHash: string): Promise<CheckpointMetadata | null> {
await this.initialize();
// 先按 ID 查找
if (this.checkpointsIndex.has(idOrHash)) {
return this.checkpointsIndex.get(idOrHash)!;
}
// 再按 commit hash 查找
for (const checkpoint of this.checkpointsIndex.values()) {
if (checkpoint.commitHash.startsWith(idOrHash)) {
return checkpoint;
}
}
return null;
}
/**
* 获取最近的检查点
*/
async getLatestCheckpoint(): Promise<CheckpointMetadata | null> {
const checkpoints = await this.listCheckpoints();
return checkpoints[0] || null;
}
/**
* 获取检查点与当前工作区的差异
*/
async getDiff(checkpointId: string): Promise<DiffInfo> {
await this.initialize();
const checkpoint = await this.getCheckpoint(checkpointId);
if (!checkpoint) {
throw new Error(`Checkpoint not found: ${checkpointId}`);
}
return this.shadowGit.getDiffSummary(checkpoint.commitHash, 'HEAD');
}
/**
* 获取两个检查点之间的差异
*/
async getDiffBetween(fromId: string, toId: string): Promise<DiffInfo> {
await this.initialize();
const fromCheckpoint = await this.getCheckpoint(fromId);
const toCheckpoint = await this.getCheckpoint(toId);
if (!fromCheckpoint) {
throw new Error(`Checkpoint not found: ${fromId}`);
}
if (!toCheckpoint) {
throw new Error(`Checkpoint not found: ${toId}`);
}
return this.shadowGit.getDiffSummary(
fromCheckpoint.commitHash,
toCheckpoint.commitHash
);
}
/**
* 获取文件差异详情
*/
async getFileDiff(checkpointId: string, filePath: string): Promise<FileDiff> {
await this.initialize();
const checkpoint = await this.getCheckpoint(checkpointId);
if (!checkpoint) {
throw new Error(`Checkpoint not found: ${checkpointId}`);
}
const head = await this.shadowGit.getHead();
return this.shadowGit.getFileDiff(checkpoint.commitHash, head, filePath);
}
/**
* 回滚到检查点
*/
async rollback(options: RollbackOptions): Promise<RollbackResult> {
await this.initialize();
const checkpoint = await this.getCheckpoint(options.target);
if (!checkpoint) {
throw new Error(`Checkpoint not found: ${options.target}`);
}
// 获取当前 HEAD 用于可能的撤销
const previousCommit = await this.shadowGit.getHead();
// 预览模式
if (options.dryRun) {
const diff = await this.getDiff(checkpoint.id);
return {
success: true,
restoredFiles: diff.files.map((f) => f.path),
errors: [],
previousCommit,
};
}
const result: RollbackResult = {
success: true,
restoredFiles: [],
errors: [],
previousCommit,
};
try {
if (options.files && options.files.length > 0) {
// 选择性回滚
await this.shadowGit.checkoutFiles(checkpoint.commitHash, options.files);
result.restoredFiles = options.files;
} else {
// 完整回滚
await this.shadowGit.resetHard(checkpoint.commitHash);
// 获取恢复的文件列表
const diff = await this.shadowGit.getDiffSummary(
previousCommit,
checkpoint.commitHash
);
result.restoredFiles = diff.files.map((f) => f.path);
}
// 触发事件
this.emitEvent({
type: 'restored',
checkpoint,
timestamp: Date.now(),
details: {
files: result.restoredFiles,
previousCommit,
},
});
} catch (error) {
result.success = false;
result.errors.push({
file: '*',
error: error instanceof Error ? error.message : String(error),
});
}
return result;
}
/**
* 撤销操作 (回滚到上一个检查点)
*/
async undo(): Promise<RollbackResult> {
const latest = await this.getLatestCheckpoint();
if (!latest) {
throw new Error('No checkpoints available');
}
// 找到倒数第二个检查点
const checkpoints = await this.listCheckpoints();
if (checkpoints.length < 2) {
// 只有一个检查点,回滚到它
return this.rollback({ target: latest.id });
}
// 回滚到倒数第二个检查点
return this.rollback({ target: checkpoints[1].id });
}
/**
* 删除检查点
*/
async deleteCheckpoint(checkpointId: string): Promise<boolean> {
await this.initialize();
if (!this.checkpointsIndex.has(checkpointId)) {
return false;
}
const checkpoint = this.checkpointsIndex.get(checkpointId)!;
this.checkpointsIndex.delete(checkpointId);
// 触发事件
this.emitEvent({
type: 'deleted',
checkpoint,
timestamp: Date.now(),
});
return true;
}
/**
* 异步清理过期检查点
*/
private async cleanupAsync(): Promise<void> {
setTimeout(async () => {
try {
await this.cleanup();
} catch (error) {
console.warn('Checkpoint cleanup failed:', error);
}
}, 100);
}
/**
* 清理过期检查点
*/
async cleanup(): Promise<number> {
await this.initialize();
const checkpoints = await this.listCheckpoints();
const now = Date.now();
let deletedCount = 0;
// 按时间过期清理
for (const checkpoint of checkpoints) {
if (now - checkpoint.timestamp > this.config.maxAge) {
await this.deleteCheckpoint(checkpoint.id);
deletedCount++;
}
}
// 按数量限制清理
const remaining = checkpoints.length - deletedCount;
if (remaining > this.config.maxCheckpoints) {
const toDelete = checkpoints.slice(this.config.maxCheckpoints);
for (const checkpoint of toDelete) {
if (this.checkpointsIndex.has(checkpoint.id)) {
await this.deleteCheckpoint(checkpoint.id);
deletedCount++;
}
}
}
if (deletedCount > 0) {
// 触发清理事件
this.emitEvent({
type: 'cleanup',
timestamp: now,
details: { deletedCount },
});
// 运行 git gc
await this.shadowGit.cleanup(this.config.maxCheckpoints);
}
return deletedCount;
}
/**
* 获取检查点存储统计
*/
async getStats(): Promise<{
count: number;
oldestTimestamp: number | null;
newestTimestamp: number | null;
}> {
const checkpoints = await this.listCheckpoints();
return {
count: checkpoints.length,
oldestTimestamp: checkpoints.length > 0
? checkpoints[checkpoints.length - 1].timestamp
: null,
newestTimestamp: checkpoints.length > 0 ? checkpoints[0].timestamp : null,
};
}
/**
* 添加事件监听器
*/
addEventListener(listener: CheckpointEventListener): void {
this.eventListeners.add(listener);
}
/**
* 移除事件监听器
*/
removeEventListener(listener: CheckpointEventListener): void {
this.eventListeners.delete(listener);
}
/**
* 触发事件
*/
private emitEvent(event: CheckpointEvent): void {
for (const listener of this.eventListeners) {
try {
listener(event);
} catch (error) {
console.warn('Checkpoint event listener error:', error);
}
}
}
/**
* 检查是否启用
*/
isEnabled(): boolean {
return this.config.enabled;
}
/**
* 获取配置
*/
getConfig(): CheckpointConfig {
return { ...this.config };
}
}
// 全局检查点管理器实例
let globalCheckpointManager: CheckpointManager | null = null;
/**
* 获取全局检查点管理器实例
*/
export function getCheckpointManager(): CheckpointManager {
if (!globalCheckpointManager) {
globalCheckpointManager = new CheckpointManager(process.cwd());
}
return globalCheckpointManager;
}
/**
* 初始化全局检查点管理器
*/
export async function initCheckpointManager(
workDir: string,
config?: Partial<CheckpointConfig>
): Promise<CheckpointManager> {
globalCheckpointManager = new CheckpointManager(workDir, config);
await globalCheckpointManager.initialize();
return globalCheckpointManager;
}
/**
* 重置全局检查点管理器 (用于测试)
*/
export function resetCheckpointManager(): void {
globalCheckpointManager = null;
}