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
+358
View File
@@ -0,0 +1,358 @@
/**
* Skill 注册表
*
* 管理所有可用的 Skills,支持:
* - 注册/注销 Skills
* - 按名称/分类/关键词查询
* - 渲染 Skill 提示模板
*/
import type {
Skill,
SkillContext,
SkillExecutionResult,
SkillSearchResult,
SkillRegistryConfig,
} from './types.js';
import { skillLoader } from './loader.js';
import { builtinSkills } from './builtin/index.js';
/**
* Skill 注册表
*/
export class SkillRegistry {
private skills = new Map<string, Skill>();
private config: SkillRegistryConfig;
private initialized = false;
constructor(config: SkillRegistryConfig = {}) {
this.config = {
autoLoad: true,
...config,
};
}
/**
* 初始化注册表
*/
async initialize(workdir: string = process.cwd()): Promise<void> {
if (this.initialized) {
return;
}
// 1. 注册内置 Skills
for (const skill of builtinSkills) {
this.register(skill);
}
// 2. 加载用户 Skills
if (this.config.autoLoad) {
const userDir =
this.config.userSkillsDir || skillLoader.getUserSkillsDir();
const userSkills = await skillLoader.loadFromDirectory(userDir, 'user');
for (const skill of userSkills) {
this.register(skill);
}
}
// 3. 加载项目 Skills
if (this.config.autoLoad) {
const projectDir =
this.config.projectSkillsDir || skillLoader.getProjectSkillsDir(workdir);
const projectSkills = await skillLoader.loadFromDirectory(
projectDir,
'project'
);
for (const skill of projectSkills) {
this.register(skill);
}
}
this.initialized = true;
}
/**
* 注册 Skill
*/
register(skill: Skill): void {
// 项目 Skills 优先级最高,可以覆盖同名的内置/用户 Skills
const existing = this.skills.get(skill.name);
if (existing) {
// 优先级: project > user > builtin
const priority = { project: 3, user: 2, builtin: 1 };
if (priority[skill.source] < priority[existing.source]) {
return; // 不覆盖更高优先级的 Skill
}
}
this.skills.set(skill.name, skill);
}
/**
* 注销 Skill
*/
unregister(name: string): boolean {
return this.skills.delete(name);
}
/**
* 获取 Skill
*/
get(name: string): Skill | undefined {
return this.skills.get(name);
}
/**
* 检查 Skill 是否存在
*/
has(name: string): boolean {
return this.skills.has(name);
}
/**
* 获取所有 Skills
*/
getAll(): Skill[] {
return Array.from(this.skills.values());
}
/**
* 获取启用的 Skills
*/
getEnabled(): Skill[] {
return this.getAll().filter((s) => s.enabled !== false);
}
/**
* 按分类获取 Skills
*/
getByCategory(category: string): Skill[] {
return this.getEnabled().filter((s) => s.category === category);
}
/**
* 获取所有分类
*/
getCategories(): string[] {
const categories = new Set<string>();
for (const skill of this.getEnabled()) {
if (skill.category) {
categories.add(skill.category);
}
}
return Array.from(categories).sort();
}
/**
* 搜索 Skills
*/
search(query: string, limit: number = 10): SkillSearchResult[] {
const queryLower = query.toLowerCase();
const results: SkillSearchResult[] = [];
for (const skill of this.getEnabled()) {
let score = 0;
let matchReason = '';
// 精确名称匹配
if (skill.name.toLowerCase() === queryLower) {
score = 100;
matchReason = '名称精确匹配';
}
// 名称前缀匹配
else if (skill.name.toLowerCase().startsWith(queryLower)) {
score = 80;
matchReason = '名称前缀匹配';
}
// 名称包含匹配
else if (skill.name.toLowerCase().includes(queryLower)) {
score = 60;
matchReason = '名称包含匹配';
}
// 描述匹配
else if (skill.description.toLowerCase().includes(queryLower)) {
score = 40;
matchReason = '描述匹配';
}
// 关键词匹配
else if (
skill.keywords?.some((k) => k.toLowerCase().includes(queryLower))
) {
score = 30;
matchReason = '关键词匹配';
}
// 分类匹配
else if (skill.category?.toLowerCase().includes(queryLower)) {
score = 20;
matchReason = '分类匹配';
}
if (score > 0) {
results.push({ skill, score, matchReason });
}
}
// 按分数降序排序
results.sort((a, b) => b.score - a.score);
return results.slice(0, limit);
}
/**
* 渲染 Skill 提示模板
*/
renderPrompt(
skill: Skill,
params: Record<string, unknown>,
context?: SkillContext
): SkillExecutionResult {
try {
// 验证必需参数
if (skill.parameters) {
for (const [name, param] of Object.entries(skill.parameters)) {
if (param.required && !(name in params)) {
return {
success: false,
error: `缺少必需参数: ${name}`,
};
}
}
}
// 构建变量映射
const variables: Record<string, string> = {};
// 添加参数值
for (const [key, value] of Object.entries(params)) {
variables[key] = String(value);
}
// 添加上下文变量
if (context?.variables) {
for (const [key, value] of Object.entries(context.variables)) {
if (!(key in variables)) {
variables[key] = value;
}
}
}
// 添加默认值
if (skill.parameters) {
for (const [name, param] of Object.entries(skill.parameters)) {
if (!(name in variables) && param.default !== undefined) {
variables[name] = String(param.default);
}
}
}
// 渲染模板
let prompt = skill.promptTemplate;
// 替换 {{variable}} 格式的变量
prompt = prompt.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
if (varName in variables) {
return variables[varName];
}
// 保留未匹配的变量(可能是用户意图保留)
return match;
});
return {
success: true,
prompt,
};
} catch (error) {
return {
success: false,
error: `渲染 Skill 提示失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
/**
* 执行 Skill(渲染模板并返回提示)
*/
execute(
name: string,
params: Record<string, unknown>,
context?: SkillContext
): SkillExecutionResult {
const skill = this.get(name);
if (!skill) {
return {
success: false,
error: `Skill 不存在: ${name}`,
};
}
if (skill.enabled === false) {
return {
success: false,
error: `Skill 已禁用: ${name}`,
};
}
return this.renderPrompt(skill, params, context);
}
/**
* 重新加载 Skills
*/
async reload(workdir: string = process.cwd()): Promise<void> {
this.skills.clear();
this.initialized = false;
await this.initialize(workdir);
}
/**
* 获取 Skill 统计信息
*/
getStats(): {
total: number;
enabled: number;
bySource: Record<string, number>;
byCategory: Record<string, number>;
} {
const skills = this.getAll();
const enabled = this.getEnabled();
const bySource: Record<string, number> = {};
const byCategory: Record<string, number> = {};
for (const skill of skills) {
bySource[skill.source] = (bySource[skill.source] || 0) + 1;
if (skill.category) {
byCategory[skill.category] = (byCategory[skill.category] || 0) + 1;
}
}
return {
total: skills.length,
enabled: enabled.length,
bySource,
byCategory,
};
}
}
/**
* 全局 Skill 注册表实例
*/
let skillRegistryInstance: SkillRegistry | null = null;
/**
* 获取全局 Skill 注册表
*/
export function getSkillRegistry(): SkillRegistry {
if (!skillRegistryInstance) {
skillRegistryInstance = new SkillRegistry();
}
return skillRegistryInstance;
}
/**
* 重置全局 Skill 注册表(用于测试)
*/
export function resetSkillRegistry(): void {
skillRegistryInstance = null;
}