Implement side-by-side preview layout when AskUserQuestion options
include markdown content, matching the Claude Code tool spec.
Hook changes (bin/amc-hook):
- Extract optional 'markdown' field from question options
- Only include when present (maintains backward compatibility)
QuestionBlock layout:
- Detect hasMarkdownPreviews via options.some(opt => opt.markdown)
- Standard layout: vertical option list (unchanged)
- Preview layout: 40% options left, 60% preview pane right
- Fixed 400px preview height prevents layout thrashing on hover
- Track previewIndex state, update on mouseEnter/focus
Content rendering (smart detection):
- Code fences (starts with ```): renderContent() for syntax highlighting
- Everything else: raw <pre> to preserve ASCII diagrams exactly
- "No preview for this option" when hovered option lacks markdown
OptionButton enhancements:
- Accept selected, onMouseEnter, onFocus props
- Visual selected state: brighter border/bg with subtle shadow
- Compact padding (py-2 vs py-3.5) for preview layout density
- Graceful undefined handling for standard layout usage
Bug fixes during development:
- Layout thrashing: Fixed height (not max-height) on preview pane
- ASCII corruption: Detect code fences vs plain text for render path
- Horizontal overflow: Use overflow-auto (not just overflow-y-auto)
- Data pipeline: Hook was stripping markdown field (root cause)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enhance the launcher script with robust process management:
Process Lifecycle:
- Use macOS launchctl to spawn server process, avoiding parent-shell
cleanup kills that terminate AMC when Claude Code's shell exits
- Fall back to setsid+nohup on non-macOS systems
- Graceful shutdown with 5-second wait loop before force-kill
Health Checking:
- Replace simple PID file check with actual HTTP health check
- curl /api/state to verify server is truly responsive, not just alive
- Report "healthy" vs "running but not responding" states
PID Recovery:
- Recover orphan PID file from port listener (lsof -tiTCP:7400)
- Validate listener is actually amc-server (not another process on 7400)
- Auto-refresh PID file when process exists but file is stale
Logging:
- New LOG_FILE at ~/.local/share/amc/server.log
- show_recent_logs() helper for diagnostics
- Consistent log routing through launchctl submit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split the 860+ line bin/amc-server into a modular Python package:
amc_server/
__init__.py - Package marker
context.py - Shared constants (DATA_DIR, PORT, CLAUDE_PROJECTS_DIR, etc.)
handler.py - AMCHandler class using mixin composition
logging_utils.py - Structured logging setup with signal handlers
server.py - Main entry point (ThreadingHTTPServer)
mixins/
__init__.py - Mixin package marker
control.py - Session control (dismiss, respond via Zellij)
conversation.py - Conversation history parsing (Claude JSONL format)
discovery.py - Session discovery (Codex pane inspection, Zellij cache)
http.py - HTTP response helpers (CORS, JSON, static files)
parsing.py - Session state parsing and aggregation
state.py - Session state endpoint logic
The monolithic bin/amc-server becomes a thin launcher that just imports
and calls main(). This separation enables:
- Easier testing of individual components
- Better IDE support (proper Python package structure)
- Cleaner separation of concerns (discovery vs parsing vs control)
- ThreadingHTTPServer instead of single-threaded (handles concurrent requests)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the zellij dump-layout approach with direct process inspection
for matching Codex sessions to Zellij panes. The old method couldn't
extract pane IDs from layout output, leaving them empty. The new
approach uses a three-step pipeline:
1. pgrep -x codex to find running Codex PIDs
2. ps eww to extract ZELLIJ_PANE_ID and ZELLIJ_SESSION_NAME from each
process's inherited environment variables
3. lsof -a -p <pids> -d cwd to batch-resolve working directories
Session-to-pane matching then uses a two-tier strategy:
- Primary: lsof -t <session_file> to find which PID has the JSONL open
- Fallback: normalized CWD comparison
Also adds:
- _codex_pane_cache with 5-second TTL to avoid running pgrep/ps/lsof
on every dashboard poll cycle
- _dismissed_codex_ids set to track Codex sessions the user has
dismissed, preventing re-discovery on subsequent polls
- Clearer error message when session lacks pane info for input routing
bin/amc:
- Add cross-platform browser opening: try 'open' (macOS) first,
fall back to 'xdg-open' (Linux) for desktop environments
- Wrap browser opening in conditional to avoid errors on headless systems
bin/amc-hook:
- Fix edge case in question extraction where tool_input.get("questions")
could return None instead of empty list, causing TypeError
Extend AMC to monitor Codex sessions alongside Claude Code:
Codex Integration:
- Discover active Codex sessions from ~/.codex/sessions/*.jsonl
- Parse Codex JSONL format (response_item/message payloads) for
conversation history, filtering out developer role injections
- Extract session metadata (cwd, timestamp) from session_meta records
- Match Codex sessions to Zellij panes via cwd for response injection
- Add ?agent=codex query param to /api/conversation endpoint
Session Lifecycle Improvements:
- Cache Zellij session list for 5 seconds to reduce subprocess calls
- Proactive liveness check: auto-delete orphan "starting" sessions
when their Zellij session no longer exists
- Clean up stale "starting" sessions after 1 hour (likely orphaned)
- Preserve existing event log cleanup (24h for orphan logs)
Code Quality:
- Refactor _serve_conversation into _parse_claude_conversation and
_parse_codex_conversation for cleaner separation
- Add _discover_active_codex_sessions for session file generation
- Add _get_codex_zellij_panes to match sessions to panes
- Use JSON error responses consistently via _json_error helper