#!/usr/bin/env npx tsx /** * E2E Test: Agent Send Command * * This test verifies the `send` command using REAL daemons and agents. * It starts an isolated daemon on a random port and runs actual CLI commands. * * Test flow: * 1. Start daemon on random port * 2. Create agent with detached mode * 3. Wait for agent to become idle (initial task complete) * 4. Send new message with `send` * 5. Wait for agent again * 6. Verify agent processed the message (check logs) * 7. Cleanup * * CRITICAL RULES: * - NEVER use port 6767 (user's running daemon) * - Always use claude provider with haiku model for fast, cheap tests * - Clean up resources after test completes */ import assert from "node:assert"; import { createE2ETestContext, type TestDaemonContext } from "../helpers/test-daemon.ts"; interface E2EContext extends TestDaemonContext { paseo: ( args: string[], opts?: { timeout?: number; cwd?: string }, ) => Promise<{ exitCode: number; stdout: string; stderr: string; }>; } let ctx: E2EContext; async function setup(): Promise { console.log("Setting up E2E test context..."); console.log("Starting test daemon on random port (this may take a few seconds)..."); try { ctx = await createE2ETestContext({ timeout: 45000 }); console.log(`Test daemon started on port ${ctx.port}`); console.log(`PASEO_HOME: ${ctx.paseoHome}`); console.log(`Work directory: ${ctx.workDir}`); } catch (err) { console.error("Failed to start test daemon:", err); throw err; } } async function cleanup(): Promise { console.log("\nCleaning up..."); if (ctx) { await ctx.stop(); console.log("Test daemon stopped and temp directories removed"); } } async function test_create_agent(): Promise { console.log("\n--- Test: Create agent in detached mode ---"); // CRITICAL: Use haiku model for fast, cheap tests // CRITICAL: Use bypassPermissions mode so agent doesn't wait for permission approvals const result = await ctx.paseo( [ "-q", "run", "-d", "--provider", "claude", "--model", "claude-3-5-haiku-20241022", "--mode", "bypassPermissions", "--name", "Send Test Agent", 'Say "initial task complete"', ], { timeout: 60000 }, ); console.log("Exit code:", result.exitCode); console.log("Stdout:", result.stdout); if (result.stderr) console.log("Stderr:", result.stderr); assert.strictEqual(result.exitCode, 0, "agent run should succeed"); const agentId = result.stdout.trim(); assert(agentId.length > 0, "Should return agent ID"); assert(agentId.match(/^[a-z0-9-]+$/), `Agent ID should be alphanumeric: ${agentId}`); console.log(`PASS: agent created with ID: ${agentId}`); return agentId; } async function test_wait_for_initial_task(agentId: string): Promise { console.log("\n--- Test: Wait for initial task to complete ---"); const result = await ctx.paseo(["wait", "--timeout", "120s", agentId], { timeout: 130000 }); console.log("Exit code:", result.exitCode); console.log("Stdout:", result.stdout); if (result.stderr) console.log("Stderr:", result.stderr); assert.strictEqual(result.exitCode, 0, "agent wait should succeed"); console.log("PASS: Initial task completed"); } async function test_agent_send(agentId: string): Promise { console.log("\n--- Test: Send follow-up message ---"); // Send a follow-up message to the agent const result = await ctx.paseo(["send", agentId, 'Now say "follow-up task complete"'], { timeout: 180000, }); console.log("Exit code:", result.exitCode); console.log("Stdout:", result.stdout); if (result.stderr) console.log("Stderr:", result.stderr); assert.strictEqual(result.exitCode, 0, "agent send should succeed"); // Verify output contains expected status assert( result.stdout.includes("completed") || result.stdout.includes("sent"), "Should indicate message was sent or completed", ); console.log("PASS: Follow-up message sent successfully"); } async function test_verify_logs(agentId: string): Promise { console.log("\n--- Test: Verify agent processed both messages ---"); const result = await ctx.paseo(["logs", "--tail", "50", agentId]); console.log("Exit code:", result.exitCode); console.log("Stdout length:", result.stdout.length); assert.strictEqual(result.exitCode, 0, "agent logs should succeed"); // Logs should have content from both tasks assert(result.stdout.length > 0, "Logs should have content"); // At minimum, there should be log entries (we can't guarantee exact content) assert(result.stdout.split("\n").length > 3, "Should have multiple log entries from both tasks"); console.log("PASS: Agent logs show activity from both tasks"); } async function test_agent_delete(agentId: string): Promise { console.log("\n--- Test: Delete agent ---"); const result = await ctx.paseo(["delete", agentId]); console.log("Exit code:", result.exitCode); console.log("Stdout:", result.stdout); if (result.stderr) console.log("Stderr:", result.stderr); assert.strictEqual(result.exitCode, 0, "agent delete should succeed"); console.log("PASS: Agent deleted successfully"); } async function main(): Promise { console.log("=== E2E Test: Agent Send Command ===\n"); console.log("This test verifies the `send` command with REAL agents."); console.log("It may take some time as it waits for agent responses.\n"); try { await setup(); // Create agent and wait for initial task const agentId = await test_create_agent(); await test_wait_for_initial_task(agentId); // Send follow-up message (this waits for completion by default) await test_agent_send(agentId); // Verify both messages were processed await test_verify_logs(agentId); // Cleanup agent await test_agent_delete(agentId); console.log("\n=== All agent-send E2E tests passed! ==="); } catch (err) { console.error("\n=== E2E test FAILED ==="); console.error(err); process.exitCode = 1; } finally { await cleanup(); } } main();