* Get teammate mailbox attachments for agent swarm communication * Teammates are independent NCode sessions running in parallel (swarms), * not parent-child subagent relationships. * * This function checks two sources for messages: * 1. File-based mailbox (for messages that arrived between poll
( toolUseContext: ToolUseContext, )
| 3537 | * allowing teammates to receive messages without waiting for the turn to end. |
| 3538 | */ |
| 3539 | async function getTeammateMailboxAttachments( |
| 3540 | toolUseContext: ToolUseContext, |
| 3541 | ): Promise<Attachment[]> { |
| 3542 | if (!isAgentSwarmsEnabled()) { |
| 3543 | return [] |
| 3544 | } |
| 3545 | if ((process.env.NCODE_BUILD_MODE !== 'noumena' && process.env.USER_TYPE !== 'ant')) { |
| 3546 | return [] |
| 3547 | } |
| 3548 | |
| 3549 | // Get AppState early to check for team lead status |
| 3550 | const appState = toolUseContext.getAppState() |
| 3551 | |
| 3552 | // Use agent name from helper (checks AsyncLocalStorage, then dynamicTeamContext) |
| 3553 | const envAgentName = getAgentName() |
| 3554 | |
| 3555 | // Get team name (checks AsyncLocalStorage, dynamicTeamContext, then AppState) |
| 3556 | const teamName = getTeamName(appState.teamContext) |
| 3557 | |
| 3558 | // Check if we're the team lead (uses shared logic from swarm utils) |
| 3559 | const teamLeadStatus = isTeamLead(appState.teamContext) |
| 3560 | |
| 3561 | // Check if viewing a teammate's transcript (for in-process teammates) |
| 3562 | const viewedTeammate = getViewedTeammateTask(appState) |
| 3563 | |
| 3564 | // Resolve agent name based on who we're VIEWING: |
| 3565 | // - If viewing a teammate, use THEIR name (to read from their mailbox) |
| 3566 | // - Otherwise use env var if set, or leader's name if we're the team lead |
| 3567 | let agentName = viewedTeammate?.identity.agentName ?? envAgentName |
| 3568 | if (!agentName && teamLeadStatus && appState.teamContext) { |
| 3569 | const leadAgentId = appState.teamContext.leadAgentId |
| 3570 | // Look up the lead's name from agents map (not the UUID) |
| 3571 | agentName = appState.teamContext.teammates[leadAgentId]?.name || 'team-lead' |
| 3572 | } |
| 3573 | |
| 3574 | logForDebugging( |
| 3575 | `[SwarmMailbox] getTeammateMailboxAttachments called: envAgentName=${envAgentName}, isTeamLead=${teamLeadStatus}, resolved agentName=${agentName}, teamName=${teamName}`, |
| 3576 | ) |
| 3577 | |
| 3578 | // Only check inbox if running as an agent in a swarm or team lead |
| 3579 | if (!agentName) { |
| 3580 | logForDebugging( |
| 3581 | `[SwarmMailbox] Not checking inbox - not in a swarm or team lead`, |
| 3582 | ) |
| 3583 | return [] |
| 3584 | } |
| 3585 | |
| 3586 | logForDebugging( |
| 3587 | `[SwarmMailbox] Checking inbox for agent="${agentName}" team="${teamName || 'default'}"`, |
| 3588 | ) |
| 3589 | |
| 3590 | // Check mailbox for unread messages (routes to in-process or file-based) |
| 3591 | // Filter out structured protocol messages (permission requests/responses, shutdown |
| 3592 | // messages, etc.) — these must be left unread for useInboxPoller to route to their |
| 3593 | // proper handlers (workerPermissions queue, sandbox queue, etc.). Without filtering, |
| 3594 | // attachment generation races with InboxPoller: whichever reads first marks all |
| 3595 | // messages as read, and if attachments wins, protocol messages get bundled as raw |
| 3596 | // LLM context text instead of being routed to their UI handlers. |
no test coverage detected