446 lines
13 KiB
TypeScript
446 lines
13 KiB
TypeScript
/**
|
|
* OpenClawEngine.ts
|
|
*
|
|
* Orquestração central de OpenClaw
|
|
* Coordena detecção de padrões, geração de skills e sugestões
|
|
*
|
|
* Fase 4: OpenClaw Embutido
|
|
*/
|
|
|
|
import { PatternDetector, getPatternDetector, DetectedPattern } from "./PatternDetector";
|
|
import { SkillEmergence } from "./SkillEmergence";
|
|
import { EventEmitter } from "events";
|
|
import { Server as SocketIOServer } from "socket.io";
|
|
|
|
interface SkillSuggestion {
|
|
id: string;
|
|
pattern: DetectedPattern;
|
|
suggested_skill_name: string;
|
|
suggested_description: string;
|
|
estimated_automation: string;
|
|
confidence: number;
|
|
created_at: Date;
|
|
status: "pending" | "accepted" | "rejected";
|
|
}
|
|
|
|
interface OpenClawEngineConfig {
|
|
pattern_detection: {
|
|
enabled: boolean;
|
|
min_occurrences: number;
|
|
time_window_days: number;
|
|
confidence_threshold: number;
|
|
scheduled_check_interval_minutes: number;
|
|
};
|
|
suggestions: {
|
|
enabled: boolean;
|
|
auto_suggest: boolean;
|
|
channels: string[];
|
|
priority_threshold: number;
|
|
};
|
|
emergence: {
|
|
create_as_draft: boolean;
|
|
auto_publish: boolean;
|
|
notify_admins: boolean;
|
|
skill_prefix: string;
|
|
};
|
|
logging: {
|
|
enabled: boolean;
|
|
store_patterns: boolean;
|
|
store_in_kg: boolean;
|
|
audit_trail: boolean;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* OpenClawEngine
|
|
*
|
|
* Motor central que coordena todo o fluxo de OpenClaw:
|
|
* Pattern Detection → Suggestions → Skill Generation → Dev Center
|
|
*/
|
|
export class OpenClawEngine extends EventEmitter {
|
|
private patternDetector: PatternDetector;
|
|
private skillEmergence: SkillEmergence;
|
|
private config: OpenClawEngineConfig;
|
|
private socketIO?: SocketIOServer;
|
|
private checkInterval?: NodeJS.Timer;
|
|
private suggestions: Map<string, SkillSuggestion> = new Map();
|
|
|
|
constructor(config: Partial<OpenClawEngineConfig>, socketIO?: SocketIOServer) {
|
|
super();
|
|
|
|
// Configuração padrão
|
|
this.config = {
|
|
pattern_detection: {
|
|
enabled: true,
|
|
min_occurrences: 3,
|
|
time_window_days: 30,
|
|
confidence_threshold: 0.8,
|
|
scheduled_check_interval_minutes: 60,
|
|
...config?.pattern_detection,
|
|
},
|
|
suggestions: {
|
|
enabled: true,
|
|
auto_suggest: false,
|
|
channels: ["widget", "chat"],
|
|
priority_threshold: 0.85,
|
|
...config?.suggestions,
|
|
},
|
|
emergence: {
|
|
create_as_draft: true,
|
|
auto_publish: false,
|
|
notify_admins: true,
|
|
skill_prefix: "emergent_",
|
|
...config?.emergence,
|
|
},
|
|
logging: {
|
|
enabled: true,
|
|
store_patterns: true,
|
|
store_in_kg: true,
|
|
audit_trail: true,
|
|
...config?.logging,
|
|
},
|
|
};
|
|
|
|
this.socketIO = socketIO;
|
|
this.patternDetector = getPatternDetector(this.config.pattern_detection);
|
|
this.skillEmergence = new SkillEmergence();
|
|
|
|
// Setup listeners
|
|
this.setupListeners();
|
|
|
|
console.log("[OpenClawEngine] Inicializado com config:", this.config);
|
|
}
|
|
|
|
/**
|
|
* Setup dos listeners de eventos internos
|
|
*/
|
|
private setupListeners(): void {
|
|
// Listener: quando PatternDetector detecta um padrão
|
|
this.patternDetector.on("pattern-detected", async (event) => {
|
|
console.log("[OpenClawEngine] Padrão detectado:", event.pattern);
|
|
await this.handlePatternDetected(event.pattern);
|
|
});
|
|
|
|
// Listener: quando usuário confirma uma sugestão
|
|
this.on("user-confirmed-suggestion", async (suggestionId: string) => {
|
|
console.log("[OpenClawEngine] Usuário confirmou sugestão:", suggestionId);
|
|
await this.handleUserConfirmation(suggestionId);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Detectar padrão manualmente
|
|
*
|
|
* Pode ser chamado por:
|
|
* 1. Sistema ao receber uma interação
|
|
* 2. Scheduler verificando regularmente
|
|
* 3. API endpoint de teste
|
|
*/
|
|
async detectPattern(userId: string, tenantId: string, actionType: string): Promise<DetectedPattern | null> {
|
|
if (!this.config.pattern_detection.enabled) {
|
|
return null;
|
|
}
|
|
|
|
const pattern = await this.patternDetector.detectPattern(userId, tenantId, actionType);
|
|
|
|
if (pattern && pattern.confidence >= this.config.suggestions.priority_threshold) {
|
|
// Padrão de alta prioridade - sugerir imediatamente
|
|
await this.suggestSkill(pattern);
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
/**
|
|
* Handler: Padrão foi detectado
|
|
*
|
|
* Fluxo:
|
|
* 1. Validar padrão
|
|
* 2. Gerar sugestão de skill
|
|
* 3. Emitir para widget via Socket.IO
|
|
* 4. Armazenar sugestão
|
|
*/
|
|
private async handlePatternDetected(pattern: DetectedPattern): Promise<void> {
|
|
try {
|
|
console.log(`[OpenClawEngine] Processando padrão detectado: ${pattern.action_type}`);
|
|
|
|
// 1. Gerar sugestão de skill baseado no padrão
|
|
const suggestion = await this.suggestSkill(pattern);
|
|
|
|
if (!suggestion) {
|
|
console.log("[OpenClawEngine] Não foi possível gerar sugestão para o padrão");
|
|
return;
|
|
}
|
|
|
|
// 2. Emitir evento para Socket.IO (chega no widget frontend)
|
|
if (this.socketIO) {
|
|
this.socketIO.emit("openclaw:pattern-detected", {
|
|
suggestion,
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
console.log("[OpenClawEngine] Evento emitido ao Socket.IO");
|
|
}
|
|
|
|
// 3. Armazenar sugestão
|
|
this.suggestions.set(suggestion.id, suggestion);
|
|
|
|
// 4. Notificar admin se configurado
|
|
if (this.config.emergence.notify_admins) {
|
|
await this.notifyAdmins(pattern, suggestion);
|
|
}
|
|
} catch (error) {
|
|
console.error("[OpenClawEngine] Erro ao processar padrão detectado:", error);
|
|
this.emit("error", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gerar sugestão de skill baseado em um padrão
|
|
*
|
|
* Analisa o padrão e cria uma sugestão descritiva
|
|
* do que a automação faria
|
|
*/
|
|
private async suggestSkill(pattern: DetectedPattern): Promise<SkillSuggestion | null> {
|
|
try {
|
|
// Gerar nome e descrição da skill baseado no padrão
|
|
const skillName = this.generateSkillName(pattern);
|
|
const skillDescription = this.generateSkillDescription(pattern);
|
|
const estimatedAutomation = this.generateAutomationPreview(pattern);
|
|
|
|
const suggestion: SkillSuggestion = {
|
|
id: `suggest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
pattern,
|
|
suggested_skill_name: skillName,
|
|
suggested_description: skillDescription,
|
|
estimated_automation: estimatedAutomation,
|
|
confidence: pattern.confidence,
|
|
created_at: new Date(),
|
|
status: "pending",
|
|
};
|
|
|
|
console.log(`[OpenClawEngine] Sugestão gerada: ${skillName}`);
|
|
|
|
return suggestion;
|
|
} catch (error) {
|
|
console.error("[OpenClawEngine] Erro ao gerar sugestão:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gerar nome da skill baseado no padrão
|
|
*/
|
|
private generateSkillName(pattern: DetectedPattern): string {
|
|
// Simples: capitalizar o tipo de ação
|
|
// Exemplo: "query_sales_report" → "Sales Report Query"
|
|
|
|
const words = pattern.action_type.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1));
|
|
return `${this.config.emergence.skill_prefix}${words.join(" ")}`.trim();
|
|
}
|
|
|
|
/**
|
|
* Gerar descrição da skill baseado no padrão
|
|
*/
|
|
private generateSkillDescription(pattern: DetectedPattern): string {
|
|
return `
|
|
Automação detectada para: ${pattern.action_type}
|
|
|
|
Padrão observado:
|
|
- Ocorrências: ${pattern.occurrences}x nos últimos ${pattern.time_window_days} dias
|
|
- Frequência: ${pattern.metadata?.frequency_per_week} vezes/semana
|
|
- Intervalo médio: ${pattern.metadata?.average_interval_hours} horas
|
|
- Confiança: ${(pattern.confidence * 100).toFixed(1)}%
|
|
|
|
Esta skill automatizará essa ação repetitiva para você.
|
|
`.trim();
|
|
}
|
|
|
|
/**
|
|
* Gerar preview da automação
|
|
*/
|
|
private generateAutomationPreview(pattern: DetectedPattern): string {
|
|
return `
|
|
# Automação Proposta
|
|
|
|
**Trigger:** Quando ocorre ${pattern.action_type}
|
|
**Frequência:** A cada ${pattern.metadata?.average_interval_hours} horas aproximadamente
|
|
**Ação:** Executar automaticamente
|
|
|
|
Esta skill vai:
|
|
1. Monitorar quando você executa a ação: ${pattern.action_type}
|
|
2. Ao detectar o padrão, executar automaticamente
|
|
3. Salvar tempo e aumentar produtividade
|
|
|
|
**Status:** Pronto para criar como DRAFT e publicar após aprovação
|
|
`.trim();
|
|
}
|
|
|
|
/**
|
|
* Handler: Usuário confirmou uma sugestão
|
|
*
|
|
* Fluxo:
|
|
* 1. Validar sugestão
|
|
* 2. Chamar SkillEmergence para gerar código
|
|
* 3. Armazenar skill como DRAFT
|
|
* 4. Notificar Dev Center
|
|
*/
|
|
private async handleUserConfirmation(suggestionId: string): Promise<void> {
|
|
try {
|
|
const suggestion = this.suggestions.get(suggestionId);
|
|
|
|
if (!suggestion) {
|
|
console.error(`[OpenClawEngine] Sugestão não encontrada: ${suggestionId}`);
|
|
return;
|
|
}
|
|
|
|
console.log(`[OpenClawEngine] Processando confirmação do usuário para: ${suggestion.suggested_skill_name}`);
|
|
|
|
// 1. Gerar skill (código) via Blackboard
|
|
const skillDraft = await this.skillEmergence.generateSkillDraft(suggestion);
|
|
|
|
if (!skillDraft) {
|
|
console.error("[OpenClawEngine] Falha ao gerar skill");
|
|
this.emit("error", new Error("Failed to generate skill draft"));
|
|
return;
|
|
}
|
|
|
|
// 2. Atualizar status da sugestão
|
|
suggestion.status = "accepted";
|
|
|
|
// 3. Emitir evento de sucesso
|
|
this.emit("skill-created", {
|
|
suggestion,
|
|
skill: skillDraft,
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
// 4. Notificar via Socket.IO (para frontend)
|
|
if (this.socketIO) {
|
|
this.socketIO.emit("openclaw:skill-created", {
|
|
skill: skillDraft,
|
|
suggestion,
|
|
message: `Skill "${suggestion.suggested_skill_name}" criada com sucesso! Vá para Dev Center para publicar.`,
|
|
});
|
|
}
|
|
|
|
console.log("[OpenClawEngine] Skill criada com sucesso:", skillDraft.id);
|
|
} catch (error) {
|
|
console.error("[OpenClawEngine] Erro ao processar confirmação:", error);
|
|
this.emit("error", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notificar admins sobre novo padrão detectado
|
|
*/
|
|
private async notifyAdmins(pattern: DetectedPattern, suggestion: SkillSuggestion): Promise<void> {
|
|
// TODO: Implementar notificação
|
|
// Opções:
|
|
// 1. Email para admins
|
|
// 2. Notificação em dashboard
|
|
// 3. Alerta no Slack/Discord
|
|
// 4. Entry em audit log
|
|
|
|
console.log(
|
|
`[OpenClawEngine] Admin notificado: Novo padrão ${pattern.action_type} (confiança: ${pattern.confidence})`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Verificar padrões periodicamente (para ser chamado por scheduler)
|
|
*/
|
|
async checkForPatternsScheduled(tenantId: string): Promise<void> {
|
|
if (!this.config.pattern_detection.enabled) {
|
|
return;
|
|
}
|
|
|
|
console.log(`[OpenClawEngine] Verificação agendada de padrões para tenant: ${tenantId}`);
|
|
|
|
try {
|
|
await this.patternDetector.checkForNewPatterns(tenantId);
|
|
} catch (error) {
|
|
console.error("[OpenClawEngine] Erro na verificação agendada:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iniciar verificação periódica de padrões
|
|
*/
|
|
startScheduledChecks(tenantId: string): void {
|
|
if (this.checkInterval) {
|
|
console.log("[OpenClawEngine] Verificação agendada já está ativa");
|
|
return;
|
|
}
|
|
|
|
const intervalMs = this.config.pattern_detection.scheduled_check_interval_minutes * 60 * 1000;
|
|
|
|
this.checkInterval = setInterval(() => {
|
|
this.checkForPatternsScheduled(tenantId);
|
|
}, intervalMs);
|
|
|
|
console.log(
|
|
`[OpenClawEngine] Verificação agendada iniciada (a cada ${this.config.pattern_detection.scheduled_check_interval_minutes} min)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parar verificação periódica
|
|
*/
|
|
stopScheduledChecks(): void {
|
|
if (this.checkInterval) {
|
|
clearInterval(this.checkInterval);
|
|
this.checkInterval = undefined;
|
|
console.log("[OpenClawEngine] Verificação agendada interrompida");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter sugestões pendentes para um usuário
|
|
*/
|
|
getPendingSuggestions(userId?: string): SkillSuggestion[] {
|
|
const suggestions = Array.from(this.suggestions.values()).filter((s) => s.status === "pending");
|
|
|
|
if (userId) {
|
|
return suggestions.filter((s) => s.pattern.user_id === userId);
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
/**
|
|
* Obter configuração
|
|
*/
|
|
getConfig(): OpenClawEngineConfig {
|
|
return JSON.parse(JSON.stringify(this.config));
|
|
}
|
|
|
|
/**
|
|
* Atualizar configuração
|
|
*/
|
|
setConfig(config: Partial<OpenClawEngineConfig>): void {
|
|
this.config = { ...this.config, ...config };
|
|
console.log("[OpenClawEngine] Configuração atualizada");
|
|
|
|
// Atualizar PatternDetector se necessário
|
|
if (config.pattern_detection) {
|
|
this.patternDetector.setConfig(config.pattern_detection);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exportar instância singleton
|
|
let engineInstance: OpenClawEngine | null = null;
|
|
|
|
export function getOpenClawEngine(
|
|
config?: Partial<OpenClawEngineConfig>,
|
|
socketIO?: SocketIOServer
|
|
): OpenClawEngine {
|
|
if (!engineInstance) {
|
|
engineInstance = new OpenClawEngine(config || {}, socketIO);
|
|
}
|
|
return engineInstance;
|
|
}
|
|
|
|
export default OpenClawEngine;
|