171 lines
4.6 KiB
Markdown
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
|