fix(team): surface provider retries in reply UI

This commit is contained in:
iliya 2026-04-07 12:42:37 +03:00
parent 8ebde439a8
commit 7f737f985b
4 changed files with 50 additions and 12 deletions

View file

@ -12,7 +12,7 @@ import {
} from '@renderer/utils/memberHelpers';
import { nameColorSet } from '@renderer/utils/projectColor';
import { formatDistanceToNowStrict } from 'date-fns';
import { ShieldQuestion, Users } from 'lucide-react';
import { Loader2, ShieldQuestion, Users } from 'lucide-react';
import type { ResolvedTeamMember } from '@shared/types';
@ -83,6 +83,7 @@ export const PendingRepliesBlock = ({
);
const advisoryLabel = getMemberRuntimeAdvisoryLabel(member.runtimeAdvisory);
const advisoryTitle = getMemberRuntimeAdvisoryTitle(member.runtimeAdvisory);
const isRetrying = advisoryLabel !== null;
return (
<article
@ -103,8 +104,12 @@ export const PendingRepliesBlock = ({
loading="lazy"
/>
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-70" />
<span className="relative inline-flex size-full rounded-full bg-emerald-500" />
<span
className={`absolute inline-flex size-full animate-ping rounded-full opacity-70 ${isRetrying ? 'bg-amber-400' : 'bg-emerald-400'}`}
/>
<span
className={`relative inline-flex size-full rounded-full ${isRetrying ? 'bg-amber-500' : 'bg-emerald-500'}`}
/>
</span>
</span>
{onMemberClick ? (
@ -139,12 +144,15 @@ export const PendingRepliesBlock = ({
</span>
) : null}
<span
className="min-w-0 flex-1 truncate text-[10px]"
style={{ color: CARD_ICON_MUTED }}
className={`min-w-0 flex-1 truncate text-[10px] ${isRetrying ? 'text-amber-300' : ''}`}
style={isRetrying ? undefined : { color: CARD_ICON_MUTED }}
title={advisoryTitle ?? 'Message sent, awaiting reply'}
>
{advisoryLabel ?? 'awaiting reply'}
</span>
{isRetrying ? (
<Loader2 className="size-3 shrink-0 animate-spin text-amber-400" />
) : null}
<span className="shrink-0 text-[10px]" style={{ color: CARD_ICON_MUTED }}>
{since}
</span>

View file

@ -179,11 +179,11 @@ export const MemberCard = ({
{!activityTask && isAwaitingReply ? (
<>
<Loader2
className="size-3 shrink-0 animate-spin"
style={{ color: colors.border }}
className={`size-3 shrink-0 animate-spin ${runtimeAdvisoryLabel ? 'text-amber-400' : ''}`}
style={runtimeAdvisoryLabel ? undefined : { color: colors.border }}
/>
<span
className="shrink-0 text-[10px] text-[var(--color-text-muted)]"
className={`shrink-0 text-[10px] ${runtimeAdvisoryLabel ? 'text-amber-300' : 'text-[var(--color-text-muted)]'}`}
title={runtimeAdvisoryTitle ?? 'Message sent, awaiting reply'}
>
{runtimeAdvisoryLabel ?? 'awaiting reply'}

View file

@ -214,13 +214,13 @@ export function getMemberRuntimeAdvisoryLabel(
}
const retryUntilMs = Date.parse(advisory.retryUntil);
if (!Number.isFinite(retryUntilMs)) {
return 'SDK retrying';
return 'retrying now';
}
const remainingMs = retryUntilMs - nowMs;
if (remainingMs <= 0) {
return 'SDK retrying';
return 'retrying now';
}
return `SDK retrying · ${formatRetryCountdown(remainingMs)}`;
return `retrying now · ${formatRetryCountdown(remainingMs)}`;
}
export function getMemberRuntimeAdvisoryTitle(
@ -229,7 +229,10 @@ export function getMemberRuntimeAdvisoryTitle(
if (!advisory || advisory.kind !== 'sdk_retrying') {
return undefined;
}
return advisory.message?.trim() || 'The SDK is retrying after a provider error.';
return (
advisory.message?.trim() ||
'The SDK is retrying this request after a provider or backend error.'
);
}
export const TASK_STATUS_STYLES: Record<TeamTaskStatus, { bg: string; text: string }> = {

View file

@ -1,6 +1,8 @@
import {
getSpawnAwareDotClass,
getSpawnAwarePresenceLabel,
getMemberRuntimeAdvisoryLabel,
getMemberRuntimeAdvisoryTitle,
} from '@renderer/utils/memberHelpers';
import type { ResolvedTeamMember } from '@shared/types';
@ -59,4 +61,29 @@ describe('memberHelpers spawn-aware presence', () => {
)
).toBe('starting');
});
it('renders unified retry advisory labels for provider retries', () => {
expect(
getMemberRuntimeAdvisoryLabel(
{
kind: 'sdk_retrying',
observedAt: '2026-04-07T09:00:00.000Z',
retryUntil: '2026-04-07T09:00:45.000Z',
retryDelayMs: 45_000,
message: 'Gemini cli backend error: capacity exceeded.',
},
Date.parse('2026-04-07T09:00:00.000Z')
)
).toBe('retrying now · 45s');
expect(
getMemberRuntimeAdvisoryTitle({
kind: 'sdk_retrying',
observedAt: '2026-04-07T09:00:00.000Z',
retryUntil: '2026-04-07T09:00:45.000Z',
retryDelayMs: 45_000,
message: 'Gemini cli backend error: capacity exceeded.',
})
).toContain('capacity exceeded');
});
});