Commit Graph

18 Commits

Author SHA1 Message Date
teernisse
4740922b8d refactor(dashboard): simplify chat rendering and add scroll behavior
Removes unnecessary complexity from ChatMessages while adding proper
scroll management to SessionCard:

ChatMessages.js:
- Remove scroll position tracking refs and effects (wasAtBottomRef,
  prevMessagesLenRef, containerRef)
- Remove spinner display logic (moved to parent components)
- Simplify to pure message filtering and rendering
- Add display limit (last 20 messages) with offset tracking for keys

SessionCard.js:
- Add chatPaneRef for scroll container
- Add useEffect to scroll to bottom when conversation updates
- Provides natural "follow" behavior for new messages

The refactor moves scroll responsibility to the component that owns
the scroll container, reducing prop drilling and effect complexity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 16:32:08 -05:00
teernisse
8578a19330 feat(dashboard): add smooth modal entrance/exit animations
Implements animated modal open/close with accessibility support:

- Add closing state with 200ms exit animation before unmount
- Refactor to React hooks-compliant structure (guards after hooks)
- Add CSS keyframes for backdrop fade and panel scale+translate
- Include prefers-reduced-motion media query to disable animations
  for users with vestibular sensitivities
- Use handleClose callback wrapper for consistent animation behavior
  across Escape key, backdrop click, and close button

The animations provide visual continuity without being distracting,
and gracefully degrade to instant transitions when reduced motion
is preferred.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 16:32:00 -05:00
teernisse
754e85445a test(server): add unit tests for context, control, and state mixins
Adds comprehensive test coverage for the amc_server package:

- test_context.py: Tests _resolve_zellij_bin preference order (which
  first, then fallback to bare name)
- test_control.py: Tests SessionControlMixin including two-step Enter
  injection with configurable delay, freeform response handling,
  plugin inject with explicit session/pane targeting, and unsafe
  fallback behavior
- test_state.py: Tests StateMixin zellij session parsing with the
  resolved binary path

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 16:31:51 -05:00
teernisse
183942fbaa fix(dashboard): spinner animation polish and accessibility
Polish the working indicator animations:

- Use cubic-bezier easing for smoother spinner rotation
- Add will-change hints for GPU acceleration
- Add display: inline-block to bounce-dot spans (required for transform)
- Add prefers-reduced-motion media query to disable animations for
  motion-sensitive users (accessibility)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:36:36 -05:00
teernisse
2f80995f8d fix(dashboard): robust tool call display and filter logic
Two fixes for tool call display in the dashboard:

1. **filterDisplayMessages includes tool_calls** (MessageBubble.js)
   Previously filtered out messages with only tool_calls (no content/thinking).
   Now correctly keeps messages that have tool_calls.

2. **Type-safe getToolSummary** (markdown.js)
   The heuristic tool summary extractor was calling .slice() without
   type checks. If a tool input had a non-string value (e.g., number),
   it would throw TypeError. Now uses a helper function to safely
   check types before calling string methods.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:36:26 -05:00
teernisse
8224acbba7 feat(dashboard): add working indicator for active sessions
Visual feedback when an agent is actively processing:

1. **Spinner on status dots** (SessionCard.js, Modal.js)
   - Status dot gets a spinning ring animation when session is active/starting
   - Uses CSS border trick with transparent borders except top

2. **Working indicator in chat** (ChatMessages.js, Modal.js)
   - Shows at bottom of conversation when agent is working
   - Bouncing dots animation ("...") next to "Agent is working" text
   - Only visible for active/starting statuses

3. **CSS animations** (styles.css)
   - spin-ring: 0.8s rotation for the status dot border
   - bounce-dot: staggered vertical bounce for the working dots

4. **Status metadata** (status.js)
   - Added `spinning: true` flag for active and starting statuses
   - Used by components to conditionally render spinner elements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:20:26 -05:00
teernisse
7cf51427b7 fix(codex): parse tool calls and reasoning in conversation API
The Codex conversation parser was only handling "message" payload types,
missing tool calls entirely. Codex uses separate response_items:

  - function_call: tool invocations with name, arguments, call_id
  - reasoning: thinking summaries (encrypted content, visible summary)
  - message: user/assistant text (previously the only type handled)

