/** * 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, MessageOrigin } 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, PendingTeammateUserMessage } 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, options: | { autonomyRunId?: string; origin?: MessageOrigin; } | undefined, setAppState: SetAppState, ): boolean { let injected = false; 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; } injected = true; const pendingMessage: PendingTeammateUserMessage = { message }; if (options?.autonomyRunId !== undefined) { pendingMessage.autonomyRunId = options.autonomyRunId; } if (options?.origin !== undefined) { pendingMessage.origin = options.origin; } const userMessageArgs: Parameters[0] = { content: message, }; if (options?.origin !== undefined) { userMessageArgs.origin = options.origin; } return { ...task, pendingUserMessages: [...task.pendingUserMessages, pendingMessage], messages: appendCappedMessage(task.messages, createUserMessage(userMessageArgs)), }; }); return injected; } /** * 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)); }