fix(team): preserve launch error and permission details

This commit is contained in:
777genius 2026-04-23 01:36:52 +03:00
parent 65fa117626
commit 3e53391a73
5 changed files with 75 additions and 0 deletions

View file

@ -63,6 +63,7 @@ export interface OpenCodeLaunchTeamCommandBody {
export interface OpenCodeTeamMemberLaunchCommandData {
sessionId: string;
launchState: OpenCodeTeamMemberLaunchBridgeState;
pendingPermissionRequestIds?: string[];
model: string;
runtimePid?: number;
evidence: Array<{ kind: string; observedAt: string }>;

View file

@ -385,6 +385,7 @@ function mapOpenCodeLaunchDataToRuntimeResult(
fallbackLaunchState,
bridgeMember?.sessionId,
bridgeMember?.runtimePid,
bridgeMember?.pendingPermissionRequestIds,
bridgeMember != null,
[
...(bridgeMember
@ -426,6 +427,7 @@ function mapBridgeMemberToRuntimeEvidence(
launchState: OpenCodeTeamMemberLaunchBridgeState,
sessionId: string | undefined,
runtimePid: number | undefined,
pendingPermissionRequestIds: string[] | undefined,
runtimeMaterialized: boolean,
diagnostics: string[]
): TeamRuntimeMemberLaunchEvidence {
@ -448,6 +450,10 @@ function mapBridgeMemberToRuntimeEvidence(
bootstrapConfirmed: confirmed,
hardFailure: failed,
hardFailureReason: failed ? 'OpenCode bridge reported member launch failure' : undefined,
pendingPermissionRequestIds:
pendingPermissionRequestIds && pendingPermissionRequestIds.length > 0
? [...new Set(pendingPermissionRequestIds)]
: undefined,
sessionId,
...(typeof runtimePid === 'number' && Number.isFinite(runtimePid) && runtimePid > 0
? { runtimePid }

View file

@ -733,6 +733,7 @@ function areMemberSpawnStatusEntriesEqual(
left.status === right.status &&
left.launchState === right.launchState &&
left.error === right.error &&
left.hardFailureReason === right.hardFailureReason &&
left.livenessSource === right.livenessSource &&
left.runtimeAlive === right.runtimeAlive &&
left.runtimeModel === right.runtimeModel &&

View file

@ -251,6 +251,7 @@ describe('OpenCodeTeamRuntimeAdapter', () => {
alice: {
sessionId: 'oc-session-1',
launchState: 'permission_blocked',
pendingPermissionRequestIds: ['perm-1', 'perm-1', 'perm-2'],
runtimePid: 123,
model: 'openai/gpt-5.4-mini',
evidence: [
@ -283,6 +284,7 @@ describe('OpenCodeTeamRuntimeAdapter', () => {
alice: {
providerId: 'opencode',
launchState: 'runtime_pending_permission',
pendingPermissionRequestIds: ['perm-1', 'perm-2'],
runtimeAlive: true,
agentToolAccepted: true,
bootstrapConfirmed: false,

View file

@ -3463,6 +3463,71 @@ describe('teamSlice actions', () => {
expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
});
it('rewrites renderer state when only hard failure reason changes', async () => {
const store = createSliceStore();
const previousSnapshot = createMemberSpawnSnapshot({
teamLaunchState: 'partial_failure',
summary: {
confirmedCount: 0,
pendingCount: 0,
failedCount: 1,
runtimeAlivePendingCount: 0,
},
statuses: {
alice: createMemberSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
livenessSource: undefined,
bootstrapConfirmed: false,
hardFailure: true,
hardFailureReason: 'initial failure',
}),
},
});
const previousStatuses = previousSnapshot.statuses;
store.setState({
currentRuntimeRunIdByTeam: {
'my-team': 'runtime-run',
},
memberSpawnStatusesByTeam: {
'my-team': previousStatuses,
},
memberSpawnSnapshotsByTeam: {
'my-team': previousSnapshot,
},
});
const nextSnapshot = createMemberSpawnSnapshot({
teamLaunchState: 'partial_failure',
summary: {
confirmedCount: 0,
pendingCount: 0,
failedCount: 1,
runtimeAlivePendingCount: 0,
},
statuses: {
alice: createMemberSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
livenessSource: undefined,
bootstrapConfirmed: false,
hardFailure: true,
hardFailureReason: 'resolved runtime reported missing auth',
}),
},
});
hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);
await store.getState().fetchMemberSpawnStatuses('my-team');
expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(previousStatuses);
expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual(nextSnapshot.statuses);
expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
});
it('rewrites renderer state when top-level launch summary changes', async () => {
const store = createSliceStore();
const previousSnapshot = createMemberSpawnSnapshot({