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