fix(team): make permission-pending launch copy honest

This commit is contained in:
777genius 2026-04-23 02:04:55 +03:00
parent 400eaf9acd
commit 3f8276147e
2 changed files with 118 additions and 9 deletions

View file

@ -32,6 +32,24 @@ interface FailedSpawnDetail {
reason: string | null;
}
function countPermissionBlockedMembers(memberSpawnStatuses: MemberSpawnStatusCollection): number {
if (!memberSpawnStatuses) {
return 0;
}
const entries =
memberSpawnStatuses instanceof Map
? [...memberSpawnStatuses.values()]
: Object.values(memberSpawnStatuses);
return entries.filter((entry) => entry.launchState === 'runtime_pending_permission').length;
}
function buildAwaitingPermissionPhrase(count: number): string {
return count === 1
? '1 teammate awaiting permission approval'
: `${count} teammates awaiting permission approval`;
}
const ACTIVE_PROVISIONING_STATES = new Set([
'validating',
'spawning',
@ -201,6 +219,7 @@ export function buildTeamProvisioningPresentation({
failedSpawnCount,
expectedTeammateCount
);
const permissionBlockedCount = countPermissionBlockedMembers(memberSpawnStatuses);
const { allTeammatesConfirmedAlive, hasMembersStillJoining, remainingJoinCount } =
getLaunchJoinState({
@ -251,12 +270,19 @@ export function buildTeamProvisioningPresentation({
remainingJoinCount === 1
? '1 teammate still joining'
: `${remainingJoinCount} teammates still joining`;
const pendingMembersAwaitApproval =
failedSpawnCount === 0 &&
permissionBlockedCount > 0 &&
permissionBlockedCount === remainingJoinCount;
const pendingDetailPhrase = pendingMembersAwaitApproval
? buildAwaitingPermissionPhrase(permissionBlockedCount)
: joiningPhrase;
const readyCompactDetail =
failedSpawnCount > 0
? (failedSpawnCompactDetail ??
`${failedSpawnCount} teammate${failedSpawnCount === 1 ? '' : 's'} failed to start`)
: hasMembersStillJoining
? joiningPhrase
? pendingDetailPhrase
: expectedTeammateCount === 0
? 'Lead online'
: `All ${expectedTeammateCount} teammates joined`;
@ -268,7 +294,7 @@ export function buildTeamProvisioningPresentation({
: allTeammatesConfirmedAlive
? `Team provisioned - all ${expectedTeammateCount} teammates joined`
: hasMembersStillJoining
? joiningPhrase
? pendingDetailPhrase
: 'Team provisioned - teammates are still joining';
const readyDetailSeverity =
failedSpawnCount > 0 ? 'warning' : hasMembersStillJoining ? 'info' : undefined;
@ -316,6 +342,17 @@ export function buildTeamProvisioningPresentation({
}
if (isActive) {
const activeJoiningPhrase =
remainingJoinCount === 1
? '1 teammate still joining'
: `${remainingJoinCount} teammates still joining`;
const activePendingDetailPhrase =
failedSpawnCount === 0 &&
hasMembersStillJoining &&
permissionBlockedCount > 0 &&
permissionBlockedCount === remainingJoinCount
? buildAwaitingPermissionPhrase(permissionBlockedCount)
: activeJoiningPhrase;
return {
progress,
isActive: true,
@ -335,7 +372,11 @@ export function buildTeamProvisioningPresentation({
panelMessage:
failedSpawnCount > 0
? (failedSpawnPanelMessage ?? genericFailedSpawnPanelMessage ?? progress.message)
: progress.message,
: hasMembersStillJoining &&
permissionBlockedCount > 0 &&
permissionBlockedCount === remainingJoinCount
? activePendingDetailPhrase
: progress.message,
panelMessageSeverity: failedSpawnCount > 0 ? 'warning' : progress.messageSeverity,
defaultLiveOutputOpen: false,
compactTitle: 'Launching team',
@ -343,9 +384,13 @@ export function buildTeamProvisioningPresentation({
failedSpawnCount > 0
? (failedSpawnCompactDetail ??
`${failedSpawnCount} teammate${failedSpawnCount === 1 ? '' : 's'} failed to start`)
: expectedTeammateCount > 0 && progressStepIndex >= 2
? `${heartbeatConfirmedCount}/${expectedTeammateCount} teammates confirmed`
: progress.message,
: hasMembersStillJoining && failedSpawnCount === 0 && permissionBlockedCount > 0
? permissionBlockedCount === remainingJoinCount
? buildAwaitingPermissionPhrase(permissionBlockedCount)
: `${heartbeatConfirmedCount}/${expectedTeammateCount} teammates confirmed`
: expectedTeammateCount > 0 && progressStepIndex >= 2
? `${heartbeatConfirmedCount}/${expectedTeammateCount} teammates confirmed`
: progress.message,
compactTone: failedSpawnCount > 0 ? 'warning' : 'default',
};
}

View file

@ -269,7 +269,7 @@ describe('buildTeamProvisioningPresentation', () => {
expect(presentation?.panelMessage).toBe('1 teammate still joining');
});
it('counts permission-blocked teammates as still joining while launch is finishing', () => {
it('surfaces permission-blocked teammates as awaiting approval while launch is finishing', () => {
const presentation = buildTeamProvisioningPresentation({
progress: {
runId: 'run-4c',
@ -329,8 +329,72 @@ describe('buildTeamProvisioningPresentation', () => {
});
expect(presentation?.compactTitle).toBe('Finishing launch');
expect(presentation?.compactDetail).toBe('1 teammate still joining');
expect(presentation?.panelMessage).toBe('1 teammate still joining');
expect(presentation?.compactDetail).toBe('1 teammate awaiting permission approval');
expect(presentation?.panelMessage).toBe('1 teammate awaiting permission approval');
});
it('surfaces permission-blocked teammates as awaiting approval while launch is still active', () => {
const presentation = buildTeamProvisioningPresentation({
progress: {
runId: 'run-4d',
teamName: 'opencode-team',
state: 'finalizing',
startedAt: '2026-04-13T10:00:00.000Z',
updatedAt: '2026-04-13T10:00:08.000Z',
message: 'Waiting for runtime confirmation',
messageSeverity: undefined,
pid: 4321,
cliLogsTail: '',
assistantOutput: '',
},
members: [
{
name: 'team-lead',
agentType: 'team-lead',
status: 'active',
currentTaskId: null,
taskCount: 0,
lastActiveAt: null,
messageCount: 0,
},
{
name: 'bob',
agentType: 'engineer',
status: 'unknown',
currentTaskId: null,
taskCount: 0,
lastActiveAt: null,
messageCount: 0,
},
],
memberSpawnStatuses: {
bob: {
status: 'online',
launchState: 'runtime_pending_permission',
updatedAt: '2026-04-13T10:00:07.000Z',
runtimeAlive: true,
livenessSource: 'process',
bootstrapConfirmed: false,
hardFailure: false,
agentToolAccepted: true,
pendingPermissionRequestIds: ['perm_1'],
firstSpawnAcceptedAt: '2026-04-13T10:00:01.000Z',
},
},
memberSpawnSnapshot: {
expectedMembers: ['bob'],
summary: {
confirmedCount: 0,
pendingCount: 1,
failedCount: 0,
runtimeAlivePendingCount: 1,
},
},
});
expect(presentation?.compactTitle).toBe('Launching team');
expect(presentation?.compactDetail).toBe('1 teammate awaiting permission approval');
expect(presentation?.panelMessage).toBe('1 teammate awaiting permission approval');
});
it('keeps a generic failed teammate message while launch is still active if only persisted failure counts remain', () => {