fix(team): prefer richer persisted launch snapshots

This commit is contained in:
777genius 2026-04-23 04:00:17 +03:00
parent 33f51b68a7
commit 98751a2cfc
2 changed files with 91 additions and 9 deletions

View file

@ -822,6 +822,7 @@ function isLaunchSnapshotLike(value: unknown): value is PersistedTeamLaunchSnaps
}
function getLaunchSnapshotRichness(snapshot: PersistedTeamLaunchSnapshot): number {
const persistedMemberCount = getPersistedLaunchMemberNames(snapshot).length;
let metadataScore = 0;
for (const member of Object.values(snapshot.members)) {
if (!member || typeof member !== 'object') continue;
@ -835,13 +836,17 @@ function getLaunchSnapshotRichness(snapshot: PersistedTeamLaunchSnapshot): numbe
if (member.launchIdentity) metadataScore += 6;
}
return (
snapshot.expectedMembers.length * 10 +
persistedMemberCount * 10 +
Object.keys(snapshot.members).length * 5 +
metadataScore +
(snapshot.bootstrapExpectedMembers?.length ? 20 : 0)
);
}
function getPersistedLaunchMemberNames(snapshot: PersistedTeamLaunchSnapshot): string[] {
return Array.from(new Set([...snapshot.expectedMembers, ...Object.keys(snapshot.members)]));
}
export function shouldIgnoreTerminalBootstrapOnlyPendingSnapshot(
snapshot: Pick<PersistedTeamLaunchSnapshot, 'launchPhase' | 'teamLaunchState' | 'updatedAt'>,
nowMs: number = Date.now()
@ -875,16 +880,12 @@ export function choosePreferredLaunchSnapshot<T extends { updatedAt?: string }>(
if (isLaunchSnapshotLike(bootstrapSnapshot) && isLaunchSnapshotLike(launchSnapshot)) {
const bootstrapRichness = getLaunchSnapshotRichness(bootstrapSnapshot);
const launchRichness = getLaunchSnapshotRichness(launchSnapshot);
if (
launchRichness > bootstrapRichness &&
launchSnapshot.expectedMembers.length >= bootstrapSnapshot.expectedMembers.length
) {
const bootstrapMemberCount = getPersistedLaunchMemberNames(bootstrapSnapshot).length;
const launchMemberCount = getPersistedLaunchMemberNames(launchSnapshot).length;
if (launchRichness > bootstrapRichness && launchMemberCount >= bootstrapMemberCount) {
return launchSnapshot as T;
}
if (
bootstrapRichness > launchRichness &&
bootstrapSnapshot.expectedMembers.length >= launchSnapshot.expectedMembers.length
) {
if (bootstrapRichness > launchRichness && bootstrapMemberCount >= launchMemberCount) {
return bootstrapSnapshot as T;
}
}

View file

@ -522,4 +522,85 @@ describe('TeamBootstrapStateReader', () => {
expect(preferred).toBeNull();
nowSpy.mockRestore();
});
it('prefers richer canonical launch snapshots when persisted members outgrow stale expectedMembers', () => {
const preferred = choosePreferredLaunchSnapshot(
{
version: 2,
teamName: 'demo',
updatedAt: '2026-04-23T10:05:00.000Z',
launchPhase: 'running',
expectedMembers: ['alice', 'bob'],
members: {
alice: {
name: 'alice',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
lastEvaluatedAt: '2026-04-23T10:05:00.000Z',
},
},
summary: {
confirmedCount: 1,
pendingCount: 1,
failedCount: 0,
runtimeAlivePendingCount: 0,
},
teamLaunchState: 'partial_pending',
},
{
version: 2,
teamName: 'demo',
updatedAt: '2026-04-23T10:00:00.000Z',
launchPhase: 'running',
expectedMembers: ['alice'],
members: {
alice: {
name: 'alice',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
lastEvaluatedAt: '2026-04-23T10:00:00.000Z',
launchIdentity: {
providerId: 'codex',
providerBackendId: 'codex-native',
source: 'codex-runtime',
},
},
bob: {
name: 'bob',
launchState: 'runtime_pending_bootstrap',
agentToolAccepted: true,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
lastEvaluatedAt: '2026-04-23T10:00:00.000Z',
laneId: 'secondary:opencode:bob',
laneKind: 'secondary',
laneOwnerProviderId: 'opencode',
},
},
summary: {
confirmedCount: 1,
pendingCount: 1,
failedCount: 0,
runtimeAlivePendingCount: 0,
},
teamLaunchState: 'partial_pending',
}
);
expect(preferred).toMatchObject({
updatedAt: '2026-04-23T10:00:00.000Z',
members: {
bob: {
laneId: 'secondary:opencode:bob',
},
},
});
});
});