fix(team): prefer richer persisted launch snapshots
This commit is contained in:
parent
33f51b68a7
commit
98751a2cfc
2 changed files with 91 additions and 9 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue