agent-orchestrator/packages/plugins/runtime-tmux/README.md

171 lines
4.6 KiB
Markdown

# @agent-orchestrator/plugin-runtime-tmux
Runtime plugin for executing agent sessions in tmux.
## What This Does
Creates isolated tmux sessions for each agent. Each session runs in a separate tmux session with:
- Working directory set to workspace path
- Environment variables from config
- Agent launch command executed automatically
## How It Works
### Creating a Session
```typescript
const handle = await runtime.create({
sessionId: "my-app-3",
workspacePath: "/Users/dev/.worktrees/my-app/my-app-3",
launchCommand: "claude -p 'Fix bug in auth module'",
environment: {
AO_SESSION_ID: "my-app-3",
AO_PROJECT_ID: "my-app",
},
});
```
**What happens:**
1. Validates `sessionId` (only alphanumeric, dash, underscore allowed)
2. Creates detached tmux session: `tmux new-session -d -s my-app-3 -c /path/to/workspace`
3. Sets environment variables: `tmux ... -e KEY=VALUE`
4. Sends launch command: `tmux send-keys -t my-app-3 "claude -p '...'" Enter`
5. Returns RuntimeHandle with tmux session name
### Sending Messages
```typescript
await runtime.sendMessage(handle, "Fix the test failure in auth.test.ts");
```
**What happens:**
1. Clears partial input: `tmux send-keys -t my-app-3 C-u`
2. For short messages (<200 chars, no newlines): sends directly with `-l` flag (literal mode)
3. For long/multiline messages: writes to temp file `tmux load-buffer` `tmux paste-buffer`
4. Waits 300ms (let tmux process the text)
5. Sends Enter: `tmux send-keys -t my-app-3 Enter`
**Why the complexity?**
- `send-keys` without `-l` interprets special strings ("Enter", "Space") as key names
- Long strings can overflow tmux's command buffer
- Multiline strings need special handling
### Getting Output
```typescript
const output = await runtime.getOutput(handle, 50); // last 50 lines
```
Uses `tmux capture-pane -t my-app-3 -p -S -50` to capture terminal buffer.
### Checking if Alive
```typescript
const alive = await runtime.isAlive(handle);
```
Uses `tmux has-session -t my-app-3` (exit code 0 = exists, 1 = doesn't exist).
### Destroying
```typescript
await runtime.destroy(handle);
```
Kills tmux session: `tmux kill-session -t my-app-3` (ignores errors if already dead).
## Attaching to Sessions
For Terminal plugins (iTerm2, web):
```typescript
const attachInfo = await runtime.getAttachInfo(handle);
// Returns: { type: "tmux", target: "my-app-3", command: "tmux attach -t my-app-3" }
```
## Security
**Session ID validation:**
```typescript
const SAFE_SESSION_ID = /^[a-zA-Z0-9_-]+$/;
```
Only allows safe characters. Prevents shell injection via session name (used in tmux commands).
## Error Handling
- **Session creation fails** cleans up (kills session) before throwing
- **Message send fails** throws (caller should handle)
- **Session already dead** `destroy()` silently succeeds (idempotent)
## Metrics
```typescript
const metrics = await runtime.getMetrics(handle);
// Returns: { uptimeMs: 123456 }
```
Tracks uptime (stored in RuntimeHandle.data.createdAt).
## Testing
This plugin is tested indirectly via `packages/core/src/__tests__/tmux.test.ts` (utility functions) and integration tests.
To test manually:
```bash
# Start a test session
tmux new-session -d -s test-session -c /tmp
tmux send-keys -t test-session "echo hello" Enter
# Capture output
tmux capture-pane -t test-session -p
# Kill session
tmux kill-session -t test-session
```
## Common Issues
### tmux not installed
If tmux is not in PATH, all operations fail. Install via:
- macOS: `brew install tmux`
- Linux: `apt-get install tmux` or `yum install tmux`
### Session name conflicts
If a session with the same ID already exists, `create()` fails. The orchestrator should ensure unique session IDs.
### Detached sessions persist after orchestrator crashes
tmux sessions keep running even if the orchestrator dies. Use `tmux list-sessions` to find orphans, `tmux kill-session -t <name>` to clean up.
## Limitations
- **macOS/Linux only** tmux is not available on Windows (use WSL)
- **No Windows native support** use runtime-process instead on Windows
- **Terminal buffer size** `getOutput()` limited by tmux buffer size (default 2000 lines)
- **No resource limits** agents can consume unlimited CPU/memory (use docker/k8s runtimes for isolation)
## Architecture Notes
**Why tmux over raw processes?**
- Sessions persist across orchestrator restarts
- Easy to attach for debugging: `tmux attach -t session-name`
- Terminal emulation (colors, ANSI codes work)
- Works well with interactive AI tools (Claude Code, Aider)
**Why detached mode?**
- Orchestrator doesn't block waiting for agent
- Multiple agents can run in parallel
- Humans can attach later without interrupting agent