Add daemon E2E test for setAgentMode()

- Added E2E test that verifies mode switching (auto → read-only → full-access)
- Test verifies mode persists across messages
- Fixed bug: setMode() now updates cachedRuntimeInfo so getRuntimeInfo()
  returns correct modeId after mode change

🤖 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:48:02 +07:00
parent 5ce0a742e7
commit 18cc63fa39
3 changed files with 121 additions and 1 deletions

View File

@@ -2763,6 +2763,13 @@ class CodexMcpAgentSession implements AgentSession {
async setMode(modeId: string): Promise<void> {
this.currentMode = modeId;
this.config.modeId = modeId;
// Update cached runtime info to reflect mode change
if (this.cachedRuntimeInfo) {
this.cachedRuntimeInfo = {
...this.cachedRuntimeInfo,
modeId,
};
}
}
getPendingPermissions(): AgentPermissionRequest[] {

View File

@@ -708,6 +708,118 @@ describe("daemon E2E", () => {
);
});
describe("setAgentMode", () => {
test(
"switches agent mode and persists across messages",
async () => {
const cwd = tmpCwd();
// Create a Codex agent with default mode ("auto")
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Mode Switch Test Agent",
});
expect(agent.id).toBeTruthy();
expect(agent.status).toBe("idle");
// Verify initial mode is "auto" (the default)
expect(agent.currentModeId).toBe("auto");
// Clear message queue before mode switch
ctx.client.clearMessageQueue();
const startPosition = ctx.client.getMessageQueue().length;
// Switch to "read-only" mode
await ctx.client.setAgentMode(agent.id, "read-only");
// Wait for agent_state update reflecting the new mode
const stateAfterModeSwitch = await new Promise<AgentSnapshotPayload>(
(resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Timeout waiting for mode change in agent_state"));
}, 10000);
const checkForModeChange = (): void => {
const queue = ctx.client.getMessageQueue();
for (let i = startPosition; i < queue.length; i++) {
const msg = queue[i];
if (
msg.type === "agent_state" &&
msg.payload.id === agent.id &&
msg.payload.currentModeId === "read-only"
) {
clearTimeout(timeout);
clearInterval(interval);
resolve(msg.payload);
return;
}
}
};
const interval = setInterval(checkForModeChange, 50);
}
);
// Verify mode changed to "read-only"
expect(stateAfterModeSwitch.currentModeId).toBe("read-only");
// Now verify the mode persists: send a message and check the mode is still "read-only"
ctx.client.clearMessageQueue();
await ctx.client.sendMessage(agent.id, "Say 'hello' and nothing else");
const finalState = await ctx.client.waitForAgentIdle(agent.id, 120000);
// Mode should still be "read-only" after the message
expect(finalState.currentModeId).toBe("read-only");
// Also verify runtimeInfo has the updated modeId
expect(finalState.runtimeInfo?.modeId).toBe("read-only");
// Switch to another mode: "full-access"
ctx.client.clearMessageQueue();
const position2 = ctx.client.getMessageQueue().length;
await ctx.client.setAgentMode(agent.id, "full-access");
// Wait for agent_state update
const stateAfterFullAccess = await new Promise<AgentSnapshotPayload>(
(resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Timeout waiting for full-access mode change"));
}, 10000);
const checkForModeChange = (): void => {
const queue = ctx.client.getMessageQueue();
for (let i = position2; i < queue.length; i++) {
const msg = queue[i];
if (
msg.type === "agent_state" &&
msg.payload.id === agent.id &&
msg.payload.currentModeId === "full-access"
) {
clearTimeout(timeout);
clearInterval(interval);
resolve(msg.payload);
return;
}
}
};
const interval = setInterval(checkForModeChange, 50);
}
);
expect(stateAfterFullAccess.currentModeId).toBe("full-access");
// Cleanup
rmSync(cwd, { recursive: true, force: true });
},
180000 // 3 minute timeout
);
});
// 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

@@ -1100,7 +1100,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- DaemonClient `cancelAgent()` method verified working
- **Done (2025-12-25 17:44)**: WHAT: Added E2E test in `packages/server/src/server/daemon.e2e.test.ts:573-708` that creates a Codex agent, sends "Run: sleep 30" to trigger a long-running operation, waits for "running" status, calls `cancelAgent(agentId)`, verifies the agent stops within 2 seconds, and checks for no zombie "sleep 30" processes. RESULT: Test passes - agent cancel request is received, turn_failed is emitted, and agent becomes idle/error within milliseconds (test completed in 291ms). EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts -t "cancelAgent"` (1 passed, 9 skipped in 846ms).
- [ ] **Test**: Add daemon E2E test for `setAgentMode()`.
- [x] **Test**: Add daemon E2E test for `setAgentMode()`.
Switch agent mode and verify it takes effect.
@@ -1115,6 +1115,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- Test passes
- Mode switch persists across messages
- DaemonClient `setAgentMode()` method verified working
- **Done (2025-12-25 17:48)**: WHAT: Added E2E test in `packages/server/src/server/daemon.e2e.test.ts:711-821` that creates Codex agent, verifies initial mode is "auto", switches to "read-only", verifies mode persists after sending a message, and switches to "full-access". FIXED BUG: `packages/server/src/server/agent/providers/codex-mcp-agent.ts:2763-2773` - `setMode()` was not updating `cachedRuntimeInfo`, so `getRuntimeInfo()` returned stale `modeId`. Fix: update `cachedRuntimeInfo.modeId` when mode changes. RESULT: Test passes - mode switch reflects in both `currentModeId` and `runtimeInfo.modeId`, persists across messages. EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts -t "setAgentMode"` (1 passed, 10 skipped in 4154ms).
- [ ] **Test**: Add daemon E2E test for `listAgents()`.