/** * 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)); }