/ utils / swarm / backends / types.ts
types.ts
  1  import type { AgentColorName } from '../../../tools/AgentTool/agentColorManager.js'
  2  
  3  /**
  4   * Types of backends available for teammate execution.
  5   * - 'tmux': Uses tmux for pane management (works in tmux or standalone)
  6   * - 'iterm2': Uses iTerm2 native split panes via the it2 CLI
  7   * - 'in-process': Runs teammate in the same Node.js process with isolated context
  8   */
  9  export type BackendType = 'tmux' | 'iterm2' | 'in-process'
 10  
 11  /**
 12   * Subset of BackendType for pane-based backends only.
 13   * Used in messages and types that specifically deal with terminal panes.
 14   */
 15  export type PaneBackendType = 'tmux' | 'iterm2'
 16  
 17  /**
 18   * Opaque identifier for a pane managed by a backend.
 19   * For tmux, this is the tmux pane ID (e.g., "%1").
 20   * For iTerm2, this is the session ID returned by it2.
 21   */
 22  export type PaneId = string
 23  
 24  /**
 25   * Result of creating a new teammate pane.
 26   */
 27  export type CreatePaneResult = {
 28    /** The pane ID for the newly created pane */
 29    paneId: PaneId
 30    /** Whether this is the first teammate pane (affects layout strategy) */
 31    isFirstTeammate: boolean
 32  }
 33  
 34  /**
 35   * Interface for pane management backends.
 36   * Abstracts operations for creating and managing terminal panes
 37   * for teammate visualization in swarm mode.
 38   */
 39  export type PaneBackend = {
 40    /** The type identifier for this backend */
 41    readonly type: BackendType
 42  
 43    /** Human-readable display name for this backend */
 44    readonly displayName: string
 45  
 46    /** Whether this backend supports hiding and showing panes */
 47    readonly supportsHideShow: boolean
 48  
 49    /**
 50     * Checks if this backend is available on the system.
 51     * For tmux: checks if tmux command exists.
 52     * For iTerm2: checks if it2 CLI is installed and configured.
 53     */
 54    isAvailable(): Promise<boolean>
 55  
 56    /**
 57     * Checks if we're currently running inside this backend's environment.
 58     * For tmux: checks if we're in a tmux session.
 59     * For iTerm2: checks if we're running in iTerm2.
 60     */
 61    isRunningInside(): Promise<boolean>
 62  
 63    /**
 64     * Creates a new pane for a teammate in the swarm view.
 65     * The backend handles layout strategy (with/without leader pane).
 66     *
 67     * @param name - The teammate's name for display
 68     * @param color - The color to use for the pane border/title
 69     * @returns The pane ID and whether this was the first teammate
 70     */
 71    createTeammatePaneInSwarmView(
 72      name: string,
 73      color: AgentColorName,
 74    ): Promise<CreatePaneResult>
 75  
 76    /**
 77     * Sends a command to execute in a specific pane.
 78     *
 79     * @param paneId - The pane to send the command to
 80     * @param command - The command string to execute
 81     * @param useExternalSession - If true, uses external session socket (tmux-specific)
 82     */
 83    sendCommandToPane(
 84      paneId: PaneId,
 85      command: string,
 86      useExternalSession?: boolean,
 87    ): Promise<void>
 88  
 89    /**
 90     * Sets the border color for a pane.
 91     *
 92     * @param paneId - The pane to style
 93     * @param color - The color to apply to the border
 94     * @param useExternalSession - If true, uses external session socket (tmux-specific)
 95     */
 96    setPaneBorderColor(
 97      paneId: PaneId,
 98      color: AgentColorName,
 99      useExternalSession?: boolean,
100    ): Promise<void>
101  
102    /**
103     * Sets the title for a pane (displayed in pane border/header).
104     *
105     * @param paneId - The pane to title
106     * @param name - The title to display
107     * @param color - The color for the title text
108     * @param useExternalSession - If true, uses external session socket (tmux-specific)
109     */
110    setPaneTitle(
111      paneId: PaneId,
112      name: string,
113      color: AgentColorName,
114      useExternalSession?: boolean,
115    ): Promise<void>
116  
117    /**
118     * Enables pane border status display (shows titles in borders).
119     *
120     * @param windowTarget - The window to enable status for (optional)
121     * @param useExternalSession - If true, uses external session socket (tmux-specific)
122     */
123    enablePaneBorderStatus(
124      windowTarget?: string,
125      useExternalSession?: boolean,
126    ): Promise<void>
127  
128    /**
129     * Rebalances panes to achieve the desired layout.
130     *
131     * @param windowTarget - The window containing the panes
132     * @param hasLeader - Whether there's a leader pane (affects layout strategy)
133     */
134    rebalancePanes(windowTarget: string, hasLeader: boolean): Promise<void>
135  
136    /**
137     * Kills/closes a specific pane.
138     *
139     * @param paneId - The pane to kill
140     * @param useExternalSession - If true, uses external session socket (tmux-specific)
141     * @returns true if the pane was killed successfully, false otherwise
142     */
143    killPane(paneId: PaneId, useExternalSession?: boolean): Promise<boolean>
144  
145    /**
146     * Hides a pane by breaking it out into a hidden window.
147     * The pane remains running but is not visible in the main layout.
148     *
149     * @param paneId - The pane to hide
150     * @param useExternalSession - If true, uses external session socket (tmux-specific)
151     * @returns true if the pane was hidden successfully, false otherwise
152     */
153    hidePane(paneId: PaneId, useExternalSession?: boolean): Promise<boolean>
154  
155    /**
156     * Shows a previously hidden pane by joining it back into the main window.
157     *
158     * @param paneId - The pane to show
159     * @param targetWindowOrPane - The window or pane to join into
160     * @param useExternalSession - If true, uses external session socket (tmux-specific)
161     * @returns true if the pane was shown successfully, false otherwise
162     */
163    showPane(
164      paneId: PaneId,
165      targetWindowOrPane: string,
166      useExternalSession?: boolean,
167    ): Promise<boolean>
168  }
169  
170  /**
171   * Result from backend detection.
172   */
173  export type BackendDetectionResult = {
174    /** The backend that should be used */
175    backend: PaneBackend
176    /** Whether we're running inside the backend's native environment */
177    isNative: boolean
178    /** If iTerm2 is detected but it2 not installed, this will be true */
179    needsIt2Setup?: boolean
180  }
181  
182  // =============================================================================
183  // In-Process Teammate Types
184  // =============================================================================
185  
186  /**
187   * Identity fields for a teammate.
188   * This is a subset shared with TeammateContext (Task #4) to avoid circular deps.
189   * lifecycle-specialist defines the full TeammateContext with additional fields.
190   */
191  export type TeammateIdentity = {
192    /** Agent name (e.g., "researcher", "tester") */
193    name: string
194    /** Team name this teammate belongs to */
195    teamName: string
196    /** Assigned color for UI differentiation */
197    color?: AgentColorName
198    /** Whether plan mode approval is required before implementation */
199    planModeRequired?: boolean
200  }
201  
202  /**
203   * Configuration for spawning a teammate (any execution mode).
204   */
205  export type TeammateSpawnConfig = TeammateIdentity & {
206    /** Initial prompt to send to the teammate */
207    prompt: string
208    /** Working directory for the teammate */
209    cwd: string
210    /** Model to use for this teammate */
211    model?: string
212    /** System prompt for this teammate (resolved from workflow config) */
213    systemPrompt?: string
214    /** How to apply the system prompt: 'replace' or 'append' to default */
215    systemPromptMode?: 'default' | 'replace' | 'append'
216    /** Optional git worktree path */
217    worktreePath?: string
218    /** Parent session ID (for context linking) */
219    parentSessionId: string
220    /** Tool permissions to grant this teammate */
221    permissions?: string[]
222    /** Whether this teammate can show permission prompts for unlisted tools.
223     * When false (default), unlisted tools are auto-denied. */
224    allowPermissionPrompts?: boolean
225  }
226  
227  /**
228   * Result from spawning a teammate.
229   */
230  export type TeammateSpawnResult = {
231    /** Whether spawn was successful */
232    success: boolean
233    /** Unique agent ID (format: agentName@teamName) */
234    agentId: string
235    /** Error message if spawn failed */
236    error?: string
237  
238    /**
239     * Abort controller for lifecycle management (in-process only).
240     * Leader uses this to cancel/kill the teammate.
241     * For pane-based teammates, use kill() method instead.
242     */
243    abortController?: AbortController
244  
245    /**
246     * Task ID in AppState.tasks (in-process only).
247     * Used for UI rendering and progress tracking.
248     * agentId is the logical identifier; taskId is for AppState indexing.
249     */
250    taskId?: string
251  
252    /** Pane ID (pane-based only) */
253    paneId?: PaneId
254  }
255  
256  /**
257   * Message to send to a teammate.
258   */
259  export type TeammateMessage = {
260    /** Message content */
261    text: string
262    /** Sender agent ID */
263    from: string
264    /** Sender display color */
265    color?: string
266    /** Message timestamp (ISO string) */
267    timestamp?: string
268    /** 5-10 word summary shown as preview in the UI */
269    summary?: string
270  }
271  
272  /**
273   * Common interface for teammate execution backends.
274   * Abstracts the differences between pane-based (tmux/iTerm2) and in-process execution.
275   *
276   * PaneBackend handles low-level pane operations; TeammateExecutor handles
277   * high-level teammate lifecycle operations that work across all backends.
278   */
279  export type TeammateExecutor = {
280    /** Backend type identifier */
281    readonly type: BackendType
282  
283    /** Check if this executor is available on the system */
284    isAvailable(): Promise<boolean>
285  
286    /** Spawn a new teammate with the given configuration */
287    spawn(config: TeammateSpawnConfig): Promise<TeammateSpawnResult>
288  
289    /** Send a message to a teammate */
290    sendMessage(agentId: string, message: TeammateMessage): Promise<void>
291  
292    /** Terminate a teammate (graceful shutdown request) */
293    terminate(agentId: string, reason?: string): Promise<boolean>
294  
295    /** Force kill a teammate (immediate termination) */
296    kill(agentId: string): Promise<boolean>
297  
298    /** Check if a teammate is still active */
299    isActive(agentId: string): Promise<boolean>
300  }
301  
302  // =============================================================================
303  // Type Guards
304  // =============================================================================
305  
306  /**
307   * Type guard to check if a backend type uses terminal panes.
308   */
309  export function isPaneBackend(type: BackendType): type is 'tmux' | 'iterm2' {
310    return type === 'tmux' || type === 'iterm2'
311  }