rhodawk-ai-devops-engine / vendor /openclaude /src /tasks /InProcessTeammateTask /InProcessTeammateTask.tsx
| /** | |
| * 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<InProcessTeammateTaskState>(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<InProcessTeammateTaskState>(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<InProcessTeammateTaskState>(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<string, TaskStateBase>): 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<string, TaskStateBase>): 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<string, TaskStateBase>): InProcessTeammateTaskState[] { | |
| return getAllInProcessTeammateTasks(tasks).filter(t => t.status === 'running').sort((a, b) => a.identity.agentName.localeCompare(b.identity.agentName)); | |
| } | |