/** * E2E test: Father Bot crea un agente via Matrix. * * Este test envia un mensaje real a Father Bot pidiendo la creacion * de un robot simple, y verifica que: * 1. Father Bot responde confirmando la creacion * 2. Los archivos del robot se crearon en el filesystem * 3. El robot compila correctamente * * IMPORTANTE: este test tiene side effects reales: * - Crea un usuario Matrix para el robot * - Crea archivos en agents/ * - Modifica cmd/launcher/main.go (blank import) * - Anade env vars a .env * * Cleanup: dev-scripts/agent/remove.sh e2e-father-test */ import { test, expect, handleElementDialogs } from "../fixtures/persistent-context"; import { goToRoom, sendMessage, waitForBotReply, } from "../fixtures/matrix-room"; import * as fs from "fs"; import * as path from "path"; import { execSync } from "child_process"; const REPO_ROOT = path.resolve(__dirname, "../.."); const TEST_AGENT_ID = "e2e-father-test"; const TEST_AGENT_DIR = path.join(REPO_ROOT, "agents", TEST_AGENT_ID); // Timeout largo: claude-code puede tardar varios minutos test.setTimeout(600_000); // 10 minutos test.describe("father-bot — creacion de agente via Matrix", () => { test.beforeAll(() => { // Cleanup previo: si el agente ya existe de un run anterior, eliminarlo if (fs.existsSync(TEST_AGENT_DIR)) { console.log(`[cleanup] Eliminando agente previo: ${TEST_AGENT_DIR}`); fs.rmSync(TEST_AGENT_DIR, { recursive: true, force: true }); } // Remover blank import si existe const launcherPath = path.join(REPO_ROOT, "cmd/launcher/main.go"); const launcherContent = fs.readFileSync(launcherPath, "utf-8"); if (launcherContent.includes(`agents/${TEST_AGENT_ID}`)) { const cleaned = launcherContent .split("\n") .filter((line) => !line.includes(`agents/${TEST_AGENT_ID}`)) .join("\n"); fs.writeFileSync(launcherPath, cleaned); console.log("[cleanup] Blank import removido del launcher"); } }); test.beforeEach(async ({ page }) => { await page.goto("/"); await handleElementDialogs(page); }); test("pide a father-bot crear un robot simple y verifica los archivos", async ({ page }) => { // 1. Crear DM con Father Bot si no existe, luego navegar const FATHER_BOT_MXID = `@father-bot:${process.env.MATRIX_SERVER_NAME || "matrix-af2f3d.organic-machine.com"}`; // Intentar encontrar el room existente primero let roomFound = false; try { await goToRoom(page, "Father Bot"); roomFound = true; } catch { console.log("[father-bot] DM no existe, creando via SDK..."); } if (!roomFound) { // Crear DM room via Matrix SDK en Element await page.evaluate(async (mxid: string) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const client = (window as any).mxMatrixClientPeg?.get?.(); if (!client) throw new Error("Matrix client no disponible"); await client.createRoom({ is_direct: true, invite: [mxid], preset: "trusted_private_chat", }); }, FATHER_BOT_MXID); console.log("[father-bot] DM room creado, esperando sync + join..."); await page.waitForTimeout(10_000); // Recargar y navegar — intentar con display name, luego con MXID await page.goto("/"); await handleElementDialogs(page); try { await goToRoom(page, "Father Bot"); } catch { console.log("[father-bot] Retry con 'father-bot'..."); await goToRoom(page, "father-bot"); } } // 2. Enviar peticion de creacion const request = `Crea un robot llamado ${TEST_AGENT_ID} con display name "E2E Father Test". ` + `Debe ser un robot simple (type: robot) que responda al comando !saludo con "Hola desde e2e-father-test". ` + `No reinicies el launcher despues de crearlo, solo crea los archivos y compila.`; await sendMessage(page, request); // 3. Esperar respuesta de father-bot (timeout largo por claude-code) const reply = await waitForBotReply(page, { timeout: 540_000, // 9 minutos sender: "Father Bot", }); console.log(`[father-bot] Respuesta: ${reply.substring(0, 200)}...`); // 4. Verificar que father-bot respondio con algo coherente expect(reply).toBeTruthy(); expect(reply.length).toBeGreaterThan(20); // La respuesta deberia mencionar exito o el nombre del agente const replyLower = reply.toLowerCase(); const hasSuccess = replyLower.includes(TEST_AGENT_ID) || replyLower.includes("creado") || replyLower.includes("completado") || replyLower.includes("exitoso") || replyLower.includes("listo") || replyLower.includes("scaffold"); expect(hasSuccess).toBe(true); // 5. Verificar que los archivos existen en el filesystem console.log("[verify] Verificando archivos del agente creado..."); const configPath = path.join(TEST_AGENT_DIR, "config.yaml"); expect(fs.existsSync(configPath)).toBe(true); const configContent = fs.readFileSync(configPath, "utf-8"); expect(configContent).toContain(TEST_AGENT_ID); expect(configContent).toMatch(/type:\s*robot/); console.log("[verify] config.yaml OK (type: robot)"); // Robots may or may not have agent.go depending on implementation const agentGoPath = path.join(TEST_AGENT_DIR, "agent.go"); if (fs.existsSync(agentGoPath)) { const agentGoContent = fs.readFileSync(agentGoPath, "utf-8"); expect(agentGoContent).toContain(TEST_AGENT_ID); console.log("[verify] agent.go OK"); } // 6. Verificar que compila console.log("[verify] Compilando..."); try { execSync("go build -tags goolm ./...", { cwd: REPO_ROOT, timeout: 60_000, stdio: "pipe", }); console.log("[verify] Compilacion exitosa"); } catch (err) { const error = err as { stderr?: Buffer }; const stderr = error.stderr?.toString() || "unknown error"; console.error(`[verify] Compilacion fallida: ${stderr}`); // No hacemos fail del test por compilacion ya que father-bot // puede haber creado los archivos parcialmente } // 7. Verificar blank import en launcher const launcherPath = path.join(REPO_ROOT, "cmd/launcher/main.go"); const launcherContent = fs.readFileSync(launcherPath, "utf-8"); expect(launcherContent).toContain(`agents/${TEST_AGENT_ID}`); console.log("[verify] Blank import en launcher OK"); }); test.afterAll(() => { // Cleanup: eliminar el agente de prueba console.log(`[cleanup] Limpiando agente de prueba: ${TEST_AGENT_ID}`); // Remover directorio del agente if (fs.existsSync(TEST_AGENT_DIR)) { fs.rmSync(TEST_AGENT_DIR, { recursive: true, force: true }); console.log("[cleanup] Directorio del agente eliminado"); } // Remover blank import del launcher const launcherPath = path.join(REPO_ROOT, "cmd/launcher/main.go"); if (fs.existsSync(launcherPath)) { const content = fs.readFileSync(launcherPath, "utf-8"); if (content.includes(`agents/${TEST_AGENT_ID}`)) { const cleaned = content .split("\n") .filter((line) => !line.includes(`agents/${TEST_AGENT_ID}`)) .join("\n"); fs.writeFileSync(launcherPath, cleaned); console.log("[cleanup] Blank import removido del launcher"); } } // Remover env vars del .env const envPath = path.join(REPO_ROOT, ".env"); if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, "utf-8"); const normalizedId = TEST_AGENT_ID.toUpperCase().replace(/-/g, "_"); const cleaned = envContent .split("\n") .filter((line) => !line.includes(normalizedId)) .join("\n"); if (cleaned !== envContent) { fs.writeFileSync(envPath, cleaned); console.log("[cleanup] Env vars eliminadas de .env"); } } console.log("[cleanup] Limpieza completada"); }); });