Changes:
- Parse function_call payloads and accumulate as tool_calls array
- Attach tool_calls to the next assistant message, or flush standalone
- Parse reasoning payloads and extract summary text as thinking
- Add _parse_codex_arguments() helper to handle JSON string arguments

This fixes the dashboard not showing Codex tool calls like exec_command,
read_file, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:20:18 -05:00
teernisse
be2dd6a4fb fix(zellij): robust binary resolution and two-step Enter injection
Two reliability fixes for response injection:

1. **Zellij binary resolution** (context.py, state.py, control.py)
   
   When AMC is started via macOS launchctl, PATH is minimal and may not
   include Homebrew's bin directory. The new `_resolve_zellij_bin()`
   function tries `shutil.which("zellij")` first, then falls back to
   common installation paths:
   - /opt/homebrew/bin/zellij (Apple Silicon Homebrew)
   - /usr/local/bin/zellij (Intel Homebrew)
   - /usr/bin/zellij
   
   All subprocess calls now use ZELLIJ_BIN instead of hardcoded "zellij".

2. **Two-step Enter injection** (control.py)
   
   Previously, text and Enter were sent together, causing race conditions
   where Claude Code would receive only the Enter key (blank submit).
   Now uses `_inject_text_then_enter()`:
   - Send text (without Enter)
   - Wait for configurable delay (default 200ms)
   - Send Enter separately
   
   Delay is configurable via AMC_SUBMIT_ENTER_DELAY_MS env var (0-2000ms).

3. **Documentation updates** (README.md)
   
   - Update file table: dashboard-preact.html → dashboard/
   - Clarify plugin is required (not optional) for pane-targeted injection
   - Document AMC_ALLOW_UNSAFE_WRITE_CHARS_FALLBACK env var
   - Note about Zellij resolution for launchctl compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:20:08 -05:00
