diff --git a/src/browser/CdpTargetSession.ts b/src/browser/CdpTargetSession.ts index 4b140e2eda..0c2b5b24f2 100644 --- a/src/browser/CdpTargetSession.ts +++ b/src/browser/CdpTargetSession.ts @@ -51,18 +51,22 @@ export const closeCdpTargetSession = async ( ): Promise => { const { connection, sessionId } = targetSession; const cleanup = new AbortController(); + const domainSignal = + signal === undefined + ? cleanup.signal + : AbortSignal.any([cleanup.signal, signal]); const timeout = setTimeout(() => cleanup.abort(), 1_000); timeout.unref(); try { if (signal?.aborted !== true) { for (const domain of enabledDomains) { - if (cleanup.signal.aborted) break; + if (domainSignal.aborted) break; try { await connection.send( `${domain}.disable`, {}, sessionId, - cleanup.signal, + domainSignal, ); } catch { // Best-effort domain cleanup; transport close is the definitive boundary. diff --git a/tests/boundary/browser/cdpCleanupCancellation.test.ts b/tests/boundary/browser/cdpCleanupCancellation.test.ts index e9508297b5..0d7075ab6b 100644 --- a/tests/boundary/browser/cdpCleanupCancellation.test.ts +++ b/tests/boundary/browser/cdpCleanupCancellation.test.ts @@ -122,10 +122,8 @@ it("closes an Electron inspection session when cleanup is cancelled", async () = operation: "inspect_electron_page", }, }); - expect(browser.commands.map(({ method }) => method)).toContain( - "Page.disable", - ); const methods = browser.commands.map(({ method }) => method); + expect(methods).not.toContain("Page.disable"); expect(methods).toContain("Target.detachFromTarget"); expect(methods).not.toContain("Target.closeTarget"); expect(methods).not.toContain("Browser.close"); diff --git a/tests/boundary/evm/evmoleInterfaceProvider.test.ts b/tests/boundary/evm/evmoleInterfaceProvider.test.ts index 5d1305d21b..708a6fc62d 100644 --- a/tests/boundary/evm/evmoleInterfaceProvider.test.ts +++ b/tests/boundary/evm/evmoleInterfaceProvider.test.ts @@ -7,6 +7,7 @@ import { projectAnalysisError } from "../../../src/domain/analysisErrorProjectio import { evmInterfaceSchema } from "../../../src/domain/evm/evmInterface.js"; import { EvmoleInterfaceProvider } from "../../../src/evm/EvmoleInterfaceProvider.js"; import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js"; +import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js"; import { waitForProviderProcessReady } from "../../fixtures/providerProcess.js"; import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js"; import { @@ -128,6 +129,7 @@ it.runIf(!unsupportedHost)( async () => { const { path } = await fixture(); let ownedPath = ""; + let failCleanup = true; const provider = new EvmoleInterfaceProvider({}, async (spawn) => { ownedPath = spawn.cwd ?? ""; const launched = await spawnOwnedProviderProcess({ @@ -135,24 +137,59 @@ it.runIf(!unsupportedHost)( command: process.execPath, arguments: ["-e", 'process.stdout.write("x".repeat(1048577))'], }); + const cleanupOwned = + launched.cleanup ?? + (() => cleanupOwnedProcessGroup(launched.ownership)); return { ...launched, cleanup: async () => { - await launched.cleanup?.(); - throw new Error("injected post-cleanup reporting failure"); + const cleaned = await cleanupOwned(); + if (failCleanup) + throw new Error("injected post-cleanup reporting failure"); + return cleaned; }, }; }); + onTestFinished(async () => { + failCleanup = false; + await provider.close(); + }); const result = await provider.inspect({ path, encoding: "hex" }); if (result.ok) throw new Error("Expected compounded lifecycle failure"); - expect(result.error).toMatchObject({ - cleanupIncomplete: true, - diagnostics: { - reason: "injected post-cleanup reporting failure", - previous_error: { failure_kind: "output-limit" }, - captured_output: { truncated: true, stderr: "" }, + expect(result.error.cleanupIncomplete).toBe(true); + // Inspect the retained error chain without diffing a megabyte of stdout. + const output = z.object({ truncated: z.boolean(), stderr: z.string() }); + const cleanup = z + .object({ + reason: z.string(), + previous_error: z.object({ + code: z.string(), + details: z.object({ + diagnostics: z.object({ + reason: z.string(), + previous_error: z.object({ failure_kind: z.string() }), + captured_output: output, + }), + }), + }), + }) + .parse(projectAnalysisError(result.error).details?.["diagnostics"]); + expect(cleanup).toEqual({ + reason: "Owned EVM worker cleanup could not be confirmed", + previous_error: { + code: "cleanup_incomplete", + details: { + diagnostics: { + reason: "injected post-cleanup reporting failure", + previous_error: { failure_kind: "output-limit" }, + captured_output: { truncated: true, stderr: "" }, + }, + }, }, }); + await access(ownedPath); + failCleanup = false; + await provider.close(); await expect(access(ownedPath)).rejects.toMatchObject({ code: "ENOENT" }); }, ); diff --git a/tests/boundary/filesystem/qwenClientConfiguration.test.ts b/tests/boundary/filesystem/qwenClientConfiguration.test.ts index 0fae29640f..55e8d0ab39 100644 --- a/tests/boundary/filesystem/qwenClientConfiguration.test.ts +++ b/tests/boundary/filesystem/qwenClientConfiguration.test.ts @@ -7,6 +7,7 @@ import { parse as parseJsonc } from "jsonc-parser"; import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js"; import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js"; import { systemSetupHost } from "../../../src/application/SetupHost.js"; +import { MANAGED_SKILL_FILES } from "../../../src/application/SetupSkill.js"; import { supportedClients } from "../../../src/application/SupportedClients.js"; import { systemUninstallHost } from "../../../src/application/Uninstall.js"; import { PRODUCT_IDENTITY } from "../../../src/identity.js"; @@ -151,6 +152,7 @@ describe("Qwen Code profile setup", () => { }); expect(await setup.installSkill([client.name])).toBe("installed"); const skill = join(home, ".agents", "skills", PRODUCT_IDENTITY.skillName); + await writeFile(join(skill, "user-notes.txt"), "user-authored"); expect(await readFile(join(skill, "SKILL.md"), "utf8")).toContain( "reverse-engineer-anything", ); @@ -173,7 +175,13 @@ describe("Qwen Code profile setup", () => { expect(registered).toEqual(client); expect((await uninstall.removeClient(client)).status).toBe("removed"); expect((await uninstall.removeSkill()).status).toBe("removed"); - await expect(access(skill)).rejects.toMatchObject({ code: "ENOENT" }); + for (const relativePath of MANAGED_SKILL_FILES) + await expect(access(join(skill, relativePath))).rejects.toMatchObject({ + code: "ENOENT", + }); + expect(await readFile(join(skill, "user-notes.txt"), "utf8")).toBe( + "user-authored", + ); if (customHome) await expect(access(join(home, ".qwen"))).rejects.toMatchObject({ code: "ENOENT", diff --git a/tests/boundary/firmware/firmwareIntegration.test.ts b/tests/boundary/firmware/firmwareIntegration.test.ts index 3924f37aba..163ee00e14 100644 --- a/tests/boundary/firmware/firmwareIntegration.test.ts +++ b/tests/boundary/firmware/firmwareIntegration.test.ts @@ -324,7 +324,7 @@ it("cancels a waiting request promptly while keeping later launches behind the a await assertFirmwareCleanup(fixture.launches); }); -it("retains an uncertain workspace and lets the next operation create a new root", async () => { +it("retains an uncertain worker and prevents another launch", async () => { const fixture = await firmwareFixture("cleanup-failure"); const first = await fixture.service.execute("inspect_firmware_regions", { path: fixture.path, @@ -341,12 +341,8 @@ it("retains an uncertain workspace and lets the next operation create a new root path: fixture.path, }); expect(next).toMatchObject({ ok: false, error: { cleanupIncomplete: true } }); - expect(fixture.launches).toHaveLength(2); - const nextWorkspace = fixture.launches[1]?.cwd; - expect(nextWorkspace).toBeDefined(); - expect(nextWorkspace).not.toBe(workspace); + expect(fixture.launches).toHaveLength(1); await access(workspace); - if (nextWorkspace !== undefined) await access(nextWorkspace); }); it("accepts another build on the verified release line and reports it", async () => {