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:
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 创建检查点工具
|
||||
*/
|
||||
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getCheckpointManager } from '../../checkpoint/index.js';
|
||||
|
||||
export const checkpointCreateTool: ToolWithMetadata = {
|
||||
name: 'checkpoint_create',
|
||||
description: loadDescription('checkpoint_create'),
|
||||
metadata: {
|
||||
name: 'checkpoint_create',
|
||||
category: 'core',
|
||||
description: '创建一个新的工作区检查点快照',
|
||||
keywords: [
|
||||
'checkpoint',
|
||||
'create',
|
||||
'snapshot',
|
||||
'save',
|
||||
'检查点',
|
||||
'快照',
|
||||
'保存',
|
||||
],
|
||||
deferLoading: true,
|
||||
},
|
||||
parameters: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '检查点名称 (可选)',
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '检查点描述 (可选)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const name = params.name as string | undefined;
|
||||
const description = params.description as string | undefined;
|
||||
|
||||
try {
|
||||
const manager = getCheckpointManager();
|
||||
|
||||
if (!manager.isEnabled()) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检查点系统已禁用',
|
||||
};
|
||||
}
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
const checkpoint = await manager.createCheckpoint({
|
||||
name,
|
||||
description,
|
||||
trigger: 'manual',
|
||||
});
|
||||
|
||||
const lines = [
|
||||
`✓ 检查点已创建`,
|
||||
` ID: ${checkpoint.id}`,
|
||||
` Commit: ${checkpoint.commitHash.slice(0, 8)}`,
|
||||
];
|
||||
|
||||
if (checkpoint.name) {
|
||||
lines.push(` 名称: ${checkpoint.name}`);
|
||||
}
|
||||
if (checkpoint.filesChanged > 0) {
|
||||
lines.push(` 文件变更: ${checkpoint.filesChanged} 个`);
|
||||
}
|
||||
lines.push(` 时间: ${new Date(checkpoint.timestamp).toLocaleString()}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 检查点差异工具
|
||||
*/
|
||||
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getCheckpointManager } from '../../checkpoint/index.js';
|
||||
|
||||
export const checkpointDiffTool: ToolWithMetadata = {
|
||||
name: 'checkpoint_diff',
|
||||
description: loadDescription('checkpoint_diff'),
|
||||
metadata: {
|
||||
name: 'checkpoint_diff',
|
||||
category: 'core',
|
||||
description: '显示检查点与当前工作区的差异',
|
||||
keywords: [
|
||||
'checkpoint',
|
||||
'diff',
|
||||
'compare',
|
||||
'changes',
|
||||
'检查点',
|
||||
'差异',
|
||||
'比较',
|
||||
'变更',
|
||||
],
|
||||
deferLoading: true,
|
||||
},
|
||||
parameters: {
|
||||
checkpoint_id: {
|
||||
type: 'string',
|
||||
description: '检查点 ID 或 commit hash (默认为最近的检查点)',
|
||||
required: false,
|
||||
},
|
||||
file: {
|
||||
type: 'string',
|
||||
description: '指定文件路径查看详细差异 (可选)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const checkpointId = params.checkpoint_id as string | undefined;
|
||||
const file = params.file as string | undefined;
|
||||
|
||||
try {
|
||||
const manager = getCheckpointManager();
|
||||
|
||||
if (!manager.isEnabled()) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检查点系统已禁用',
|
||||
};
|
||||
}
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
// 获取目标检查点
|
||||
let targetCheckpoint;
|
||||
if (checkpointId) {
|
||||
targetCheckpoint = await manager.getCheckpoint(checkpointId);
|
||||
if (!targetCheckpoint) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `找不到检查点: ${checkpointId}`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
targetCheckpoint = await manager.getLatestCheckpoint();
|
||||
if (!targetCheckpoint) {
|
||||
return {
|
||||
success: true,
|
||||
output: '暂无检查点',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 显示文件详细差异
|
||||
if (file) {
|
||||
const fileDiff = await manager.getFileDiff(targetCheckpoint.id, file);
|
||||
|
||||
const lines = [
|
||||
`文件差异: ${file}`,
|
||||
`检查点: ${targetCheckpoint.commitHash.slice(0, 8)}`,
|
||||
`变更类型: ${fileDiff.type}`,
|
||||
'',
|
||||
];
|
||||
|
||||
if (fileDiff.patch) {
|
||||
lines.push('```diff');
|
||||
lines.push(fileDiff.patch);
|
||||
lines.push('```');
|
||||
} else if (fileDiff.type === 'added') {
|
||||
lines.push('(新文件)');
|
||||
} else if (fileDiff.type === 'deleted') {
|
||||
lines.push('(已删除)');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
// 显示概要差异
|
||||
const diff = await manager.getDiff(targetCheckpoint.id);
|
||||
|
||||
if (diff.files.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
output: `检查点 ${targetCheckpoint.commitHash.slice(0, 8)} 与当前工作区相同`,
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`检查点 ${targetCheckpoint.commitHash.slice(0, 8)} 与当前工作区的差异:`,
|
||||
'',
|
||||
` +${diff.totalInsertions} 行添加 -${diff.totalDeletions} 行删除`,
|
||||
'',
|
||||
'变更的文件:',
|
||||
];
|
||||
|
||||
for (const fileChange of diff.files) {
|
||||
const symbol =
|
||||
fileChange.type === 'added'
|
||||
? '+'
|
||||
: fileChange.type === 'deleted'
|
||||
? '-'
|
||||
: fileChange.type === 'renamed'
|
||||
? 'R'
|
||||
: 'M';
|
||||
|
||||
let line = ` ${symbol} ${fileChange.path}`;
|
||||
if (fileChange.oldPath) {
|
||||
line = ` ${symbol} ${fileChange.oldPath} -> ${fileChange.path}`;
|
||||
}
|
||||
|
||||
if (fileChange.insertions || fileChange.deletions) {
|
||||
line += ` (+${fileChange.insertions || 0} -${fileChange.deletions || 0})`;
|
||||
}
|
||||
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 列出检查点工具
|
||||
*/
|
||||
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getCheckpointManager } from '../../checkpoint/index.js';
|
||||
|
||||
export const checkpointListTool: ToolWithMetadata = {
|
||||
name: 'checkpoint_list',
|
||||
description: loadDescription('checkpoint_list'),
|
||||
metadata: {
|
||||
name: 'checkpoint_list',
|
||||
category: 'core',
|
||||
description: '列出所有可用的检查点',
|
||||
keywords: [
|
||||
'checkpoint',
|
||||
'list',
|
||||
'show',
|
||||
'history',
|
||||
'检查点',
|
||||
'列表',
|
||||
'历史',
|
||||
],
|
||||
deferLoading: true,
|
||||
},
|
||||
parameters: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: '最多显示的检查点数量 (默认 10)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const limit = (params.limit as number) || 10;
|
||||
|
||||
try {
|
||||
const manager = getCheckpointManager();
|
||||
|
||||
if (!manager.isEnabled()) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检查点系统已禁用',
|
||||
};
|
||||
}
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
const checkpoints = await manager.listCheckpoints();
|
||||
|
||||
if (checkpoints.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
output: '暂无检查点',
|
||||
};
|
||||
}
|
||||
|
||||
const displayCheckpoints = checkpoints.slice(0, limit);
|
||||
const lines = [`共 ${checkpoints.length} 个检查点:\n`];
|
||||
|
||||
for (const cp of displayCheckpoints) {
|
||||
const date = new Date(cp.timestamp).toLocaleString();
|
||||
const hash = cp.commitHash.slice(0, 8);
|
||||
const name = cp.name ? ` "${cp.name}"` : '';
|
||||
const files = cp.filesChanged > 0 ? ` (${cp.filesChanged} files)` : '';
|
||||
|
||||
lines.push(` ${hash}${name}${files}`);
|
||||
lines.push(` ${cp.description || cp.trigger}`);
|
||||
lines.push(` ${date}`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (checkpoints.length > limit) {
|
||||
lines.push(` ... 还有 ${checkpoints.length - limit} 个检查点`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 恢复检查点工具
|
||||
*/
|
||||
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getCheckpointManager } from '../../checkpoint/index.js';
|
||||
|
||||
export const checkpointRestoreTool: ToolWithMetadata = {
|
||||
name: 'checkpoint_restore',
|
||||
description: loadDescription('checkpoint_restore'),
|
||||
metadata: {
|
||||
name: 'checkpoint_restore',
|
||||
category: 'core',
|
||||
description: '恢复到指定的检查点',
|
||||
keywords: [
|
||||
'checkpoint',
|
||||
'restore',
|
||||
'rollback',
|
||||
'undo',
|
||||
'检查点',
|
||||
'恢复',
|
||||
'回滚',
|
||||
'撤销',
|
||||
],
|
||||
deferLoading: true,
|
||||
},
|
||||
parameters: {
|
||||
checkpoint_id: {
|
||||
type: 'string',
|
||||
description: '要恢复的检查点 ID 或 commit hash',
|
||||
required: true,
|
||||
},
|
||||
files: {
|
||||
type: 'string',
|
||||
description: '只恢复指定文件,多个文件用逗号分隔 (可选)',
|
||||
required: false,
|
||||
},
|
||||
dry_run: {
|
||||
type: 'boolean',
|
||||
description: '预览模式,不实际执行恢复',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const checkpointId = params.checkpoint_id as string;
|
||||
const filesStr = params.files as string | undefined;
|
||||
const dryRun = params.dry_run as boolean | undefined;
|
||||
|
||||
if (!checkpointId) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '请指定要恢复的检查点 ID',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = getCheckpointManager();
|
||||
|
||||
if (!manager.isEnabled()) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检查点系统已禁用',
|
||||
};
|
||||
}
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
// 验证检查点存在
|
||||
const checkpoint = await manager.getCheckpoint(checkpointId);
|
||||
if (!checkpoint) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `找不到检查点: ${checkpointId}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 解析文件列表
|
||||
const files = filesStr
|
||||
? filesStr.split(',').map((f) => f.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
|
||||
// 执行恢复
|
||||
const result = await manager.rollback({
|
||||
target: checkpointId,
|
||||
files,
|
||||
dryRun,
|
||||
});
|
||||
|
||||
if (dryRun) {
|
||||
const lines = [
|
||||
`预览: 恢复到检查点 ${checkpoint.commitHash.slice(0, 8)}`,
|
||||
'',
|
||||
'将恢复以下文件:',
|
||||
];
|
||||
|
||||
for (const file of result.restoredFiles) {
|
||||
lines.push(` - ${file}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('(使用 dry_run=false 执行实际恢复)');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
const errorLines = ['恢复失败:'];
|
||||
for (const err of result.errors) {
|
||||
errorLines.push(` ${err.file}: ${err.error}`);
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: errorLines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`✓ 已恢复到检查点 ${checkpoint.commitHash.slice(0, 8)}`,
|
||||
'',
|
||||
`恢复了 ${result.restoredFiles.length} 个文件:`,
|
||||
];
|
||||
|
||||
for (const file of result.restoredFiles.slice(0, 10)) {
|
||||
lines.push(` - ${file}`);
|
||||
}
|
||||
|
||||
if (result.restoredFiles.length > 10) {
|
||||
lines.push(` ... 还有 ${result.restoredFiles.length - 10} 个文件`);
|
||||
}
|
||||
|
||||
if (result.previousCommit) {
|
||||
lines.push('');
|
||||
lines.push(`提示: 可以使用 checkpoint_restore 恢复到 ${result.previousCommit.slice(0, 8)} 来撤销此操作`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 检查点工具模块
|
||||
*/
|
||||
|
||||
export { checkpointCreateTool } from './checkpoint_create.js';
|
||||
export { checkpointListTool } from './checkpoint_list.js';
|
||||
export { checkpointDiffTool } from './checkpoint_diff.js';
|
||||
export { checkpointRestoreTool } from './checkpoint_restore.js';
|
||||
export { undoTool } from './undo.js';
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 撤销操作工具 (快捷回滚)
|
||||
*/
|
||||
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getCheckpointManager } from '../../checkpoint/index.js';
|
||||
|
||||
export const undoTool: ToolWithMetadata = {
|
||||
name: 'undo',
|
||||
description: loadDescription('undo'),
|
||||
metadata: {
|
||||
name: 'undo',
|
||||
category: 'core',
|
||||
description: '撤销上一次文件操作,回滚到最近的检查点',
|
||||
keywords: ['undo', 'rollback', 'revert', '撤销', '回滚', '恢复'],
|
||||
deferLoading: false, // 常用命令,始终加载
|
||||
},
|
||||
parameters: {
|
||||
confirm: {
|
||||
type: 'boolean',
|
||||
description: '确认执行撤销操作 (默认 true)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const confirm = params.confirm !== false;
|
||||
|
||||
try {
|
||||
const manager = getCheckpointManager();
|
||||
|
||||
if (!manager.isEnabled()) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检查点系统已禁用,无法执行撤销操作',
|
||||
};
|
||||
}
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
// 获取最近两个检查点
|
||||
const checkpoints = await manager.listCheckpoints();
|
||||
|
||||
if (checkpoints.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '没有可用的检查点,无法执行撤销操作',
|
||||
};
|
||||
}
|
||||
|
||||
// 预览模式
|
||||
if (!confirm) {
|
||||
const targetCheckpoint =
|
||||
checkpoints.length > 1 ? checkpoints[1] : checkpoints[0];
|
||||
|
||||
const diff = await manager.getDiff(targetCheckpoint.id);
|
||||
|
||||
const lines = [
|
||||
'预览: 撤销将恢复以下文件变更:',
|
||||
'',
|
||||
`目标检查点: ${targetCheckpoint.commitHash.slice(0, 8)}`,
|
||||
` ${targetCheckpoint.description || targetCheckpoint.trigger}`,
|
||||
` ${new Date(targetCheckpoint.timestamp).toLocaleString()}`,
|
||||
'',
|
||||
];
|
||||
|
||||
if (diff.files.length > 0) {
|
||||
lines.push('将恢复的文件:');
|
||||
for (const file of diff.files.slice(0, 10)) {
|
||||
const symbol =
|
||||
file.type === 'added'
|
||||
? '+'
|
||||
: file.type === 'deleted'
|
||||
? '-'
|
||||
: 'M';
|
||||
lines.push(` ${symbol} ${file.path}`);
|
||||
}
|
||||
if (diff.files.length > 10) {
|
||||
lines.push(` ... 还有 ${diff.files.length - 10} 个文件`);
|
||||
}
|
||||
} else {
|
||||
lines.push('(无文件变更)');
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('使用 confirm=true 执行撤销');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
// 执行撤销
|
||||
const result = await manager.undo();
|
||||
|
||||
if (!result.success) {
|
||||
const errorLines = ['撤销失败:'];
|
||||
for (const err of result.errors) {
|
||||
errorLines.push(` ${err.file}: ${err.error}`);
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: errorLines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [
|
||||
'✓ 撤销成功',
|
||||
'',
|
||||
`恢复了 ${result.restoredFiles.length} 个文件`,
|
||||
];
|
||||
|
||||
if (result.restoredFiles.length > 0 && result.restoredFiles.length <= 5) {
|
||||
for (const file of result.restoredFiles) {
|
||||
lines.push(` - ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.previousCommit) {
|
||||
lines.push('');
|
||||
lines.push(
|
||||
`提示: 使用 checkpoint_restore --checkpoint_id ${result.previousCommit.slice(0, 8)} 可以撤销此操作`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: lines.join('\n'),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user