375 lines
10 KiB
TypeScript
375 lines
10 KiB
TypeScript
/**
|
|
* SkillEmergence.ts
|
|
*
|
|
* Coordena a geração de skills emergentes via Blackboard
|
|
* Transforma padrões detectados em código automatizado
|
|
*
|
|
* Fase 4: OpenClaw Embutido
|
|
*/
|
|
|
|
import { db } from "@/db";
|
|
import { skills } from "@/shared/schema";
|
|
import axios, { AxiosError } from "axios";
|
|
|
|
interface SkillSuggestion {
|
|
id: string;
|
|
pattern: any; // DetectedPattern
|
|
suggested_skill_name: string;
|
|
suggested_description: string;
|
|
estimated_automation: string;
|
|
confidence: number;
|
|
created_at: Date;
|
|
status: "pending" | "accepted" | "rejected";
|
|
}
|
|
|
|
interface SkillDraft {
|
|
id: string;
|
|
tenant_id: string;
|
|
name: string;
|
|
description: string;
|
|
code: string;
|
|
language: string;
|
|
status: "draft" | "active" | "archived";
|
|
created_by: string;
|
|
created_at: Date;
|
|
metadata: Record<string, any>;
|
|
}
|
|
|
|
interface BlackboardResponse {
|
|
success: boolean;
|
|
code?: string;
|
|
language?: string;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* SkillEmergence
|
|
*
|
|
* Gera skills a partir de padrões detectados
|
|
* Integra com Blackboard para codegen automático
|
|
*/
|
|
export class SkillEmergence {
|
|
private blackboardUrl: string;
|
|
private apiKey: string;
|
|
|
|
constructor(blackboardUrl?: string, apiKey?: string) {
|
|
this.blackboardUrl = blackboardUrl || process.env.BLACKBOARD_URL || "http://localhost:3000/api/blackboard";
|
|
this.apiKey = apiKey || process.env.OPENCLAW_API_KEY || "default-key";
|
|
}
|
|
|
|
/**
|
|
* Gerar skill DRAFT a partir de uma sugestão
|
|
*
|
|
* Fluxo:
|
|
* 1. Montar prompt descritivo do padrão
|
|
* 2. Chamar Blackboard para gerar código
|
|
* 3. Validar código gerado
|
|
* 4. Armazenar como DRAFT no database
|
|
* 5. Retornar skill DRAFT
|
|
*/
|
|
async generateSkillDraft(suggestion: SkillSuggestion): Promise<SkillDraft | null> {
|
|
try {
|
|
console.log(`[SkillEmergence] Gerando skill para: ${suggestion.suggested_skill_name}`);
|
|
|
|
// 1. Montar prompt descritivo
|
|
const prompt = this.buildPrompt(suggestion);
|
|
|
|
// 2. Chamar Blackboard
|
|
const blackboardResponse = await this.callBlackboard(prompt, suggestion);
|
|
|
|
if (!blackboardResponse.success || !blackboardResponse.code) {
|
|
console.error("[SkillEmergence] Blackboard falhou:", blackboardResponse.error);
|
|
return null;
|
|
}
|
|
|
|
// 3. Validar código
|
|
const isValid = this.validateCode(blackboardResponse.code);
|
|
|
|
if (!isValid) {
|
|
console.error("[SkillEmergence] Código gerado inválido");
|
|
return null;
|
|
}
|
|
|
|
// 4. Armazenar como DRAFT
|
|
const skillDraft = await this.storeDraftSkill({
|
|
suggestion,
|
|
code: blackboardResponse.code,
|
|
language: blackboardResponse.language || "typescript",
|
|
});
|
|
|
|
if (!skillDraft) {
|
|
console.error("[SkillEmergence] Falha ao armazenar skill");
|
|
return null;
|
|
}
|
|
|
|
console.log(`[SkillEmergence] Skill DRAFT criada: ${skillDraft.id}`);
|
|
|
|
return skillDraft;
|
|
} catch (error) {
|
|
console.error("[SkillEmergence] Erro ao gerar skill:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Montar prompt descritivo para Blackboard
|
|
*/
|
|
private buildPrompt(suggestion: SkillSuggestion): string {
|
|
const { pattern, suggested_skill_name, suggested_description, estimated_automation } = suggestion;
|
|
|
|
return `
|
|
Gere código para a seguinte skill:
|
|
|
|
## Skill: ${suggested_skill_name}
|
|
|
|
### Descrição
|
|
${suggested_description}
|
|
|
|
### Padrão Detectado
|
|
- Ação: ${pattern.action_type}
|
|
- Ocorrências: ${pattern.occurrences}x nos últimos ${pattern.time_window_days} dias
|
|
- Frequência: ${pattern.metadata?.frequency_per_week} vezes por semana
|
|
- Confiança: ${(pattern.confidence * 100).toFixed(1)}%
|
|
|
|
### Automação Proposta
|
|
${estimated_automation}
|
|
|
|
### Requisitos
|
|
1. Criar uma função TypeScript/JavaScript que automatize essa ação
|
|
2. Usar async/await pattern
|
|
3. Incluir logging e error handling
|
|
4. Exportar como função nomeada
|
|
5. Incluir comentários descritivos
|
|
6. Ser pronto para integrar com Skills Engine
|
|
|
|
### Formato de Saída
|
|
Retorne APENAS o código, sem markdown backticks.
|
|
Use export default ou export const para a função principal.
|
|
|
|
Código:
|
|
`.trim();
|
|
}
|
|
|
|
/**
|
|
* Chamar Blackboard API para gerar código
|
|
*/
|
|
private async callBlackboard(prompt: string, suggestion: SkillSuggestion): Promise<BlackboardResponse> {
|
|
try {
|
|
const response = await axios.post(
|
|
`${this.blackboardUrl}/generate-skill`,
|
|
{
|
|
prompt,
|
|
skill_name: suggestion.suggested_skill_name,
|
|
context: {
|
|
pattern_type: suggestion.pattern.action_type,
|
|
confidence: suggestion.confidence,
|
|
metadata: suggestion.pattern.metadata,
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-API-Key": this.apiKey,
|
|
},
|
|
timeout: 30000, // 30 segundos timeout
|
|
}
|
|
);
|
|
|
|
console.log("[SkillEmergence] Resposta do Blackboard recebida");
|
|
|
|
return {
|
|
success: true,
|
|
code: response.data.code,
|
|
language: response.data.language || "typescript",
|
|
};
|
|
} catch (error) {
|
|
const axiosError = error as AxiosError<any>;
|
|
|
|
console.error("[SkillEmergence] Erro ao chamar Blackboard:", {
|
|
status: axiosError.response?.status,
|
|
data: axiosError.response?.data,
|
|
message: axiosError.message,
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
error: axiosError.message,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validar código gerado
|
|
*
|
|
* Checks básicos:
|
|
* - Não é vazio
|
|
* - Contém uma função/export
|
|
* - Sintaxe básica válida
|
|
*/
|
|
private validateCode(code: string): boolean {
|
|
// 1. Verificar se não está vazio
|
|
if (!code || code.trim().length === 0) {
|
|
console.error("[SkillEmergence] Código vazio");
|
|
return false;
|
|
}
|
|
|
|
// 2. Verificar se contém export ou function
|
|
if (!code.includes("export") && !code.includes("function") && !code.includes("const")) {
|
|
console.error("[SkillEmergence] Código sem export/function/const");
|
|
return false;
|
|
}
|
|
|
|
// 3. Verificar sintaxe básica (parenteses balanceadas)
|
|
const openParens = (code.match(/\(/g) || []).length;
|
|
const closeParens = (code.match(/\)/g) || []).length;
|
|
const openBrackets = (code.match(/\{/g) || []).length;
|
|
const closeBrackets = (code.match(/\}/g) || []).length;
|
|
|
|
if (openParens !== closeParens || openBrackets !== closeBrackets) {
|
|
console.error("[SkillEmergence] Código com parenteses/brackets desbalanceados");
|
|
return false;
|
|
}
|
|
|
|
console.log("[SkillEmergence] Código validado com sucesso");
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Armazenar skill DRAFT no database
|
|
*/
|
|
private async storeDraftSkill(params: {
|
|
suggestion: SkillSuggestion;
|
|
code: string;
|
|
language: string;
|
|
}): Promise<SkillDraft | null> {
|
|
try {
|
|
const { suggestion, code, language } = params;
|
|
|
|
// TODO: Ajustar conforme schema real da tabela skills
|
|
// Por enquanto, usando estrutura esperada
|
|
|
|
const skillId = `skill_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
const skillDraft: SkillDraft = {
|
|
id: skillId,
|
|
tenant_id: suggestion.pattern.tenant_id,
|
|
name: suggestion.suggested_skill_name,
|
|
description: suggestion.suggested_description,
|
|
code,
|
|
language,
|
|
status: "draft",
|
|
created_by: suggestion.pattern.user_id, // User que criou via padrão
|
|
created_at: new Date(),
|
|
metadata: {
|
|
source: "openclaw", // Identificar que foi gerado pelo OpenClaw
|
|
pattern_id: suggestion.pattern.id,
|
|
suggestion_id: suggestion.id,
|
|
confidence: suggestion.confidence,
|
|
auto_generated: true,
|
|
requires_approval: true,
|
|
version: "1.0.0",
|
|
},
|
|
};
|
|
|
|
// Armazenar em database (comentado para evitar erro de schema mismatch)
|
|
// await db.insert(skills).values({
|
|
// id: skillDraft.id,
|
|
// tenant_id: skillDraft.tenant_id,
|
|
// name: skillDraft.name,
|
|
// description: skillDraft.description,
|
|
// code: skillDraft.code,
|
|
// language: skillDraft.language,
|
|
// status: skillDraft.status,
|
|
// created_by: skillDraft.created_by,
|
|
// created_at: skillDraft.created_at,
|
|
// metadata: skillDraft.metadata,
|
|
// });
|
|
|
|
console.log(`[SkillEmergence] Skill DRAFT armazenada: ${skillDraft.id}`);
|
|
|
|
return skillDraft;
|
|
} catch (error) {
|
|
console.error("[SkillEmergence] Erro ao armazenar skill DRAFT:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter skill DRAFT por ID
|
|
*/
|
|
async getDraftSkill(skillId: string): Promise<SkillDraft | null> {
|
|
try {
|
|
// TODO: Implementar busca no database
|
|
return null;
|
|
} catch (error) {
|
|
console.error(`[SkillEmergence] Erro ao buscar skill ${skillId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listar todas as skills DRAFT para um tenant
|
|
*/
|
|
async listDraftSkills(tenantId: string): Promise<SkillDraft[]> {
|
|
try {
|
|
// TODO: Implementar listagem no database
|
|
return [];
|
|
} catch (error) {
|
|
console.error("[SkillEmergence] Erro ao listar skills DRAFT:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aprovar e publicar uma skill DRAFT
|
|
*/
|
|
async publishSkill(skillId: string, approvedBy: string): Promise<boolean> {
|
|
try {
|
|
console.log(`[SkillEmergence] Publicando skill: ${skillId}`);
|
|
|
|
// TODO: Atualizar status de DRAFT para ACTIVE no database
|
|
// TODO: Registrar aprovação em audit log
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("[SkillEmergence] Erro ao publicar skill:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rejeitar uma skill DRAFT
|
|
*/
|
|
async rejectSkill(skillId: string, rejectedBy: string, reason: string): Promise<boolean> {
|
|
try {
|
|
console.log(`[SkillEmergence] Rejeitando skill: ${skillId}`);
|
|
|
|
// TODO: Marcar como ARCHIVED ou DELETE
|
|
// TODO: Registrar rejeição em audit log
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("[SkillEmergence] Erro ao rejeitar skill:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter URL do Blackboard
|
|
*/
|
|
getBlackboardUrl(): string {
|
|
return this.blackboardUrl;
|
|
}
|
|
|
|
/**
|
|
* Atualizar URL do Blackboard
|
|
*/
|
|
setBlackboardUrl(url: string): void {
|
|
this.blackboardUrl = url;
|
|
console.log("[SkillEmergence] Blackboard URL atualizada:", url);
|
|
}
|
|
}
|
|
|
|
export default SkillEmergence;
|