From 1223fa236a66b9c6ccdf23e046ea920329c4a5da Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 04:09:08 +0300 Subject: [PATCH] fix(team): reconcile richer persisted launch members --- .../services/team/TeamProvisioningService.ts | 5 +- .../team/TeamProvisioningService.test.ts | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 99fc0668..f2aa0343 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -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, diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index fb919037..e43b8bfb 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -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);