Files
clawtap/docs/superpowers/plans/2026-03-27-interactive-prompts.md
kuannnn 0fcf66fc22 feat: ClawTap v0.2.0
Interactive Prompts:
- Unified InteractivePrompt type across all 3 adapters (Claude/Codex/Gemini)
- InteractivePromptOverlay component with options, text input, countdown
- Gemini + Codex pane monitors detect tool confirmation, ask user, plan approval
- respondInteractivePrompt routing: permission → respondPermission, options → _selectOption
- Claude AskUserQuestion nested questions[0] structure parsing

Cross-AI Review:
- Client-generated reviewId, removed pendingReview state
- FloatingReviewPanel uses CSS display:none instead of unmount (keeps hooks alive)
- Child review sessions default to YOLO/bypass permission mode
- Send back to parent, send to existing/new review, tab switching, end review
- Collapsed review cards with read-only panel for ended reviews
- Full reconnect support: active + ended reviews restore correctly

AskUserQuestion Tool Card UI:
- Dedicated renderer replaces raw JSON display
- Options shown with selected (green) / unselected (gray) indicators
- Free text answers shown in quoted format with green border
- Collapsed summary: question → answer
- Shared parseAskQuestionInput utility (client + server)
- Historical tool results attached via _result on tool_use blocks

Adapter Fixes:
- Session→adapter mapping persisted in SQLite (survives server restart)
- SESSION_CREATED deferred for pendingRekey adapters (Codex/Gemini)
- session-rekeyed handler sends complete SESSION_CREATED with adapter + cwd
- Gemini: auto-accept folder trust, privacy notice, IDE nudge, YOLO * prompt
- Claude: auto-accept bypass permissions confirmation (v2.1.85+)
- Port fallback (EADDRINUSE → try +1), statusLine shell script wrapper

Other:
- Desktop Enter sends / Shift+Enter newline; Mobile Enter newline
- Strip CLAWTAP_REF marker from session list
- Active sessions tab shows adapter badge
- Rename CLAUDE_UI_PASSWORD → CLAWTAP_PASSWORD

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:46:00 +08:00

196 lines
9.0 KiB
Markdown

