Overview
The Terminal interface manages how humans view and interact with running sessions. Terminal plugins open IDE tabs, browser windows, or terminal sessions for direct agent interaction.
Plugin Slot: terminal
Default Plugin: iterm2
Interface Definition
export interface Terminal {
readonly name: string;
openSession(session: Session): Promise<void>;
openAll(sessions: Session[]): Promise<void>;
isSessionOpen?(session: Session): Promise<boolean>;
}
Methods
Plugin name identifier (e.g. "iterm2", "web", "none").
openSession
(session: Session) => Promise<void>
required
Open a session for human interaction.Parameters:
session - Session to open
Implementation:
- iTerm2: Create new tab with tmux attach command
- Web: Open browser to web terminal URL
- None: No-op (headless mode)
openAll
(sessions: Session[]) => Promise<void>
required
Open all sessions for a project.Parameters:
sessions - Array of sessions to open
Implementation:
- iTerm2: Create window with multiple tabs
- Web: Open browser with multiple tabs
- None: No-op
isSessionOpen
(session: Session) => Promise<boolean>
Optional: Check if a session is already open in a tab/window.Parameters:
session - Session to check
Returns: true if session is open, false otherwise
Usage Examples
Implementing a Terminal Plugin (iTerm2)
import type { Terminal, Session } from "@composio/ao-core";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export function create(): Terminal {
return {
name: "iterm2",
async openSession(session: Session): Promise<void> {
if (!session.runtimeHandle) {
throw new Error("Cannot open session: no runtime handle");
}
const tmuxSession = session.runtimeHandle.id;
// AppleScript to create iTerm2 tab with tmux attach
const script = `
tell application "iTerm"
activate
tell current window
create tab with default profile
tell current session
write text "tmux attach -t ${tmuxSession}"
end tell
end tell
end tell
`;
await execFileAsync("osascript", ["-e", script], { timeout: 5_000 });
},
async openAll(sessions: Session[]): Promise<void> {
if (sessions.length === 0) return;
// Create window with tabs for each session
const tmuxSessions = sessions
.map(s => s.runtimeHandle?.id)
.filter(Boolean);
if (tmuxSessions.length === 0) return;
const script = `
tell application "iTerm"
activate
create window with default profile
tell current window
${tmuxSessions.map((id, i) => `
${i > 0 ? 'create tab with default profile' : ''}
tell current session
write text "tmux attach -t ${id}"
end tell
`).join('')}
end tell
end tell
`;
await execFileAsync("osascript", ["-e", script], { timeout: 10_000 });
},
async isSessionOpen(session: Session): Promise<boolean> {
// Check if iTerm has a tab attached to this tmux session
// This is complex - simplified implementation returns false
return false;
}
};
}
Implementing a Web Terminal Plugin
import type { Terminal, Session } from "@composio/ao-core";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export function create(config: { baseUrl: string }): Terminal {
return {
name: "web",
async openSession(session: Session): Promise<void> {
const url = `${config.baseUrl}/terminal/${session.id}`;
// Open in browser
await execFileAsync("open", [url], { timeout: 5_000 });
},
async openAll(sessions: Session[]): Promise<void> {
// Open browser with multiple tabs
const urls = sessions.map(s => `${config.baseUrl}/terminal/${s.id}`);
for (const url of urls) {
await execFileAsync("open", [url], { timeout: 5_000 });
}
}
};
}
Using Terminal in CLI
import type { Terminal } from "@composio/ao-core";
const terminal: Terminal = registry.get("terminal", "iterm2");
// Open single session
await terminal.openSession(session);
// Open all sessions for project
const sessions = await sessionManager.list("my-app");
await terminal.openAll(sessions);
Implementation Notes
Runtime Integration
Terminal plugins should use runtime.getAttachInfo() to get connection details:
import type { AttachInfo } from "@composio/ao-core";
const runtime = registry.get("runtime", session.runtimeHandle!.runtimeName);
const attachInfo: AttachInfo | undefined = await runtime.getAttachInfo?.(session.runtimeHandle!);
if (attachInfo) {
switch (attachInfo.type) {
case "tmux":
// Open iTerm2 tab with tmux attach
break;
case "docker":
// Run docker exec in terminal
break;
case "web":
// Open browser to URL
break;
}
}
Terminal plugins should check platform:
if (process.platform === "darwin") {
// Use iTerm2 or Terminal.app
} else if (process.platform === "linux") {
// Use gnome-terminal, konsole, or xterm
} else {
// Fallback to web terminal
}
Session Deduplication
The isSessionOpen() method helps avoid opening duplicate tabs:
if (terminal.isSessionOpen && await terminal.isSessionOpen(session)) {
console.log("Session already open");
return;
}
await terminal.openSession(session);
Built-in Plugins
- iterm2 - iTerm2 on macOS (default)
- web - Web-based terminal (browser)
- none - No-op for headless/CI environments
Future plugins could support:
- terminal-app - macOS Terminal.app
- gnome-terminal - GNOME Terminal on Linux
- konsole - KDE Konsole on Linux
- vscode - VS Code integrated terminal
- warp - Warp terminal
See Also
- Runtime - Runtime execution environment interface
- Session - Session interface
- Notifier - Notification interface