import { Session } from "./packages/server/src/server/session.js"; import { AgentManager } from "./packages/server/src/server/acp/agent-manager.js"; import type { SessionInboundMessage, SessionOutboundMessage } from "./packages/server/src/server/messages.js"; import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); async function testWorktreeE2E() { console.log("=== Testing End-to-End Worktree Agent Creation ==="); console.log("(Simulating frontend -> WebSocket -> Session -> AgentManager flow)\n"); const testBranchName = "test-e2e-worktree"; const cwd = process.cwd(); // voice-dev directory let capturedMessages: SessionOutboundMessage[] = []; let createdAgentId: string | null = null; let worktreePath: string | null = null; try { // Step 1: Set up Session (like the WebSocket handler does) console.log("Step 1: Creating Session and AgentManager..."); const agentManager = new AgentManager(); await agentManager.initialize(); const session = new Session( "test-client", (msg: SessionOutboundMessage) => { capturedMessages.push(msg); console.log(` [Session emit] ${msg.type}`); // Capture agent_created message if (msg.type === "agent_created") { createdAgentId = msg.payload.agentId; worktreePath = msg.payload.cwd; console.log(` Agent ID: ${createdAgentId}`); console.log(` CWD: ${worktreePath}`); } }, agentManager ); console.log("✓ Session created\n"); // Step 2: Send create_agent_request message (like frontend does) console.log("Step 2: Sending create_agent_request with worktreeName..."); const createAgentMessage: SessionInboundMessage = { type: "create_agent_request", cwd: cwd, worktreeName: testBranchName, requestId: "test-request-123", }; console.log(` Message: ${JSON.stringify(createAgentMessage, null, 2)}`); await session.handleMessage(createAgentMessage); console.log("✓ Message handled\n"); // Step 3: Verify agent_created message was emitted console.log("Step 3: Verifying agent_created message..."); const agentCreatedMsg = capturedMessages.find(m => m.type === "agent_created"); if (!agentCreatedMsg) { throw new Error("No agent_created message emitted!"); } if (agentCreatedMsg.type !== "agent_created") { throw new Error("Wrong message type!"); } console.log(`✓ agent_created message emitted`); console.log(` Agent ID: ${agentCreatedMsg.payload.agentId}`); console.log(` CWD: ${agentCreatedMsg.payload.cwd}`); console.log(` Request ID: ${agentCreatedMsg.payload.requestId}\n`); if (!createdAgentId) { throw new Error("Agent ID not captured!"); } if (!worktreePath) { throw new Error("Worktree path not captured!"); } // Step 4: Verify worktree was actually created console.log("Step 4: Verifying worktree exists..."); const { stdout: worktreeList } = await execAsync("git worktree list"); if (!worktreeList.includes(worktreePath)) { console.error("Git worktree list:"); console.error(worktreeList); throw new Error(`Worktree not found in git! Expected: ${worktreePath}`); } console.log("✓ Worktree exists in git\n"); // Step 5: Verify the worktree path contains branch name console.log("Step 5: Verifying worktree path..."); if (!worktreePath.includes(testBranchName)) { throw new Error(`Worktree path doesn't contain branch name! Path: ${worktreePath}`); } console.log(`✓ Worktree path correct: ${worktreePath}\n`); // Step 6: Verify agent is in the worktree console.log("Step 6: Verifying agent CWD..."); const agentInfo = agentManager.listAgents().find(a => a.id === createdAgentId); if (!agentInfo) { throw new Error("Agent not found in manager!"); } console.log(` Agent CWD: ${agentInfo.cwd}`); console.log(` Expected: ${worktreePath}`); if (agentInfo.cwd !== worktreePath) { throw new Error(`Agent CWD mismatch! Expected ${worktreePath}, got ${agentInfo.cwd}`); } console.log("✓ Agent CWD matches worktree path\n"); // Step 7: Test agent can run commands in worktree console.log("Step 7: Testing agent execution in worktree..."); await agentManager.sendPrompt( createdAgentId, "Run 'pwd' and 'git branch --show-current'. Just show me the output.", ); let status = agentManager.getAgentStatus(createdAgentId); let attempts = 0; while (status === "processing" && attempts < 60) { await new Promise((resolve) => setTimeout(resolve, 1000)); status = agentManager.getAgentStatus(createdAgentId); attempts++; } console.log(` Agent status after prompt: ${status}`); const updates = agentManager.getAgentUpdates(createdAgentId); const messageChunks = updates .filter(u => u.notification.type === "session" && u.notification.notification.update.sessionUpdate === "agent_message_chunk") .map(u => { const update = u.notification as any; return update.notification.update.content?.text || ""; }) .join(""); console.log("\n Agent response:"); console.log(messageChunks); if (!messageChunks.includes(testBranchName)) { throw new Error("Agent response doesn't mention branch name!"); } if (!messageChunks.includes(worktreePath)) { throw new Error("Agent response doesn't show worktree path!"); } console.log("\n✓ Agent successfully executing in worktree\n"); // Step 8: Cleanup console.log("Step 8: Cleaning up..."); await agentManager.killAgent(createdAgentId); console.log("✓ Agent killed\n"); console.log("=== END-TO-END TEST PASSED ==="); console.log("\n✅ The complete workflow works:"); console.log(" Frontend creates agent with worktreeName"); console.log(" → Message flows through Session"); console.log(" → Session creates worktree"); console.log(" → Agent is created in worktree"); console.log(" → Agent executes commands in worktree"); console.log("\n⚠️ Worktree still exists at:"); console.log(` ${worktreePath}`); console.log("\nCleanup commands:"); console.log(` git worktree remove ${worktreePath}`); console.log(` git branch -D ${testBranchName}`); process.exit(0); } catch (error) { console.error("\n❌ END-TO-END TEST FAILED:"); console.error(error); console.log("\n⚠️ Manual cleanup may be needed:"); console.log(` git worktree remove voice-dev-${testBranchName} 2>/dev/null || true`); console.log(` git branch -D ${testBranchName} 2>/dev/null || true`); process.exit(1); } } testWorktreeE2E();