fix(team): make permission-pending launch copy honest
This commit is contained in:
parent
400eaf9acd
commit
3f8276147e
2 changed files with 118 additions and 9 deletions
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue