({
enabled,
isLoading,
focusedInputDialog,
onSubmitMessage,
}: Props)
| 124 | * 3. When busy: queues messages in AppState.inbox for UI display, delivers when turn ends |
| 125 | */ |
| 126 | export function useInboxPoller({ |
| 127 | enabled, |
| 128 | isLoading, |
| 129 | focusedInputDialog, |
| 130 | onSubmitMessage, |
| 131 | }: Props): void { |
| 132 | // Assign to original name for clarity within the function |
| 133 | const onSubmitTeammateMessage = onSubmitMessage |
| 134 | const store = useAppStateStore() |
| 135 | const setAppState = useSetAppState() |
| 136 | const inboxMessageCount = useAppState(s => s.inbox.messages.length) |
| 137 | const terminal = useTerminalNotification() |
| 138 | |
| 139 | const poll = useCallback(async () => { |
| 140 | if (!enabled) return |
| 141 | |
| 142 | // Use ref to avoid dependency on appState object (prevents infinite loop) |
| 143 | const currentAppState = store.getState() |
| 144 | const agentName = getAgentNameToPoll(currentAppState) |
| 145 | if (!agentName) return |
| 146 | |
| 147 | const unread = await readUnreadMessages( |
| 148 | agentName, |
| 149 | currentAppState.teamContext?.teamName, |
| 150 | ) |
| 151 | |
| 152 | if (unread.length === 0) return |
| 153 | |
| 154 | logForDebugging(`[InboxPoller] Found ${unread.length} unread message(s)`) |
| 155 | |
| 156 | // Check for plan approval responses and transition out of plan mode if approved |
| 157 | // Security: Only accept approval responses from the team lead |
| 158 | if (isTeammate() && isPlanModeRequired()) { |
| 159 | for (const msg of unread) { |
| 160 | const approvalResponse = isPlanApprovalResponse(msg.text) |
| 161 | // Verify the message is from the team lead to prevent teammates from forging approvals |
| 162 | if (approvalResponse && msg.from === 'team-lead') { |
| 163 | logForDebugging( |
| 164 | `[InboxPoller] Received plan approval response from team-lead: approved=${approvalResponse.approved}`, |
| 165 | ) |
| 166 | if (approvalResponse.approved) { |
| 167 | // Use leader's permission mode if provided, otherwise default |
| 168 | const targetMode = approvalResponse.permissionMode ?? 'default' |
| 169 | |
| 170 | // Transition out of plan mode |
| 171 | setAppState(prev => ({ |
| 172 | ...prev, |
| 173 | toolPermissionContext: applyPermissionUpdate( |
| 174 | prev.toolPermissionContext, |
| 175 | { |
| 176 | type: 'setMode', |
| 177 | mode: toExternalPermissionMode(targetMode), |
| 178 | destination: 'session', |
| 179 | }, |
| 180 | ), |
| 181 | })) |
| 182 | logForDebugging( |
| 183 | `[InboxPoller] Plan approved by team lead, exited plan mode to ${targetMode}`, |
no test coverage detected