init
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,121 @@
|
||||
import type { TaskStateBase } from '../../Task.js'
|
||||
import type { AgentToolResult } from '../../tools/AgentTool/agentToolUtils.js'
|
||||
import type { AgentDefinition } from '../../tools/AgentTool/loadAgentsDir.js'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import type { PermissionMode } from '../../utils/permissions/PermissionMode.js'
|
||||
import type { AgentProgress } from '../LocalAgentTask/LocalAgentTask.js'
|
||||
|
||||
/**
|
||||
* Teammate identity stored in task state.
|
||||
* Same shape as TeammateContext (runtime) but stored as plain data.
|
||||
* TeammateContext is for AsyncLocalStorage; this is for AppState persistence.
|
||||
*/
|
||||
export type TeammateIdentity = {
|
||||
agentId: string // e.g., "researcher@my-team"
|
||||
agentName: string // e.g., "researcher"
|
||||
teamName: string
|
||||
color?: string
|
||||
planModeRequired: boolean
|
||||
parentSessionId: string // Leader's session ID
|
||||
}
|
||||
|
||||
export type InProcessTeammateTaskState = TaskStateBase & {
|
||||
type: 'in_process_teammate'
|
||||
|
||||
// Identity as sub-object (matches TeammateContext shape for consistency)
|
||||
// Stored as plain data in AppState, NOT a reference to AsyncLocalStorage
|
||||
identity: TeammateIdentity
|
||||
|
||||
// Execution
|
||||
prompt: string
|
||||
// Optional model override for this teammate
|
||||
model?: string
|
||||
// Optional: Only set if teammate uses a specific agent definition
|
||||
// Many teammates run as general-purpose agents without a predefined definition
|
||||
selectedAgent?: AgentDefinition
|
||||
abortController?: AbortController // Runtime only, not serialized to disk - kills WHOLE teammate
|
||||
currentWorkAbortController?: AbortController // Runtime only - aborts current turn without killing teammate
|
||||
unregisterCleanup?: () => void // Runtime only
|
||||
|
||||
// Plan mode approval tracking (planModeRequired is in identity)
|
||||
awaitingPlanApproval: boolean
|
||||
|
||||
// Permission mode for this teammate (cycled independently via Shift+Tab when viewing)
|
||||
permissionMode: PermissionMode
|
||||
|
||||
// State
|
||||
error?: string
|
||||
result?: AgentToolResult // Reuse existing type since teammates run via runAgent()
|
||||
progress?: AgentProgress
|
||||
|
||||
// Conversation history for zoomed view (NOT mailbox messages)
|
||||
// Mailbox messages are stored separately in teamContext.inProcessMailboxes
|
||||
messages?: Message[]
|
||||
|
||||
// Tool use IDs currently being executed (for animation in transcript view)
|
||||
inProgressToolUseIDs?: Set<string>
|
||||
|
||||
// Queue of user messages to deliver when viewing teammate transcript
|
||||
pendingUserMessages: string[]
|
||||
|
||||
// UI: random spinner verbs (stable across re-renders, shared between components)
|
||||
spinnerVerb?: string
|
||||
pastTenseVerb?: string
|
||||
|
||||
// Lifecycle
|
||||
isIdle: boolean
|
||||
shutdownRequested: boolean
|
||||
|
||||
// Callbacks to notify when teammate becomes idle (runtime only)
|
||||
// Used by leader to efficiently wait without polling
|
||||
onIdleCallbacks?: Array<() => void>
|
||||
|
||||
// Progress tracking (for computing deltas in notifications)
|
||||
lastReportedToolCount: number
|
||||
lastReportedTokenCount: number
|
||||
}
|
||||
|
||||
export function isInProcessTeammateTask(
|
||||
task: unknown,
|
||||
): task is InProcessTeammateTaskState {
|
||||
return (
|
||||
typeof task === 'object' &&
|
||||
task !== null &&
|
||||
'type' in task &&
|
||||
task.type === 'in_process_teammate'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cap on the number of messages kept in task.messages (the AppState UI mirror).
|
||||
*
|
||||
* task.messages exists purely for the zoomed transcript dialog, which only
|
||||
* needs recent context. The full conversation lives in the local allMessages
|
||||
* array (inProcessRunner) and on disk at the agent transcript path.
|
||||
*
|
||||
* BQ analysis (round 9, 2026-03-20) showed ~20MB RSS per agent at 500+ turn
|
||||
* sessions and ~125MB per concurrent agent in swarm bursts. Whale session
|
||||
* 9a990de8 launched 292 agents in 2 minutes and reached 36.8GB. The dominant
|
||||
* cost is this array holding a second full copy of every message.
|
||||
*/
|
||||
export const TEAMMATE_MESSAGES_UI_CAP = 50
|
||||
|
||||
/**
|
||||
* Append an item to a message array, capping the result at
|
||||
* TEAMMATE_MESSAGES_UI_CAP entries by dropping the oldest. Always returns
|
||||
* a new array (AppState immutability).
|
||||
*/
|
||||
export function appendCappedMessage<T>(
|
||||
prev: readonly T[] | undefined,
|
||||
item: T,
|
||||
): T[] {
|
||||
if (prev === undefined || prev.length === 0) {
|
||||
return [item]
|
||||
}
|
||||
if (prev.length >= TEAMMATE_MESSAGES_UI_CAP) {
|
||||
const next = prev.slice(-(TEAMMATE_MESSAGES_UI_CAP - 1))
|
||||
next.push(item)
|
||||
return next
|
||||
}
|
||||
return [...prev, item]
|
||||
}
|
||||
Reference in New Issue
Block a user