fix(team): reconcile richer persisted launch members

This commit is contained in:
777genius 2026-04-23 04:09:08 +03:00
parent be76b332c9
commit 1223fa236a
2 changed files with 67 additions and 2 deletions

View file

@ -12639,8 +12639,9 @@ export class TeamProvisioningService {
const liveAgentNames = await this.getLiveTeamAgentNames(teamName);
const nextMembers = { ...persisted.members };
const persistedMemberNames = this.getPersistedLaunchMemberNames(persisted);
const now = nowIso();
for (const expected of persisted.expectedMembers) {
for (const expected of persistedMemberNames) {
const bootstrapMember = bootstrapSnapshot?.members[expected];
const current = nextMembers[expected] ?? {
name: expected,
@ -12772,7 +12773,7 @@ export class TeamProvisioningService {
const reconciled = createPersistedLaunchSnapshot({
teamName,
expectedMembers: persisted.expectedMembers,
expectedMembers: persistedMemberNames,
leadSessionId: persisted.leadSessionId,
launchPhase: persisted.launchPhase === 'active' ? 'active' : 'reconciled',
members: nextMembers,

View file

@ -5738,6 +5738,70 @@ describe('TeamProvisioningService', () => {
});
});
it('reconciles extra persisted launch members when bootstrap state proves they were registered', async () => {
const teamName = 'registered-bootstrap-extra-member-team';
const teamDir = path.join(tempTeamsBase, teamName);
fs.mkdirSync(teamDir, { recursive: true });
fs.writeFileSync(
path.join(teamDir, 'launch-state.json'),
JSON.stringify(
createPersistedLaunchSnapshot({
teamName,
leadSessionId: 'lead-session',
launchPhase: 'active',
expectedMembers: ['alice'],
members: {
alice: {
name: 'alice',
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
lastEvaluatedAt: new Date().toISOString(),
},
bob: {
name: 'bob',
launchState: 'failed_to_start',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: true,
hardFailureReason: 'Teammate was never spawned during launch.',
lastEvaluatedAt: new Date().toISOString(),
},
},
updatedAt: new Date().toISOString(),
})
),
'utf8'
);
writeBootstrapState(
teamName,
[
{
name: 'bob',
status: 'registered',
lastAttemptAt: Date.now() - 60_000,
lastObservedAt: Date.now() - 60_000,
},
],
new Date(Date.now() - 30_000).toISOString()
);
const svc = new TeamProvisioningService();
const result = await svc.getMemberSpawnStatuses(teamName);
expect(result.expectedMembers).toEqual(['alice', 'bob']);
expect(result.statuses.bob).toMatchObject({
status: 'waiting',
launchState: 'runtime_pending_bootstrap',
hardFailure: false,
hardFailureReason: undefined,
agentToolAccepted: true,
});
});
it('returns persisted expectedMembers as the union of expected and materialized launch members', async () => {
const teamName = 'persisted-union-member-spawn-team';
const teamDir = path.join(tempTeamsBase, teamName);