// @vitest-environment node import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; import type { AgentBrowserModule } from '../../electron/agent-browser'; import { createCodingComposition } from '../../electron/api/coding-composition'; import { PiAgentServerProcess } from '../../electron/coding-runtime/pi/agent-server-process'; import { createMemoryCodingProjectStorage } from '../../electron/coding-projects/project-store'; const roots: string[] = []; afterEach(async () => { vi.restoreAllMocks(); await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); describe('coding composition background sleep', () => { it('closes the shared browser before an idle background runtime sleeps', async () => { const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-sleep-browser-project-')); const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-sleep-browser-user-')); roots.push(projectPath, userDataDir); const close = vi.fn(async () => undefined); const composition = createCodingComposition({ storage: createMemoryCodingProjectStorage(), browser: { close } as unknown as AgentBrowserModule, paths: { executablePath: process.execPath, cliPath: path.join(projectPath, 'unused-cli.js'), serverPath: path.join(projectPath, 'unused-server.mjs'), userDataDir, bundledSkillsDir: path.resolve('resources/coding-skills'), }, }); const stopAgentServer = vi.spyOn(PiAgentServerProcess.prototype, 'stop') .mockResolvedValue(undefined); try { await composition.sleep('background_sleep'); expect(close).toHaveBeenCalledTimes(1); expect(stopAgentServer).toHaveBeenCalledTimes(1); } finally { stopAgentServer.mockRestore(); await composition.shutdown(); } }); it('does not stop the shared Agent Server when work starts during worker cleanup', async () => { const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-sleep-race-project-')); const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-sleep-race-user-')); roots.push(projectPath, userDataDir); const composition = createCodingComposition({ storage: createMemoryCodingProjectStorage(), browser: { close: vi.fn(async () => undefined) } as unknown as AgentBrowserModule, paths: { executablePath: process.execPath, cliPath: path.join(projectPath, 'unused-cli.js'), serverPath: path.join(projectPath, 'unused-server.mjs'), userDataDir, bundledSkillsDir: path.resolve('resources/coding-skills'), }, }); const stopAgentServer = vi.spyOn(PiAgentServerProcess.prototype, 'stop') .mockResolvedValue(undefined); let activeWork = false; const hasActiveWork = vi.spyOn(composition.runtime, 'hasActiveWork') .mockImplementation(() => activeWork); const getDiagnostics = vi.spyOn(composition.runtime, 'getDiagnostics') .mockReturnValue({ workers: [{ conversationId: 'conversation-a' }], } as ReturnType); const dispose = vi.spyOn(composition.runtime, 'dispose').mockImplementation(async () => { activeWork = true; }); try { await composition.sleep('background_sleep'); expect(dispose).toHaveBeenCalledWith('conversation-a', 'background_sleep'); expect(hasActiveWork).toHaveBeenCalledTimes(2); expect(stopAgentServer).not.toHaveBeenCalled(); } finally { dispose.mockRestore(); getDiagnostics.mockRestore(); hasActiveWork.mockRestore(); stopAgentServer.mockRestore(); await composition.shutdown(); } }); });