/** * InProcessTeammateTask - Manages in-process teammate lifecycle * * This component implements the Task interface for in-process teammates. * Unlike LocalAgentTask (background agents), in-process teammates: * 1. Run in the same Node.js process using AsyncLocalStorage for isolation * 2. Have team-aware identity (agentName@teamName) * 3. Support plan mode approval flow * 4. Can be idle (waiting for work) or active (processing) */ import { isTerminalTaskStatus, type SetAppState, type Task, type TaskStateBase, } from '../../Task.js' import type { Message } from '../../types/message.js' import { logForDebugging } from '../../utils/debug.js' import { createUserMessage } from '../../utils/messages.js' import { killInProcessTeammate } from '../../utils/swarm/spawnInProcess.js' import { updateTaskState } from '../../utils/task/framework.js' import type { InProcessTeammateTaskState } from './types.js' import { appendCappedMessage, isInProcessTeammateTask } from './types.js' /** * InProcessTeammateTask - Handles in-process teammate execution. */ export const InProcessTeammateTask: Task = { name: 'InProcessTeammateTask', type: 'in_process_teammate', async kill(taskId, setAppState) { killInProcessTeammate(taskId, setAppState) }, } /** * Request shutdown for a teammate. */ export function requestTeammateShutdown( taskId: string, setAppState: SetAppState, ): void { updateTaskState(taskId, setAppState, task => { if (task.status !== 'running' || task.shutdownRequested) { return task } return { ...task, shutdownRequested: true, } }) } /** * Append a message to a teammate's conversation history. * Used for zoomed view to show the teammate's conversation. */ export function appendTeammateMessage( taskId: string, message: Message, setAppState: SetAppState, ): void { updateTaskState(taskId, setAppState, task => { if (task.status !== 'running') { return task } return { ...task, messages: appendCappedMessage(task.messages, message), } }) } /** * Inject a user message to a teammate's pending queue. * Used when viewing a teammate's transcript to send typed messages to them. * Also adds the message to task.messages so it appears immediately in the transcript. */ export function injectUserMessageToTeammate( taskId: string, message: string, setAppState: SetAppState, ): void { updateTaskState(taskId, setAppState, task => { // Allow message injection when teammate is running or idle (waiting for input) // Only reject if teammate is in a terminal state if (isTerminalTaskStatus(task.status)) { logForDebugging( `Dropping message for teammate task ${taskId}: task status is "${task.status}"`, ) return task } return { ...task, pendingUserMessages: [...task.pendingUserMessages, message], messages: appendCappedMessage( task.messages, createUserMessage({ content: message }), ), } }) } /** * Get teammate task by agent ID from AppState. * Prefers running tasks over killed/completed ones in case multiple tasks * with the same agentId exist. * Returns undefined if not found. */ export function findTeammateTaskByAgentId( agentId: string, tasks: Record, ): InProcessTeammateTaskState | undefined { let fallback: InProcessTeammateTaskState | undefined for (const task of Object.values(tasks)) { if (isInProcessTeammateTask(task) && task.identity.agentId === agentId) { // Prefer running tasks in case old killed tasks still exist in AppState // alongside new running ones with the same agentId if (task.status === 'running') { return task } // Keep first match as fallback in case no running task exists if (!fallback) { fallback = task } } } return fallback } /** * Get all in-process teammate tasks from AppState. */ export function getAllInProcessTeammateTasks( tasks: Record, ): InProcessTeammateTaskState[] { return Object.values(tasks).filter(isInProcessTeammateTask) } /** * Get running in-process teammates sorted alphabetically by agentName. * Shared between TeammateSpinnerTree display, PromptInput footer selector, * and useBackgroundTaskNavigation — selectedIPAgentIndex maps into this * array, so all three must agree on sort order. */ export function getRunningTeammatesSorted( tasks: Record, ): InProcessTeammateTaskState[] { return getAllInProcessTeammateTasks(tasks) .filter(t => t.status === 'running') .sort((a, b) => a.identity.agentName.localeCompare(b.identity.agentName)) }