fix(runner): resolve exported ACP package manifests

This commit is contained in:
Dotta 2026-09-03 02:52:10 -05:00
parent d8ab1f0f3b
commit 40830f61e8
2 changed files with 57 additions and 2 deletions

View File

@ -82,8 +82,13 @@ describe("ACPX installation integrity", () => {
await mkdir(nestedDependencyDirectory, { recursive: true });
await writeFile(
nestedDependencyPackageJson,
JSON.stringify({ name: "qualified-dependency", version: "1.0.0" }),
JSON.stringify({
name: "qualified-dependency",
version: "1.0.0",
exports: "./index.js",
}),
);
await writeFile(join(nestedDependencyDirectory, "index.js"), "export {};");
expect(
createAcpxPackageJsonResolver(root)(
"qualified-dependency",
@ -549,6 +554,18 @@ describe("ACPX installation integrity", () => {
);
});
it.runIf(process.platform === "linux" && process.arch === "x64")(
"resolves and pins the installed Claude ACP dependency graph",
async () => {
const profile = resolveQualifiedAcpxProfile("claude", "claude-sonnet-5");
const installation = await verifyQualifiedAcpxInstallation(profile);
expect(installation.agentServerPackageJsonPath).toContain(
"/@agentclientprotocol/claude-agent-acp/package.json",
);
await (await installation.openCommand()).close();
},
);
it.runIf(process.platform === "linux" && process.arch === "x64")(
"resolves and pins the qualified Codex native runtime through its transitive packages",
async () => {

View File

@ -295,7 +295,7 @@ export function createAcpxPackageJsonResolver(
);
}
const packageJsonPath = realpathSync(
createRequire(canonicalIssuer).resolve(`${packageName}/package.json`),
resolvePackageJsonFromIssuer(packageName, canonicalIssuer),
);
if (!pathIsInside(canonicalNodeModules, packageJsonPath)) {
throw new Error(
@ -306,6 +306,44 @@ export function createAcpxPackageJsonResolver(
};
}
function resolvePackageJsonFromIssuer(
packageName: string,
issuerPackageJsonPath: string,
): string {
const issuerRequire = createRequire(issuerPackageJsonPath);
try {
return issuerRequire.resolve(`${packageName}/package.json`);
} catch (error) {
if (
(error as NodeJS.ErrnoException).code !== "ERR_PACKAGE_PATH_NOT_EXPORTED"
)
throw error;
}
const packageSegments = packageName.split("/");
if (
packageSegments.length < 1 ||
packageSegments.length > 2 ||
packageSegments.some((segment) => segment.length === 0)
) {
throw new Error(`ACPX provider package name is invalid: ${packageName}`);
}
let directory = dirname(realpathSync(issuerRequire.resolve(packageName)));
for (let count = 0; count < MAX_DEPENDENCY_ANCESTORS; count += 1) {
const matchesPackage =
basename(directory) === packageSegments.at(-1) &&
(packageSegments.length === 1 ||
basename(dirname(directory)) === packageSegments[0]);
if (matchesPackage) return resolve(directory, "package.json");
const parent = dirname(directory);
if (parent === directory) break;
directory = parent;
}
throw new Error(
`ACPX provider package manifest could not be located for ${packageName}`,
);
}
function pathIsInside(root: string, candidate: string): boolean {
const candidateRelativePath = relative(root, candidate);
return (