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