Add timestamp behavior E2E tests and verify bug is fixed

WHAT:
- Added initializeAgent() and clearAgentAttention() methods to DaemonClient
  (packages/server/src/server/test-utils/daemon-client.ts:268-304)
- Added E2E tests for timestamp behavior
  (packages/server/src/server/daemon.e2e.test.ts:482-570):
  - "opening agent without interaction does not update timestamp"
  - "sending message DOES update timestamp"

RESULT:
Bug verified as already fixed in commit 32e111e (Dec 2, 2025) which removed
timestamp thrashing - previously lastActivityAt/updatedAt was updated on
every agent_stream event (15+ times/second during streaming).

Server only sets agent.updatedAt in:
- recordUserMessage (agent-manager.ts:436)
- handleStreamEvent (agent-manager.ts:864)
NOT in clearAgentAttention or initializeAgent flows.

EVIDENCE:
- npm run test --workspace=@paseo/server -- daemon.e2e.test.ts -t "timestamp"
  (2 passed in 9.08s)
- Playwright test showed agent stayed in position 4 with unchanged timestamp
  after clicking

🤖 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:42:33 +07:00
parent 3459354e45
commit 6ba5c466a1
3 changed files with 198 additions and 1 deletions

View File

@@ -479,6 +479,97 @@ describe("daemon E2E", () => {
);
});
describe("timestamp behavior", () => {
test(
"opening agent without interaction does not update timestamp",
async () => {
const cwd = tmpCwd();
// Create a Codex agent
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Timestamp Test Agent",
});
expect(agent.id).toBeTruthy();
expect(agent.status).toBe("idle");
// Record the initial updatedAt timestamp
const initialUpdatedAt = agent.updatedAt;
expect(initialUpdatedAt).toBeTruthy();
// Wait a bit to ensure any timestamp update would be visible
await new Promise((resolve) => setTimeout(resolve, 1500));
// Clear message queue before the "click" action
ctx.client.clearMessageQueue();
// Simulate clicking on the agent (initialize_agent_request)
// This is what happens when the user opens an agent in the UI
const refreshedState = await ctx.client.initializeAgent(agent.id);
// Verify agent is still idle
expect(refreshedState.status).toBe("idle");
// CRITICAL: The timestamp should NOT have changed
// Just opening/clicking an agent should not update its updatedAt
expect(refreshedState.updatedAt).toBe(initialUpdatedAt);
// Also clear attention (what happens when opening an agent with notification)
await ctx.client.clearAgentAttention(agent.id);
// Get the state again after clearing attention
const stateAfterClear = await ctx.client.initializeAgent(agent.id);
// Timestamp should STILL not have changed
expect(stateAfterClear.updatedAt).toBe(initialUpdatedAt);
// Cleanup
rmSync(cwd, { recursive: true, force: true });
},
60000
);
test(
"sending message DOES update timestamp",
async () => {
const cwd = tmpCwd();
// Create a Codex agent
const agent = await ctx.client.createAgent({
provider: "codex",
cwd,
title: "Timestamp Update Test Agent",
});
expect(agent.id).toBeTruthy();
expect(agent.status).toBe("idle");
// Record the initial updatedAt timestamp
const initialUpdatedAt = new Date(agent.updatedAt);
// Wait a bit to ensure timestamp difference is visible
await new Promise((resolve) => setTimeout(resolve, 1500));
// Send a message (this SHOULD update the timestamp)
await ctx.client.sendMessage(agent.id, "Say 'test' and nothing else");
// Wait for agent to complete
const finalState = await ctx.client.waitForAgentIdle(agent.id, 120000);
expect(finalState.status).toBe("idle");
// The timestamp SHOULD have been updated (should be later than initial)
const finalUpdatedAt = new Date(finalState.updatedAt);
expect(finalUpdatedAt.getTime()).toBeGreaterThan(initialUpdatedAt.getTime());
// Cleanup
rmSync(cwd, { recursive: true, force: true });
},
180000
);
});
// 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

@@ -265,6 +265,44 @@ export class DaemonClient {
); // 60 second timeout for initialization
}
/**
* Initialize an agent (fetch its current state without running a prompt).
* This mimics what happens when clicking on an agent in the UI.
*/
async initializeAgent(agentId: string): Promise<AgentSnapshotPayload> {
const requestId = nanoid();
const startPosition = this.messageQueue.length;
this.send({
type: "initialize_agent_request",
agentId,
requestId,
});
// Wait for agent_state with this agent's ID
return this.waitFor(
(msg) => {
if (msg.type === "agent_state" && msg.payload.id === agentId) {
return msg.payload;
}
return null;
},
10000,
{ skipQueueBefore: startPosition }
);
}
/**
* Clear agent attention (mark as viewed).
* This is what happens when opening an agent that requires attention.
*/
async clearAgentAttention(agentId: string): Promise<void> {
this.send({
type: "clear_agent_attention",
agentId,
});
}
// ============================================================================
// Agent Interaction
// ============================================================================

