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>
186 lines
6.5 KiB
Markdown
186 lines
6.5 KiB
Markdown
# AMC — Agent Mission Control
|
|
|
|
A real-time dashboard for monitoring and interacting with Claude Code sessions.
|
|
|
|
## Overview
|
|
|
|
AMC provides a unified view of all running Claude Code sessions across projects. It displays session status, conversation history, and pending questions — and allows you to respond directly from the dashboard without switching terminal tabs.
|
|
|
|
## Features
|
|
|
|
- **Real-time monitoring** — Sessions are grouped by status (needs attention, active, starting, done) with 3-second polling
|
|
- **Conversation history** — View the full chat history for any session, pulled from Claude Code's JSONL logs
|
|
- **Response injection** — Send responses to agents directly via Zellij pane integration
|
|
- **Question detection** — Detects both structured `AskUserQuestion` prompts and prose questions (messages ending with "?")
|
|
- **Session lifecycle** — Automatically cleans up orphaned sessions and stale event logs
|
|
|
|
## Installation
|
|
|
|
1. Clone this repository
|
|
2. Symlink the launcher to your PATH:
|
|
|
|
```bash
|
|
ln -s /path/to/amc/bin/amc ~/.local/bin/amc
|
|
```
|
|
|
|
3. Configure Claude Code hooks (see [Hook Setup](#hook-setup))
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
amc start # Start the server and open the dashboard
|
|
amc stop # Stop the server
|
|
amc status # Check if the server is running
|
|
```
|
|
|
|
The dashboard opens at `http://127.0.0.1:7400`.
|
|
|
|
## Hook Setup
|
|
|
|
AMC requires Claude Code hooks to report session state. Add this to your `~/.claude/settings.json`:
|
|
|
|
```json
|
|
{
|
|
"hooks": {
|
|
"SessionStart": [
|
|
{ "command": "/path/to/amc/bin/amc-hook" }
|
|
],
|
|
"UserPromptSubmit": [
|
|
{ "command": "/path/to/amc/bin/amc-hook" }
|
|
],
|
|
"Stop": [
|
|
{ "command": "/path/to/amc/bin/amc-hook" }
|
|
],
|
|
"SessionEnd": [
|
|
{ "command": "/path/to/amc/bin/amc-hook" }
|
|
],
|
|
"PreToolUse": [
|
|
{ "matcher": "AskUserQuestion", "command": "/path/to/amc/bin/amc-hook" }
|
|
],
|
|
"PostToolUse": [
|
|
{ "matcher": "AskUserQuestion", "command": "/path/to/amc/bin/amc-hook" }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────┐ ┌─────────────────────┐
|
|
│ Claude Code │────▶│ amc-hook │
|
|
│ (hooks) │ │ (writes state) │
|
|
└─────────────────────┘ └──────────┬──────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────┐
|
|
│ ~/.local/share/ │
|
|
│ amc/sessions/ │
|
|
│ amc/events/ │
|
|
└──────────┬──────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────┐ ┌─────────────────────┐
|
|
│ Dashboard │◀────│ amc-server │
|
|
│ (Preact UI) │ │ (Python HTTP) │
|
|
└─────────────────────┘ └─────────────────────┘
|
|
```
|
|
|
|
### Components
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `bin/amc` | Launcher script — start/stop/status commands |
|
|
| `bin/amc-server` | Python HTTP server serving the API and dashboard |
|
|
| `bin/amc-hook` | Hook script called by Claude Code to write session state |
|
|
| `dashboard/` | Modular Preact dashboard (index.html, components/, lib/, utils/) |
|
|
|
|
### Data Storage
|
|
|
|
All runtime data lives in `~/.local/share/amc/`:
|
|
|
|
| Path | Contents |
|
|
|------|----------|
|
|
| `sessions/*.json` | Current session state (one file per session) |
|
|
| `events/*.jsonl` | Event log for each session (append-only) |
|
|
| `server.pid` | PID file for the running server |
|
|
| `server.log` | Server stdout/stderr |
|
|
|
|
## API Reference
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/` | Dashboard HTML |
|
|
| GET | `/api/state` | All sessions as JSON |
|
|
| GET | `/api/events/{id}` | Event timeline for a session |
|
|
| GET | `/api/conversation/{id}?project_dir=...` | Conversation history (from Claude Code logs) |
|
|
| POST | `/api/dismiss/{id}` | Dismiss (delete) a completed session |
|
|
| POST | `/api/respond/{id}` | Send a response to a session |
|
|
|
|
### Response Injection
|
|
|
|
The `/api/respond/{id}` endpoint injects text into a session's Zellij pane. Request body:
|
|
|
|
```json
|
|
{
|
|
"text": "your response here",
|
|
"freeform": true,
|
|
"optionCount": 3
|
|
}
|
|
```
|
|
|
|
- `text` — The response to send
|
|
- `freeform` — If true, treats as freeform text (selects "Other" option first)
|
|
- `optionCount` — Number of options in the current question (used for freeform)
|
|
|
|
Response injection works via:
|
|
1. **Zellij plugin** (`~/.config/zellij/plugins/zellij-send-keys.wasm`) — Required for pane-targeted sends and Enter submission
|
|
2. **Optional unsafe fallback** (`AMC_ALLOW_UNSAFE_WRITE_CHARS_FALLBACK=1`) — Uses focused-pane `write-chars` only when explicitly enabled
|
|
|
|
AMC resolves the Zellij binary from PATH plus common Homebrew locations (`/opt/homebrew/bin/zellij`, `/usr/local/bin/zellij`) so response injection still works when started via `launchctl`.
|
|
|
|
## Session Statuses
|
|
|
|
| Status | Meaning |
|
|
|--------|---------|
|
|
| `starting` | Session started, no prompt submitted yet |
|
|
| `active` | Session is processing work |
|
|
| `needs_attention` | Agent is waiting for user input (question or AskUserQuestion) |
|
|
| `done` | Session stopped (can be dismissed) |
|
|
|
|
## Cleanup Behavior
|
|
|
|
- **Orphan sessions**: Sessions with missing Zellij sessions in "starting" status are auto-deleted
|
|
- **Stale "starting" sessions**: Sessions stuck in "starting" for >1 hour are removed
|
|
- **Stale event logs**: Event logs without matching sessions are deleted after 24 hours
|
|
- **SessionEnd**: Deletes the session file immediately
|
|
|
|
## Requirements
|
|
|
|
- Python 3.8+
|
|
- Zellij (for response injection)
|
|
- Claude Code with hooks support
|
|
|
|
## Testing
|
|
|
|
Run the server test suite:
|
|
|
|
```bash
|
|
python3 -m unittest discover -s tests -v
|
|
```
|
|
|
|
## Zellij Plugin
|
|
|
|
For pane-targeted response injection (including reliable Enter submission), install the `zellij-send-keys` plugin:
|
|
|
|
```bash
|
|
# Build and install the plugin
|
|
# (See zellij-send-keys repository for instructions)
|
|
```
|
|
|
|
Place the compiled WASM at `~/.config/zellij/plugins/zellij-send-keys.wasm`.
|
|
|
|
## License
|
|
|
|
MIT
|