source dump of claude code
at main 121 lines 4.3 kB view raw
1import type { TaskStateBase } from '../../Task.js' 2import type { AgentToolResult } from '../../tools/AgentTool/agentToolUtils.js' 3import type { AgentDefinition } from '../../tools/AgentTool/loadAgentsDir.js' 4import type { Message } from '../../types/message.js' 5import type { PermissionMode } from '../../utils/permissions/PermissionMode.js' 6import type { AgentProgress } from '../LocalAgentTask/LocalAgentTask.js' 7 8/** 9 * Teammate identity stored in task state. 10 * Same shape as TeammateContext (runtime) but stored as plain data. 11 * TeammateContext is for AsyncLocalStorage; this is for AppState persistence. 12 */ 13export type TeammateIdentity = { 14 agentId: string // e.g., "researcher@my-team" 15 agentName: string // e.g., "researcher" 16 teamName: string 17 color?: string 18 planModeRequired: boolean 19 parentSessionId: string // Leader's session ID 20} 21 22export type InProcessTeammateTaskState = TaskStateBase & { 23 type: 'in_process_teammate' 24 25 // Identity as sub-object (matches TeammateContext shape for consistency) 26 // Stored as plain data in AppState, NOT a reference to AsyncLocalStorage 27 identity: TeammateIdentity 28 29 // Execution 30 prompt: string 31 // Optional model override for this teammate 32 model?: string 33 // Optional: Only set if teammate uses a specific agent definition 34 // Many teammates run as general-purpose agents without a predefined definition 35 selectedAgent?: AgentDefinition 36 abortController?: AbortController // Runtime only, not serialized to disk - kills WHOLE teammate 37 currentWorkAbortController?: AbortController // Runtime only - aborts current turn without killing teammate 38 unregisterCleanup?: () => void // Runtime only 39 40 // Plan mode approval tracking (planModeRequired is in identity) 41 awaitingPlanApproval: boolean 42 43 // Permission mode for this teammate (cycled independently via Shift+Tab when viewing) 44 permissionMode: PermissionMode 45 46 // State 47 error?: string 48 result?: AgentToolResult // Reuse existing type since teammates run via runAgent() 49 progress?: AgentProgress 50 51 // Conversation history for zoomed view (NOT mailbox messages) 52 // Mailbox messages are stored separately in teamContext.inProcessMailboxes 53 messages?: Message[] 54 55 // Tool use IDs currently being executed (for animation in transcript view) 56 inProgressToolUseIDs?: Set<string> 57 58 // Queue of user messages to deliver when viewing teammate transcript 59 pendingUserMessages: string[] 60 61 // UI: random spinner verbs (stable across re-renders, shared between components) 62 spinnerVerb?: string 63 pastTenseVerb?: string 64 65 // Lifecycle 66 isIdle: boolean 67 shutdownRequested: boolean 68 69 // Callbacks to notify when teammate becomes idle (runtime only) 70 // Used by leader to efficiently wait without polling 71 onIdleCallbacks?: Array<() => void> 72 73 // Progress tracking (for computing deltas in notifications) 74 lastReportedToolCount: number 75 lastReportedTokenCount: number 76} 77 78export function isInProcessTeammateTask( 79 task: unknown, 80): task is InProcessTeammateTaskState { 81 return ( 82 typeof task === 'object' && 83 task !== null && 84 'type' in task && 85 task.type === 'in_process_teammate' 86 ) 87} 88 89/** 90 * Cap on the number of messages kept in task.messages (the AppState UI mirror). 91 * 92 * task.messages exists purely for the zoomed transcript dialog, which only 93 * needs recent context. The full conversation lives in the local allMessages 94 * array (inProcessRunner) and on disk at the agent transcript path. 95 * 96 * BQ analysis (round 9, 2026-03-20) showed ~20MB RSS per agent at 500+ turn 97 * sessions and ~125MB per concurrent agent in swarm bursts. Whale session 98 * 9a990de8 launched 292 agents in 2 minutes and reached 36.8GB. The dominant 99 * cost is this array holding a second full copy of every message. 100 */ 101export const TEAMMATE_MESSAGES_UI_CAP = 50 102 103/** 104 * Append an item to a message array, capping the result at 105 * TEAMMATE_MESSAGES_UI_CAP entries by dropping the oldest. Always returns 106 * a new array (AppState immutability). 107 */ 108export function appendCappedMessage<T>( 109 prev: readonly T[] | undefined, 110 item: T, 111): T[] { 112 if (prev === undefined || prev.length === 0) { 113 return [item] 114 } 115 if (prev.length >= TEAMMATE_MESSAGES_UI_CAP) { 116 const next = prev.slice(-(TEAMMATE_MESSAGES_UI_CAP - 1)) 117 next.push(item) 118 return next 119 } 120 return [...prev, item] 121}