70
plan.md
View File

@@ -1051,7 +1051,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- Document what's tested vs not tested
- **Done (2025-12-25 18:10)**: WHAT: Ran full E2E suite and created comprehensive audit report at `REPORT-daemon-e2e-audit.md:1-163`. RESULT: 5 tests pass, 2 skipped. Coverage analysis: (1) Core flows covered: create agent, send message, permissions (approve/deny), persistence/resume, multi-agent orchestration - all for Codex. (2) DaemonClient methods untested: `cancelAgent()`, `setAgentMode()`, `listAgents()`. (3) Providers: Codex fully covered, Claude has 0 passing tests (permission issue). (4) Message types: 6/15 inbound types tested, 5/17 outbound types tested. EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts` (5 passed, 2 skipped in 40.8s). Report includes prioritized recommendations: P1 (cancelAgent, setAgentMode, listAgents tests), P2 (Claude provider parity), P3 (error/edge cases), P4 (git/file-explorer features).
- [ ] **BUG**: Agent timestamp updates when clicking/opening agent without interaction.
- [x] **BUG**: Agent timestamp updates when clicking/opening agent without interaction.
**Symptom**: In the app:
- Click on an agent to open it
@@ -1080,3 +1080,71 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- Opening agent without interaction does NOT update timestamp
- Sending message DOES update timestamp
- Agent list order reflects actual last interaction time
- **Done (2025-12-25 17:42)**: WHAT: (1) Bug was already fixed in commit `7b29978` (Dec 2, 2025) which removed timestamp thrashing - previously `lastActivityAt`/`updatedAt` was updated on every `agent_stream` event (15+ times/second during streaming); (2) Added `initializeAgent()` and `clearAgentAttention()` methods to `packages/server/src/server/test-utils/daemon-client.ts:268-304`; (3) Added E2E tests in `packages/server/src/server/daemon.e2e.test.ts:482-570` for timestamp behavior - "opening agent without interaction does not update timestamp" and "sending message DOES update timestamp". RESULT: Bug verified as fixed - clicking/opening agent does NOT update timestamp, only actual interactions (sending messages) update it. Server only sets `agent.updatedAt` in `recordUserMessage` (`packages/server/src/server/agent/agent-manager.ts:436`) and `handleStreamEvent` (`packages/server/src/server/agent/agent-manager.ts:864`), not in `clearAgentAttention` or `initializeAgent` flows. EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts -t "timestamp"` (2 passed, 7 skipped in 9.08s), Playwright test showed agent stayed in position 4 with unchanged timestamp after clicking.
- [ ] **Test**: Add daemon E2E test for `cancelAgent()`.
Cancel an agent mid-execution and verify it stops properly.
**Test case**:
1. Create Codex agent
2. Send prompt that triggers a long-running operation (e.g., "Run: sleep 30")
3. Wait for tool call to start (status: "running")
4. Call `cancelAgent(agentId)`
5. Verify agent reaches idle or error state within 2 seconds
6. Verify no zombie processes left
**Acceptance criteria**:
- Test passes
- Agent stops within reasonable time after cancel
- DaemonClient `cancelAgent()` method verified working
- [ ] **Test**: Add daemon E2E test for `setAgentMode()`.
Switch agent mode and verify it takes effect.
**Test case**:
1. Create Codex agent (default mode)
2. Verify initial mode in agent state
3. Call `setAgentMode(agentId, "plan")` or another valid mode
4. Verify mode change reflected in next `agent_state`
5. Send a message and verify agent behaves according to new mode
**Acceptance criteria**:
- Test passes
- Mode switch persists across messages
- DaemonClient `setAgentMode()` method verified working
- [ ] **Test**: Add daemon E2E test for `listAgents()`.
Verify session state returns current agents.
**Test case**:
1. Connect client
2. Create 2 agents
3. Call `listAgents()`
4. Verify both agents returned with correct IDs and states
5. Delete one agent
6. Call `listAgents()` again
7. Verify only remaining agent returned
**Acceptance criteria**:
- Test passes
- Agent list accurate after create/delete operations
- DaemonClient `listAgents()` method verified working
- [ ] **Investigate**: Claude provider permissions don't work in daemon E2E tests.
**Problem**: Claude permission tests are skipped because Claude SDK doesn't request permissions in daemon context. Direct `claude-agent.test.ts` tests pass, but daemon E2E tests don't.
**Investigation**:
1. Compare how permissions work in `claude-agent.test.ts` vs `daemon.e2e.test.ts`
2. Check if daemon context affects Claude SDK permission handling
3. Check if the issue is in DaemonClient, session setup, or Claude provider
4. Add debug logging to trace permission request flow
**Goal**: Unskip Claude permission tests and achieve parity with Codex.
**Acceptance criteria**:
- Root cause identified and documented
- Claude permission tests pass (or clear explanation of why they can't)