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
+196
View File
@@ -0,0 +1,196 @@
/**
* 自动提交管理器
*
* 参考 aider 的 auto_commit 实现
* 支持 immediate、batch、manual 三种模式
*/
import { minimatch } from 'minimatch';
import type { GitRepo } from './repo.js';
import type { AutoCommitConfig, CommitResult } from './types.js';
import { MessageGenerator } from './message-generator.js';
export class AutoCommitManager {
private repo: GitRepo;
private config: AutoCommitConfig;
private messageGenerator: MessageGenerator;
/** 待提交的文件 */
private pendingFiles: Set<string> = new Set();
/** 批量提交定时器 */
private batchTimer: NodeJS.Timeout | null = null;
/** 提交回调 */
private onCommitCallback?: (result: CommitResult) => void;
constructor(repo: GitRepo, config: AutoCommitConfig, messageGenerator: MessageGenerator) {
this.repo = repo;
this.config = config;
this.messageGenerator = messageGenerator;
}
/**
* 设置提交回调
*/
setOnCommit(callback: (result: CommitResult) => void): void {
this.onCommitCallback = callback;
}
/**
* 文件变更后调用
*/
async onFileChanged(filePath: string, changeType: 'create' | 'modify' | 'delete'): Promise<void> {
if (!this.config.enabled) {
return;
}
// 检查是否应该排除
if (this.shouldExclude(filePath)) {
return;
}
// 如果启用脏文件提交,检查文件是否为脏状态
if (this.config.dirtyCommits && changeType !== 'create') {
const isDirty = await this.repo.isDirty(filePath);
if (isDirty) {
// 在新的编辑之前,先提交脏文件
await this.commitDirtyFile(filePath);
}
}
// 添加到待提交列表
this.pendingFiles.add(filePath);
// 根据模式处理
switch (this.config.mode) {
case 'immediate':
await this.executeCommit();
break;
case 'batch':
this.scheduleBatchCommit();
break;
case 'manual':
// 不自动提交,只记录
break;
}
}
/**
* 计划批量提交
*/
private scheduleBatchCommit(): void {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
}
this.batchTimer = setTimeout(async () => {
this.batchTimer = null;
await this.executeCommit();
}, this.config.batchDelay);
}
/**
* 执行提交
*/
private async executeCommit(): Promise<CommitResult | null> {
if (this.pendingFiles.size === 0) {
return null;
}
const files = Array.from(this.pendingFiles);
this.pendingFiles.clear();
try {
// 获取差异用于生成消息
const diff = await this.repo.getDiff({ staged: false });
// 生成提交消息
const message = this.messageGenerator.generate(diff, files);
// 执行提交
const result = await this.repo.commit({
files,
message,
aiEdits: true,
});
// 调用回调
if (result.success && this.onCommitCallback) {
this.onCommitCallback(result);
}
return result;
} catch (error) {
// 恢复 pending 状态
files.forEach((f) => this.pendingFiles.add(f));
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* 提交脏文件
*/
private async commitDirtyFile(filePath: string): Promise<void> {
try {
await this.repo.commit({
files: [filePath],
message: `chore: save changes to ${filePath} before AI edit`,
aiEdits: false, // 用户的脏文件,不标记为 AI 编辑
});
} catch {
// 脏文件提交失败不影响主流程
}
}
/**
* 检查文件是否应该排除
*/
private shouldExclude(filePath: string): boolean {
return this.config.excludePatterns.some((pattern) =>
minimatch(filePath, pattern, { matchBase: true })
);
}
/**
* 强制立即提交
*/
async flush(): Promise<CommitResult | null> {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
return this.executeCommit();
}
/**
* 获取待提交文件
*/
getPendingFiles(): string[] {
return Array.from(this.pendingFiles);
}
/**
* 清除待提交文件
*/
clearPending(): void {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
this.pendingFiles.clear();
}
/**
* 是否有待提交文件
*/
hasPendingFiles(): boolean {
return this.pendingFiles.size > 0;
}
}