teernisse
da08d7a588 refactor(dashboard): extract modular Preact component structure
Replace the monolithic single-file dashboards (dashboard.html,
dashboard-preact.html) with a proper modular directory structure:

  dashboard/
    index.html              - Entry point, loads main.js
    main.js                 - App bootstrap, mounts <App> to #root
    styles.css              - Global styles (dark theme, typography)
    components/
      App.js                - Root component, state management, polling
      Header.js             - Top bar with refresh/timing info
      Sidebar.js            - Project tree navigation
      SessionCard.js        - Individual session card with status/actions
      SessionGroup.js       - Group sessions by project path
      Modal.js              - Full conversation viewer overlay
      ChatMessages.js       - Message list with role styling
      MessageBubble.js      - Individual message with markdown
      QuestionBlock.js      - User question input with quick options
      EmptyState.js         - "No sessions" placeholder
      OptionButton.js       - Quick response button component
      SimpleInput.js        - Text input with send button
    lib/
      preact.js             - Preact + htm ESM bundle (CDN shim)
      markdown.js           - Lightweight markdown-to-HTML renderer
    utils/
      api.js                - fetch wrappers for /api/* endpoints
      formatting.js         - Time formatting, truncation helpers
      status.js             - Session status logic, action availability

This structure enables:
- Browser-native ES modules (no build step required)
- Component reuse and isolation
- Easier styling and theming
- IDE support for component navigation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 15:01:47 -05:00
teernisse
9cd91f6b4e feat(launcher): improve bin/amc with launchctl, health checks, and PID recovery
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>
2026-02-25 15:01:35 -05:00
teernisse
a7b2b3b902 refactor(server): extract amc_server package from monolithic script
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>
2026-02-25 15:01:26 -05:00
teernisse
e718c44555 feat(dashboard): add markdown rendering, project grouping, and visual refresh
Markdown & syntax highlighting:
- Replace homegrown backtick parser with marked + DOMPurify + highlight.js
- Register 10 language grammars (JS, TS, Bash, JSON, Python, Rust, CSS,
  HTML/XML, SQL, YAML) for fenced code block highlighting
- Sanitize rendered HTML via DOMPurify to prevent XSS from message content

Project-based session grouping:
- Replace four status-based groups (attention/active/starting/done) with
  project-directory grouping via groupSessionsByProject()
- Groups sort by most urgent status first, then most recent activity
- Sessions within each group sort by urgency then recency
- Group headers show project name, agent count, and per-status chips
- Project directory path shown below group header

Visual refinements:
- Darken color palette across the board (bg, surface, scrollbar, glass
  panels) for deeper contrast
- Add agent-header-codex and agent-header-claude CSS classes for
  color-coded card and modal headers (teal vs purple tints)
- Increase agent badge visibility with higher opacity borders/backgrounds
- Cards now fixed-width 600px with taller height (850px) in flex-wrap
  layout instead of responsive grid
- Input focus border color now matches the session's status color
  instead of hardcoded active/attention colors
- Send button background also matches status color for visual coherence
- Lock body scroll when session modal is open
- Add agent badge to modal header for consistent identification
2026-02-25 11:51:39 -05:00
teernisse
e994c7a0e8 feat(server): rewrite Codex pane discovery using process inspection
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
2026-02-25 11:51:28 -05:00
teernisse
c777ef05b6 docs: add comprehensive README with architecture and API reference
Document AMC (Agent Mission Control) for new users and contributors:

- Overview: real-time dashboard for Claude Code session monitoring
- Features: session grouping, conversation history, response injection,
  question detection (AskUserQuestion and prose), session lifecycle
- Installation: symlink setup and PATH configuration
- Hook Setup: complete settings.json configuration for all required
  Claude Code hooks (SessionStart, UserPromptSubmit, Stop, SessionEnd,
  PreToolUse/PostToolUse for AskUserQuestion)
- Architecture: ASCII diagram showing data flow from Claude Code hooks
  through amc-hook to disk storage to amc-server to dashboard
- Components: descriptions of amc launcher, amc-server, amc-hook,
  and dashboard-preact.html
- Data Storage: runtime data paths in ~/.local/share/amc/
- API Reference: all 6 endpoints with methods and descriptions
- Response Injection: request body format and two-phase freeform flow
- Session Statuses: starting, active, needs_attention, done
- Cleanup Behavior: orphan detection, stale session removal, event log gc
- Requirements: Python 3.8+, Zellij, Claude Code hooks
- Optional Zellij Plugin: for focus-free response injection
2026-02-25 11:21:48 -05:00
teernisse
0551b9fd89 fix: cross-platform browser opening and hook robustness
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
2026-02-25 11:21:35 -05:00
teernisse
a9ed8f90f4 feat(server): add Codex session support and improve session lifecycle
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
2026-02-25 11:21:30 -05:00
teernisse
4d2fcc9a3b feat(dashboard): rewrite UI with Preact for reactive session monitoring
Replace vanilla JS dashboard with a modern Preact-based implementation:

- Component architecture: Header, SessionCard, SessionGroup, Modal,
  ChatMessages, QuestionBlock, SimpleInput, OptionButton, EmptyState
- Status-grouped display: sessions organized by needs_attention, active,
  starting, and done with color-coded visual indicators
- Real-time conversation view: fetch and display chat history from
  Claude Code JSONL logs with auto-scroll on new messages
- Inline response input: send messages directly from cards without
  opening modal, supports both structured options and freeform text
- Modal detail view: full conversation with markdown rendering,
  timestamps, and keyboard navigation (Enter to send, Escape to close)
- Smart polling: 3-second state refresh with silent conversation
  updates for active sessions to minimize UI flicker
- Flexoki color scheme: custom dark theme with attention (amber),
  active (green), starting (blue), and done (purple) status colors
- Berkeley Mono font stack with graceful fallbacks
- Tailwind CSS via CDN with custom color configuration
- Responsive card layout with fixed 600px height and overflow handling

The dashboard provides a unified view for monitoring multiple Claude
Code sessions, detecting when agents need human input, and responding
directly via Zellij pane injection.
2026-02-25 11:21:15 -05:00
teernisse
b2a5712202 Initial work, pre-preact refactor 2026-02-25 09:21:59 -05:00