From 1cb9af3fc701da3d46bf296517b0c8b714fc1923 Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 00:36:22 +0300 Subject: [PATCH] fix(team): tighten pending launch runtime reporting --- .../services/team/TeamProvisioningService.ts | 9 +-- src/renderer/utils/memberRuntimeSummary.ts | 6 +- .../team/TeamProvisioningService.test.ts | 80 +++++++++++++++++++ .../utils/memberRuntimeSummary.test.ts | 30 ++++++- 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 6f5f4f06..6c540a24 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -11888,11 +11888,10 @@ export class TeamProvisioningService { return false; } const member = snapshot.members[memberName]; - return ( - member && - member.launchState !== 'confirmed_alive' && - member.launchState !== 'failed_to_start' - ); + if (!member) { + return true; + } + return member.launchState !== 'confirmed_alive' && member.launchState !== 'failed_to_start'; }); if (secondaryPendingMembers.length === 0) { return this.buildPendingBootstrapStatusMessage(prefix, run, launchSummary); diff --git a/src/renderer/utils/memberRuntimeSummary.ts b/src/renderer/utils/memberRuntimeSummary.ts index f0c3353a..edf0a61d 100644 --- a/src/renderer/utils/memberRuntimeSummary.ts +++ b/src/renderer/utils/memberRuntimeSummary.ts @@ -77,7 +77,11 @@ export function resolveMemberRuntimeSummary( } if (isMemberLaunchPending(spawnEntry)) { - return undefined; + if (!configuredModel.length && !memorySuffix) { + return undefined; + } + const summary = formatTeamModelSummary(configuredProvider, configuredModel, configuredEffort); + return `${summary}${backendLabel ? ` · ${backendLabel}` : ''}${memorySuffix}`; } const summary = formatTeamModelSummary(configuredProvider, configuredModel, configuredEffort); diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index a3012588..7dd5363c 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -1921,6 +1921,86 @@ describe('TeamProvisioningService', () => { expect(message).toBe('Finishing launch - waiting for secondary runtime lane: bob'); }); + it('treats missing secondary-lane snapshot members as still pending', async () => { + const svc = new TeamProvisioningService(); + const run = createMemberSpawnRun({ + teamName: 'mixed-team', + expectedMembers: ['alice'], + memberSpawnStatuses: new Map([ + [ + 'alice', + createMemberSpawnStatusEntry({ + status: 'online', + launchState: 'confirmed_alive', + runtimeAlive: true, + bootstrapConfirmed: true, + }), + ], + ]), + }); + run.isLaunch = true; + run.mixedSecondaryLanes = [ + { + laneId: 'secondary:opencode:bob', + providerId: 'opencode', + member: { + name: 'bob', + providerId: 'opencode', + model: 'minimax-m2.5-free', + effort: 'medium', + }, + runId: 'opencode-run-1', + state: 'launching', + result: null, + warnings: [], + diagnostics: [], + }, + ]; + + const message = (svc as any).buildAggregatePendingLaunchMessage( + 'Finishing launch', + run, + { + confirmedCount: 1, + pendingCount: 1, + failedCount: 0, + runtimeAlivePendingCount: 0, + }, + { + version: 2, + teamName: 'mixed-team', + updatedAt: '2026-04-22T12:00:00.000Z', + launchPhase: 'active', + expectedMembers: ['alice', 'bob'], + bootstrapExpectedMembers: ['alice'], + members: { + alice: { + name: 'alice', + providerId: 'codex', + laneId: 'primary', + laneKind: 'primary', + laneOwnerProviderId: 'codex', + launchState: 'confirmed_alive', + agentToolAccepted: true, + runtimeAlive: true, + bootstrapConfirmed: true, + hardFailure: false, + lastEvaluatedAt: '2026-04-22T12:00:00.000Z', + }, + }, + summary: { + confirmedCount: 1, + pendingCount: 1, + failedCount: 0, + runtimeAlivePendingCount: 0, + }, + teamLaunchState: 'partial_pending', + } + ); + + expect(message).toBe('Finishing launch - waiting for secondary runtime lane: bob'); + }); + it('launches the OpenCode secondary lane with side-lane provider and member runtime identity', async () => { const svc = new TeamProvisioningService(); const adapterLaunch = vi.fn(async (input: Record) => ({ diff --git a/test/renderer/utils/memberRuntimeSummary.test.ts b/test/renderer/utils/memberRuntimeSummary.test.ts index f8b2b0b3..e7828086 100644 --- a/test/renderer/utils/memberRuntimeSummary.test.ts +++ b/test/renderer/utils/memberRuntimeSummary.test.ts @@ -47,8 +47,17 @@ describe('resolveMemberRuntimeSummary', () => { ); }); - it('keeps the loading skeleton when a pending member has no live runtime model yet', () => { - const member = createMember(); + it('keeps the configured summary visible while a pending member waits for the live runtime model', () => { + const member = createMember({ model: 'gpt-5.4-mini' }); + const spawnEntry = createSpawnEntry(); + + expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBe( + '5.4 Mini · Medium · Codex' + ); + }); + + it('still keeps the loading skeleton when a pending member has neither live nor configured model truth', () => { + const member = createMember({ model: undefined }); const spawnEntry = createSpawnEntry(); expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBeUndefined(); @@ -85,6 +94,23 @@ describe('resolveMemberRuntimeSummary', () => { ); }); + it('appends runtime memory while a configured member is still pending', () => { + const member = createMember({ model: 'gpt-5.4-mini' }); + const spawnEntry = createSpawnEntry(); + const runtimeEntry = { + memberName: 'alice', + alive: true, + restartable: true, + pid: 4242, + rssBytes: 256 * 1024 * 1024, + updatedAt: '2026-04-18T18:00:00.000Z', + }; + + expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry, runtimeEntry as never)).toBe( + '5.4 Mini · Medium · Codex · 256.0 MB' + ); + }); + it('keeps the persisted backend lane visible in the runtime summary', () => { const member = createMember({ model: 'gpt-5.4-mini' });