diff --git a/src/main/index.ts b/src/main/index.ts index 773d7443..196494b0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1431,6 +1431,7 @@ function wireFileWatcherEvents(context: ServiceContext): void { teamChangeCleanup = () => { context.fileWatcher.off('team-change', teamChangeHandler); + setAliveTeamsProvider(null); setTeamWatchScopeChangeListener(null); context.fileWatcher.setTeamWatchScopeProvider(null); reconcileScheduler?.dispose(); diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 7778199f..17955bae 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -1939,6 +1939,9 @@ async function handleCreateTeam( return wrapTeamHandler('create', async () => { addMainBreadcrumb('team', 'create', { teamName: validation.value.teamName }); launchIoGovernor?.noteLaunchIntent(validation.value.teamName, 'create'); + // Keep this team's team-root/task artifacts file-watched while createTeam writes + // its initial config, tasks, inboxes, and launch state. + markTeamEngaged(validation.value.teamName); try { const response = await getTeamProvisioningService().createTeam( validation.value, @@ -2104,6 +2107,9 @@ async function handleLaunchTeam( return wrapTeamHandler('create', async () => { launchIoGovernor?.noteLaunchIntent(tn, 'draft-launch'); + // Draft launch runs through createTeam, so it needs the same immediate watch scope + // as a normal launch before startup files begin changing. + markTeamEngaged(tn); try { const response = await getTeamProvisioningService().createTeam( createRequest, diff --git a/test/main/ipc/teams.test.ts b/test/main/ipc/teams.test.ts index 3b39d005..ca43152d 100644 --- a/test/main/ipc/teams.test.ts +++ b/test/main/ipc/teams.test.ts @@ -101,6 +101,10 @@ import { removeTeamHandlers, } from '../../../src/main/ipc/teams'; import { ConfigManager } from '../../../src/main/services/infrastructure/ConfigManager'; +import { + computeTeamWatchScope, + resetTeamWatchScopeForTests, +} from '../../../src/main/services/infrastructure/teamWatchScope'; import { LaunchIoGovernor } from '../../../src/main/services/team/LaunchIoGovernor'; import { getAppDataPath } from '../../../src/main/utils/pathDecoder'; import { @@ -355,6 +359,7 @@ describe('ipc teams handlers', () => { }; beforeEach(() => { + resetTeamWatchScopeForTests(); handlers.clear(); vi.clearAllMocks(); service.listTeams.mockReset(); @@ -427,6 +432,7 @@ describe('ipc teams handlers', () => { }); afterEach(() => { + resetTeamWatchScopeForTests(); launchIoGovernor.clearForTests(); vi.useRealTimers(); setClaudeBasePathOverride(null); @@ -1314,6 +1320,23 @@ describe('ipc teams handlers', () => { }); }); + it('marks created teams engaged before provisioning writes startup artifacts', async () => { + const createTeam = 'created-watch-scope'; + provisioningService.createTeam.mockImplementationOnce(async () => { + expect(computeTeamWatchScope().has(createTeam)).toBe(true); + return { runId: 'run-created-watch-scope' }; + }); + + const result = (await handlers.get(TEAM_CREATE)!({ sender: { send: vi.fn() } } as never, { + teamName: createTeam, + members: [{ name: 'alice' }], + cwd: os.tmpdir(), + })) as { success: boolean }; + + expect(result.success).toBe(true); + expect(computeTeamWatchScope().has(createTeam)).toBe(true); + }); + it('returns cached TEAM_LIST data under active launch pressure without starting another scan', async () => { const first = (await handlers.get(TEAM_LIST)!({} as never)) as { success: boolean; @@ -4378,6 +4401,7 @@ describe('ipc teams handlers', () => { })) as { success: boolean }; expect(result.success).toBe(true); + expect(computeTeamWatchScope().has('draft-team')).toBe(true); expect(provisioningService.launchTeam).not.toHaveBeenCalled(); expect(provisioningService.createTeam).toHaveBeenCalledWith( {