fix(team): prefer live launch truth over stale summary

This commit is contained in:
777genius 2026-04-23 00:30:01 +03:00
parent 2db49d694c
commit 9005deb05c
2 changed files with 133 additions and 28 deletions

View file

@ -51,39 +51,18 @@ function getSpawnEntry(
return memberSpawnStatuses[memberName]; return memberSpawnStatuses[memberName];
} }
export function getLaunchJoinMilestonesFromMembers({ function summarizeLiveLaunchJoinMilestones(params: {
members, teammateNames: readonly string[];
memberSpawnStatuses,
memberSpawnSnapshot,
}: {
members: readonly LaunchJoinMemberLike[];
memberSpawnStatuses?: MemberSpawnStatusCollection; memberSpawnStatuses?: MemberSpawnStatusCollection;
memberSpawnSnapshot?: Pick<MemberSpawnStatusesSnapshot, 'expectedMembers' | 'summary'>; }): Omit<LaunchJoinMilestones, 'expectedTeammateCount'> {
}): LaunchJoinMilestones { const { teammateNames, memberSpawnStatuses } = params;
const teammates = members.filter((member) => !member.removedAt && !isLeadMember(member));
const expectedTeammateCount = memberSpawnSnapshot?.expectedMembers?.length ?? teammates.length;
const snapshotSummary = memberSpawnSnapshot?.summary;
if (snapshotSummary) {
return {
expectedTeammateCount,
heartbeatConfirmedCount: snapshotSummary.confirmedCount,
processOnlyAliveCount: snapshotSummary.runtimeAlivePendingCount,
pendingSpawnCount: Math.max(
0,
snapshotSummary.pendingCount - snapshotSummary.runtimeAlivePendingCount
),
failedSpawnCount: snapshotSummary.failedCount,
};
}
let heartbeatConfirmedCount = 0; let heartbeatConfirmedCount = 0;
let processOnlyAliveCount = 0; let processOnlyAliveCount = 0;
let pendingSpawnCount = 0; let pendingSpawnCount = 0;
let failedSpawnCount = 0; let failedSpawnCount = 0;
for (const member of teammates) { for (const memberName of teammateNames) {
const entry = getSpawnEntry(memberSpawnStatuses, member.name); const entry = getSpawnEntry(memberSpawnStatuses, memberName);
if (!entry) { if (!entry) {
pendingSpawnCount += 1; pendingSpawnCount += 1;
continue; continue;
@ -110,7 +89,6 @@ export function getLaunchJoinMilestonesFromMembers({
} }
return { return {
expectedTeammateCount,
heartbeatConfirmedCount, heartbeatConfirmedCount,
processOnlyAliveCount, processOnlyAliveCount,
pendingSpawnCount, pendingSpawnCount,
@ -118,6 +96,68 @@ export function getLaunchJoinMilestonesFromMembers({
}; };
} }
export function getLaunchJoinMilestonesFromMembers({
members,
memberSpawnStatuses,
memberSpawnSnapshot,
}: {
members: readonly LaunchJoinMemberLike[];
memberSpawnStatuses?: MemberSpawnStatusCollection;
memberSpawnSnapshot?: Pick<MemberSpawnStatusesSnapshot, 'expectedMembers' | 'summary'>;
}): LaunchJoinMilestones {
const teammates = members.filter((member) => !member.removedAt && !isLeadMember(member));
const teammateNames =
memberSpawnSnapshot?.expectedMembers?.length && memberSpawnSnapshot.expectedMembers.length > 0
? memberSpawnSnapshot.expectedMembers
: teammates.map((member) => member.name);
const expectedTeammateCount = teammateNames.length;
const snapshotSummary = memberSpawnSnapshot?.summary;
const liveSummary = summarizeLiveLaunchJoinMilestones({
teammateNames,
memberSpawnStatuses,
});
if (snapshotSummary) {
const snapshotMilestones = {
expectedTeammateCount,
heartbeatConfirmedCount: snapshotSummary.confirmedCount,
processOnlyAliveCount: snapshotSummary.runtimeAlivePendingCount,
pendingSpawnCount: Math.max(
0,
snapshotSummary.pendingCount - snapshotSummary.runtimeAlivePendingCount
),
failedSpawnCount: snapshotSummary.failedCount,
};
const snapshotAccountedFor =
snapshotMilestones.heartbeatConfirmedCount +
snapshotMilestones.processOnlyAliveCount +
snapshotMilestones.failedSpawnCount;
const liveAccountedFor =
liveSummary.heartbeatConfirmedCount +
liveSummary.processOnlyAliveCount +
liveSummary.failedSpawnCount;
const liveSummaryIsMoreAdvanced =
liveSummary.failedSpawnCount > snapshotMilestones.failedSpawnCount ||
liveSummary.heartbeatConfirmedCount > snapshotMilestones.heartbeatConfirmedCount ||
liveSummary.processOnlyAliveCount > snapshotMilestones.processOnlyAliveCount ||
liveAccountedFor > snapshotAccountedFor;
return liveSummaryIsMoreAdvanced
? {
expectedTeammateCount,
...liveSummary,
}
: snapshotMilestones;
}
return {
expectedTeammateCount,
...liveSummary,
};
}
export function getLaunchJoinState({ export function getLaunchJoinState({
expectedTeammateCount, expectedTeammateCount,
heartbeatConfirmedCount, heartbeatConfirmedCount,

View file

@ -217,4 +217,69 @@ describe('buildTeamProvisioningPresentation', () => {
expect(presentation?.compactDetail).toBe('1 teammate still joining'); expect(presentation?.compactDetail).toBe('1 teammate still joining');
expect(presentation?.panelMessage).toBe('1 teammate still joining'); expect(presentation?.panelMessage).toBe('1 teammate still joining');
}); });
it('prefers live confirmed teammates over a stale persisted launch summary', () => {
const presentation = buildTeamProvisioningPresentation({
progress: {
runId: 'run-5',
teamName: 'codex-team',
state: 'ready',
startedAt: '2026-04-13T10:00:00.000Z',
updatedAt: '2026-04-13T10:00:08.000Z',
message: 'Launch completed',
messageSeverity: undefined,
pid: 4321,
cliLogsTail: '',
assistantOutput: '',
},
members: [
{
name: 'team-lead',
agentType: 'team-lead',
status: 'active',
currentTaskId: null,
taskCount: 0,
lastActiveAt: null,
messageCount: 0,
},
{
name: 'bob',
agentType: 'engineer',
status: 'unknown',
currentTaskId: null,
taskCount: 0,
lastActiveAt: null,
messageCount: 0,
},
],
memberSpawnStatuses: {
bob: {
status: 'online',
launchState: 'confirmed_alive',
updatedAt: '2026-04-13T10:00:07.000Z',
runtimeAlive: true,
livenessSource: 'heartbeat',
bootstrapConfirmed: true,
hardFailure: false,
agentToolAccepted: true,
firstSpawnAcceptedAt: '2026-04-13T10:00:01.000Z',
lastHeartbeatAt: '2026-04-13T10:00:07.000Z',
},
},
memberSpawnSnapshot: {
expectedMembers: ['bob'],
summary: {
confirmedCount: 0,
pendingCount: 1,
failedCount: 0,
runtimeAlivePendingCount: 0,
},
},
});
expect(presentation?.compactTitle).toBe('Team launched');
expect(presentation?.compactDetail).toBe('All 1 teammates joined');
expect(presentation?.panelMessage).toBeNull();
expect(presentation?.currentStepIndex).toBe(4);
});
}); });