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
6.1 KiB
Unified Session Creation Path for Cross-AI Review
Context
Cross-AI Review child sessions currently use a different creation path than normal sessions:
- Normal session: WebUI sends WS
QUERY→handleQuery→startSession+registerClient+sendMessage— all in one handler, atomically. - Review child: HTTP
POST /api/reviews→startSession+pasteToSessionon server → broadcastREVIEW_STARTED→ FloatingReviewPanel mounts →useChatsends WSRECONNECT→registerClient— split across HTTP and WS.
This split causes race conditions (rekey happens before WS client connects) and requires defensive mechanisms (rekeyAliases, session-rekeyed event forwarding) that wouldn't be needed if both paths were the same.
Insight: A review child session IS a normal new session. The only difference is the first message is review context instead of user-typed text. It should go through the same QUERY flow.
Design
1. Codex sendMessage — auto-handle large/multiline content
Currently Codex adapter has two methods:
sendMessage— usessendKeys(character-by-character, doesn't handle newlines)pasteToSession— usespasteBuffer(bulk paste, replaces\nwith\\n)
Review context is large (30KB+) and multiline. If it goes through sendMessage via QUERY, sendKeys would be extremely slow and newlines would be treated as separate message submissions.
Fix: Make sendMessage auto-detect and use pasteBuffer for large/multiline content. Transparent to all callers.
Important: Fresh Codex sessions have TUI placeholder text (e.g., "Use /skills to list available skills"). Pasting via pasteBuffer appends to the placeholder, truncating the first ~20 chars. The existing fix (from this session) splits the paste: send the [CODETAP_REF:...] marker via sendKeys first (triggers TUI to clear placeholder), wait 200ms, then pasteBuffer the rest. The unified sendMessage must preserve this behavior.
sendMessage(sessionId, text):
if text.length > 500 || text.includes('\n'):
singleLine = text.replace(/\n/g, '\\n')
// Check for CODETAP_REF marker at start (fresh session with placeholder)
markerMatch = singleLine.match(/^\[CODETAP_REF:[^\]]+\]/)
if markerMatch:
sendKeys(marker) // clears TUI placeholder
wait 200ms
pasteBuffer(rest) // fast, placeholder already cleared
else:
pasteBuffer(singleLine) // existing session, no placeholder issue
wait 300ms
sendControl('Enter')
else:
sendKeys(text) // character-by-character, fine for short text
wait 200ms
sendControl('Enter')
This merges sendMessage and pasteToSession into one method that handles all cases.
Files: server/adapters/codex/codex-tmux-adapter.ts
2. Frontend — review child uses QUERY, not RECONNECT
Current flow:
POST /api/reviews → server creates session + DB record → broadcast REVIEW_STARTED
→ parent useChat sets activeReview → FloatingReviewPanel mounts
→ useChat(childSessionId) → WS RECONNECT → handleReconnect
New flow:
User clicks "Send to Codex" → selects template
→ ChatView locally sets activeReview state (no server call)
→ FloatingReviewPanel mounts with { context, targetAdapter, cwd }
→ FloatingReviewPanel's useChat auto-sends context as first WS QUERY
→ handleQuery → startSession → registerClient → sendMessage (same as normal!)
→ SESSION_CREATED received → useChat has childSessionId
→ POST /api/reviews { parentSessionId, childSessionId, ... } → DB record created
Key changes:
ChatView.handleReviewSelect: instead of callingapi.createReview(), locally mount FloatingReviewPanel with review propsFloatingReviewPanel: receivesinitialPromptprop, useChat auto-sends it as first QUERY- After
SESSION_CREATED, callapi.registerReview()to persist the DB record
Files: src/components/ChatView.tsx, src/components/FloatingReviewPanel.tsx, src/hooks/useChat.ts, src/lib/api.ts
3. Server — POST /api/reviews simplified
From:
adapter.startSession(cwd)— REMOVEadapter.pasteToSession(childSessionId, markerContext)— REMOVEsessionReviews.create(...)— KEEPbroadcastReviewStarted(...)— KEEP (for multi-device sync)- Returns
{ reviewId, childSessionId }— childSessionId now comes from client
To:
POST /api/reviews (renamed or new endpoint: POST /api/reviews/register)
Body: { parentSessionId, childSessionId, targetAdapter, anchorMessageId, prompt, title }
→ sessionReviews.create(...)
→ broadcastReviewStarted(parentSessionId, { reviewId, childSessionId, ... })
→ Returns { reviewId }
Files: server/index.ts
4. CODETAP_REF marker — already handled
handleQuery in session-manager.ts already injects [CODETAP_REF:tempKey] for non-Claude new sessions. No change needed — the marker injection works naturally through the QUERY flow.
5. pasteToSession — can be removed from Codex adapter public API
After sendMessage handles all content sizes, pasteToSession is no longer needed as a separate public method. It can be:
- Removed from the adapter interface
- Or kept as internal helper called by
sendMessage
The only remaining caller is POST /api/reviews/:id/send-back (sends feedback to parent). This also goes through sendMessage if we update it.
Files: server/adapters/codex/codex-tmux-adapter.ts, server/adapters/codex/index.ts, server/adapters/interface.ts
Not Changed
POST /api/reviews/:id/send-back— still HTTP (different concern: sending message to an existing session)POST /api/reviews/:id/end— still HTTPrekeyAliases— kept as defensive mechanism (handleQuery's registerClient vs hook timing)session-rekeyedforwarding — kept (still needed for handleQuery flow)
Verification
- New Codex session from WebUI — send message, verify response appears
- Cross-AI Review: click "Send to Codex" → panel opens → Codex responds in panel (same QUERY flow)
- Send back to parent — verify message appears
- End review — verify markers appear
- Reconnect — verify active review restored