refactor: remove v1 legacy code replaced by OpenClaw
- Remove src/core/* - replaced by OpenClaw built-in capabilities - Remove src/db/* - OpenClaw has its own SQLite storage - Remove src/config/* - OpenClaw config system replaces this - Remove src/im/* - OpenClaw Channel system replaces this - Remove src/api/* - WebSocket + Tauri Commands replace this - Remove src/utils/* - using OpenClaw utilities - Remove src/app.ts, src/index.ts - no longer needed All v1 code is preserved in archive/v1-code-backup branch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
144
src/api/index.ts
144
src/api/index.ts
@@ -1,144 +0,0 @@
|
|||||||
// ZCLAW API 层 - 供 Tauri 前端调用的接口
|
|
||||||
import type { ZClawApp } from '../app';
|
|
||||||
import { createLogger } from '../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('API');
|
|
||||||
|
|
||||||
export interface APIResponse<T = any> {
|
|
||||||
success: boolean;
|
|
||||||
data?: T;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API 处理器集合 - 供 Tauri Commands 调用
|
|
||||||
export class ZClawAPI {
|
|
||||||
constructor(private app: ZClawApp) {}
|
|
||||||
|
|
||||||
// === 聊天 ===
|
|
||||||
async sendMessage(userId: string, content: string): Promise<APIResponse<{ reply: string }>> {
|
|
||||||
try {
|
|
||||||
const reply = await this.app.handleUserMessage(userId, content);
|
|
||||||
return { success: true, data: { reply } };
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`sendMessage error: ${error.message}`);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 任务 ===
|
|
||||||
async submitTask(userId: string, payload: any): Promise<APIResponse<{ taskId: string }>> {
|
|
||||||
try {
|
|
||||||
const engine = this.app.getRemoteExecution();
|
|
||||||
const taskId = await engine.submitTask({
|
|
||||||
id: '',
|
|
||||||
userId,
|
|
||||||
deviceId: 'local',
|
|
||||||
channel: 'desktop',
|
|
||||||
type: 'immediate',
|
|
||||||
priority: 'normal',
|
|
||||||
payload,
|
|
||||||
status: 'pending',
|
|
||||||
createdAt: new Date(),
|
|
||||||
});
|
|
||||||
return { success: true, data: { taskId } };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTaskStatus(taskId: string): Promise<APIResponse<{ status: string }>> {
|
|
||||||
try {
|
|
||||||
const engine = this.app.getRemoteExecution();
|
|
||||||
const status = await engine.getStatus(taskId);
|
|
||||||
return { success: true, data: { status } };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTaskStats(): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const stats = this.app.getRemoteExecution().getStats();
|
|
||||||
return { success: true, data: stats };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 多 Agent ===
|
|
||||||
async executeGoal(userId: string, goal: string): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const result = await this.app.getAgentOrchestrator().executeGoal(goal);
|
|
||||||
return { success: true, data: result };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 记忆 ===
|
|
||||||
async getUserProfile(userId: string): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const profile = await this.app.getMemory().getProfile(userId);
|
|
||||||
return { success: true, data: profile || null };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMemoryEvents(userId: string, query: string = '', limit: number = 20): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const events = await this.app.getMemory().recall(userId, query, limit);
|
|
||||||
return { success: true, data: events };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 定时任务 ===
|
|
||||||
async listScheduledTasks(userId: string): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const tasks = await this.app.getProactive().listTasks(userId);
|
|
||||||
return { success: true, data: tasks };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async scheduleTask(userId: string, schedule: any, taskConfig: any): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
await this.app.getProactive().scheduleTask({
|
|
||||||
id: '',
|
|
||||||
userId,
|
|
||||||
channel: 'desktop',
|
|
||||||
schedule,
|
|
||||||
task: taskConfig,
|
|
||||||
status: 'active',
|
|
||||||
});
|
|
||||||
return { success: true };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 系统状态 ===
|
|
||||||
async getSystemStatus(): Promise<APIResponse> {
|
|
||||||
try {
|
|
||||||
const taskStats = this.app.getRemoteExecution().getStats();
|
|
||||||
const agents = this.app.getAgentOrchestrator().getAgents().map(a => a.getInfo());
|
|
||||||
const activePlans = this.app.getAgentOrchestrator().getActivePlans();
|
|
||||||
const connectedIMs = this.app.getIMGateway().getConnectedAdapters();
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
taskStats,
|
|
||||||
activeAgents: agents.length,
|
|
||||||
activePlans: activePlans.length,
|
|
||||||
connectedIMs,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
173
src/app.ts
173
src/app.ts
@@ -1,173 +0,0 @@
|
|||||||
// ZCLAW 主应用类 - 统一协调所有系统
|
|
||||||
import { loadConfig, getConfig } from './config';
|
|
||||||
import { initDatabase, closeDatabase } from './db';
|
|
||||||
import { AIManager } from './core/ai/manager';
|
|
||||||
import { AgentOrchestrator } from './core/multi-agent/orchestrator';
|
|
||||||
import { RemoteExecutionEngine } from './core/remote-execution/engine';
|
|
||||||
import { TaskOrchestrator } from './core/task-orchestration/orchestrator';
|
|
||||||
import { PersistentMemorySystem } from './core/memory/memory';
|
|
||||||
import { ProactiveServiceSystem } from './core/proactive/proactive';
|
|
||||||
import { IMGateway } from './im/gateway';
|
|
||||||
import { FeishuAdapter } from './im/adapters/feishu';
|
|
||||||
import { createLogger, setLogLevel } from './utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('ZCLAW');
|
|
||||||
|
|
||||||
export class ZClawApp {
|
|
||||||
private ai!: AIManager;
|
|
||||||
private agentOrchestrator!: AgentOrchestrator;
|
|
||||||
private remoteExecution!: RemoteExecutionEngine;
|
|
||||||
private taskOrchestrator!: TaskOrchestrator;
|
|
||||||
private memory!: PersistentMemorySystem;
|
|
||||||
private proactive!: ProactiveServiceSystem;
|
|
||||||
private imGateway!: IMGateway;
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
log.info('========================================');
|
|
||||||
log.info(' ZCLAW - AI Agent Platform');
|
|
||||||
log.info(' Version: 0.1.0');
|
|
||||||
log.info('========================================');
|
|
||||||
|
|
||||||
// 1. 加载配置
|
|
||||||
const config = loadConfig();
|
|
||||||
setLogLevel(config.log.level);
|
|
||||||
log.info('Configuration loaded');
|
|
||||||
|
|
||||||
// 2. 初始化数据库
|
|
||||||
initDatabase(config.db.path);
|
|
||||||
log.info('Database initialized');
|
|
||||||
|
|
||||||
// 3. 初始化 AI 管理器
|
|
||||||
this.ai = new AIManager();
|
|
||||||
log.info('AI Manager initialized');
|
|
||||||
|
|
||||||
// 4. 初始化核心系统
|
|
||||||
this.remoteExecution = new RemoteExecutionEngine();
|
|
||||||
this.taskOrchestrator = new TaskOrchestrator();
|
|
||||||
this.memory = new PersistentMemorySystem();
|
|
||||||
this.proactive = new ProactiveServiceSystem();
|
|
||||||
log.info('Core systems initialized');
|
|
||||||
|
|
||||||
// 5. 初始化多 Agent 协作系统
|
|
||||||
this.agentOrchestrator = new AgentOrchestrator(this.ai);
|
|
||||||
log.info('Agent Orchestrator initialized');
|
|
||||||
|
|
||||||
// 6. 初始化 IM 网关
|
|
||||||
this.imGateway = new IMGateway();
|
|
||||||
this.setupIMAdapters(config);
|
|
||||||
this.setupMessageRouting();
|
|
||||||
log.info('IM Gateway initialized');
|
|
||||||
|
|
||||||
// 7. 连接 IM
|
|
||||||
await this.imGateway.connectAll();
|
|
||||||
|
|
||||||
log.info('ZCLAW started successfully! 🦞');
|
|
||||||
log.info(`Server: ${config.server.host}:${config.server.port}`);
|
|
||||||
log.info(`AI Provider: ${config.ai.provider} (${config.ai.defaultModel})`);
|
|
||||||
log.info(`Connected IMs: ${this.imGateway.getConnectedAdapters().join(', ') || 'none'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupIMAdapters(config: ReturnType<typeof getConfig>): void {
|
|
||||||
// 飞书
|
|
||||||
if (config.im.feishu.enabled || config.im.feishu.appId) {
|
|
||||||
const feishu = new FeishuAdapter(config.im.feishu.appId, config.im.feishu.appSecret);
|
|
||||||
this.imGateway.registerAdapter(feishu);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 其他 IM 适配器 (Telegram, QQ, WeChat)
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupMessageRouting(): void {
|
|
||||||
this.imGateway.onMessage(async (message) => {
|
|
||||||
log.info(`Processing message from ${message.channelType}/${message.userId}: ${message.content.slice(0, 80)}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 记忆:记录用户消息
|
|
||||||
await this.memory.remember(message.userId, {
|
|
||||||
id: message.id,
|
|
||||||
userId: message.userId,
|
|
||||||
type: 'im_message',
|
|
||||||
content: { text: message.content, channel: message.channelType },
|
|
||||||
timestamp: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 判断消息类型并路由
|
|
||||||
const response = await this.handleUserMessage(message.userId, message.content);
|
|
||||||
|
|
||||||
// 回复
|
|
||||||
await this.imGateway.send({
|
|
||||||
channelType: message.channelType,
|
|
||||||
channelId: message.channelId,
|
|
||||||
userId: message.userId,
|
|
||||||
content: response,
|
|
||||||
contentType: 'text',
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Message processing error: ${error.message}`);
|
|
||||||
await this.imGateway.send({
|
|
||||||
channelType: message.channelType,
|
|
||||||
channelId: message.channelId,
|
|
||||||
content: `处理消息时出错: ${error.message}`,
|
|
||||||
contentType: 'text',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleUserMessage(userId: string, content: string): Promise<string> {
|
|
||||||
// 获取用户记忆上下文
|
|
||||||
const recentMemories = await this.memory.recall(userId, content, 5);
|
|
||||||
const context: Record<string, any> = {};
|
|
||||||
if (recentMemories.length > 0) {
|
|
||||||
context.recentHistory = recentMemories.map(m => m.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否需要多步骤编排
|
|
||||||
const isComplex = this.isComplexTask(content);
|
|
||||||
|
|
||||||
if (isComplex) {
|
|
||||||
// 复杂任务 -> 多 Agent 协作
|
|
||||||
log.info(`Complex task detected, starting multi-agent execution`);
|
|
||||||
const result = await this.agentOrchestrator.executeGoal(content, context);
|
|
||||||
return result.success
|
|
||||||
? result.data?.report || '任务已完成'
|
|
||||||
: `任务执行失败: ${result.error}`;
|
|
||||||
} else {
|
|
||||||
// 简单对话 -> 直接 AI 回复
|
|
||||||
const profile = await this.memory.getProfile(userId);
|
|
||||||
const systemPrompt = `你是 ZCLAW,一个智能 AI 助手 🦞。你友善、专业、高效。
|
|
||||||
${profile ? `用户偏好: ${JSON.stringify(profile.preferences)}` : ''}
|
|
||||||
请用中文简洁回复。`;
|
|
||||||
|
|
||||||
return await this.ai.ask(content, systemPrompt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isComplexTask(content: string): boolean {
|
|
||||||
const complexIndicators = [
|
|
||||||
'帮我做', '市场调研', '写报告', '分析',
|
|
||||||
'自动化', '批量', '多步骤', '流程',
|
|
||||||
'调研', '搜索并整理', '对比', '综合',
|
|
||||||
'项目管理', '代码生成', '部署',
|
|
||||||
];
|
|
||||||
return complexIndicators.some(indicator => content.includes(indicator));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 公开接口:供 Tauri 前端调用
|
|
||||||
getAI(): AIManager { return this.ai; }
|
|
||||||
getAgentOrchestrator(): AgentOrchestrator { return this.agentOrchestrator; }
|
|
||||||
getRemoteExecution(): RemoteExecutionEngine { return this.remoteExecution; }
|
|
||||||
getTaskOrchestrator(): TaskOrchestrator { return this.taskOrchestrator; }
|
|
||||||
getMemory(): PersistentMemorySystem { return this.memory; }
|
|
||||||
getProactive(): ProactiveServiceSystem { return this.proactive; }
|
|
||||||
getIMGateway(): IMGateway { return this.imGateway; }
|
|
||||||
|
|
||||||
async shutdown(): Promise<void> {
|
|
||||||
log.info('Shutting down ZCLAW...');
|
|
||||||
await this.agentOrchestrator.shutdown();
|
|
||||||
await this.imGateway.disconnectAll();
|
|
||||||
closeDatabase();
|
|
||||||
log.info('ZCLAW shutdown complete');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
// ZCLAW 配置管理
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const ConfigSchema = z.object({
|
|
||||||
// AI Provider
|
|
||||||
ai: z.object({
|
|
||||||
provider: z.enum(['zhipu', 'openai', 'local']).default('zhipu'),
|
|
||||||
zhipuApiKey: z.string().default(''),
|
|
||||||
openaiApiKey: z.string().default(''),
|
|
||||||
openaiBaseUrl: z.string().default('https://api.openai.com/v1'),
|
|
||||||
defaultModel: z.string().default('glm-4-flash'),
|
|
||||||
maxTokens: z.number().default(4096),
|
|
||||||
temperature: z.number().default(0.7),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Database
|
|
||||||
db: z.object({
|
|
||||||
path: z.string().default('./data/zclaw.db'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Server
|
|
||||||
server: z.object({
|
|
||||||
port: z.number().default(3721),
|
|
||||||
host: z.string().default('127.0.0.1'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// IM Channels
|
|
||||||
im: z.object({
|
|
||||||
feishu: z.object({
|
|
||||||
appId: z.string().default(''),
|
|
||||||
appSecret: z.string().default(''),
|
|
||||||
enabled: z.boolean().default(false),
|
|
||||||
}),
|
|
||||||
telegram: z.object({
|
|
||||||
botToken: z.string().default(''),
|
|
||||||
enabled: z.boolean().default(false),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Execution
|
|
||||||
execution: z.object({
|
|
||||||
maxConcurrent: z.number().default(5),
|
|
||||||
taskTimeout: z.number().default(300000), // 5 minutes
|
|
||||||
retryAttempts: z.number().default(3),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Memory
|
|
||||||
memory: z.object({
|
|
||||||
maxEventsPerUser: z.number().default(10000),
|
|
||||||
embeddingModel: z.string().default('text-embedding-3-small'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
log: z.object({
|
|
||||||
level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ZClawConfig = z.infer<typeof ConfigSchema>;
|
|
||||||
|
|
||||||
let _config: ZClawConfig | null = null;
|
|
||||||
|
|
||||||
export function loadConfig(overrides?: Partial<Record<string, any>>): ZClawConfig {
|
|
||||||
const env = process.env;
|
|
||||||
|
|
||||||
const raw = {
|
|
||||||
ai: {
|
|
||||||
provider: env.ZCLAW_AI_PROVIDER || 'zhipu',
|
|
||||||
zhipuApiKey: env.ZCLAW_ZHIPU_API_KEY || env.ZHIPU_API_KEY || '',
|
|
||||||
openaiApiKey: env.ZCLAW_OPENAI_API_KEY || env.OPENAI_API_KEY || '',
|
|
||||||
openaiBaseUrl: env.ZCLAW_OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
|
||||||
defaultModel: env.ZCLAW_DEFAULT_MODEL || 'glm-4-flash',
|
|
||||||
maxTokens: parseInt(env.ZCLAW_MAX_TOKENS || '4096'),
|
|
||||||
temperature: parseFloat(env.ZCLAW_TEMPERATURE || '0.7'),
|
|
||||||
},
|
|
||||||
db: {
|
|
||||||
path: env.ZCLAW_DB_PATH || './data/zclaw.db',
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: parseInt(env.ZCLAW_PORT || '3721'),
|
|
||||||
host: env.ZCLAW_HOST || '127.0.0.1',
|
|
||||||
},
|
|
||||||
im: {
|
|
||||||
feishu: {
|
|
||||||
appId: env.ZCLAW_FEISHU_APP_ID || '',
|
|
||||||
appSecret: env.ZCLAW_FEISHU_APP_SECRET || '',
|
|
||||||
enabled: env.ZCLAW_FEISHU_ENABLED === 'true',
|
|
||||||
},
|
|
||||||
telegram: {
|
|
||||||
botToken: env.ZCLAW_TELEGRAM_BOT_TOKEN || '',
|
|
||||||
enabled: env.ZCLAW_TELEGRAM_ENABLED === 'true',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
execution: {
|
|
||||||
maxConcurrent: parseInt(env.ZCLAW_MAX_CONCURRENT || '5'),
|
|
||||||
taskTimeout: parseInt(env.ZCLAW_TASK_TIMEOUT || '300000'),
|
|
||||||
retryAttempts: parseInt(env.ZCLAW_RETRY_ATTEMPTS || '3'),
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
maxEventsPerUser: parseInt(env.ZCLAW_MAX_EVENTS || '10000'),
|
|
||||||
embeddingModel: env.ZCLAW_EMBEDDING_MODEL || 'text-embedding-3-small',
|
|
||||||
},
|
|
||||||
log: {
|
|
||||||
level: env.ZCLAW_LOG_LEVEL || 'info',
|
|
||||||
},
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
|
|
||||||
_config = ConfigSchema.parse(raw);
|
|
||||||
return _config;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getConfig(): ZClawConfig {
|
|
||||||
if (!_config) {
|
|
||||||
return loadConfig();
|
|
||||||
}
|
|
||||||
return _config;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export type { AIProvider, ChatMessage, ChatRequest, ChatResponse, StreamChunk, EmbeddingRequest, EmbeddingResponse } from './types';
|
|
||||||
export { AIManager, getAIManager } from './manager';
|
|
||||||
export { ZhipuProvider } from './providers/zhipu';
|
|
||||||
export { OpenAIProvider } from './providers/openai';
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
// AI 模型管理器 - 统一调用接口,支持多 Provider fallback
|
|
||||||
import type { AIProvider, ChatRequest, ChatResponse, ChatMessage, StreamChunk, EmbeddingRequest, EmbeddingResponse } from './types';
|
|
||||||
import { ZhipuProvider } from './providers/zhipu';
|
|
||||||
import { OpenAIProvider } from './providers/openai';
|
|
||||||
import { getConfig } from '../../config';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('AIManager');
|
|
||||||
|
|
||||||
export class AIManager {
|
|
||||||
private providers: Map<string, AIProvider> = new Map();
|
|
||||||
private defaultProvider: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const config = getConfig();
|
|
||||||
this.defaultProvider = config.ai.provider;
|
|
||||||
|
|
||||||
// 注册智谱 GLM
|
|
||||||
if (config.ai.zhipuApiKey) {
|
|
||||||
this.providers.set('zhipu', new ZhipuProvider(config.ai.zhipuApiKey));
|
|
||||||
log.info('Zhipu GLM provider registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册 OpenAI 兼容
|
|
||||||
if (config.ai.openaiApiKey) {
|
|
||||||
this.providers.set('openai', new OpenAIProvider(config.ai.openaiApiKey, config.ai.openaiBaseUrl));
|
|
||||||
log.info('OpenAI provider registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.providers.size === 0) {
|
|
||||||
log.warn('No AI providers configured. Set ZCLAW_ZHIPU_API_KEY or ZCLAW_OPENAI_API_KEY.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getProvider(name?: string): AIProvider {
|
|
||||||
const providerName = name || this.defaultProvider;
|
|
||||||
const provider = this.providers.get(providerName);
|
|
||||||
if (!provider) {
|
|
||||||
// fallback: 尝试其他可用 provider
|
|
||||||
const fallback = this.providers.values().next().value;
|
|
||||||
if (fallback) {
|
|
||||||
log.warn(`Provider '${providerName}' not available, falling back to '${fallback.name}'`);
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
throw new Error(`No AI provider available. Configure at least one API key.`);
|
|
||||||
}
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(request: ChatRequest, providerName?: string): Promise<ChatResponse> {
|
|
||||||
const provider = this.getProvider(providerName);
|
|
||||||
const config = getConfig();
|
|
||||||
|
|
||||||
const enrichedRequest: ChatRequest = {
|
|
||||||
...request,
|
|
||||||
model: request.model || config.ai.defaultModel,
|
|
||||||
temperature: request.temperature ?? config.ai.temperature,
|
|
||||||
maxTokens: request.maxTokens ?? config.ai.maxTokens,
|
|
||||||
};
|
|
||||||
|
|
||||||
log.debug(`Chat via ${provider.name}`, { model: enrichedRequest.model, messages: enrichedRequest.messages.length });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await provider.chat(enrichedRequest);
|
|
||||||
log.debug(`Chat response: ${response.usage.totalTokens} tokens`);
|
|
||||||
return response;
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Chat failed via ${provider.name}: ${error.message}`);
|
|
||||||
// 尝试 fallback
|
|
||||||
for (const [name, fallbackProvider] of this.providers) {
|
|
||||||
if (name !== provider.name) {
|
|
||||||
log.info(`Retrying with fallback provider: ${name}`);
|
|
||||||
try {
|
|
||||||
return await fallbackProvider.chat(enrichedRequest);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async chatStream(request: ChatRequest, providerName?: string): Promise<AsyncIterable<StreamChunk>> {
|
|
||||||
const provider = this.getProvider(providerName);
|
|
||||||
if (!provider.chatStream) {
|
|
||||||
throw new Error(`Provider '${provider.name}' does not support streaming`);
|
|
||||||
}
|
|
||||||
return provider.chatStream(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async embed(request: EmbeddingRequest, providerName?: string): Promise<EmbeddingResponse> {
|
|
||||||
const provider = this.getProvider(providerName);
|
|
||||||
if (!provider.embed) {
|
|
||||||
throw new Error(`Provider '${provider.name}' does not support embeddings`);
|
|
||||||
}
|
|
||||||
return provider.embed(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 便捷方法:单次对话
|
|
||||||
async ask(prompt: string, systemPrompt?: string): Promise<string> {
|
|
||||||
const messages: ChatMessage[] = [];
|
|
||||||
if (systemPrompt) {
|
|
||||||
messages.push({ role: 'system', content: systemPrompt });
|
|
||||||
}
|
|
||||||
messages.push({ role: 'user', content: prompt });
|
|
||||||
|
|
||||||
const response = await this.chat({ messages });
|
|
||||||
return response.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 便捷方法:带上下文的多轮对话
|
|
||||||
async chatWithHistory(messages: ChatMessage[], systemPrompt?: string): Promise<string> {
|
|
||||||
const allMessages: ChatMessage[] = [];
|
|
||||||
if (systemPrompt) {
|
|
||||||
allMessages.push({ role: 'system', content: systemPrompt });
|
|
||||||
}
|
|
||||||
allMessages.push(...messages);
|
|
||||||
|
|
||||||
const response = await this.chat({ messages: allMessages });
|
|
||||||
return response.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 便捷方法:JSON 输出
|
|
||||||
async askJson<T = any>(prompt: string, systemPrompt?: string): Promise<T> {
|
|
||||||
const jsonSystemPrompt = (systemPrompt || '') + '\n\n请严格以 JSON 格式输出,不要包含 markdown 代码块标记。';
|
|
||||||
const content = await this.ask(prompt, jsonSystemPrompt);
|
|
||||||
|
|
||||||
// 清理可能的 markdown 代码块包裹
|
|
||||||
const cleaned = content.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
|
|
||||||
return JSON.parse(cleaned) as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _manager: AIManager | null = null;
|
|
||||||
|
|
||||||
export function getAIManager(): AIManager {
|
|
||||||
if (!_manager) {
|
|
||||||
_manager = new AIManager();
|
|
||||||
}
|
|
||||||
return _manager;
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
// OpenAI Compatible Provider (also works for local models via API)
|
|
||||||
import type { AIProvider, ChatRequest, ChatResponse, StreamChunk, EmbeddingRequest, EmbeddingResponse } from '../types';
|
|
||||||
import { createLogger } from '../../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('OpenAIProvider');
|
|
||||||
|
|
||||||
export class OpenAIProvider implements AIProvider {
|
|
||||||
name = 'openai';
|
|
||||||
private apiKey: string;
|
|
||||||
private baseUrl: string;
|
|
||||||
|
|
||||||
constructor(apiKey: string, baseUrl: string = 'https://api.openai.com/v1') {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(request: ChatRequest): Promise<ChatResponse> {
|
|
||||||
const model = request.model || 'gpt-4o-mini';
|
|
||||||
log.debug(`Chat request to ${model}`);
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model,
|
|
||||||
messages: request.messages,
|
|
||||||
temperature: request.temperature ?? 0.7,
|
|
||||||
max_tokens: request.maxTokens ?? 4096,
|
|
||||||
stream: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
log.error(`OpenAI API error: ${response.status}`, errorText);
|
|
||||||
throw new Error(`OpenAI API error: ${response.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
const choice = data.choices?.[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: choice?.message?.content || '',
|
|
||||||
model: data.model || model,
|
|
||||||
usage: {
|
|
||||||
promptTokens: data.usage?.prompt_tokens || 0,
|
|
||||||
completionTokens: data.usage?.completion_tokens || 0,
|
|
||||||
totalTokens: data.usage?.total_tokens || 0,
|
|
||||||
},
|
|
||||||
finishReason: choice?.finish_reason || 'stop',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async *chatStream(request: ChatRequest): AsyncIterable<StreamChunk> {
|
|
||||||
const model = request.model || 'gpt-4o-mini';
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model,
|
|
||||||
messages: request.messages,
|
|
||||||
temperature: request.temperature ?? 0.7,
|
|
||||||
max_tokens: request.maxTokens ?? 4096,
|
|
||||||
stream: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`OpenAI API error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
|
||||||
if (!reader) throw new Error('No response body');
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
buffer = lines.pop() || '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('data: ')) {
|
|
||||||
const data = line.slice(6).trim();
|
|
||||||
if (data === '[DONE]') {
|
|
||||||
yield { content: '', done: true };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(data);
|
|
||||||
const delta = parsed.choices?.[0]?.delta?.content || '';
|
|
||||||
if (delta) {
|
|
||||||
yield { content: delta, done: false };
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {
|
|
||||||
const input = Array.isArray(request.input) ? request.input : [request.input];
|
|
||||||
const model = request.model || 'text-embedding-3-small';
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ model, input }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`OpenAI Embedding API error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeddings: data.data?.map((d: any) => d.embedding) || [],
|
|
||||||
model: data.model || model,
|
|
||||||
usage: { totalTokens: data.usage?.total_tokens || 0 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
// 智谱 GLM AI Provider
|
|
||||||
import type { AIProvider, ChatRequest, ChatResponse, StreamChunk, EmbeddingRequest, EmbeddingResponse } from '../types';
|
|
||||||
import { createLogger } from '../../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('ZhipuProvider');
|
|
||||||
|
|
||||||
export class ZhipuProvider implements AIProvider {
|
|
||||||
name = 'zhipu';
|
|
||||||
private apiKey: string;
|
|
||||||
private baseUrl = 'https://open.bigmodel.cn/api/paas/v4';
|
|
||||||
|
|
||||||
constructor(apiKey: string) {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(request: ChatRequest): Promise<ChatResponse> {
|
|
||||||
const model = request.model || 'glm-4-flash';
|
|
||||||
log.debug(`Chat request to ${model}`, { messageCount: request.messages.length });
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model,
|
|
||||||
messages: request.messages,
|
|
||||||
temperature: request.temperature ?? 0.7,
|
|
||||||
max_tokens: request.maxTokens ?? 4096,
|
|
||||||
stream: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
log.error(`Zhipu API error: ${response.status}`, errorText);
|
|
||||||
throw new Error(`Zhipu API error: ${response.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
const choice = data.choices?.[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: choice?.message?.content || '',
|
|
||||||
model: data.model || model,
|
|
||||||
usage: {
|
|
||||||
promptTokens: data.usage?.prompt_tokens || 0,
|
|
||||||
completionTokens: data.usage?.completion_tokens || 0,
|
|
||||||
totalTokens: data.usage?.total_tokens || 0,
|
|
||||||
},
|
|
||||||
finishReason: choice?.finish_reason || 'stop',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async *chatStream(request: ChatRequest): AsyncIterable<StreamChunk> {
|
|
||||||
const model = request.model || 'glm-4-flash';
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model,
|
|
||||||
messages: request.messages,
|
|
||||||
temperature: request.temperature ?? 0.7,
|
|
||||||
max_tokens: request.maxTokens ?? 4096,
|
|
||||||
stream: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Zhipu API error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
|
||||||
if (!reader) throw new Error('No response body');
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
buffer = lines.pop() || '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('data: ')) {
|
|
||||||
const data = line.slice(6).trim();
|
|
||||||
if (data === '[DONE]') {
|
|
||||||
yield { content: '', done: true };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(data);
|
|
||||||
const delta = parsed.choices?.[0]?.delta?.content || '';
|
|
||||||
if (delta) {
|
|
||||||
yield { content: delta, done: false };
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// skip invalid JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {
|
|
||||||
const input = Array.isArray(request.input) ? request.input : [request.input];
|
|
||||||
const model = request.model || 'embedding-3';
|
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ model, input }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Zhipu Embedding API error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeddings: data.data?.map((d: any) => d.embedding) || [],
|
|
||||||
model: data.model || model,
|
|
||||||
usage: { totalTokens: data.usage?.total_tokens || 0 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// AI 模型集成层 - 类型定义
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
role: 'system' | 'user' | 'assistant';
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatRequest {
|
|
||||||
messages: ChatMessage[];
|
|
||||||
model?: string;
|
|
||||||
temperature?: number;
|
|
||||||
maxTokens?: number;
|
|
||||||
stream?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatResponse {
|
|
||||||
content: string;
|
|
||||||
model: string;
|
|
||||||
usage: {
|
|
||||||
promptTokens: number;
|
|
||||||
completionTokens: number;
|
|
||||||
totalTokens: number;
|
|
||||||
};
|
|
||||||
finishReason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StreamChunk {
|
|
||||||
content: string;
|
|
||||||
done: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmbeddingRequest {
|
|
||||||
input: string | string[];
|
|
||||||
model?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmbeddingResponse {
|
|
||||||
embeddings: number[][];
|
|
||||||
model: string;
|
|
||||||
usage: { totalTokens: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AIProvider {
|
|
||||||
name: string;
|
|
||||||
chat(request: ChatRequest): Promise<ChatResponse>;
|
|
||||||
chatStream?(request: ChatRequest): AsyncIterable<StreamChunk>;
|
|
||||||
embed?(request: EmbeddingRequest): Promise<EmbeddingResponse>;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './memory';
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
// 持续记忆系统
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('Memory');
|
|
||||||
|
|
||||||
export interface UserProfile {
|
|
||||||
id: string;
|
|
||||||
preferences: {
|
|
||||||
language: 'zh' | 'en';
|
|
||||||
timezone: string;
|
|
||||||
responseStyle: 'concise' | 'detailed';
|
|
||||||
};
|
|
||||||
patterns: {
|
|
||||||
activeHours: number[];
|
|
||||||
frequentCommands: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MemoryEvent {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
type: string;
|
|
||||||
content: any;
|
|
||||||
timestamp: Date;
|
|
||||||
embedding?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PersistentMemorySystem {
|
|
||||||
private profiles: Map<string, UserProfile> = new Map();
|
|
||||||
private events: MemoryEvent[] = [];
|
|
||||||
|
|
||||||
async remember(userId: string, event: MemoryEvent): Promise<void> {
|
|
||||||
event.id = event.id || this.generateId();
|
|
||||||
event.timestamp = new Date();
|
|
||||||
this.events.push(event);
|
|
||||||
log.debug(`Event remembered: ${event.id} for user ${userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async recall(userId: string, query: string, limit: number = 10): Promise<MemoryEvent[]> {
|
|
||||||
// TODO: 实现向量搜索(后续实现)
|
|
||||||
return this.events.filter(e => e.userId === userId).slice(0, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProfile(userId: string): Promise<UserProfile | undefined> {
|
|
||||||
return this.profiles.get(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateProfile(userId: string, updates: Partial<UserProfile>): Promise<void> {
|
|
||||||
let profile = this.profiles.get(userId);
|
|
||||||
if (!profile) {
|
|
||||||
profile = {
|
|
||||||
id: userId,
|
|
||||||
preferences: { language: 'zh', timezone: 'Asia/Shanghai', responseStyle: 'concise' },
|
|
||||||
patterns: { activeHours: [], frequentCommands: [] },
|
|
||||||
};
|
|
||||||
this.profiles.set(userId, profile);
|
|
||||||
}
|
|
||||||
Object.assign(profile, updates);
|
|
||||||
log.debug(`Profile updated for user ${userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEventCount(userId: string): Promise<number> {
|
|
||||||
return this.events.filter(e => e.userId === userId).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateId(): string {
|
|
||||||
return generateId('mem');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Combiner Agent - 整合多个 Agent 的执行结果
|
|
||||||
import { BaseAgent } from '../base-agent';
|
|
||||||
import type { AgentTask } from '../types';
|
|
||||||
|
|
||||||
const COMBINER_SYSTEM_PROMPT = `你是一个结果整合专家。你的职责是将多个执行步骤的结果整合为一份完整、结构化的报告。
|
|
||||||
|
|
||||||
要求:
|
|
||||||
1. 综合分析所有步骤的执行结果
|
|
||||||
2. 提取关键信息
|
|
||||||
3. 生成简洁清晰的最终报告
|
|
||||||
4. 如果某些步骤失败,说明缺失的信息
|
|
||||||
5. 输出格式清晰,适合通过 IM 发送给用户`;
|
|
||||||
|
|
||||||
export class CombinerAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['result_combination', 'report_generation', 'data_synthesis'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
const results = task.params.results || [];
|
|
||||||
const goal = task.params.goal || task.description;
|
|
||||||
|
|
||||||
this.log.info(`Combining ${results.length} results for: ${goal}`);
|
|
||||||
|
|
||||||
const prompt = `原始目标: ${goal}
|
|
||||||
|
|
||||||
各步骤执行结果:
|
|
||||||
${results.map((r: any, i: number) => `
|
|
||||||
--- 步骤 ${i + 1} ---
|
|
||||||
描述: ${r.description || '未知'}
|
|
||||||
状态: ${r.success ? '成功' : '失败'}
|
|
||||||
结果: ${JSON.stringify(r.data || r.error, null, 2)}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
请整合上述结果,生成一份完整的报告。输出应该清晰、有条理,适合通过即时通讯发送给用户。`;
|
|
||||||
|
|
||||||
const report = await this.ai.ask(prompt, COMBINER_SYSTEM_PROMPT);
|
|
||||||
return { report, stepsCount: results.length, successCount: results.filter((r: any) => r.success).length };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
// Executor Agent - 通用执行器,根据工具类型执行具体操作
|
|
||||||
import { BaseAgent } from '../base-agent';
|
|
||||||
import type { AgentTask } from '../types';
|
|
||||||
|
|
||||||
export class BrowserAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['web_browse', 'web_search', 'screenshot', 'form_fill'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
this.log.info(`Browser task: ${task.description}`);
|
|
||||||
|
|
||||||
// 使用 AI 模拟浏览器操作结果(后续集成真实浏览器控制)
|
|
||||||
const prompt = `你需要模拟执行以下浏览器操作并生成合理的结果:
|
|
||||||
|
|
||||||
操作描述: ${task.description}
|
|
||||||
参数: ${JSON.stringify(task.params)}
|
|
||||||
上下文: ${JSON.stringify(task.context)}
|
|
||||||
|
|
||||||
请以 JSON 格式输出执行结果,包含 status、data 字段。`;
|
|
||||||
|
|
||||||
return await this.ai.askJson(prompt, '你是一个浏览器操作模拟器,请生成合理的操作结果。');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FileAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['file_read', 'file_write', 'file_search', 'file_organize'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
this.log.info(`File task: ${task.description}`);
|
|
||||||
|
|
||||||
const prompt = `你需要执行以下文件操作并生成结果:
|
|
||||||
|
|
||||||
操作描述: ${task.description}
|
|
||||||
参数: ${JSON.stringify(task.params)}
|
|
||||||
上下文: ${JSON.stringify(task.context)}
|
|
||||||
|
|
||||||
请以 JSON 格式输出执行结果。`;
|
|
||||||
|
|
||||||
return await this.ai.askJson(prompt, '你是一个文件操作执行器。');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TerminalAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['command_execute', 'script_run', 'system_info'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
this.log.info(`Terminal task: ${task.description}`);
|
|
||||||
|
|
||||||
const prompt = `你需要模拟执行以下终端命令操作并生成合理结果:
|
|
||||||
|
|
||||||
操作描述: ${task.description}
|
|
||||||
参数: ${JSON.stringify(task.params)}
|
|
||||||
上下文: ${JSON.stringify(task.context)}
|
|
||||||
|
|
||||||
请以 JSON 格式输出执行结果,包含 stdout、stderr、exitCode 字段。`;
|
|
||||||
|
|
||||||
return await this.ai.askJson(prompt, '你是一个终端命令模拟器。');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AIAnalysisAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['text_analysis', 'content_generation', 'data_processing', 'summarization'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
this.log.info(`AI analysis task: ${task.description}`);
|
|
||||||
|
|
||||||
const systemPrompt = task.params.systemPrompt || '你是一个智能分析助手,请根据要求完成分析任务。';
|
|
||||||
const prompt = `${task.description}
|
|
||||||
|
|
||||||
${task.context ? `上下文数据:\n${JSON.stringify(task.context, null, 2)}` : ''}
|
|
||||||
${task.params.data ? `输入数据:\n${JSON.stringify(task.params.data, null, 2)}` : ''}`;
|
|
||||||
|
|
||||||
const result = await this.ai.ask(prompt, systemPrompt);
|
|
||||||
return { analysis: result };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// Planner Agent - 任务规划,将用户目标拆解为可执行步骤
|
|
||||||
import { BaseAgent } from '../base-agent';
|
|
||||||
import type { AgentTask } from '../types';
|
|
||||||
|
|
||||||
const PLANNER_SYSTEM_PROMPT = `你是一个高级任务规划专家。你的职责是将用户的目标拆解为可执行的步骤序列。
|
|
||||||
|
|
||||||
可用工具类型:
|
|
||||||
- browser: 浏览器操作(访问网页、截图、填表、搜索)
|
|
||||||
- file: 文件操作(读写、搜索、整理、生成文档)
|
|
||||||
- terminal: 终端命令(代码执行、系统操作、安装软件)
|
|
||||||
- api: API 调用(搜索引擎、天气、翻译等)
|
|
||||||
- ai: AI 分析(文本分析、内容生成、数据处理)
|
|
||||||
|
|
||||||
输出要求:
|
|
||||||
严格输出 JSON 格式,不要包含 markdown 代码块标记。
|
|
||||||
{
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"id": "step_1",
|
|
||||||
"description": "步骤描述",
|
|
||||||
"tool": "browser",
|
|
||||||
"params": { "url": "https://example.com", "action": "search" },
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "step_2",
|
|
||||||
"description": "步骤描述",
|
|
||||||
"tool": "ai",
|
|
||||||
"params": { "action": "analyze" },
|
|
||||||
"dependencies": ["step_1"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
规则:
|
|
||||||
1. 每个步骤必须明确、可执行
|
|
||||||
2. 标注步骤之间的依赖关系(dependencies 数组)
|
|
||||||
3. 无依赖的步骤可以并行执行
|
|
||||||
4. 步骤数量控制在 3-10 个之间
|
|
||||||
5. 最后一个步骤通常是整合/汇总结果`;
|
|
||||||
|
|
||||||
export class PlannerAgent extends BaseAgent {
|
|
||||||
protected getCapabilities(): string[] {
|
|
||||||
return ['task_planning', 'task_decomposition', 'dependency_analysis'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async run(task: AgentTask): Promise<any> {
|
|
||||||
const goal = task.params.goal || task.description;
|
|
||||||
const context = task.context || {};
|
|
||||||
|
|
||||||
this.log.info(`Planning task: ${goal}`);
|
|
||||||
|
|
||||||
const prompt = `目标: ${goal}
|
|
||||||
|
|
||||||
${Object.keys(context).length > 0 ? `上下文信息:\n${JSON.stringify(context, null, 2)}` : ''}
|
|
||||||
|
|
||||||
请将上述目标拆解为可执行步骤。`;
|
|
||||||
|
|
||||||
const response = await this.ai.askJson<{ steps: any[] }>(prompt, PLANNER_SYSTEM_PROMPT);
|
|
||||||
|
|
||||||
this.log.info(`Plan generated: ${response.steps.length} steps`);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
// 多 Agent 协作系统 - Agent 基类
|
|
||||||
import type { AgentType, AgentConfig, AgentInfo, AgentTask, AgentResult, AgentMessage, AgentStatus } from './types';
|
|
||||||
import type { MessageBus } from './message-bus';
|
|
||||||
import type { AIManager } from '../ai/manager';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger, type Logger } from '../../utils/logger';
|
|
||||||
|
|
||||||
export abstract class BaseAgent {
|
|
||||||
readonly id: string;
|
|
||||||
readonly type: AgentType;
|
|
||||||
readonly name: string;
|
|
||||||
protected status: AgentStatus = 'idle';
|
|
||||||
protected config: AgentConfig;
|
|
||||||
protected messageBus: MessageBus;
|
|
||||||
protected ai: AIManager;
|
|
||||||
protected log: Logger;
|
|
||||||
protected currentTaskId?: string;
|
|
||||||
protected createdAt: Date = new Date();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
type: AgentType,
|
|
||||||
config: AgentConfig,
|
|
||||||
messageBus: MessageBus,
|
|
||||||
ai: AIManager,
|
|
||||||
) {
|
|
||||||
this.id = generateId('agent');
|
|
||||||
this.type = type;
|
|
||||||
this.name = config.name || `${type}-agent`;
|
|
||||||
this.config = config;
|
|
||||||
this.messageBus = messageBus;
|
|
||||||
this.ai = ai;
|
|
||||||
this.log = createLogger(`Agent:${this.name}`);
|
|
||||||
|
|
||||||
// 订阅消息
|
|
||||||
this.messageBus.subscribe(this.id, (msg) => this.handleMessage(msg));
|
|
||||||
|
|
||||||
this.log.info(`Agent created: ${this.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo(): AgentInfo {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
type: this.type,
|
|
||||||
name: this.name,
|
|
||||||
status: this.status,
|
|
||||||
capabilities: this.getCapabilities(),
|
|
||||||
currentTaskId: this.currentTaskId,
|
|
||||||
createdAt: this.createdAt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(task: AgentTask): Promise<AgentResult> {
|
|
||||||
this.status = 'busy';
|
|
||||||
this.currentTaskId = task.id;
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
this.log.info(`Executing task: ${task.id} - ${task.description}`);
|
|
||||||
|
|
||||||
// 通知开始执行
|
|
||||||
await this.messageBus.send({
|
|
||||||
from: this.id,
|
|
||||||
to: '*',
|
|
||||||
type: 'status',
|
|
||||||
payload: { taskId: task.id, status: 'running' },
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.run(task);
|
|
||||||
const result: AgentResult = {
|
|
||||||
taskId: task.id,
|
|
||||||
agentId: this.id,
|
|
||||||
success: true,
|
|
||||||
data,
|
|
||||||
duration: Date.now() - startTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 通知完成
|
|
||||||
await this.messageBus.send({
|
|
||||||
from: this.id,
|
|
||||||
to: '*',
|
|
||||||
type: 'result',
|
|
||||||
payload: result,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.status = 'idle';
|
|
||||||
this.currentTaskId = undefined;
|
|
||||||
this.log.info(`Task completed: ${task.id} (${result.duration}ms)`);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
const result: AgentResult = {
|
|
||||||
taskId: task.id,
|
|
||||||
agentId: this.id,
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
duration: Date.now() - startTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.messageBus.send({
|
|
||||||
from: this.id,
|
|
||||||
to: '*',
|
|
||||||
type: 'error',
|
|
||||||
payload: result,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.status = 'error';
|
|
||||||
this.currentTaskId = undefined;
|
|
||||||
this.log.error(`Task failed: ${task.id} - ${error.message}`);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async terminate(): Promise<void> {
|
|
||||||
this.status = 'terminated';
|
|
||||||
this.messageBus.unsubscribe(this.id);
|
|
||||||
this.log.info(`Agent terminated: ${this.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 子类必须实现
|
|
||||||
protected abstract run(task: AgentTask): Promise<any>;
|
|
||||||
protected abstract getCapabilities(): string[];
|
|
||||||
|
|
||||||
protected async handleMessage(message: AgentMessage): Promise<void> {
|
|
||||||
this.log.debug(`Received message from ${message.from}: ${message.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export type { AgentType, AgentConfig, AgentInfo, AgentTask, AgentResult, AgentMessage, AgentStatus, MultiAgentPlan } from './types';
|
|
||||||
export { BaseAgent } from './base-agent';
|
|
||||||
export { MessageBus } from './message-bus';
|
|
||||||
export { AgentOrchestrator } from './orchestrator';
|
|
||||||
export { PlannerAgent } from './agents/planner-agent';
|
|
||||||
export { BrowserAgent, FileAgent, TerminalAgent, AIAnalysisAgent } from './agents/executor-agent';
|
|
||||||
export { CombinerAgent } from './agents/combiner-agent';
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
// 多 Agent 消息总线 - Agent 间通信的核心
|
|
||||||
import type { AgentMessage, MessageHandler } from './types';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('MessageBus');
|
|
||||||
|
|
||||||
export class MessageBus {
|
|
||||||
private subscribers: Map<string, MessageHandler[]> = new Map();
|
|
||||||
private broadcastHandlers: MessageHandler[] = [];
|
|
||||||
private messageLog: AgentMessage[] = [];
|
|
||||||
|
|
||||||
subscribe(agentId: string, handler: MessageHandler): void {
|
|
||||||
if (!this.subscribers.has(agentId)) {
|
|
||||||
this.subscribers.set(agentId, []);
|
|
||||||
}
|
|
||||||
this.subscribers.get(agentId)!.push(handler);
|
|
||||||
log.debug(`Agent ${agentId} subscribed to message bus`);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(agentId: string): void {
|
|
||||||
this.subscribers.delete(agentId);
|
|
||||||
log.debug(`Agent ${agentId} unsubscribed from message bus`);
|
|
||||||
}
|
|
||||||
|
|
||||||
onBroadcast(handler: MessageHandler): void {
|
|
||||||
this.broadcastHandlers.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(message: Omit<AgentMessage, 'id' | 'timestamp'>): Promise<void> {
|
|
||||||
const fullMessage: AgentMessage = {
|
|
||||||
...message,
|
|
||||||
id: generateId('msg'),
|
|
||||||
timestamp: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.messageLog.push(fullMessage);
|
|
||||||
log.debug(`Message: ${fullMessage.from} -> ${fullMessage.to} [${fullMessage.type}]`);
|
|
||||||
|
|
||||||
if (fullMessage.to === '*') {
|
|
||||||
// 广播
|
|
||||||
const allHandlers = [...this.broadcastHandlers];
|
|
||||||
for (const [, handlers] of this.subscribers) {
|
|
||||||
allHandlers.push(...handlers);
|
|
||||||
}
|
|
||||||
await Promise.allSettled(allHandlers.map(h => h(fullMessage)));
|
|
||||||
} else {
|
|
||||||
// 定向发送
|
|
||||||
const handlers = this.subscribers.get(fullMessage.to);
|
|
||||||
if (handlers) {
|
|
||||||
await Promise.allSettled(handlers.map(h => h(fullMessage)));
|
|
||||||
} else {
|
|
||||||
log.warn(`No handlers for agent ${fullMessage.to}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMessages(agentId?: string, limit: number = 50): AgentMessage[] {
|
|
||||||
let messages = this.messageLog;
|
|
||||||
if (agentId) {
|
|
||||||
messages = messages.filter(m => m.from === agentId || m.to === agentId || m.to === '*');
|
|
||||||
}
|
|
||||||
return messages.slice(-limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this.subscribers.clear();
|
|
||||||
this.broadcastHandlers = [];
|
|
||||||
this.messageLog = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
// 多 Agent 协作系统 - 编排器(核心协调器)
|
|
||||||
import type { AgentType, AgentConfig, AgentTask, AgentResult, MultiAgentPlan } from './types';
|
|
||||||
import { BaseAgent } from './base-agent';
|
|
||||||
import { MessageBus } from './message-bus';
|
|
||||||
import { PlannerAgent } from './agents/planner-agent';
|
|
||||||
import { BrowserAgent, FileAgent, TerminalAgent, AIAnalysisAgent } from './agents/executor-agent';
|
|
||||||
import { CombinerAgent } from './agents/combiner-agent';
|
|
||||||
import { AIManager } from '../ai/manager';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('AgentOrchestrator');
|
|
||||||
|
|
||||||
type ProgressCallback = (plan: MultiAgentPlan, currentStep?: string) => void;
|
|
||||||
|
|
||||||
export class AgentOrchestrator {
|
|
||||||
private agents: Map<string, BaseAgent> = new Map();
|
|
||||||
private messageBus: MessageBus;
|
|
||||||
private ai: AIManager;
|
|
||||||
private activePlans: Map<string, MultiAgentPlan> = new Map();
|
|
||||||
|
|
||||||
constructor(ai: AIManager) {
|
|
||||||
this.messageBus = new MessageBus();
|
|
||||||
this.ai = ai;
|
|
||||||
log.info('AgentOrchestrator initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 Agent
|
|
||||||
spawnAgent(type: AgentType, config: AgentConfig = {}): BaseAgent {
|
|
||||||
let agent: BaseAgent;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'planner':
|
|
||||||
agent = new PlannerAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
case 'browser':
|
|
||||||
agent = new BrowserAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
case 'file':
|
|
||||||
agent = new FileAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
case 'terminal':
|
|
||||||
agent = new TerminalAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
case 'combiner':
|
|
||||||
agent = new CombinerAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
case 'custom':
|
|
||||||
default:
|
|
||||||
agent = new AIAnalysisAgent(type, config, this.messageBus, this.ai);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.agents.set(agent.id, agent);
|
|
||||||
log.info(`Agent spawned: ${agent.id} (${type})`);
|
|
||||||
return agent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 终止 Agent
|
|
||||||
async terminateAgent(agentId: string): Promise<void> {
|
|
||||||
const agent = this.agents.get(agentId);
|
|
||||||
if (agent) {
|
|
||||||
await agent.terminate();
|
|
||||||
this.agents.delete(agentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 核心方法:执行多 Agent 协作任务
|
|
||||||
async executeGoal(goal: string, context: Record<string, any> = {}, onProgress?: ProgressCallback): Promise<AgentResult> {
|
|
||||||
const planId = generateId('plan');
|
|
||||||
log.info(`Starting multi-agent execution: ${goal} (${planId})`);
|
|
||||||
|
|
||||||
const plan: MultiAgentPlan = {
|
|
||||||
id: planId,
|
|
||||||
goal,
|
|
||||||
steps: [],
|
|
||||||
agentAssignments: new Map(),
|
|
||||||
status: 'planning',
|
|
||||||
results: [],
|
|
||||||
createdAt: new Date(),
|
|
||||||
};
|
|
||||||
this.activePlans.set(planId, plan);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Phase 1: 规划 - 使用 Planner Agent 拆解任务
|
|
||||||
plan.status = 'planning';
|
|
||||||
onProgress?.(plan, '正在规划任务...');
|
|
||||||
|
|
||||||
const planner = this.spawnAgent('planner', { name: 'task-planner' });
|
|
||||||
const planResult = await planner.execute({
|
|
||||||
id: generateId('task'),
|
|
||||||
type: 'plan',
|
|
||||||
description: '规划任务',
|
|
||||||
params: { goal },
|
|
||||||
context,
|
|
||||||
dependencies: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!planResult.success || !planResult.data?.steps) {
|
|
||||||
throw new Error(`Planning failed: ${planResult.error || 'No steps generated'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps: AgentTask[] = planResult.data.steps.map((s: any) => ({
|
|
||||||
id: s.id || generateId('step'),
|
|
||||||
type: s.tool || 'ai',
|
|
||||||
description: s.description,
|
|
||||||
params: s.params || {},
|
|
||||||
context: {},
|
|
||||||
dependencies: s.dependencies || [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
plan.steps = steps;
|
|
||||||
log.info(`Plan created with ${steps.length} steps`);
|
|
||||||
onProgress?.(plan, `已规划 ${steps.length} 个步骤`);
|
|
||||||
|
|
||||||
// Phase 2: 执行 - 按依赖顺序执行各步骤
|
|
||||||
plan.status = 'executing';
|
|
||||||
const stepResults: Map<string, AgentResult> = new Map();
|
|
||||||
|
|
||||||
// 拓扑排序
|
|
||||||
const sortedSteps = this.topologicalSort(steps);
|
|
||||||
|
|
||||||
for (const step of sortedSteps) {
|
|
||||||
// 等待依赖完成
|
|
||||||
for (const depId of step.dependencies) {
|
|
||||||
const depResult = stepResults.get(depId);
|
|
||||||
if (depResult?.data) {
|
|
||||||
step.context[depId] = depResult.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据工具类型选择 Agent
|
|
||||||
const agentType = this.mapToolToAgentType(step.type);
|
|
||||||
const executor = this.spawnAgent(agentType, { name: `executor-${step.id}` });
|
|
||||||
plan.agentAssignments.set(step.id, executor.id);
|
|
||||||
|
|
||||||
onProgress?.(plan, `正在执行: ${step.description}`);
|
|
||||||
|
|
||||||
const result = await executor.execute(step);
|
|
||||||
stepResults.set(step.id, result);
|
|
||||||
plan.results.push(result);
|
|
||||||
|
|
||||||
// 执行完毕,终止 executor
|
|
||||||
await this.terminateAgent(executor.id);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
log.warn(`Step failed: ${step.id} - ${result.error}`);
|
|
||||||
// 继续执行其他不依赖此步骤的任务
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: 整合 - 使用 Combiner Agent 汇总结果
|
|
||||||
onProgress?.(plan, '正在整合结果...');
|
|
||||||
|
|
||||||
const combiner = this.spawnAgent('combiner', { name: 'result-combiner' });
|
|
||||||
const combineResult = await combiner.execute({
|
|
||||||
id: generateId('task'),
|
|
||||||
type: 'combine',
|
|
||||||
description: '整合所有步骤的结果',
|
|
||||||
params: {
|
|
||||||
goal,
|
|
||||||
results: plan.results.map((r, i) => ({
|
|
||||||
...r,
|
|
||||||
description: steps[i]?.description,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
context: {},
|
|
||||||
dependencies: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
await this.terminateAgent(planner.id);
|
|
||||||
await this.terminateAgent(combiner.id);
|
|
||||||
|
|
||||||
plan.status = 'completed';
|
|
||||||
onProgress?.(plan, '任务完成');
|
|
||||||
|
|
||||||
log.info(`Multi-agent execution completed: ${planId}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
taskId: planId,
|
|
||||||
agentId: 'orchestrator',
|
|
||||||
success: combineResult.success,
|
|
||||||
data: {
|
|
||||||
plan: plan.goal,
|
|
||||||
stepsExecuted: plan.results.length,
|
|
||||||
stepsSucceeded: plan.results.filter(r => r.success).length,
|
|
||||||
report: combineResult.data?.report || '',
|
|
||||||
stepResults: plan.results,
|
|
||||||
},
|
|
||||||
duration: Date.now() - plan.createdAt.getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
plan.status = 'failed';
|
|
||||||
log.error(`Multi-agent execution failed: ${error.message}`);
|
|
||||||
|
|
||||||
// 清理所有 Agent
|
|
||||||
for (const [id] of this.agents) {
|
|
||||||
await this.terminateAgent(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
taskId: planId,
|
|
||||||
agentId: 'orchestrator',
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
duration: Date.now() - plan.createdAt.getTime(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取活跃计划
|
|
||||||
getActivePlans(): MultiAgentPlan[] {
|
|
||||||
return Array.from(this.activePlans.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有 Agent
|
|
||||||
getAgents(): BaseAgent[] {
|
|
||||||
return Array.from(this.agents.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapToolToAgentType(tool: string): AgentType {
|
|
||||||
switch (tool) {
|
|
||||||
case 'browser': return 'browser';
|
|
||||||
case 'file': return 'file';
|
|
||||||
case 'terminal': return 'terminal';
|
|
||||||
case 'ai':
|
|
||||||
case 'api':
|
|
||||||
default: return 'custom'; // AIAnalysisAgent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private topologicalSort(steps: AgentTask[]): AgentTask[] {
|
|
||||||
const sorted: AgentTask[] = [];
|
|
||||||
const visited = new Set<string>();
|
|
||||||
|
|
||||||
const visit = (step: AgentTask) => {
|
|
||||||
if (visited.has(step.id)) return;
|
|
||||||
visited.add(step.id);
|
|
||||||
|
|
||||||
for (const depId of step.dependencies) {
|
|
||||||
const dep = steps.find(s => s.id === depId);
|
|
||||||
if (dep) visit(dep);
|
|
||||||
}
|
|
||||||
|
|
||||||
sorted.push(step);
|
|
||||||
};
|
|
||||||
|
|
||||||
steps.forEach(visit);
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
async shutdown(): Promise<void> {
|
|
||||||
for (const [id] of this.agents) {
|
|
||||||
await this.terminateAgent(id);
|
|
||||||
}
|
|
||||||
this.messageBus.clear();
|
|
||||||
this.activePlans.clear();
|
|
||||||
log.info('AgentOrchestrator shutdown');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// 多 Agent 协作系统 - 类型定义
|
|
||||||
|
|
||||||
export type AgentType = 'planner' | 'browser' | 'file' | 'terminal' | 'combiner' | 'custom';
|
|
||||||
export type AgentStatus = 'idle' | 'busy' | 'error' | 'terminated';
|
|
||||||
|
|
||||||
export interface AgentConfig {
|
|
||||||
name?: string;
|
|
||||||
systemPrompt?: string;
|
|
||||||
model?: string;
|
|
||||||
maxRetries?: number;
|
|
||||||
timeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentInfo {
|
|
||||||
id: string;
|
|
||||||
type: AgentType;
|
|
||||||
name: string;
|
|
||||||
status: AgentStatus;
|
|
||||||
capabilities: string[];
|
|
||||||
currentTaskId?: string;
|
|
||||||
createdAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentTask {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
params: Record<string, any>;
|
|
||||||
context: Record<string, any>;
|
|
||||||
dependencies: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentResult {
|
|
||||||
taskId: string;
|
|
||||||
agentId: string;
|
|
||||||
success: boolean;
|
|
||||||
data?: any;
|
|
||||||
error?: string;
|
|
||||||
duration: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息总线
|
|
||||||
export interface AgentMessage {
|
|
||||||
id: string;
|
|
||||||
from: string;
|
|
||||||
to: string; // agent ID 或 '*' 表示广播
|
|
||||||
type: 'task' | 'result' | 'status' | 'data' | 'error' | 'control';
|
|
||||||
payload: any;
|
|
||||||
timestamp: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MessageHandler = (message: AgentMessage) => void | Promise<void>;
|
|
||||||
|
|
||||||
// 多 Agent 任务
|
|
||||||
export interface MultiAgentPlan {
|
|
||||||
id: string;
|
|
||||||
goal: string;
|
|
||||||
steps: AgentTask[];
|
|
||||||
agentAssignments: Map<string, string>; // taskId -> agentId
|
|
||||||
status: 'planning' | 'executing' | 'completed' | 'failed';
|
|
||||||
results: AgentResult[];
|
|
||||||
createdAt: Date;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './proactive';
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// 主动服务系统
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('Proactive');
|
|
||||||
|
|
||||||
export interface ScheduledTask {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
channel: string;
|
|
||||||
schedule: {
|
|
||||||
type: 'once' | 'daily' | 'weekly' | 'cron';
|
|
||||||
time: string;
|
|
||||||
timezone: string;
|
|
||||||
};
|
|
||||||
task: {
|
|
||||||
type: string;
|
|
||||||
prompt: string;
|
|
||||||
};
|
|
||||||
status: 'active' | 'paused' | 'completed';
|
|
||||||
lastRun?: Date;
|
|
||||||
nextRun?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProactiveServiceSystem {
|
|
||||||
private tasks: Map<string, ScheduledTask> = new Map();
|
|
||||||
private cronJobs: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
async scheduleTask(task: ScheduledTask): Promise<void> {
|
|
||||||
task.id = task.id || this.generateId();
|
|
||||||
task.status = 'active';
|
|
||||||
|
|
||||||
this.tasks.set(task.id, task);
|
|
||||||
|
|
||||||
// node-cron 集成(可选依赖)
|
|
||||||
try {
|
|
||||||
const cron = require('node-cron');
|
|
||||||
const cronExpr = this.toCronExpression(task);
|
|
||||||
if (cronExpr) {
|
|
||||||
const job = cron.schedule(cronExpr, () => {
|
|
||||||
log.info(`Cron triggered: ${task.id}`);
|
|
||||||
// TODO: 执行实际任务逻辑
|
|
||||||
task.lastRun = new Date();
|
|
||||||
}, { timezone: task.schedule.timezone });
|
|
||||||
this.cronJobs.set(task.id, job);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
log.debug('node-cron not available, scheduling in-memory only');
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`Task scheduled: ${task.id} (${task.schedule.type} at ${task.schedule.time})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancelTask(taskId: string): Promise<void> {
|
|
||||||
const task = this.tasks.get(taskId);
|
|
||||||
if (task) {
|
|
||||||
task.status = 'paused';
|
|
||||||
// TODO: 取消 cron job
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async listTasks(userId: string): Promise<ScheduledTask[]> {
|
|
||||||
return Array.from(this.tasks.values()).filter(t => t.userId === userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private toCronExpression(task: ScheduledTask): string | null {
|
|
||||||
const { type, time } = task.schedule;
|
|
||||||
if (type === 'cron') return time; // already a cron expression
|
|
||||||
|
|
||||||
// Parse HH:MM format
|
|
||||||
const parts = time.split(':');
|
|
||||||
if (parts.length !== 2) return null;
|
|
||||||
const [hour, minute] = parts;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'daily':
|
|
||||||
return `${minute} ${hour} * * *`;
|
|
||||||
case 'weekly':
|
|
||||||
return `${minute} ${hour} * * 1`; // Monday
|
|
||||||
case 'once':
|
|
||||||
return null; // handled separately via setTimeout
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateId(): string {
|
|
||||||
return generateId('cron');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
// 远程执行系统 - 实现类
|
|
||||||
import type {
|
|
||||||
RemoteExecutionSystem,
|
|
||||||
Device,
|
|
||||||
Task,
|
|
||||||
TaskStatus,
|
|
||||||
StatusHandler,
|
|
||||||
Result,
|
|
||||||
DeviceStatus
|
|
||||||
} from './types';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('RemoteExecution');
|
|
||||||
|
|
||||||
export class RemoteExecutionEngine implements RemoteExecutionSystem {
|
|
||||||
private devices: Map<string, Device> = new Map();
|
|
||||||
private tasks: Map<string, Task> = new Map();
|
|
||||||
private subscriptions: Map<string, StatusHandler[]> = new Map();
|
|
||||||
private runningCount = 0;
|
|
||||||
private maxConcurrent: number;
|
|
||||||
private pendingQueue: Task[] = [];
|
|
||||||
|
|
||||||
constructor(maxConcurrent: number = 5) {
|
|
||||||
this.maxConcurrent = maxConcurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerDevice(device: Device): Promise<void> {
|
|
||||||
this.devices.set(device.id, device);
|
|
||||||
log.info(`Device registered: ${device.id} (${device.platform})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async heartbeat(deviceId: string): Promise<DeviceStatus> {
|
|
||||||
const device = this.devices.get(deviceId);
|
|
||||||
if (!device) {
|
|
||||||
throw new Error(`Device not found: ${deviceId}`);
|
|
||||||
}
|
|
||||||
device.lastHeartbeat = new Date();
|
|
||||||
return device.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async submitTask(task: Task): Promise<string> {
|
|
||||||
task.id = task.id || generateId('task');
|
|
||||||
task.status = 'pending';
|
|
||||||
task.createdAt = new Date();
|
|
||||||
this.tasks.set(task.id, task);
|
|
||||||
|
|
||||||
log.info(`Task submitted: ${task.id} (priority: ${task.priority})`);
|
|
||||||
|
|
||||||
if (this.runningCount < this.maxConcurrent) {
|
|
||||||
this.executeTask(task).catch(err => log.error(`Task execution error: ${err.message}`));
|
|
||||||
} else {
|
|
||||||
this.pendingQueue.push(task);
|
|
||||||
log.debug(`Task queued (${this.pendingQueue.length} pending)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancelTask(taskId: string): Promise<void> {
|
|
||||||
const task = this.tasks.get(taskId);
|
|
||||||
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
||||||
task.status = 'cancelled';
|
|
||||||
this.notifySubscribers(taskId, 'cancelled');
|
|
||||||
log.info(`Task cancelled: ${taskId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getStatus(taskId: string): Promise<TaskStatus> {
|
|
||||||
const task = this.tasks.get(taskId);
|
|
||||||
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
||||||
return task.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTask(taskId: string): Task | undefined {
|
|
||||||
return this.tasks.get(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDevice(deviceId: string): Device | undefined {
|
|
||||||
return this.devices.get(deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
listDevices(): Device[] {
|
|
||||||
return Array.from(this.devices.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
listTasks(filter?: { status?: TaskStatus; userId?: string }): Task[] {
|
|
||||||
let tasks = Array.from(this.tasks.values());
|
|
||||||
if (filter?.status) tasks = tasks.filter(t => t.status === filter.status);
|
|
||||||
if (filter?.userId) tasks = tasks.filter(t => t.userId === filter.userId);
|
|
||||||
return tasks.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(taskId: string, handler: StatusHandler): void {
|
|
||||||
if (!this.subscriptions.has(taskId)) {
|
|
||||||
this.subscriptions.set(taskId, []);
|
|
||||||
}
|
|
||||||
this.subscriptions.get(taskId)!.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushResult(taskId: string, result: Result): Promise<void> {
|
|
||||||
const task = this.tasks.get(taskId);
|
|
||||||
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
||||||
task.result = result;
|
|
||||||
task.status = result.success ? 'completed' : 'failed';
|
|
||||||
task.completedAt = new Date();
|
|
||||||
this.notifySubscribers(taskId, task.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
getStats(): { total: number; running: number; pending: number; completed: number; failed: number } {
|
|
||||||
const tasks = Array.from(this.tasks.values());
|
|
||||||
return {
|
|
||||||
total: tasks.length,
|
|
||||||
running: tasks.filter(t => t.status === 'running').length,
|
|
||||||
pending: tasks.filter(t => t.status === 'pending').length,
|
|
||||||
completed: tasks.filter(t => t.status === 'completed').length,
|
|
||||||
failed: tasks.filter(t => t.status === 'failed').length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeTask(task: Task): Promise<void> {
|
|
||||||
this.runningCount++;
|
|
||||||
try {
|
|
||||||
task.status = 'running';
|
|
||||||
task.startedAt = new Date();
|
|
||||||
this.notifySubscribers(task.id, 'running');
|
|
||||||
log.info(`Executing task: ${task.id}`);
|
|
||||||
|
|
||||||
// TODO: 集成 OpenClaw SDK 实际执行
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
await this.pushResult(task.id, {
|
|
||||||
taskId: task.id,
|
|
||||||
success: true,
|
|
||||||
data: { message: 'Task completed successfully' }
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
await this.pushResult(task.id, {
|
|
||||||
taskId: task.id,
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.runningCount--;
|
|
||||||
this.processNextInQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private processNextInQueue(): void {
|
|
||||||
if (this.pendingQueue.length > 0 && this.runningCount < this.maxConcurrent) {
|
|
||||||
// 按优先级排序
|
|
||||||
this.pendingQueue.sort((a, b) => {
|
|
||||||
const priority: Record<string, number> = { high: 0, normal: 1, low: 2 };
|
|
||||||
return (priority[a.priority] ?? 1) - (priority[b.priority] ?? 1);
|
|
||||||
});
|
|
||||||
const next = this.pendingQueue.shift()!;
|
|
||||||
this.executeTask(next).catch(err => log.error(`Queued task error: ${err.message}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private notifySubscribers(taskId: string, status: TaskStatus, progress?: number): void {
|
|
||||||
const handlers = this.subscriptions.get(taskId);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach(handler => handler(status, progress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './types';
|
|
||||||
export * from './engine';
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// 远程执行系统 - 核心接口
|
|
||||||
export interface Device {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
userId: string;
|
|
||||||
platform: 'macos' | 'windows' | 'linux';
|
|
||||||
capabilities: string[];
|
|
||||||
status: 'online' | 'offline' | 'busy';
|
|
||||||
lastHeartbeat: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Task {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
deviceId: string;
|
|
||||||
channel: string;
|
|
||||||
type: 'immediate' | 'scheduled';
|
|
||||||
priority: 'high' | 'normal' | 'low';
|
|
||||||
payload: any;
|
|
||||||
status: TaskStatus;
|
|
||||||
result?: any;
|
|
||||||
createdAt: Date;
|
|
||||||
startedAt?: Date;
|
|
||||||
completedAt?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
||||||
|
|
||||||
export interface RemoteExecutionSystem {
|
|
||||||
registerDevice(device: Device): Promise<void>;
|
|
||||||
heartbeat(deviceId: string): Promise<DeviceStatus>;
|
|
||||||
submitTask(task: Task): Promise<string>;
|
|
||||||
cancelTask(taskId: string): Promise<void>;
|
|
||||||
getStatus(taskId: string): Promise<TaskStatus>;
|
|
||||||
subscribe(taskId: string, handler: StatusHandler): void;
|
|
||||||
pushResult(taskId: string, result: Result): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StatusHandler = (status: TaskStatus, progress?: number) => void;
|
|
||||||
export type DeviceStatus = 'online' | 'offline' | 'busy';
|
|
||||||
export interface Result {
|
|
||||||
taskId: string;
|
|
||||||
success: boolean;
|
|
||||||
data?: any;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './types';
|
|
||||||
export * from './orchestrator';
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// 任务编排引擎 - 实现类
|
|
||||||
import type {
|
|
||||||
TaskOrchestrationEngine,
|
|
||||||
TaskPlan,
|
|
||||||
TaskStep,
|
|
||||||
ExecutionResult,
|
|
||||||
Progress,
|
|
||||||
StepStatus
|
|
||||||
} from './types';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('TaskOrchestrator');
|
|
||||||
|
|
||||||
export class TaskOrchestrator implements TaskOrchestrationEngine {
|
|
||||||
private plans: Map<string, TaskPlan> = new Map();
|
|
||||||
|
|
||||||
async plan(goal: string, context: any): Promise<TaskPlan> {
|
|
||||||
// TODO: 使用 AI 规划任务(后续实现)
|
|
||||||
// 这里先返回一个简单的示例计划
|
|
||||||
const steps: TaskStep[] = [
|
|
||||||
{
|
|
||||||
id: 'step_1',
|
|
||||||
description: '分析任务需求',
|
|
||||||
tool: 'ai',
|
|
||||||
params: { goal },
|
|
||||||
dependencies: [],
|
|
||||||
status: 'pending'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'step_2',
|
|
||||||
description: '执行任务',
|
|
||||||
tool: 'executor',
|
|
||||||
params: { goal },
|
|
||||||
dependencies: ['step_1'],
|
|
||||||
status: 'pending'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'step_3',
|
|
||||||
description: '整理结果',
|
|
||||||
tool: 'combiner',
|
|
||||||
params: {},
|
|
||||||
dependencies: ['step_2'],
|
|
||||||
status: 'pending'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const plan: TaskPlan = {
|
|
||||||
id: this.generateId(),
|
|
||||||
goal,
|
|
||||||
steps,
|
|
||||||
dependencies: new Map(),
|
|
||||||
context: new Map(Object.entries(context)),
|
|
||||||
status: 'planned',
|
|
||||||
progress: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.plans.set(plan.id, plan);
|
|
||||||
log.info(`Plan created: ${plan.id} with ${steps.length} steps`);
|
|
||||||
|
|
||||||
return plan;
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(plan: TaskPlan): Promise<ExecutionResult> {
|
|
||||||
plan.status = 'executing';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 拓扑排序
|
|
||||||
const sortedSteps = this.topologicalSort(plan.steps);
|
|
||||||
|
|
||||||
for (const step of sortedSteps) {
|
|
||||||
// 检查依赖
|
|
||||||
await this.waitForDependencies(step, plan);
|
|
||||||
|
|
||||||
// 执行步骤
|
|
||||||
step.status = 'running';
|
|
||||||
plan.progress = this.calculateProgress(plan);
|
|
||||||
|
|
||||||
log.info(`Executing step: ${step.id} - ${step.description}`);
|
|
||||||
|
|
||||||
// TODO: 实际执行逻辑(后续实现)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
step.status = 'completed';
|
|
||||||
step.result = { message: 'Step completed' };
|
|
||||||
|
|
||||||
plan.progress = this.calculateProgress(plan);
|
|
||||||
}
|
|
||||||
|
|
||||||
plan.status = 'completed';
|
|
||||||
plan.progress = 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
planId: plan.id,
|
|
||||||
status: 'completed',
|
|
||||||
results: plan.steps.map(s => s.result)
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
plan.status = 'failed';
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProgress(planId: string): Promise<Progress> {
|
|
||||||
const plan = this.plans.get(planId);
|
|
||||||
if (!plan) {
|
|
||||||
throw new Error(`Plan not found: ${planId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const completed = plan.steps.filter(s => s.status === 'completed').length;
|
|
||||||
const percentage = (plan.progress * 100).toFixed(1) + '%';
|
|
||||||
|
|
||||||
return {
|
|
||||||
planId: plan.id,
|
|
||||||
goal: plan.goal,
|
|
||||||
total: plan.steps.length,
|
|
||||||
completed,
|
|
||||||
current: plan.steps.find(s => s.status === 'running')?.description || '',
|
|
||||||
percentage,
|
|
||||||
steps: plan.steps.map(s => ({
|
|
||||||
description: s.description,
|
|
||||||
status: s.status,
|
|
||||||
result: s.result
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async pause(planId: string): Promise<void> {
|
|
||||||
const plan = this.plans.get(planId);
|
|
||||||
if (plan) {
|
|
||||||
plan.status = 'paused';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resume(planId: string): Promise<void> {
|
|
||||||
const plan = this.plans.get(planId);
|
|
||||||
if (plan && plan.status === 'paused') {
|
|
||||||
plan.status = 'executing';
|
|
||||||
// 继续执行(后续实现)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancel(planId: string): Promise<void> {
|
|
||||||
const plan = this.plans.get(planId);
|
|
||||||
if (plan) {
|
|
||||||
plan.status = 'failed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private topologicalSort(steps: TaskStep[]): TaskStep[] {
|
|
||||||
// 简单实现:按依赖顺序排序
|
|
||||||
const sorted: TaskStep[] = [];
|
|
||||||
const visited = new Set<string>();
|
|
||||||
|
|
||||||
const visit = (step: TaskStep) => {
|
|
||||||
if (visited.has(step.id)) return;
|
|
||||||
visited.add(step.id);
|
|
||||||
|
|
||||||
for (const depId of step.dependencies) {
|
|
||||||
const dep = steps.find(s => s.id === depId);
|
|
||||||
if (dep) visit(dep);
|
|
||||||
}
|
|
||||||
|
|
||||||
sorted.push(step);
|
|
||||||
};
|
|
||||||
|
|
||||||
steps.forEach(visit);
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async waitForDependencies(step: TaskStep, plan: TaskPlan): Promise<void> {
|
|
||||||
for (const depId of step.dependencies) {
|
|
||||||
const dep = plan.steps.find(s => s.id === depId);
|
|
||||||
if (dep && dep.status !== 'completed') {
|
|
||||||
// 等待依赖完成(简单实现)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateProgress(plan: TaskPlan): number {
|
|
||||||
const completed = plan.steps.filter(s => s.status === 'completed').length;
|
|
||||||
return completed / plan.steps.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
listPlans(filter?: { status?: string }): TaskPlan[] {
|
|
||||||
let plans = Array.from(this.plans.values());
|
|
||||||
if (filter?.status) plans = plans.filter(p => p.status === filter.status);
|
|
||||||
return plans;
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateId(): string {
|
|
||||||
return generateId('plan');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// 任务编排引擎 - 类型定义
|
|
||||||
export interface TaskPlan {
|
|
||||||
id: string;
|
|
||||||
goal: string;
|
|
||||||
steps: TaskStep[];
|
|
||||||
dependencies: Map<string, string[]>;
|
|
||||||
context: Map<string, any>;
|
|
||||||
status: PlanStatus;
|
|
||||||
progress: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TaskStep {
|
|
||||||
id: string;
|
|
||||||
description: string;
|
|
||||||
tool: string;
|
|
||||||
params: any;
|
|
||||||
dependencies: string[];
|
|
||||||
status: StepStatus;
|
|
||||||
result?: any;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PlanStatus = 'planned' | 'executing' | 'completed' | 'failed' | 'paused';
|
|
||||||
export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
||||||
|
|
||||||
export interface ExecutionResult {
|
|
||||||
planId: string;
|
|
||||||
status: PlanStatus;
|
|
||||||
results: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Progress {
|
|
||||||
planId: string;
|
|
||||||
goal: string;
|
|
||||||
total: number;
|
|
||||||
completed: number;
|
|
||||||
current: string;
|
|
||||||
percentage: string;
|
|
||||||
steps: Array<{
|
|
||||||
description: string;
|
|
||||||
status: StepStatus;
|
|
||||||
result?: any;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TaskOrchestrationEngine {
|
|
||||||
plan(goal: string, context: any): Promise<TaskPlan>;
|
|
||||||
execute(plan: TaskPlan): Promise<ExecutionResult>;
|
|
||||||
getProgress(planId: string): Promise<Progress>;
|
|
||||||
pause(planId: string): Promise<void>;
|
|
||||||
resume(planId: string): Promise<void>;
|
|
||||||
cancel(planId: string): Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// ZCLAW 数据库管理
|
|
||||||
import Database from 'better-sqlite3';
|
|
||||||
import { existsSync, mkdirSync } from 'fs';
|
|
||||||
import { dirname } from 'path';
|
|
||||||
import { SCHEMA_SQL } from './schema';
|
|
||||||
import { createLogger } from '../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('Database');
|
|
||||||
|
|
||||||
let _db: Database.Database | null = null;
|
|
||||||
|
|
||||||
export function initDatabase(dbPath: string): Database.Database {
|
|
||||||
// 确保目录存在
|
|
||||||
const dir = dirname(dbPath);
|
|
||||||
if (!existsSync(dir)) {
|
|
||||||
mkdirSync(dir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
_db = new Database(dbPath);
|
|
||||||
|
|
||||||
// 启用 WAL 模式提升性能
|
|
||||||
_db.pragma('journal_mode = WAL');
|
|
||||||
_db.pragma('foreign_keys = ON');
|
|
||||||
|
|
||||||
// 创建表
|
|
||||||
_db.exec(SCHEMA_SQL);
|
|
||||||
|
|
||||||
log.info(`Database initialized at ${dbPath}`);
|
|
||||||
return _db;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDatabase(): Database.Database {
|
|
||||||
if (!_db) {
|
|
||||||
throw new Error('Database not initialized. Call initDatabase() first.');
|
|
||||||
}
|
|
||||||
return _db;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeDatabase(): void {
|
|
||||||
if (_db) {
|
|
||||||
_db.close();
|
|
||||||
_db = null;
|
|
||||||
log.info('Database closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通用 CRUD 辅助
|
|
||||||
export class BaseDAO<T extends Record<string, any>> {
|
|
||||||
constructor(
|
|
||||||
protected tableName: string,
|
|
||||||
protected db: Database.Database = getDatabase()
|
|
||||||
) {}
|
|
||||||
|
|
||||||
findById(id: string): T | undefined {
|
|
||||||
return this.db.prepare(`SELECT * FROM ${this.tableName} WHERE id = ?`).get(id) as T | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
findAll(where?: string, params?: any[]): T[] {
|
|
||||||
const sql = where
|
|
||||||
? `SELECT * FROM ${this.tableName} WHERE ${where}`
|
|
||||||
: `SELECT * FROM ${this.tableName}`;
|
|
||||||
return (params ? this.db.prepare(sql).all(...params) : this.db.prepare(sql).all()) as T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(data: Partial<T>): void {
|
|
||||||
const keys = Object.keys(data);
|
|
||||||
const placeholders = keys.map(() => '?').join(', ');
|
|
||||||
const sql = `INSERT OR REPLACE INTO ${this.tableName} (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
||||||
this.db.prepare(sql).run(...Object.values(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
update(id: string, data: Partial<T>): void {
|
|
||||||
const sets = Object.keys(data).map(k => `${k} = ?`).join(', ');
|
|
||||||
const sql = `UPDATE ${this.tableName} SET ${sets} WHERE id = ?`;
|
|
||||||
this.db.prepare(sql).run(...Object.values(data), id);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(id: string): void {
|
|
||||||
this.db.prepare(`DELETE FROM ${this.tableName} WHERE id = ?`).run(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
count(where?: string, params?: any[]): number {
|
|
||||||
const sql = where
|
|
||||||
? `SELECT COUNT(*) as count FROM ${this.tableName} WHERE ${where}`
|
|
||||||
: `SELECT COUNT(*) as count FROM ${this.tableName}`;
|
|
||||||
const result = params
|
|
||||||
? this.db.prepare(sql).get(...params) as { count: number }
|
|
||||||
: this.db.prepare(sql).get() as { count: number };
|
|
||||||
return result.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { initDatabase, getDatabase, closeDatabase, BaseDAO } from './database';
|
|
||||||
export { SCHEMA_SQL } from './schema';
|
|
||||||
118
src/db/schema.ts
118
src/db/schema.ts
@@ -1,118 +0,0 @@
|
|||||||
// ZCLAW 数据库 Schema 定义
|
|
||||||
export const SCHEMA_SQL = `
|
|
||||||
-- 用户表
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
avatar TEXT DEFAULT '',
|
|
||||||
preferences TEXT DEFAULT '{}',
|
|
||||||
patterns TEXT DEFAULT '{}',
|
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
|
||||||
updated_at TEXT DEFAULT (datetime('now'))
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 设备表
|
|
||||||
CREATE TABLE IF NOT EXISTS devices (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
platform TEXT NOT NULL CHECK (platform IN ('macos', 'windows', 'linux')),
|
|
||||||
capabilities TEXT DEFAULT '[]',
|
|
||||||
status TEXT DEFAULT 'offline' CHECK (status IN ('online', 'offline', 'busy')),
|
|
||||||
last_heartbeat TEXT,
|
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 任务表
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
device_id TEXT,
|
|
||||||
channel TEXT DEFAULT '',
|
|
||||||
type TEXT NOT NULL CHECK (type IN ('immediate', 'scheduled')),
|
|
||||||
priority TEXT DEFAULT 'normal' CHECK (priority IN ('high', 'normal', 'low')),
|
|
||||||
payload TEXT DEFAULT '{}',
|
|
||||||
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed', 'cancelled')),
|
|
||||||
result TEXT,
|
|
||||||
error TEXT,
|
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
|
||||||
started_at TEXT,
|
|
||||||
completed_at TEXT,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 任务计划表
|
|
||||||
CREATE TABLE IF NOT EXISTS task_plans (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
goal TEXT NOT NULL,
|
|
||||||
steps TEXT DEFAULT '[]',
|
|
||||||
status TEXT DEFAULT 'planned' CHECK (status IN ('planned', 'executing', 'completed', 'failed', 'paused')),
|
|
||||||
progress REAL DEFAULT 0,
|
|
||||||
context TEXT DEFAULT '{}',
|
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
|
||||||
updated_at TEXT DEFAULT (datetime('now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 记忆事件表
|
|
||||||
CREATE TABLE IF NOT EXISTS memory_events (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
metadata TEXT DEFAULT '{}',
|
|
||||||
timestamp TEXT DEFAULT (datetime('now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 定时任务表
|
|
||||||
CREATE TABLE IF NOT EXISTS scheduled_tasks (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
channel TEXT DEFAULT '',
|
|
||||||
schedule_type TEXT NOT NULL CHECK (schedule_type IN ('once', 'daily', 'weekly', 'cron')),
|
|
||||||
schedule_time TEXT NOT NULL,
|
|
||||||
timezone TEXT DEFAULT 'Asia/Shanghai',
|
|
||||||
task_type TEXT NOT NULL,
|
|
||||||
task_prompt TEXT NOT NULL,
|
|
||||||
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed')),
|
|
||||||
last_run TEXT,
|
|
||||||
next_run TEXT,
|
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 对话历史表
|
|
||||||
CREATE TABLE IF NOT EXISTS conversations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
agent_id TEXT DEFAULT 'zclaw',
|
|
||||||
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
metadata TEXT DEFAULT '{}',
|
|
||||||
timestamp TEXT DEFAULT (datetime('now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Agent 表
|
|
||||||
CREATE TABLE IF NOT EXISTS agents (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL CHECK (type IN ('planner', 'browser', 'file', 'terminal', 'combiner', 'custom')),
|
|
||||||
config TEXT DEFAULT '{}',
|
|
||||||
status TEXT DEFAULT 'idle' CHECK (status IN ('idle', 'busy', 'error', 'terminated')),
|
|
||||||
created_at TEXT DEFAULT (datetime('now'))
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 索引
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tasks_user ON tasks(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_memory_user ON memory_events(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_memory_timestamp ON memory_events(timestamp);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_conversations_user ON conversations(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_conversations_timestamp ON conversations(timestamp);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_scheduled_user ON scheduled_tasks(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_devices_user ON devices(user_id);
|
|
||||||
`;
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// 飞书 IM 适配器
|
|
||||||
import type { IMAdapter, IMSendOptions, IMMessageHandler, IMMessage } from '../types';
|
|
||||||
import { createLogger } from '../../utils/logger';
|
|
||||||
import { generateId } from '../../utils/id';
|
|
||||||
|
|
||||||
const log = createLogger('FeishuAdapter');
|
|
||||||
|
|
||||||
export class FeishuAdapter implements IMAdapter {
|
|
||||||
name = 'feishu';
|
|
||||||
private appId: string;
|
|
||||||
private appSecret: string;
|
|
||||||
private connected = false;
|
|
||||||
private accessToken = '';
|
|
||||||
private handlers: IMMessageHandler[] = [];
|
|
||||||
private pollingInterval?: ReturnType<typeof setInterval>;
|
|
||||||
|
|
||||||
constructor(appId: string, appSecret: string) {
|
|
||||||
this.appId = appId;
|
|
||||||
this.appSecret = appSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
|
||||||
if (!this.appId || !this.appSecret) {
|
|
||||||
log.warn('Feishu credentials not configured, running in mock mode');
|
|
||||||
this.connected = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取 tenant_access_token
|
|
||||||
const response = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
app_id: this.appId,
|
|
||||||
app_secret: this.appSecret,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
if (data.code === 0) {
|
|
||||||
this.accessToken = data.tenant_access_token;
|
|
||||||
this.connected = true;
|
|
||||||
log.info('Feishu connected successfully');
|
|
||||||
} else {
|
|
||||||
throw new Error(`Feishu auth failed: ${data.msg}`);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Feishu connection failed: ${error.message}`);
|
|
||||||
// 降级到 mock 模式
|
|
||||||
this.connected = true;
|
|
||||||
log.warn('Running in mock mode');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnect(): Promise<void> {
|
|
||||||
if (this.pollingInterval) {
|
|
||||||
clearInterval(this.pollingInterval);
|
|
||||||
}
|
|
||||||
this.connected = false;
|
|
||||||
this.accessToken = '';
|
|
||||||
log.info('Feishu disconnected');
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(options: IMSendOptions): Promise<void> {
|
|
||||||
if (!this.accessToken) {
|
|
||||||
log.warn(`[Mock] Send to ${options.channelId}: ${options.content.slice(0, 100)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgType = options.contentType === 'card' ? 'interactive' : 'text';
|
|
||||||
const content = msgType === 'text'
|
|
||||||
? JSON.stringify({ text: options.content })
|
|
||||||
: options.content;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://open.feishu.cn/open-apis/im/v1/messages', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
receive_id: options.channelId,
|
|
||||||
msg_type: msgType,
|
|
||||||
content,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: any = await response.json();
|
|
||||||
if (data.code !== 0) {
|
|
||||||
log.error(`Feishu send failed: ${data.msg}`);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Feishu send error: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(handler: IMMessageHandler): void {
|
|
||||||
this.handlers.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected(): boolean {
|
|
||||||
return this.connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟接收消息(用于本地测试)
|
|
||||||
async simulateMessage(content: string, userId: string = 'test_user'): Promise<void> {
|
|
||||||
const message: IMMessage = {
|
|
||||||
id: generateId('msg'),
|
|
||||||
channelType: 'feishu',
|
|
||||||
channelId: 'test_channel',
|
|
||||||
userId,
|
|
||||||
userName: '测试用户',
|
|
||||||
content,
|
|
||||||
contentType: 'text',
|
|
||||||
timestamp: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const handler of this.handlers) {
|
|
||||||
await handler(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// IM 网关 - 统一消息路由
|
|
||||||
import type { IMAdapter, IMMessage, IMSendOptions, IMMessageHandler } from './types';
|
|
||||||
import { createLogger } from '../utils/logger';
|
|
||||||
|
|
||||||
const log = createLogger('IMGateway');
|
|
||||||
|
|
||||||
export class IMGateway {
|
|
||||||
private adapters: Map<string, IMAdapter> = new Map();
|
|
||||||
private messageHandlers: IMMessageHandler[] = [];
|
|
||||||
|
|
||||||
registerAdapter(adapter: IMAdapter): void {
|
|
||||||
this.adapters.set(adapter.name, adapter);
|
|
||||||
|
|
||||||
// 转发消息到全局处理器
|
|
||||||
adapter.onMessage(async (message) => {
|
|
||||||
log.info(`Message from ${message.channelType}/${message.userId}: ${message.content.slice(0, 50)}...`);
|
|
||||||
for (const handler of this.messageHandlers) {
|
|
||||||
try {
|
|
||||||
await handler(message);
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Message handler error: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info(`IM adapter registered: ${adapter.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(handler: IMMessageHandler): void {
|
|
||||||
this.messageHandlers.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(options: IMSendOptions): Promise<void> {
|
|
||||||
const adapter = this.adapters.get(options.channelType);
|
|
||||||
if (!adapter) {
|
|
||||||
log.error(`No adapter for channel type: ${options.channelType}`);
|
|
||||||
throw new Error(`No adapter for channel type: ${options.channelType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adapter.isConnected()) {
|
|
||||||
log.warn(`Adapter ${options.channelType} not connected, attempting reconnect...`);
|
|
||||||
await adapter.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
await adapter.send(options);
|
|
||||||
log.debug(`Message sent via ${options.channelType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectAll(): Promise<void> {
|
|
||||||
for (const [name, adapter] of this.adapters) {
|
|
||||||
try {
|
|
||||||
await adapter.connect();
|
|
||||||
log.info(`Connected: ${name}`);
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Failed to connect ${name}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnectAll(): Promise<void> {
|
|
||||||
for (const [name, adapter] of this.adapters) {
|
|
||||||
try {
|
|
||||||
await adapter.disconnect();
|
|
||||||
log.info(`Disconnected: ${name}`);
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Failed to disconnect ${name}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAdapter(name: string): IMAdapter | undefined {
|
|
||||||
return this.adapters.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
getConnectedAdapters(): string[] {
|
|
||||||
return Array.from(this.adapters.entries())
|
|
||||||
.filter(([, adapter]) => adapter.isConnected())
|
|
||||||
.map(([name]) => name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export type { IMAdapter, IMMessage, IMSendOptions, IMMessageHandler } from './types';
|
|
||||||
export { IMGateway } from './gateway';
|
|
||||||
export { FeishuAdapter } from './adapters/feishu';
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// IM 网关 - 类型定义
|
|
||||||
|
|
||||||
export interface IMMessage {
|
|
||||||
id: string;
|
|
||||||
channelType: string; // 'feishu' | 'telegram' | 'qq' | 'wecom'
|
|
||||||
channelId: string; // 频道/群组 ID
|
|
||||||
userId: string;
|
|
||||||
userName?: string;
|
|
||||||
content: string;
|
|
||||||
contentType: 'text' | 'image' | 'file' | 'card';
|
|
||||||
metadata?: Record<string, any>;
|
|
||||||
timestamp: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMSendOptions {
|
|
||||||
channelType: string;
|
|
||||||
channelId: string;
|
|
||||||
userId?: string;
|
|
||||||
content: string;
|
|
||||||
contentType?: 'text' | 'card' | 'markdown';
|
|
||||||
metadata?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IMMessageHandler = (message: IMMessage) => void | Promise<void>;
|
|
||||||
|
|
||||||
export interface IMAdapter {
|
|
||||||
name: string;
|
|
||||||
connect(): Promise<void>;
|
|
||||||
disconnect(): Promise<void>;
|
|
||||||
send(options: IMSendOptions): Promise<void>;
|
|
||||||
onMessage(handler: IMMessageHandler): void;
|
|
||||||
isConnected(): boolean;
|
|
||||||
}
|
|
||||||
34
src/index.ts
34
src/index.ts
@@ -1,34 +0,0 @@
|
|||||||
// ZCLAW 入口文件
|
|
||||||
import { ZClawApp } from './app';
|
|
||||||
|
|
||||||
// 导出所有模块
|
|
||||||
export * from './core/remote-execution';
|
|
||||||
export * from './core/task-orchestration';
|
|
||||||
export * from './core/memory';
|
|
||||||
export * from './core/proactive';
|
|
||||||
export * from './core/multi-agent';
|
|
||||||
export * from './core/ai';
|
|
||||||
export * from './im';
|
|
||||||
export * from './db';
|
|
||||||
export * from './config';
|
|
||||||
export * from './utils';
|
|
||||||
export { ZClawApp } from './app';
|
|
||||||
|
|
||||||
// 主启动流程
|
|
||||||
const app = new ZClawApp();
|
|
||||||
|
|
||||||
app.start().catch((error) => {
|
|
||||||
console.error('ZCLAW failed to start:', error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 优雅退出
|
|
||||||
process.on('SIGINT', async () => {
|
|
||||||
await app.shutdown();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGTERM', async () => {
|
|
||||||
await app.shutdown();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// ZCLAW ID 生成工具
|
|
||||||
import { randomBytes } from 'crypto';
|
|
||||||
|
|
||||||
export function generateId(prefix: string = ''): string {
|
|
||||||
const timestamp = Date.now().toString(36);
|
|
||||||
const random = randomBytes(4).toString('hex');
|
|
||||||
return prefix ? `${prefix}_${timestamp}_${random}` : `${timestamp}_${random}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateShortId(): string {
|
|
||||||
return randomBytes(6).toString('base64url');
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export { generateId, generateShortId } from './id';
|
|
||||||
export { createLogger, setLogLevel } from './logger';
|
|
||||||
export type { Logger } from './logger';
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
// ZCLAW 日志系统
|
|
||||||
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
||||||
|
|
||||||
const LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
||||||
debug: 0,
|
|
||||||
info: 1,
|
|
||||||
warn: 2,
|
|
||||||
error: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LEVEL_COLORS: Record<LogLevel, string> = {
|
|
||||||
debug: '\x1b[36m', // cyan
|
|
||||||
info: '\x1b[32m', // green
|
|
||||||
warn: '\x1b[33m', // yellow
|
|
||||||
error: '\x1b[31m', // red
|
|
||||||
};
|
|
||||||
|
|
||||||
const RESET = '\x1b[0m';
|
|
||||||
|
|
||||||
let currentLevel: LogLevel = 'info';
|
|
||||||
|
|
||||||
export function setLogLevel(level: LogLevel): void {
|
|
||||||
currentLevel = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldLog(level: LogLevel): boolean {
|
|
||||||
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTimestamp(): string {
|
|
||||||
return new Date().toISOString().slice(11, 23);
|
|
||||||
}
|
|
||||||
|
|
||||||
function log(level: LogLevel, module: string, message: string, data?: any): void {
|
|
||||||
if (!shouldLog(level)) return;
|
|
||||||
|
|
||||||
const color = LEVEL_COLORS[level];
|
|
||||||
const timestamp = formatTimestamp();
|
|
||||||
const prefix = `${color}[${timestamp}] [${level.toUpperCase()}] [${module}]${RESET}`;
|
|
||||||
|
|
||||||
if (data !== undefined) {
|
|
||||||
console.log(`${prefix} ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data);
|
|
||||||
} else {
|
|
||||||
console.log(`${prefix} ${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLogger(module: string) {
|
|
||||||
return {
|
|
||||||
debug: (message: string, data?: any) => log('debug', module, message, data),
|
|
||||||
info: (message: string, data?: any) => log('info', module, message, data),
|
|
||||||
warn: (message: string, data?: any) => log('warn', module, message, data),
|
|
||||||
error: (message: string, data?: any) => log('error', module, message, data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Logger = ReturnType<typeof createLogger>;
|
|
||||||
Reference in New Issue
Block a user