From b955901e1549a4c913937eb683d9ff3264ad7833 Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 04:30:33 +0300 Subject: [PATCH] fix(team): keep persisted launch failure details visible --- .../utils/teamProvisioningPresentation.ts | 50 +++++++++++---- .../teamProvisioningPresentation.test.ts | 64 +++++++++++++++++++ 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/renderer/utils/teamProvisioningPresentation.ts b/src/renderer/utils/teamProvisioningPresentation.ts index 477e7bef..08dd3ca7 100644 --- a/src/renderer/utils/teamProvisioningPresentation.ts +++ b/src/renderer/utils/teamProvisioningPresentation.ts @@ -86,25 +86,46 @@ const ACTIVE_PROVISIONING_STATES = new Set([ 'verifying', ]); -function getFailedSpawnDetails( - memberSpawnStatuses: MemberSpawnStatusCollection -): FailedSpawnDetail[] { - if (!memberSpawnStatuses) { +function getFailedSpawnDetails(params: { + memberSpawnStatuses: MemberSpawnStatusCollection; + memberSpawnSnapshotStatuses?: MemberSpawnStatusesSnapshot['statuses']; +}): FailedSpawnDetail[] { + const names = new Set(); + if (params.memberSpawnStatuses instanceof Map) { + for (const name of params.memberSpawnStatuses.keys()) { + names.add(name); + } + } else if (params.memberSpawnStatuses) { + for (const name of Object.keys(params.memberSpawnStatuses)) { + names.add(name); + } + } + for (const name of Object.keys(params.memberSpawnSnapshotStatuses ?? {})) { + names.add(name); + } + + if (names.size === 0) { return []; } - const entries = - memberSpawnStatuses instanceof Map - ? [...memberSpawnStatuses.entries()] - : Object.entries(memberSpawnStatuses); - return entries - .filter(([, entry]) => entry.launchState === 'failed_to_start' || entry.status === 'error') + return [...names] + .map((name) => { + const liveEntry = + params.memberSpawnStatuses instanceof Map + ? params.memberSpawnStatuses.get(name) + : params.memberSpawnStatuses?.[name]; + const snapshotEntry = params.memberSpawnSnapshotStatuses?.[name]; + return [name, liveEntry ?? snapshotEntry] as const; + }) + .filter( + ([, entry]) => entry && (entry.launchState === 'failed_to_start' || entry.status === 'error') + ) .map(([name, entry]) => ({ name, reason: - typeof entry.hardFailureReason === 'string' && entry.hardFailureReason.trim().length > 0 + typeof entry?.hardFailureReason === 'string' && entry.hardFailureReason.trim().length > 0 ? entry.hardFailureReason.trim() - : typeof entry.error === 'string' && entry.error.trim().length > 0 + : typeof entry?.error === 'string' && entry.error.trim().length > 0 ? entry.error.trim() : null, })) @@ -241,7 +262,10 @@ export function buildTeamProvisioningPresentation({ memberSpawnStatuses, memberSpawnSnapshot, }); - const failedSpawnDetails = getFailedSpawnDetails(memberSpawnStatuses); + const failedSpawnDetails = getFailedSpawnDetails({ + memberSpawnStatuses, + memberSpawnSnapshotStatuses: memberSpawnSnapshot?.statuses, + }); const failedSpawnPanelMessage = buildFailedSpawnPanelMessage(failedSpawnDetails); const failedSpawnCompactDetail = buildFailedSpawnCompactDetail(failedSpawnDetails); const genericFailedSpawnPanelMessage = buildGenericFailedSpawnPanelMessage( diff --git a/test/renderer/utils/teamProvisioningPresentation.test.ts b/test/renderer/utils/teamProvisioningPresentation.test.ts index af00073d..c02b2515 100644 --- a/test/renderer/utils/teamProvisioningPresentation.test.ts +++ b/test/renderer/utils/teamProvisioningPresentation.test.ts @@ -576,6 +576,70 @@ describe('buildTeamProvisioningPresentation', () => { expect(presentation?.compactTone).toBe('warning'); }); + it('surfaces persisted failed teammate reasons when live member statuses are missing', () => { + const presentation = buildTeamProvisioningPresentation({ + progress: { + runId: 'run-4c', + teamName: 'codex-team', + state: 'ready', + startedAt: '2026-04-13T10:00:00.000Z', + updatedAt: '2026-04-13T10:00:08.000Z', + message: 'Launch completed with teammate errors', + messageSeverity: 'warning', + pid: 4321, + cliLogsTail: '', + assistantOutput: '', + }, + members: [ + { + name: 'team-lead', + agentType: 'team-lead', + status: 'active', + currentTaskId: null, + taskCount: 0, + lastActiveAt: null, + messageCount: 0, + }, + { + name: 'jack', + agentType: 'engineer', + status: 'unknown', + currentTaskId: null, + taskCount: 0, + lastActiveAt: null, + messageCount: 0, + }, + ], + memberSpawnStatuses: {}, + memberSpawnSnapshot: { + expectedMembers: ['jack'], + summary: { + confirmedCount: 0, + pendingCount: 0, + failedCount: 1, + runtimeAlivePendingCount: 0, + }, + statuses: { + jack: { + status: 'error', + launchState: 'failed_to_start', + hardFailureReason: 'The requested model is not available for your account.', + updatedAt: '2026-04-13T10:00:03.000Z', + runtimeAlive: false, + bootstrapConfirmed: false, + hardFailure: true, + agentToolAccepted: true, + firstSpawnAcceptedAt: '2026-04-13T10:00:01.000Z', + }, + }, + }, + }); + + expect(presentation?.panelMessage).toContain('jack failed to start'); + expect(presentation?.panelMessage).toContain('requested model is not available'); + expect(presentation?.compactDetail).toBe('jack failed to start'); + }); + it('prefers live confirmed teammates over a stale persisted launch summary', () => { const presentation = buildTeamProvisioningPresentation({ progress: {