From 48d080e97167fd76b96ba7affcf4de3217ff09a1 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:13:57 -0700 Subject: [PATCH] feat(acp): a reclamation primitive removes all but the kept managed runtime release (#28507) * feat(acp): ACP agents can run with a private per-process temp folder * feat(acp): a reclamation primitive removes all but the kept managed runtime release The installer calls it while it owns the runtime's installs and launches; it answers the entries it could not remove so that caller can report them. Nothing calls it from adapter construction. --- .../node-runtime/managed-runtime-release.test.ts | 13 +++++++++++++ src/main/node-runtime/managed-runtime-release.ts | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/node-runtime/managed-runtime-release.test.ts diff --git a/src/main/node-runtime/managed-runtime-release.test.ts b/src/main/node-runtime/managed-runtime-release.test.ts new file mode 100644 index 00000000000..300093a4cac --- /dev/null +++ b/src/main/node-runtime/managed-runtime-release.test.ts @@ -0,0 +1,13 @@ +import { mkdir, mkdtemp, readdir, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { expect, it } from 'vitest' +import { reclaimRuntimeReleases } from './managed-runtime-release' + +it('removes every release and leftover but the kept one', async () => { + const root = await mkdtemp(join(tmpdir(), 'orca-runtime-reclaim-')) + await Promise.all(['1.0', '0.9', '.download-1-left'].map((name) => mkdir(join(root, name)))) + expect(await reclaimRuntimeReleases(root, '1.0')).toEqual([]) + expect(await readdir(root)).toEqual(['1.0']) + await rm(root, { recursive: true, force: true }) +}) diff --git a/src/main/node-runtime/managed-runtime-release.ts b/src/main/node-runtime/managed-runtime-release.ts index f16ade04e42..446a7fc41e1 100644 --- a/src/main/node-runtime/managed-runtime-release.ts +++ b/src/main/node-runtime/managed-runtime-release.ts @@ -3,6 +3,7 @@ // cheap enough for every launch: a full hash is only ever taken by an install. import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs' +import { readdir, rm } from 'node:fs/promises' import { basename, dirname, join } from 'node:path' import { z } from 'zod' import { isDefinitiveAbsence } from '../../shared/definitive-filesystem-absence' @@ -107,6 +108,20 @@ export function copyBesidePinnedMembers( } } +/** Removes everything under `root` but the release named `keep`: called only while the caller + * owns every install and launch of this runtime, so nothing else runs from or publishes into it. + * Answers the entries it could not remove. */ +export async function reclaimRuntimeReleases(root: string, keep: string | null): Promise { + const names = await readdir(root).catch((error: NodeJS.ErrnoException) => + error.code === 'ENOENT' ? [] : Promise.reject(error) + ) + const stale = names.filter((name) => name !== keep) + const results = await Promise.allSettled( + stale.map((name) => rm(join(root, name), { recursive: true, force: true })) + ) + return stale.filter((_, index) => results[index]?.status === 'rejected') +} + function readRelease(folder: string, version: string): ReleaseRead { try { const receiptPath = join(folder, RUNTIME_RELEASE_RECEIPT)