fix(team): sync mixed launch state back into live runs
This commit is contained in:
parent
b955901e15
commit
fe830da37d
2 changed files with 115 additions and 1 deletions
|
|
@ -6175,6 +6175,9 @@ export class TeamProvisioningService {
|
|||
await this.persistLaunchStateSnapshot(run, run.provisioningComplete ? 'finished' : 'active');
|
||||
|
||||
const persisted = await this.launchStateStore.read(teamName);
|
||||
if (persisted) {
|
||||
this.syncRunMemberSpawnStatusesFromSnapshot(run, persisted);
|
||||
}
|
||||
const liveSnapshot =
|
||||
this.buildLiveLaunchSnapshotForRun(run, run.provisioningComplete ? 'finished' : 'active') ??
|
||||
snapshotFromRuntimeMemberStatuses({
|
||||
|
|
@ -12005,6 +12008,21 @@ export class TeamProvisioningService {
|
|||
return statuses;
|
||||
}
|
||||
|
||||
private syncRunMemberSpawnStatusesFromSnapshot(
|
||||
run: ProvisioningRun,
|
||||
snapshot: PersistedTeamLaunchSnapshot
|
||||
): void {
|
||||
const memberNames = this.getPersistedLaunchMemberNames(snapshot);
|
||||
const snapshotStatuses = snapshotToMemberSpawnStatuses(snapshot);
|
||||
run.expectedMembers = memberNames;
|
||||
for (const memberName of memberNames) {
|
||||
const entry = snapshotStatuses[memberName];
|
||||
if (entry) {
|
||||
run.memberSpawnStatuses.set(memberName, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private countRunPermissionPendingMembers(run: ProvisioningRun): number {
|
||||
let count = 0;
|
||||
for (const expected of run.expectedMembers ?? []) {
|
||||
|
|
@ -12088,8 +12106,12 @@ export class TeamProvisioningService {
|
|||
run: ProvisioningRun,
|
||||
lane: MixedSecondaryRuntimeLaneState
|
||||
): Promise<void> {
|
||||
let snapshot: PersistedTeamLaunchSnapshot | null = null;
|
||||
if (run.isLaunch) {
|
||||
await this.persistLaunchStateSnapshot(run, this.getMixedSecondaryLaunchPhase(run));
|
||||
snapshot = await this.persistLaunchStateSnapshot(run, this.getMixedSecondaryLaunchPhase(run));
|
||||
}
|
||||
if (snapshot) {
|
||||
this.syncRunMemberSpawnStatusesFromSnapshot(run, snapshot);
|
||||
}
|
||||
if (!this.isCurrentTrackedRun(run)) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6299,4 +6299,96 @@ describe('TeamProvisioningService', () => {
|
|||
launchState: 'starting',
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs stale live mixed-lane failures from a healthier persisted snapshot', async () => {
|
||||
const svc = new TeamProvisioningService();
|
||||
const run = createMemberSpawnRun({
|
||||
teamName: 'forge-labs-4',
|
||||
runId: 'run-mixed-sync-1',
|
||||
expectedMembers: ['alice', 'jack'],
|
||||
memberSpawnStatuses: new Map([
|
||||
[
|
||||
'alice',
|
||||
createMemberSpawnStatusEntry({
|
||||
status: 'waiting',
|
||||
launchState: 'runtime_pending_bootstrap',
|
||||
runtimeAlive: true,
|
||||
bootstrapConfirmed: false,
|
||||
hardFailure: false,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'jack',
|
||||
createMemberSpawnStatusEntry({
|
||||
status: 'error',
|
||||
launchState: 'failed_to_start',
|
||||
runtimeAlive: false,
|
||||
bootstrapConfirmed: false,
|
||||
hardFailure: true,
|
||||
error: 'Teammate was never spawned during launch.',
|
||||
hardFailureReason: 'Teammate was never spawned during launch.',
|
||||
}),
|
||||
],
|
||||
]),
|
||||
});
|
||||
run.isLaunch = true;
|
||||
|
||||
const snapshot = createPersistedLaunchSnapshot({
|
||||
teamName: 'forge-labs-4',
|
||||
leadSessionId: 'lead-session',
|
||||
launchPhase: 'finished',
|
||||
expectedMembers: ['alice', 'jack'],
|
||||
members: {
|
||||
alice: {
|
||||
name: 'alice',
|
||||
launchState: 'runtime_pending_bootstrap',
|
||||
agentToolAccepted: true,
|
||||
runtimeAlive: true,
|
||||
bootstrapConfirmed: false,
|
||||
hardFailure: false,
|
||||
lastEvaluatedAt: '2026-04-23T08:08:27.067Z',
|
||||
},
|
||||
jack: {
|
||||
name: 'jack',
|
||||
providerId: 'opencode',
|
||||
launchState: 'confirmed_alive',
|
||||
agentToolAccepted: true,
|
||||
runtimeAlive: true,
|
||||
bootstrapConfirmed: true,
|
||||
hardFailure: false,
|
||||
lastEvaluatedAt: '2026-04-23T08:08:27.067Z',
|
||||
},
|
||||
},
|
||||
updatedAt: '2026-04-23T08:08:27.067Z',
|
||||
});
|
||||
|
||||
vi.spyOn(svc as any, 'persistLaunchStateSnapshot').mockResolvedValue(snapshot);
|
||||
vi.spyOn(svc as any, 'isCurrentTrackedRun').mockReturnValue(true);
|
||||
|
||||
await (svc as any).publishMixedSecondaryLaneStatusChange(run, {
|
||||
laneId: 'secondary:opencode:jack',
|
||||
providerId: 'opencode',
|
||||
member: {
|
||||
name: 'jack',
|
||||
providerId: 'opencode',
|
||||
model: 'opencode/ling-2.6-flash-free',
|
||||
},
|
||||
runId: 'lane-run-jack',
|
||||
state: 'finished',
|
||||
result: null,
|
||||
warnings: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
expect(run.memberSpawnStatuses.get('jack')).toMatchObject({
|
||||
status: 'online',
|
||||
launchState: 'confirmed_alive',
|
||||
hardFailure: false,
|
||||
hardFailureReason: undefined,
|
||||
error: undefined,
|
||||
bootstrapConfirmed: true,
|
||||
runtimeAlive: true,
|
||||
});
|
||||
expect(run.expectedMembers).toEqual(['alice', 'jack']);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue