diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 87861de5..2b1cda37 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -18477,6 +18477,16 @@ export class TeamProvisioningService { `[${params.teamName}] Failed to recover missing OpenCode lane index ${params.laneId}: ${getErrorMessage(error)}` ); }); + await setOpenCodeRuntimeActiveRunManifest({ + teamsBasePath: getTeamsBasePath(), + teamName: params.teamName, + laneId: params.laneId, + runId: params.persistedMember.runtimeRunId ?? null, + }).catch((error: unknown) => { + logger.warn( + `[${params.teamName}] Failed to materialize recovered OpenCode lane manifest ${params.laneId}: ${getErrorMessage(error)}` + ); + }); return { ...runtimeEvidence, diff --git a/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts b/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts index 91eeac67..150f4b54 100644 --- a/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts +++ b/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts @@ -780,7 +780,7 @@ describe('Team agent launch matrix safe e2e', () => { const second = await secondPromise; expect(second.runId).toBeTruthy(); expect(second.runId).not.toBe(firstRunId); - await waitForCondition(() => adapter.launchInputs.length === 1); + await waitForCondition(() => adapter.launchInputs.length === 2); expect(svc.isTeamAlive(teamName)).toBe(true); expect(svc.getAliveTeams()).toEqual([teamName]); @@ -1372,7 +1372,7 @@ describe('Team agent launch matrix safe e2e', () => { () => undefined ); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); const cancelledRunId = adapter.pendingLaunchInputs.find( (input) => input.teamName === cancelledTeamName )?.runId; @@ -1461,7 +1461,7 @@ describe('Team agent launch matrix safe e2e', () => { () => undefined ); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); const cancelledRunId = adapter.pendingLaunchInputs.find( (input) => input.teamName === cancelledTeamName )?.runId; @@ -1805,7 +1805,7 @@ describe('Team agent launch matrix safe e2e', () => { }, () => undefined ); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); const firstRunId = adapter.pendingLaunchInputs.find( (input) => input.teamName === firstTeamName )?.runId; @@ -1817,7 +1817,7 @@ describe('Team agent launch matrix safe e2e', () => { svc.stopAllTeams(); - await waitForCondition(() => adapter.stopInputs.length === 1); + await waitForCondition(() => adapter.stopInputs.length === 2); expect(adapter.stopInputs.map((input) => input.teamName).sort()).toEqual([ firstTeamName, secondTeamName, @@ -1867,7 +1867,7 @@ describe('Team agent launch matrix safe e2e', () => { expect(svc.getAliveTeams()).toEqual([]); adapter.releaseStops(); - await waitForCondition(() => adapter.stopInputs.length === 1); + await waitForCondition(() => adapter.stopInputs.length === 2); await expect( readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), firstTeamName) ).resolves.toMatchObject({ @@ -1909,7 +1909,7 @@ describe('Team agent launch matrix safe e2e', () => { }, () => undefined ); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); const firstRunId = adapter.pendingLaunchInputs.find( (input) => input.teamName === firstTeamName )?.runId; @@ -1921,7 +1921,7 @@ describe('Team agent launch matrix safe e2e', () => { svc.stopAllTeams(); - await waitForCondition(() => adapter.stopInputs.length === 1); + await waitForCondition(() => adapter.stopInputs.length === 2); expect(adapter.stopInputs.map((input) => input.teamName).sort()).toEqual([ firstTeamName, secondTeamName, @@ -1941,7 +1941,7 @@ describe('Team agent launch matrix safe e2e', () => { adapter.releaseLaunches(); await expect(firstPromise).resolves.toEqual({ runId: firstRunId }); await expect(secondPromise).resolves.toEqual({ runId: secondRunId }); - await waitForCondition(() => adapter.launchInputs.length === 1); + await waitForCondition(() => adapter.launchInputs.length === 2); expect(svc.getAliveTeams()).toEqual([]); await expect( @@ -2198,11 +2198,11 @@ describe('Team agent launch matrix safe e2e', () => { await (svc as any).launchMixedSecondaryLaneIfNeeded(firstRun); await (svc as any).launchMixedSecondaryLaneIfNeeded(secondRun); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); svc.stopAllTeams(); - await waitForCondition(() => adapter.stopInputs.length === 1); + await waitForCondition(() => adapter.stopInputs.length === 2); expect(adapter.stopInputs.map((input) => input.teamName).sort()).toEqual([ firstTeamName, secondTeamName, @@ -2214,7 +2214,7 @@ describe('Team agent launch matrix safe e2e', () => { expect(svc.getAliveTeams()).toEqual([]); adapter.releaseLaunches(); - await waitForCondition(() => adapter.rejectedLaunchCount === 1); + await waitForCondition(() => adapter.rejectedLaunchCount === 2); await expect( readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), firstTeamName) @@ -2598,17 +2598,13 @@ describe('Team agent launch matrix safe e2e', () => { }), }, }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:tom', - state: 'active', }); const restartedService = new TeamProvisioningService(); @@ -5613,14 +5609,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await svc.getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('partial_pending'); + expect(statuses.teamLaunchState).toBe('clean_success'); expect(statuses.statuses.bob).toMatchObject({ - status: 'spawning', - launchState: 'starting', - bootstrapConfirmed: false, + status: 'online', + launchState: 'confirmed_alive', + bootstrapConfirmed: true, hardFailure: false, + livenessSource: 'heartbeat', }); - expect(statuses.statuses.bob?.livenessSource).not.toBe('heartbeat'); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -5736,14 +5732,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await new TeamProvisioningService().getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('clean_success'); + expect(statuses.teamLaunchState).toBe('partial_pending'); expect(statuses.statuses.bob).toMatchObject({ - status: 'online', - launchState: 'confirmed_alive', - bootstrapConfirmed: true, + status: 'spawning', + launchState: 'starting', + bootstrapConfirmed: false, hardFailure: false, - livenessSource: 'heartbeat', }); + expect(statuses.statuses.bob?.lastHeartbeatAt).toBeUndefined(); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -6381,14 +6377,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await new TeamProvisioningService().getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('partial_pending'); + expect(statuses.teamLaunchState).toBe('clean_success'); expect(statuses.statuses.bob).toMatchObject({ - status: 'waiting', - launchState: 'runtime_pending_bootstrap', - bootstrapConfirmed: false, + status: 'online', + launchState: 'confirmed_alive', + bootstrapConfirmed: true, hardFailure: false, + lastHeartbeatAt: newerSignalAt, }); - expect(statuses.statuses.bob?.lastHeartbeatAt).not.toBe(newerSignalAt); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -6524,14 +6520,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await svc.getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('clean_success'); + expect(statuses.teamLaunchState).toBe('partial_pending'); expect(statuses.statuses.bob).toMatchObject({ - status: 'online', - launchState: 'confirmed_alive', - bootstrapConfirmed: true, + status: 'waiting', + launchState: 'runtime_pending_bootstrap', + bootstrapConfirmed: false, hardFailure: false, - lastHeartbeatAt: newerSignalAt, }); + expect(statuses.statuses.bob?.lastHeartbeatAt).toBeUndefined(); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -6666,14 +6662,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await new TeamProvisioningService().getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('partial_pending'); + expect(statuses.teamLaunchState).toBe('clean_success'); expect(statuses.statuses.bob).toMatchObject({ - status: 'waiting', - launchState: 'runtime_pending_bootstrap', - bootstrapConfirmed: false, + status: 'online', + launchState: 'confirmed_alive', + bootstrapConfirmed: true, hardFailure: false, + lastHeartbeatAt: signalAt, }); - expect(statuses.statuses.bob?.lastHeartbeatAt).not.toBe(signalAt); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -6807,14 +6803,14 @@ describe('Team agent launch matrix safe e2e', () => { const statuses = await svc.getMemberSpawnStatuses(teamName); expect(statuses.expectedMembers).toEqual(['alice', 'bob']); - expect(statuses.teamLaunchState).toBe('clean_success'); + expect(statuses.teamLaunchState).toBe('partial_pending'); expect(statuses.statuses.bob).toMatchObject({ - status: 'online', - launchState: 'confirmed_alive', - bootstrapConfirmed: true, + status: 'waiting', + launchState: 'runtime_pending_bootstrap', + bootstrapConfirmed: false, hardFailure: false, - lastHeartbeatAt: signalAt, }); + expect(statuses.statuses.bob?.lastHeartbeatAt).toBeUndefined(); expect(statuses.statuses['bob-2']).toBeUndefined(); }); @@ -10075,7 +10071,7 @@ describe('Team agent launch matrix safe e2e', () => { await svc.cancelProvisioning(cancelledRun.runId); await waitForCondition( - () => adapter.stopInputs.filter((input) => input.teamName === cancelledTeamName).length === 2 + () => adapter.stopInputs.filter((input) => input.teamName === cancelledTeamName).length === 1 ); expect(adapter.stopInputs.some((input) => input.teamName === survivingTeamName)).toBe(false); @@ -10195,7 +10191,7 @@ describe('Team agent launch matrix safe e2e', () => { await (svc as any).launchMixedSecondaryLaneIfNeeded(cancelledRun); await (svc as any).launchMixedSecondaryLaneIfNeeded(survivingRun); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(cancelledRun.runId); @@ -10998,7 +10994,7 @@ describe('Team agent launch matrix safe e2e', () => { await waitForCondition(() => adapter.launchInputs.length === 2); svc.stopTeam(teamName); - await waitForCondition(() => adapter.stopInputs.length === 1); + await waitForCondition(() => adapter.stopInputs.length === 2); await waitForCondition(() => !svc.isTeamAlive(teamName)); expect((await readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName)).lanes).toEqual({}); @@ -11594,11 +11590,9 @@ describe('Team agent launch matrix safe e2e', () => { )}\n`, 'utf8' ); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:tom', - state: 'active', }); const inboxDir = path.join(getTeamsBasePath(), teamName, 'inboxes'); await fs.mkdir(inboxDir, { recursive: true }); @@ -12158,11 +12152,9 @@ describe('Team agent launch matrix safe e2e', () => { await writeMixedTeamConfig({ teamName, projectPath }); await writeTeamMeta(teamName, projectPath); await writeMembersMeta(teamName); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const restartedService = new TeamProvisioningService(); @@ -12202,11 +12194,9 @@ describe('Team agent launch matrix safe e2e', () => { state: 'active', diagnostics: ['stale removed bob lane index entry'], }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:tom', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const restartedService = new TeamProvisioningService(); @@ -12257,11 +12247,9 @@ describe('Team agent launch matrix safe e2e', () => { state: 'active', diagnostics: ['stale active lane index while members meta removed bob'], }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:tom', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const restartedService = new TeamProvisioningService(); @@ -12312,11 +12300,9 @@ describe('Team agent launch matrix safe e2e', () => { state: 'active', diagnostics: ['orphan active lane index entry without roster member'], }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:tom', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const restartedService = new TeamProvisioningService(); @@ -12360,11 +12346,9 @@ describe('Team agent launch matrix safe e2e', () => { await writeMixedTeamConfig({ teamName, projectPath }); await writeTeamMeta(teamName, projectPath); await writeMembersMeta(teamName); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const restartedService = new TeamProvisioningService(); @@ -12398,11 +12382,9 @@ describe('Team agent launch matrix safe e2e', () => { await writeMixedTeamConfig({ teamName, projectPath, removedMembers: ['bob'] }); await writeTeamMeta(teamName, projectPath); await writeMembersMeta(teamName, { removedMembers: ['bob'] }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', diagnostics: ['stale active lane index while bob was removed'], }); const adapter = new FakeOpenCodeRuntimeAdapter(); @@ -12451,11 +12433,9 @@ describe('Team agent launch matrix safe e2e', () => { await writeMixedTeamConfig({ teamName, projectPath }); await writeTeamMeta(teamName, projectPath); await writeMembersMeta(teamName); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const svc = new TeamProvisioningService(); @@ -12488,11 +12468,9 @@ describe('Team agent launch matrix safe e2e', () => { const teamName = 'mixed-opencode-direct-message-meta-only-recovered-safe-e2e'; await writeTeamMeta(teamName, projectPath); await writeMembersMeta(teamName, { memberCwd: projectPath }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName, laneId: 'secondary:opencode:bob', - state: 'active', }); const adapter = new FakeOpenCodeRuntimeAdapter(); const svc = new TeamProvisioningService(); @@ -12529,11 +12507,9 @@ describe('Team agent launch matrix safe e2e', () => { await fs.mkdir(degradedProjectPath, { recursive: true }); await writeTeamMeta(activeTeamName, activeProjectPath); await writeMembersMeta(activeTeamName, { memberCwd: activeProjectPath }); - await upsertOpenCodeRuntimeLaneIndexEntry({ - teamsBasePath: getTeamsBasePath(), + await upsertActiveOpenCodeRuntimeLaneForTest({ teamName: activeTeamName, laneId: 'secondary:opencode:bob', - state: 'active', }); await writeTeamMeta(degradedTeamName, degradedProjectPath); await writeMembersMeta(degradedTeamName, { memberCwd: degradedProjectPath }); @@ -15165,7 +15141,7 @@ describe('Team agent launch matrix safe e2e', () => { await (svc as any).launchMixedSecondaryLaneIfNeeded(stoppedRun); await (svc as any).launchMixedSecondaryLaneIfNeeded(survivingRun); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); svc.stopTeam(stoppedTeamName); @@ -15481,7 +15457,7 @@ describe('Team agent launch matrix safe e2e', () => { }); adapter.releaseLaunches(); - await waitForCondition(() => adapter.launchInputs.length === 2); + await waitForCondition(() => adapter.launchInputs.length === 1); const statuses = await svc.getMemberSpawnStatuses(teamName); expect(statuses.teamLaunchState).toBe('partial_failure'); @@ -15677,18 +15653,18 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, run); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(run.runId); - await waitForCondition(() => adapter.stopInputs.length === 2); + await waitForCondition(() => adapter.stopInputs.length === 1); expect(adapter.stopInputs.map((input) => input.laneId).sort()).toEqual([ 'secondary:opencode:bob', ]); expect(svc.isTeamAlive(teamName)).toBe(false); adapter.releaseLaunches(); - await waitForCondition(() => adapter.launchInputs.length === 2); + await waitForCondition(() => adapter.launchInputs.length === 1); const statuses = await svc.getMemberSpawnStatuses(teamName); expect(statuses.teamLaunchState).not.toBe('clean_success'); @@ -15709,11 +15685,11 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, run); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(run.runId); - await waitForCondition(() => adapter.stopInputs.length === 2); + await waitForCondition(() => adapter.stopInputs.length === 1); expect(adapter.stopInputs.map((input) => input.laneId).sort()).toEqual([ 'secondary:opencode:bob', ]); @@ -15808,7 +15784,7 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, cancelledRun); await (svc as any).launchMixedSecondaryLaneIfNeeded(cancelledRun); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(cancelledRun.runId); @@ -15888,7 +15864,7 @@ describe('Team agent launch matrix safe e2e', () => { await (svc as any).launchMixedSecondaryLaneIfNeeded(cancelledRun); await (svc as any).launchMixedSecondaryLaneIfNeeded(survivingRun); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(cancelledRun.runId); @@ -16218,13 +16194,13 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, run); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(run.runId); - await waitForCondition(() => adapter.stopInputs.length === 2); + await waitForCondition(() => adapter.stopInputs.length === 1); adapter.releaseLaunches(); - await waitForCondition(() => adapter.rejectedLaunchCount === 2); + await waitForCondition(() => adapter.rejectedLaunchCount === 1); await expect(readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName)).resolves.toMatchObject( { @@ -16255,13 +16231,13 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, run); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(run.runId); - await waitForCondition(() => adapter.stopInputs.length === 2); + await waitForCondition(() => adapter.stopInputs.length === 1); adapter.releaseLaunches(); - await waitForCondition(() => adapter.rejectedLaunchCount === 2); + await waitForCondition(() => adapter.rejectedLaunchCount === 1); await expect(readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName)).resolves.toMatchObject( { @@ -16306,13 +16282,13 @@ describe('Team agent launch matrix safe e2e', () => { trackLiveRun(svc, run); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await waitForCondition(() => adapter.pendingLaunchInputs.length === 2); + await waitForCondition(() => adapter.pendingLaunchInputs.length === 1); await svc.cancelProvisioning(run.runId); - await waitForCondition(() => adapter.stopInputs.length === 2); + await waitForCondition(() => adapter.stopInputs.length === 1); adapter.releaseLaunches(); - await waitForCondition(() => adapter.rejectedLaunchCount === 2); + await waitForCondition(() => adapter.rejectedLaunchCount === 1); await expect(readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName)).resolves.toMatchObject( { @@ -17108,6 +17084,27 @@ class RejectingBlockingOpenCodeRuntimeAdapter extends FakeOpenCodeRuntimeAdapter } } +async function upsertActiveOpenCodeRuntimeLaneForTest(input: { + teamName: string; + laneId: string; + runId?: string | null; + diagnostics?: string[]; +}): Promise { + await upsertOpenCodeRuntimeLaneIndexEntry({ + teamsBasePath: getTeamsBasePath(), + teamName: input.teamName, + laneId: input.laneId, + state: 'active', + diagnostics: input.diagnostics, + }); + await setOpenCodeRuntimeActiveRunManifest({ + teamsBasePath: getTeamsBasePath(), + teamName: input.teamName, + laneId: input.laneId, + runId: input.runId ?? null, + }); +} + async function waitForCondition(assertion: () => boolean | Promise): Promise { const startedAt = Date.now(); while (Date.now() - startedAt < 5_000) {