Files
clawtap/docs/superpowers/specs/2026-03-25-unified-session-path-design.md
kuannnn 42861ea7fa feat: ClawTap v0.1.0 — initial release
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
2026-03-26 10:40:26 +08:00

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 QUERYhandleQuerystartSession + registerClient + sendMessage — all in one handler, atomically.
  • Review child: HTTP POST /api/reviewsstartSession + pasteToSession on server → broadcast REVIEW_STARTED → FloatingReviewPanel mounts → useChat sends WS RECONNECTregisterClient — 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 — uses sendKeys (character-by-character, doesn't handle newlines)
  • pasteToSession — uses pasteBuffer (bulk paste, replaces \n with \\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 calling api.createReview(), locally mount FloatingReviewPanel with review props
  • FloatingReviewPanel: receives initialPrompt prop, useChat auto-sends it as first QUERY
  • After SESSION_CREATED, call api.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) — REMOVE
  • adapter.pasteToSession(childSessionId, markerContext) — REMOVE
  • sessionReviews.create(...) — KEEP
  • broadcastReviewStarted(...) — 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 HTTP
  • rekeyAliases — kept as defensive mechanism (handleQuery's registerClient vs hook timing)
  • session-rekeyed forwarding — kept (still needed for handleQuery flow)

Verification

  1. New Codex session from WebUI — send message, verify response appears
  2. Cross-AI Review: click "Send to Codex" → panel opens → Codex responds in panel (same QUERY flow)
  3. Send back to parent — verify message appears
  4. End review — verify markers appear
  5. Reconnect — verify active review restored