Add multi-agent E2E test (DaemonClient Phase 4)

Adds test that verifies parent agent can create child agent via
agent-control MCP. Test creates Codex parent, prompts it to call
create_agent tool, and verifies both agents are visible.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-25 17:28:52 +07:00
parent 1fb73b1045
commit 5f642f12ea
2 changed files with 115 additions and 1 deletions

View File

@@ -366,6 +366,119 @@ describe("daemon E2E", () => {
);
});
describe("multi-agent orchestration", () => {
test(
"parent agent creates child agent via agent-control MCP",
async () => {
const cwd = tmpCwd();
const childCwd = tmpCwd();
// Create parent Codex agent
const parent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Parent Agent",
});
expect(parent.id).toBeTruthy();
expect(parent.status).toBe("idle");
// Clear message queue before sending prompt
ctx.client.clearMessageQueue();
// Prompt the parent to create a child agent using agent-control MCP
const prompt = [
`Use the create_agent tool from the agent-control MCP server to create a new codex agent.`,
`Set the cwd to: ${childCwd}`,
`Set the title to: Child Agent`,
`Set agentType to: codex`,
`Do NOT set an initialPrompt - just create the agent.`,
`After creating the agent, reply with "CREATED" followed by the child's agentId.`,
].join(" ");
await ctx.client.sendMessage(parent.id, prompt);
// Wait for parent to complete
const afterCreate = await ctx.client.waitForAgentIdle(
parent.id,
120000
);
expect(afterCreate.status).toBe("idle");
// Verify timeline contains a tool call to create_agent
const queue = ctx.client.getMessageQueue();
const timelineItems: AgentTimelineItem[] = [];
for (const m of queue) {
if (
m.type === "agent_stream" &&
m.payload.agentId === parent.id &&
m.payload.event.type === "timeline"
) {
timelineItems.push(m.payload.event.item);
}
}
// Should have a tool call to create_agent from agent-control
const hasCreateAgentCall = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.tool === "create_agent" &&
item.server === "agent-control"
);
expect(hasCreateAgentCall).toBe(true);
// Now verify we can see both agents via session_state
// Send a list_persisted_agents_request to trigger session_state refresh
// Or we can check the queue for agent_state messages
const agentStateMessages = queue.filter(
(m) => m.type === "agent_state"
);
// Extract unique agent IDs from state messages
const agentIds = new Set<string>();
for (const m of agentStateMessages) {
if (m.type === "agent_state") {
agentIds.add(m.payload.id);
}
}
// Should have at least 2 agents (parent + child)
expect(agentIds.size).toBeGreaterThanOrEqual(2);
expect(agentIds.has(parent.id)).toBe(true);
// Get the child agent ID from the tool call output
const createAgentCall = timelineItems.find(
(item) =>
item.type === "tool_call" &&
item.tool === "create_agent" &&
item.server === "agent-control"
);
let childAgentId: string | null = null;
if (
createAgentCall &&
createAgentCall.type === "tool_call" &&
createAgentCall.output
) {
// The output contains the agentId
const output = createAgentCall.output as { agentId?: string };
if (output.agentId) {
childAgentId = output.agentId;
}
}
// Verify we found the child agent ID
expect(childAgentId).toBeTruthy();
expect(agentIds.has(childAgentId!)).toBe(true);
// Cleanup
rmSync(cwd, { recursive: true, force: true });
rmSync(childCwd, { recursive: true, force: true });
},
300000 // 5 minute timeout for multi-agent E2E
);
});
// Claude permission tests are skipped due to SDK behavior:
// - The sandbox config IS passed correctly to Claude SDK
// - Claude executes tool calls without requesting permission

View File

@@ -1032,7 +1032,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- Persistence round-trip works via DaemonClient
- **Done (2025-12-25 17:23)**: WHAT: Modified `daemon-client.ts:217-265` to fix `resumeAgent()` to properly wait for the new agent's idle state using `skipQueueBefore` option (not cached old agent messages). Added E2E test `daemon.e2e.test.ts:255-366` "persists and resumes Codex agent with conversation history" that creates agent, sends message, deletes, and resumes from persistence handle. RESULT: Persistence round-trip works - agent is deleted, resumed via persistence handle with conversationId, and responds to follow-up messages. EVIDENCE: `npm run test -- daemon.e2e.test.ts -t "persists and resumes"` passed (9.2s). Note: `listPersistedAgents()` and `resumeAgent()` methods already existed; the fix was to make `resumeAgent()` properly skip stale queue messages when waiting for the new agent.
- [ ] **Implement**: Multi-agent E2E test (Phase 4).
- [x] **Implement**: Multi-agent E2E test (Phase 4).
**Add E2E test**:
- `multi-agent: agent A launches agent B` - parent agent uses agent-control MCP to create child
@@ -1040,6 +1040,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
**Acceptance criteria**:
- Multi-agent orchestration works via DaemonClient
- Both parent and child agents visible in listAgents()
- **Done (2025-12-25 17:32)**: WHAT: Added `daemon.e2e.test.ts:369-483` describe block "multi-agent orchestration" with test "parent agent creates child agent via agent-control MCP". Test creates a parent Codex agent, prompts it to call `create_agent` tool via agent-control MCP, then verifies: (1) tool call to `create_agent` with server `agent-control` exists in timeline, (2) both parent and child agent IDs are visible via `agent_state` messages, (3) child agent ID from tool output matches tracked agents. RESULT: Multi-agent orchestration verified - parent agent successfully creates child agent using agent-control MCP, both agents tracked by daemon. EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts` (5 passed, 2 skipped in 40.8s), `npm run typecheck --workspace=@paseo/server` (exit 0).
- [ ] **Review**: Audit daemon E2E test coverage.