When daemon restarts, SESSION_HISTORY is lost because it's an in-memory Map. This fix adds disk-based timeline loading from Codex rollout files. Changes: - Add loadPersistedHistoryFromDisk() method to load from rollout files - Add helper functions for finding and parsing rollout JSONL files - Call disk loading in connect() when resumeHandle exists but SESSION_HISTORY is empty - Add E2E test for timeline persistence across daemon restart The rollout files are stored at ~/.codex/sessions/<date>/rollout-*.jsonl and contain JSONL entries with response_item and event_msg types. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
101 lines
5.3 KiB
Markdown
101 lines
5.3 KiB
Markdown
# Plan
|
||
|
||
## Context
|
||
|
||
Build a new Codex MCP provider side‑by‑side with the existing Codex SDK provider. The new provider lives in `packages/server/src/server/agent/providers/codex-mcp-agent.ts` and is selected via a new provider id (e.g. `codex-mcp`). All testing is **E2E only** (no mocks/fakes). Use `/Users/moboudra/dev/voice-dev/.tmp/happy-cli/src/codex/` as reference for MCP + elicitation.
|
||
|
||
## CRITICAL RULES - READ BEFORE EVERY TASK
|
||
|
||
1. **NO VAGUE REPORTS**: Never say "test hung", "was interrupted", "failed locally" without:
|
||
|
||
- The EXACT error message or stack trace
|
||
- The SPECIFIC line of code causing the issue
|
||
- A concrete hypothesis for the root cause
|
||
|
||
2. **NO SKIPPING/DISABLING TESTS**: Skipping tests, adding `.skip`, or "opt-in gating" is **NOT ACCEPTABLE**. Fix the actual problem. If a test hangs, find out WHY and fix the code, not the test.
|
||
|
||
3. **NO WORKAROUNDS**: Adding timeouts, fallbacks, or "defensive" code that hides bugs is forbidden. The code must work correctly, not appear to work.
|
||
|
||
4. **INVESTIGATE DEEPLY**: When something fails:
|
||
|
||
- Read the actual source code
|
||
- Add debug logging if needed
|
||
- Trace the exact execution path
|
||
- Find the ROOT CAUSE, not symptoms
|
||
|
||
5. **BE SPECIFIC**: Every "Done" entry must include:
|
||
- What the actual problem was (specific)
|
||
- What code was changed (file:line)
|
||
- How you verified it works
|
||
|
||
## Completed Work (Compacted)
|
||
|
||
### Codex MCP Provider (2025-12-24)
|
||
|
||
- ✅ Created `codex-mcp-agent.ts` with MCP stdio client, event mapping, permissions, persistence, abort handling
|
||
- ✅ Fixed model availability (removed hardcoded gpt-4.1), permission elicitation, exit code handling
|
||
- ✅ Fixed thread/item event mapping for file_change, mcp_tool_call, web_search, todo_list
|
||
- ✅ Fixed persistence to include conversationId metadata
|
||
- ✅ Resolved Codex MCP vs Codex SDK elicitation parity (CRITICAL FINDING: `codex exec` ignores approval events)
|
||
- ✅ All 14 Codex MCP unit tests pass; Codex is now the only provider (deprecated SDK provider removed)
|
||
- Reports: `CODEX_MCP_MISMATCH_REPORT.md`, `REPORT-codex-mcp-audit.md`
|
||
|
||
### UI/UX Fixes (2025-12-25)
|
||
|
||
- ✅ Removed duplicate "Codex MCP" option - now shows only "Codex"
|
||
- ✅ Fixed duplicate user/assistant messages (provider was emitting, but agent-manager already dispatches)
|
||
- ✅ Fixed Codex agent-control MCP parity with Claude (added MCP servers to Codex config)
|
||
- ✅ Fixed agent timestamp not updating on click without interaction
|
||
|
||
### DaemonClient Implementation (2025-12-25)
|
||
|
||
- ✅ Created `packages/server/src/server/test-utils/daemon-client.ts` (~550 lines)
|
||
- ✅ Created `packages/server/src/server/test-utils/daemon-test-context.ts`
|
||
- ✅ Created `packages/server/src/server/daemon.e2e.test.ts` (25 passing tests)
|
||
- Reports: `REPORT-daemon-client-design.md`, `REPORT-daemon-e2e-audit.md`, `REPORT-claude-permission-tests.md`
|
||
|
||
**DaemonClient API:**
|
||
|
||
- Connection: `connect()`, `close()`
|
||
- Agent lifecycle: `createAgent()`, `deleteAgent()`, `listAgents()`, `listPersistedAgents()`, `resumeAgent()`
|
||
- Agent interaction: `sendMessage()`, `cancelAgent()`, `setAgentMode()`, `initializeAgent()`, `clearAgentAttention()`
|
||
- Permissions: `respondToPermission()`, `waitForPermission()`
|
||
- Git: `getGitDiff()`, `getGitRepoInfo()`
|
||
- Files: `exploreFileSystem()`
|
||
- Models: `listProviderModels()`
|
||
- Waiting: `waitForAgentIdle()`
|
||
- Events: `on()`, `getMessageQueue()`, `clearMessageQueue()`
|
||
|
||
**E2E Test Coverage:**
|
||
|
||
- Basic flow (Codex + Claude): create agent, send message, verify response
|
||
- Permissions (Codex + Claude): approve/deny, permission_requested/resolved cycle
|
||
- Persistence: delete agent, resume from handle, verify conversation context
|
||
- Multi-agent: parent creates child via agent-control MCP
|
||
- Agent management: cancelAgent, setAgentMode, listAgents
|
||
- Timestamp: verify clicking agent doesn't update timestamp
|
||
- Git: diff (staged/unstaged/modified), repo info (branch, dirty state)
|
||
- Files: list directory, read file content
|
||
- Models: list Codex and Claude provider models
|
||
- Images: single and multiple image attachments to Claude
|
||
|
||
## Tasks
|
||
|
||
- [x] **BUG**: Codex MCP agent returns 0 timeline events after daemon restart.
|
||
- **Done (2025-12-25 19:13)**: Fixed by implementing disk-based timeline history loading in `codex-mcp-agent.ts`.
|
||
|
||
**WHAT**: Added rollout file parsing to load persisted timeline from `~/.codex/sessions/` when `SESSION_HISTORY` is empty after daemon restart.
|
||
|
||
**CHANGES**:
|
||
- `codex-mcp-agent.ts:2-6`: Added imports for `fs`, `Dirent`, `os`, `path`
|
||
- `codex-mcp-agent.ts:2521`: Added `resumeHandle` field to store handle for async loading
|
||
- `codex-mcp-agent.ts:2530`: Save `resumeHandle` in constructor
|
||
- `codex-mcp-agent.ts:2592-2596`: Load history from disk in `connect()` when `persistedHistory.length === 0`
|
||
- `codex-mcp-agent.ts:2616-2628`: New `loadPersistedHistoryFromDisk()` method
|
||
- `codex-mcp-agent.ts:3919-4181`: New helper functions: `resolveCodexSessionRoot()`, `findRolloutFile()`, `parseRolloutFile()`, `loadCodexPersistedTimeline()`, plus rollout entry parsing functions
|
||
|
||
**TEST ADDED**:
|
||
- `daemon.e2e.test.ts:1638-1757`: New test "Codex agent timeline survives daemon restart" that verifies timeline is preserved via `agent_stream_snapshot` message after resume
|
||
|
||
**RESULT**: E2E test passes, typecheck passes. Timeline items are loaded from rollout files when resuming an agent after daemon restart.
|