# Interactive Prompts Implementation Plan (v2)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Detect Gemini/Codex interactive prompts from tmux pane, normalize all adapters to a single InteractivePrompt format, and render a unified overlay in the frontend.
**Architecture:** Pane monitors detect prompts and adapters emit interactive-prompt event. Session manager converts ALL prompt events (old Claude hooks + new pane detections) into WS.INTERACTIVE_PROMPT. Frontend uses a single InteractivePromptOverlay component. Response flows back via WS.PROMPT_RESPONSE and adapter sends keystrokes to tmux.
**Tech Stack:** TypeScript, React, WebSocket, tmux pane monitoring
**Spec:** docs/superpowers/specs/2026-03-27-interactive-prompts-design.md
---
## File Map
| File | Action | Change |
|---|---|---|
| server/types/messages.ts | Modify | Add InteractivePrompt, PromptResponse types |
| src/lib/ws-types.ts | Modify | Add INTERACTIVE_PROMPT, PROMPT_RESPONSE, PROMPT_DISMISSED |
| server/adapters/interface.ts | Modify | Add respondInteractivePrompt method |
| server/session-manager.ts | Modify | Convert old events to new format; add interactive-prompt listener; add handlePromptResponse; broadcast PROMPT_DISMISSED |
| server/adapters/gemini/pane-monitor.ts | Modify | Detect 4 prompt types, emit interactive-prompt |
| server/adapters/codex/pane-monitor.ts | Modify | Detect approval + user input, emit interactive-prompt |
| server/adapters/gemini/gemini-tmux-adapter.ts | Modify | Implement respondInteractivePrompt (numbered options + text) |
| server/adapters/codex/codex-tmux-adapter.ts | Modify | Implement respondInteractivePrompt (keyboard shortcuts + text) |
| server/adapters/claude/tmux-adapter.ts | Modify | Implement respondInteractivePrompt (delegates to existing) |
| src/hooks/useChat.ts | Modify | State type to InteractivePrompt; handle INTERACTIVE_PROMPT; add respondPrompt |
| src/components/InteractivePromptOverlay.tsx | Create | Unified overlay: options (buttons), textInput (field), or both (plan mode) |
| src/components/ChatView.tsx | Modify | Replace PermissionOverlay/AskQuestion with InteractivePromptOverlay |
| src/components/FloatingReviewPanel.tsx | Modify | Add prompt handling to ReviewTab |
| server/adapters/gemini/gemini-tmux-adapter.ts | Modify | _waitForReady: privacy notice, multi-folder trust, IDE nudge |
---
### Task 1: Types + WS message types
**Files:**
- Modify: server/types/messages.ts
- Modify: src/lib/ws-types.ts
- Modify: server/adapters/interface.ts
- [ ] **Step 1:** Add InteractivePrompt and PromptResponse types to server/types/messages.ts
- [ ] **Step 2:** Add INTERACTIVE_PROMPT, PROMPT_RESPONSE, PROMPT_DISMISSED to src/lib/ws-types.ts WS enum
- [ ] **Step 3:** Add respondInteractivePrompt(requestId: string, selectedOption?: string, textValue?: string): void {} to IAdapter in server/adapters/interface.ts
- [ ] **Step 4:** Commit
---
### Task 2: Session manager -- unified event conversion + response handling
**Files:**
- Modify: server/session-manager.ts
- [ ] **Step 1:** Replace existing permission-request listener to convert Claude hook data into InteractivePrompt format and broadcast as WS.INTERACTIVE_PROMPT (not WS.PERMISSION_REQUEST)
- [ ] **Step 2:** Replace existing ask-question listener to convert Claude hook data into InteractivePrompt format (detect options vs free text) and broadcast as WS.INTERACTIVE_PROMPT
- [ ] **Step 3:** Add interactive-prompt event listener for Gemini/Codex (already in InteractivePrompt format, just broadcast)
- [ ] **Step 4:** Add WS.PROMPT_RESPONSE case to handleIncomingMessage switch. Create handlePromptResponse function that calls adapter.respondInteractivePrompt and broadcasts WS.PROMPT_DISMISSED to all clients for multi-tab sync.
- [ ] **Step 5:** Commit
---
### Task 3: Gemini pane monitor -- detect prompts
**Files:**
- Modify: server/adapters/gemini/pane-monitor.ts
- [ ] **Step 1:** Add lastPromptId instance variable for dedup. At start of _poll, call _detectPrompt. If detected and different from lastPromptId, emit interactive-prompt event. If no longer detected, reset lastPromptId. Return early (skip streaming text detection while prompt is showing).
- [ ] **Step 2:** Implement _detectPrompt method. Detect 4 types:
- Tool Confirmation: content includes "Action Required" AND numbered options pattern
- AskUser: content includes "Answer Questions"
- Plan Approval: content includes "Approval" AND "Yes" options AND "feedback"
- Loop Detection: content includes "potential loop was detected"
For each, extract title, description, options (via _parseNumberedOptions), and textInput if applicable. Return InteractivePrompt or null.
- [ ] **Step 3:** Implement _parseNumberedOptions helper. Match regex for numbered list items. Return array of { value: String(index), label } where index is 0-based.
- [ ] **Step 4:** Commit
---
### Task 4: Codex pane monitor -- detect prompts
**Files:**
- Modify: server/adapters/codex/pane-monitor.ts
- [ ] **Step 1:** Add lastPromptId + detection at start of _poll (same dedup pattern as Gemini)
- [ ] **Step 2:** Detect command/file/network approval: content includes "(y)" AND "proceed". Parse options with _parseCodexOptions matching (letter) label pattern.
- [ ] **Step 3:** Detect user input: content includes "enter to submit" AND "esc to cancel". Determine if choice or free text.
- [ ] **Step 4:** Implement _parseCodexOptions helper. Match regex for (x) Label format. Return array of { value: letter, label }.
- [ ] **Step 5:** Commit
---
### Task 5: Adapter respondInteractivePrompt implementations
**Files:**
- Modify: server/adapters/gemini/gemini-tmux-adapter.ts
- Modify: server/adapters/codex/codex-tmux-adapter.ts
- Modify: server/adapters/claude/tmux-adapter.ts
- [ ] **Step 1:** Gemini respondInteractivePrompt. If selectedOption: parse as integer index, navigate Down x index + Enter. If textValue: sendKeys text + Enter.
- [ ] **Step 2:** Codex respondInteractivePrompt. If selectedOption: sendKeys with that single character (y/a/p/d/n). If textValue: sendKeys text + Enter.
- [ ] **Step 3:** Claude respondInteractivePrompt. If textValue: delegate to existing respondQuestion. If selectedOption: delegate to existing respondPermission with value as PermissionBehavior.
- [ ] **Step 4:** Commit
---
### Task 6: Frontend -- InteractivePromptOverlay component
**Files:**
- Create: src/components/InteractivePromptOverlay.tsx
- [ ] **Step 1:** Create component with props { prompt: InteractivePrompt, onRespond: (requestId, selectedOption?, textValue?) => void }. Use BottomSheet container. Render:
- Title bar with type badge (permission=orange, question=blue, plan=purple, loop-detected=yellow)
- Description text
- If toolName + toolInput: collapsible tool info card
- If options: vertical button list
- If textInput: text input field + submit button
- If both options AND textInput: buttons above, text input below (plan mode)
- 120s countdown timer
- [ ] **Step 2:** Commit
---
### Task 7: useChat -- InteractivePrompt state + respondPrompt
**Files:**
- Modify: src/hooks/useChat.ts
- [ ] **Step 1:** Add InteractivePrompt type (mirror from server types). Replace PermissionRequest state with InteractivePrompt state.
- [ ] **Step 2:** Replace WS.PERMISSION_REQUEST handler with WS.INTERACTIVE_PROMPT handler. Replace WS.PERMISSION_DISMISSED handler with WS.PROMPT_DISMISSED handler.
- [ ] **Step 3:** Add respondPrompt callback that sends WS.PROMPT_RESPONSE and clears interactivePrompt state.
- [ ] **Step 4:** Update return object: replace permissionRequest with interactivePrompt, add respondPrompt. Keep respondPermission and respondAsk temporarily for any remaining direct callers.
- [ ] **Step 5:** Commit
---
### Task 8: ChatView + ReviewTab -- use InteractivePromptOverlay
**Files:**
- Modify: src/components/ChatView.tsx
- Modify: src/components/FloatingReviewPanel.tsx
- [ ] **Step 1:** ChatView: replace PermissionOverlay/AskQuestion with InteractivePromptOverlay. Update useChat destructuring (interactivePrompt, respondPrompt).
- [ ] **Step 2:** ReviewTab: destructure interactivePrompt + respondPrompt from useChat. Render InteractivePromptOverlay after ChatBody.
- [ ] **Step 3:** Commit
---
### Task 9: Gemini _waitForReady -- remaining startup prompts
**Files:**
- Modify: server/adapters/gemini/gemini-tmux-adapter.ts
- [ ] **Step 1:** Add detection in _waitForReady for:
- Privacy Notice / Terms of Service: dismiss with Esc
- Multi-folder trust: accept default with Enter
- IDE integration nudge: decline (Down + Enter)
All before hasPrompt check, after existing folder trust check.
- [ ] **Step 2:** Commit
---
### Task 10: Build + E2E
- [ ] **Step 1:** TypeScript check
- [ ] **Step 2:** Build
- [ ] **Step 3:** E2E: Gemini tool confirmation (default mode)
- [ ] **Step 4:** E2E: Gemini AskUser
- [ ] **Step 5:** E2E: Codex command approval (suggest mode)
- [ ] **Step 6:** E2E: Claude permissions (backwards compatible)
- [ ] **Step 7:** E2E: Multi-tab dismiss