42861ea7fa
Multi-adapter mobile UI for AI coding assistants. Supports Claude Code, Codex CLI, and Gemini CLI through one interface. Features: - Real-time bidirectional sync via tmux + WebSocket - Cross-AI review (send one AI's output to another for review) - Multi-review tabs with minimize/expand - Push notifications (PWA) with smart session-aware filtering - Three-channel event system (hooks, file watcher, pane monitor) - Voice input, image paste, draft persistence - Terminal-native design (JetBrains Mono, dark theme, pixel art claw) - CLI with --adapter flag on every command - Zero-overhead fire-and-forget hooks
121 lines
6.4 KiB
Markdown
121 lines
6.4 KiB
Markdown
# Codex UUID Discovery Fix + Session Architecture Cleanup
|
|
|
|
## Problems Found
|
|
|
|
### 1. Deadlock: `_waitForCliUUID` blocks `startSession` (Critical)
|
|
|
|
`startSession()` calls `_waitForCliUUID()` which polls for `session.cliSessionId` to be set. But the UUID is only set when `handleSessionStart` hook fires, which requires Codex to process a prompt. The prompt is sent AFTER `startSession` returns. Deadlock: 15-second timeout, session creation fails.
|
|
|
|
Affects both `handleQuery` (new Codex chat from Web UI) and `POST /api/reviews` (Cross-AI Review child session).
|
|
|
|
### 2. Pending Session Matching by Count (Medium)
|
|
|
|
`handleSessionStart` matches hook to pending session by checking `pendingSessions.length === 1`. If 0 pending: treated as desktop-started. If 2 pending: neither matches, hook creates a spurious session entry. This is a guess, not a precise match.
|
|
|
|
### 3. `_findAndAttachWindow` uses `command.includes('codex')` (Medium)
|
|
|
|
Grabs the first tmux window whose command contains `codex`. If multiple codex windows exist, picks the wrong one. After Session ID Unification, window names are UUIDs, so this method is both incorrect and unnecessary (see solution).
|
|
|
|
### 4. `_watchForTranscript` matches by recency (Low)
|
|
|
|
Scans the day directory for JSONL files modified within 120 seconds, picks the first match. If two Codex sessions start simultaneously, can pick the wrong file.
|
|
|
|
### 5. Server shutdown leaves tmux windows running (Resource waste)
|
|
|
|
`adapter.destroy()` cleans up monitors and watchers but does NOT kill tmux windows. After server stops, CLI processes continue running in tmux, consuming resources.
|
|
|
|
### 6. DB sessions table is unnecessary (Complexity)
|
|
|
|
The `sessions` DB table stores `id`, `cwd`, `window_id`, `adapter`. After the Session ID Unification, all runtime data is in the in-memory `sessions` Map. The DB was used for:
|
|
- `_findAndAttachWindow` window recovery after restart: unnecessary if windows are killed on shutdown
|
|
- `handleReconnect` cwd lookup for resumeSession: unnecessary if handleReconnect doesn't resume
|
|
- Review endpoints cwd lookup: can use in-memory Map instead
|
|
|
|
## Solution
|
|
|
|
### A. Remove `_waitForCliUUID` entirely
|
|
|
|
`startSession()` returns the temp key immediately. UUID discovery happens asynchronously via `handleSessionStart` or `_watchForTranscript`.
|
|
|
|
### B. CODETAP_REF marker for precise matching
|
|
|
|
Every first message sent to a new Codex session includes a marker:
|
|
|
|
```
|
|
[CODETAP_REF:codex-1774316492094]
|
|
actual prompt or context here...
|
|
```
|
|
|
|
Where `codex-1774316492094` is the temp key (tmux window name at creation time).
|
|
|
|
**Injection points:**
|
|
- `handleQuery` in session-manager.ts: when creating a new session (no existing sessionId), prepend marker to the prompt
|
|
- `POST /api/reviews` in index.ts: prepend marker to the context
|
|
|
|
**Matching in `handleSessionStart`:**
|
|
1. Read the JSONL file at `body.transcript_path`
|
|
2. Find the first user message
|
|
3. Extract `CODETAP_REF:xxx` marker
|
|
4. Match `xxx` to a pending session's temp key
|
|
5. Call `_rekeyAndRename` to finalize
|
|
|
|
**Matching in `_watchForTranscript`:**
|
|
- After finding a candidate JSONL file, verify it contains `CODETAP_REF:tempKey`
|
|
|
|
**Frontend filtering:**
|
|
- Strip `[CODETAP_REF:...]` from user messages in `convertMessages` (useChat.ts)
|
|
|
|
### C. `_rekeyAndRename` — finalize UUID discovery
|
|
|
|
New method called when UUID is discovered (by handleSessionStart or _watchForTranscript):
|
|
- Delete temp key from sessions Map
|
|
- Set CLI UUID as new key
|
|
- Rename tmux window from temp name to CLI UUID
|
|
- Update monitor's sessionId
|
|
|
|
### D. Server shutdown kills all tmux windows
|
|
|
|
`adapter.destroy()` calls `tmuxManager.killSession()` to kill the entire codetap tmux session. No resource leaks.
|
|
|
|
### E. Remove `_findAndAttachWindow`
|
|
|
|
With shutdown killing all windows, no tmux windows survive restart. No need to rediscover windows. Delete the method and all call sites.
|
|
|
|
### F. Remove DB sessions table
|
|
|
|
The `sessions` table serves no purpose after changes D and G:
|
|
- `_findAndAttachWindow` (deleted in E) was the main consumer
|
|
- `handleReconnect` no longer calls `resumeSession` (changed in G)
|
|
- Review endpoints get `cwd` from in-memory Map (changed in H)
|
|
|
|
Delete: CREATE TABLE, prepared statements, SessionRow interface, `sessions` export, all `dbSessions.*` calls across the codebase.
|
|
|
|
DB retains only `session_reviews` table (for Cross-AI Review).
|
|
|
|
### G. Simplify `handleReconnect`
|
|
|
|
Remove the `hasActiveWindow` + `resumeSession` block. After shutdown kills windows, there is no scenario where a session is not in the Map but has an active tmux window.
|
|
|
|
`handleReconnect` becomes: register client, load JSONL history, replay pending state. Building tmux windows is `handleQuery`'s job (when the user sends a message).
|
|
|
|
### H. Review endpoints get cwd from Map + add parent_adapter to session_reviews
|
|
|
|
Replace `dbSessions.get(parentCliSessionId)` with `adapter.getSession(parentCliSessionId)` to get `cwd` from the in-memory Map. The parent session is always active (user is interacting with it) so it is always in the Map.
|
|
|
|
Add `parent_adapter TEXT NOT NULL` column to `session_reviews` table. Store it when creating a review. This way `send-back` and `delete` endpoints can find the correct adapter directly from the review row, without needing to iterate all adapters or query the sessions DB.
|
|
|
|
## Files Affected
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `server/adapters/codex/codex-tmux-adapter.ts` | A: remove _waitForCliUUID. B: add _matchByTranscriptMarker. C: add _rekeyAndRename. D: destroy calls killSession. E: remove _findAndAttachWindow. F: remove all dbSessions calls |
|
|
| `server/adapters/claude/tmux-adapter.ts` | D: destroy calls killSession. F: remove all dbSessions calls |
|
|
| `server/adapters/claude/index.ts` | No change (doesn't use dbSessions directly) |
|
|
| `server/adapters/codex/index.ts` | No change |
|
|
| `server/session-manager.ts` | B: inject marker in handleQuery. F: remove dbSessions import and calls. G: simplify handleReconnect. H: review restoration uses adapter.getSession for cwd |
|
|
| `server/index.ts` | B: inject marker in POST /api/reviews. F: remove dbSessions import, clearAll call in shutdown. H: review endpoints use adapter.getSession for cwd |
|
|
| `server/db.ts` | F: delete sessions table schema, SessionRow, prepared statements, sessions export. Keep session_reviews |
|
|
| `src/lib/content-utils.ts` | B: add stripMarker function |
|
|
| `src/hooks/useChat.ts` | B: strip marker in convertMessages |
|
|
| `bin/codetap` | F: remove SQL queries that reference sessions table (get_project_sessions, -a listing, --resume lookup) |
|