import * as fs from 'fs/promises'; import * as os from 'os'; import * as path from 'path'; import { Worker } from 'worker_threads'; import { afterEach, describe, expect, it } from 'vitest'; import { createPersistedLaunchSummaryProjection } from '../../../../src/main/services/team/TeamLaunchSummaryProjection'; interface WorkerResponse { id: string; ok: boolean; result?: unknown; error?: string; } function getWorkerPath(): string { return path.join(process.cwd(), 'dist-electron', 'main', 'team-fs-worker.cjs'); } function callListTeams(worker: Worker, teamsDir: string): Promise { const requestId = `req-${Date.now()}`; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { cleanup(); reject(new Error('team-fs-worker test timed out')); }, 10_000); const cleanup = () => { clearTimeout(timeout); worker.off('message', onMessage); worker.off('error', onError); }; const onError = (error: Error) => { cleanup(); reject(error); }; const onMessage = (message: WorkerResponse) => { if (!message || message.id !== requestId) { return; } cleanup(); if (!message.ok) { reject(new Error(message.error || 'team-fs-worker returned an unknown error')); return; } resolve(Array.isArray(message.result) ? message.result : []); }; worker.on('message', onMessage); worker.on('error', onError); worker.postMessage({ id: requestId, op: 'listTeams', payload: { teamsDir, largeConfigBytes: 8 * 1024, configHeadBytes: 4 * 1024, maxConfigBytes: 256 * 1024, maxConfigReadMs: 5_000, maxMembersMetaBytes: 256 * 1024, maxSessionHistoryInSummary: 10, maxProjectPathHistoryInSummary: 10, concurrency: 2, }, }); }); } describe('team-fs-worker integration', () => { let tempDir = ''; afterEach(async () => { if (tempDir) { await fs.rm(tempDir, { recursive: true, force: true }); tempDir = ''; } }); it('uses launch-summary.json when launch-state.json is too large for mixed-team summaries', async () => { const workerPath = getWorkerPath(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'team-fs-worker-')); const teamName = 'mixed-worker-team'; const teamDir = path.join(tempDir, teamName); await fs.mkdir(teamDir, { recursive: true }); await fs.writeFile( path.join(teamDir, 'config.json'), JSON.stringify({ name: 'Mixed Worker Team', members: [{ name: 'team-lead', agentType: 'team-lead' }], }), 'utf8' ); await fs.writeFile(path.join(teamDir, 'launch-state.json'), 'x'.repeat(40 * 1024), 'utf8'); await fs.writeFile( path.join(teamDir, 'launch-summary.json'), JSON.stringify( createPersistedLaunchSummaryProjection({ version: 2, teamName, updatedAt: '2026-04-22T12:00:00.000Z', launchPhase: 'finished', 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', }, bob: { name: 'bob', providerId: 'opencode', laneId: 'secondary:opencode:bob', laneKind: 'secondary', laneOwnerProviderId: 'opencode', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: true, hardFailureReason: 'Side lane failed', lastEvaluatedAt: '2026-04-22T12:00:00.000Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never), null, 2 ), 'utf8' ); const worker = new Worker(workerPath); try { const teams = (await callListTeams(worker, tempDir)) as Array>; expect(teams).toHaveLength(1); expect(teams[0]).toMatchObject({ teamName, displayName: 'Mixed Worker Team', partialLaunchFailure: true, expectedMemberCount: 2, confirmedMemberCount: 1, missingMembers: ['bob'], teamLaunchState: 'partial_failure', confirmedCount: 1, pendingCount: 0, failedCount: 1, }); } finally { await worker.terminate(); } }); it('ignores removed and lead members when draft-team worker summary counts members', async () => { const workerPath = getWorkerPath(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'team-fs-worker-')); const teamName = 'draft-worker-team'; const teamDir = path.join(tempDir, teamName); await fs.mkdir(teamDir, { recursive: true }); await fs.writeFile( path.join(teamDir, 'team.meta.json'), JSON.stringify({ version: 1, cwd: tempDir, displayName: 'Draft Worker Team', createdAt: Date.parse('2026-04-22T12:00:00.000Z'), }), 'utf8' ); await fs.writeFile( path.join(teamDir, 'members.meta.json'), JSON.stringify({ version: 1, members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'alice', removedAt: Date.parse('2026-04-22T12:01:00.000Z') }, { name: 'bob', role: 'developer' }, ], }), 'utf8' ); const worker = new Worker(workerPath); try { const teams = (await callListTeams(worker, tempDir)) as Array>; expect(teams).toHaveLength(1); expect(teams[0]).toMatchObject({ teamName, displayName: 'Draft Worker Team', memberCount: 1, }); } finally { await worker.terminate(); } }); });