diff --git a/src/main/index.ts b/src/main/index.ts index 388c89ff..1516c0d9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -287,29 +287,42 @@ function buildMemberWorkSyncReviewPickupEscalationText(input: { }): string { const taskLines = input.taskRefs.length ? input.taskRefs - .map( - (taskRef) => `- ${taskRef.displayId ?? taskRef.taskId.slice(0, 8)} (${taskRef.taskId})` - ) + .map((taskRef) => `- ${taskRef.displayId ?? taskRef.taskId.slice(0, 8)}`) .join('\n') : '- No task refs recorded'; - const diagnostics = [...new Set(input.diagnostics ?? [])].filter(Boolean); + const reasonText = describeMemberWorkSyncReviewPickupEscalationReason(input.reason); return [ - 'Review pickup still pending in member work-sync.', + 'Review pickup needs lead attention.', '', `Reviewer: ${input.memberName}`, - `Reason: ${input.reason}`, + reasonText, '', 'Tasks:', taskLines, '', - 'No review_start, review_approve, or review_request_changes was recorded for the current review request after the member correction path.', + 'No review_start, review_approve, or review_request_changes was recorded for the current review request.', 'Consider reassigning the reviewer or sending a direct instruction.', - diagnostics.length ? `Diagnostics: ${diagnostics.join(', ')}` : '', ] .filter(Boolean) .join('\n'); } +function describeMemberWorkSyncReviewPickupEscalationReason(reason: string): string { + if (reason.startsWith('provider_not_supported:')) { + return 'Direct review-pickup wake is not available for this member runtime, so the lead needs to handle the stuck review.'; + } + if (reason === 'review_pickup_already_delivered_still_stuck') { + return 'A review-pickup reminder was delivered, but the review is still waiting for a review tool action.'; + } + if (reason === 'review_pickup_delivery_failed_still_stuck') { + return 'The review-pickup reminder could not be delivered reliably, and the review is still waiting.'; + } + if (reason.includes('delivery_port_unavailable')) { + return 'No reliable review-pickup delivery path is available for this member runtime.'; + } + return 'The current review request is still waiting for explicit review pickup.'; +} + async function createOpenCodeRuntimeAdapterRegistry( reportProgress: (phase: string, message: string) => void = () => undefined ): Promise { diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index fea15833..2e5bc646 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -24,7 +24,10 @@ import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { shouldExcludeInboxTextFromReplyCandidates } from '@shared/utils/idleNotificationSemantics'; import { isLeadMember } from '@shared/utils/leadDetection'; -import { isTaskStallRemediationMessage } from '@shared/utils/teamAutomationMessages'; +import { + isReviewPickupEscalationMessage, + isTaskStallRemediationMessage, +} from '@shared/utils/teamAutomationMessages'; import { CheckCheck, ChevronsDownUp, @@ -603,6 +606,7 @@ export const MessagesPanel = memo(function MessagesPanel({ (m) => m.messageKind !== 'task_comment_notification' && !isTaskStallRemediationMessage(m) && + !isReviewPickupEscalationMessage(m) && !shouldExcludeInboxTextFromReplyCandidates(typeof m.text === 'string' ? m.text : '') ), [effectiveMessages] diff --git a/src/renderer/utils/teamMessageFiltering.ts b/src/renderer/utils/teamMessageFiltering.ts index 589accc7..a4d697f0 100644 --- a/src/renderer/utils/teamMessageFiltering.ts +++ b/src/renderer/utils/teamMessageFiltering.ts @@ -4,7 +4,10 @@ import { } from '@renderer/utils/bootstrapPromptSanitizer'; import { shouldKeepIdleMessageInActivityWhenNoiseHidden } from '@renderer/utils/idleNotificationSemantics'; import { isInboxNoiseMessage } from '@shared/utils/inboxNoise'; -import { isTaskStallRemediationMessage } from '@shared/utils/teamAutomationMessages'; +import { + isReviewPickupEscalationMessage, + isTaskStallRemediationMessage, +} from '@shared/utils/teamAutomationMessages'; import { isTeamInternalControlMessageEnvelope } from '@shared/utils/teamInternalControlMessages'; import type { InboxMessage } from '@shared/types'; @@ -133,6 +136,7 @@ export function filterTeamMessages( (m) => m.messageKind !== 'task_comment_notification' && (includeAutomationEvents || !isTaskStallRemediationMessage(m)) && + !isReviewPickupEscalationMessage(m) && !isTeamInternalControlMessageEnvelope(m) ); if (timeWindow) { diff --git a/src/shared/utils/teamAutomationMessages.ts b/src/shared/utils/teamAutomationMessages.ts index 9c86a94e..b4c7818c 100644 --- a/src/shared/utils/teamAutomationMessages.ts +++ b/src/shared/utils/teamAutomationMessages.ts @@ -2,15 +2,26 @@ import type { InboxMessage } from '@shared/types'; type AutomationMessageLike = Pick; +function getMessageId(message: AutomationMessageLike): string { + return typeof message.messageId === 'string' ? message.messageId.trim() : ''; +} + export function isTaskStallRemediationMessage(message: AutomationMessageLike): boolean { if (message.messageKind === 'task_stall_remediation') { return true; } - const messageId = typeof message.messageId === 'string' ? message.messageId.trim() : ''; return ( message.source === 'system_notification' && message.from === 'system' && - messageId.startsWith('task-stall:') + getMessageId(message).startsWith('task-stall:') + ); +} + +export function isReviewPickupEscalationMessage(message: AutomationMessageLike): boolean { + return ( + message.source === 'system_notification' && + message.from === 'system' && + getMessageId(message).startsWith('member-work-sync-review-pickup-escalation:') ); } diff --git a/test/renderer/utils/teamMessageFiltering.test.ts b/test/renderer/utils/teamMessageFiltering.test.ts index 3e9c3dc6..184cf7ad 100644 --- a/test/renderer/utils/teamMessageFiltering.test.ts +++ b/test/renderer/utils/teamMessageFiltering.test.ts @@ -585,6 +585,31 @@ Messages: expect(result.map((message) => message.messageId)).toEqual(['msg-2']); }); + it('hides review pickup escalation automation rows from conversational message counts by default', () => { + const messages = [ + makeMessage({ + messageId: 'member-work-sync-review-pickup-escalation:abc123', + from: 'system', + to: 'lead', + source: 'system_notification', + summary: 'Review pickup still pending', + text: 'Review pickup needs lead attention.\n\nReviewer: tom', + }), + makeMessage({ + messageId: 'msg-2', + text: 'Visible message', + }), + ]; + + const result = filterTeamMessages(messages, { + timeWindow: null, + filter: { from: new Set(), to: new Set(), showNoise: true }, + searchQuery: '', + }); + + expect(result.map((message) => message.messageId)).toEqual(['msg-2']); + }); + it('can include task stall remediation automation rows for the activity timeline', () => { const messages = [ makeMessage({ @@ -608,4 +633,27 @@ Messages: 'task-stall:demo:task-a:legacy-epoch', ]); }); + + it('keeps review pickup escalation hidden even when regular automation rows are included', () => { + const messages = [ + makeMessage({ + messageId: 'member-work-sync-review-pickup-escalation:abc123', + from: 'system', + to: 'lead', + source: 'system_notification', + summary: 'Review pickup still pending', + text: 'Review pickup needs lead attention.\n\nReviewer: tom', + }), + ]; + + const result = filterTeamMessages(messages, { + includeAutomationEvents: true, + timeWindow: null, + filter: { from: new Set(), to: new Set(), showNoise: true }, + searchQuery: '', + }); + + expect(result).toEqual([]); + }); + });