Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/agent-runtime/src/native-pi-session.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { basename, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
import { createAssistantMessageEventStream, type AssistantMessage } from "@earendil-works/pi-ai";
Expand Down Expand Up @@ -781,7 +781,7 @@ describe("native fork children", () => {
expect(failure?.message).not.toContain(f.group);
expect(existsSync(foreignPath)).toBe(true);
expect(readFileSync(foreignPath, "utf8")).toContain("collision");
expect(groupEntries(f.group).sort()).toEqual(before.concat([foreignPath.split("/").at(-1)!]).sort());
expect(groupEntries(f.group).sort()).toEqual(before.concat([basename(foreignPath)]).sort());
expect(readFileSync(f.file, "utf8")).toBe(parentBytes);
} finally { service.disposeAll(); }
} finally {
Expand Down
21 changes: 20 additions & 1 deletion scripts/check-pi-dependencies.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ import { fileURLToPath } from "node:url";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const targetVersion = "1.0.1";
// Proof chain that pnpm installed the patched instance: the root lockfile's
// patchedDependencies section maps `name@version` to a 64-hex patch hash, and
// the virtual-store lockfile embeds that hash in the installed snapshot's
// `version: <v>(patch_hash=<hash>)` line. realpath-based matching cannot work
// on Windows, where pnpm shortens `.pnpm` directory names (long-path limit)
// and the literal `patch_hash=` segment disappears from resolved paths
// (#1361).
const rootLockfile = readFileSync(join(root, "pnpm-lock.yaml"), "utf8");
const virtualStoreLockfile = readFileSync(join(root, "node_modules/.pnpm/lock.yaml"), "utf8");

function installedPatchHash(packageName) {
const declared = rootLockfile.match(
new RegExp(`'?${packageName.replace("/", "/")}@${targetVersion}'?:[ \\t]*([a-f0-9]{64})`),
)?.[1];
if (!declared) return undefined;
return virtualStoreLockfile.includes(`(patch_hash=${declared}`)
? declared
: undefined;
}

function readJson(path) {
return JSON.parse(readFileSync(join(root, path), "utf8"));
Expand All @@ -25,7 +44,7 @@ function assertInstalled(packagePath, expectedName, { patched = false } = {}) {
if (manifest.name !== expectedName || manifest.version !== targetVersion) {
throw new Error(`${packagePath} resolves to ${manifest.name}@${manifest.version}, expected ${expectedName}@${targetVersion}`);
}
if (patched && !resolved.includes("patch_hash=")) {
if (patched && !installedPatchHash(expectedName)) {
throw new Error(`${packagePath} does not resolve to pnpm's patched package instance`);
}
}
Expand Down
24 changes: 22 additions & 2 deletions scripts/check-pi-patches.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ const entries = [
];
const workspace = readFileSync(join(root, "pnpm-workspace.yaml"), "utf8");
const lockfile = readFileSync(join(root, "pnpm-lock.yaml"), "utf8");
// Proof chain that pnpm installed the patched instance: the root lockfile's
// patchedDependencies section maps `name@version` to a 64-hex patch hash, and
// the virtual-store lockfile embeds that hash in the installed snapshot's
// `version: <v>(patch_hash=<hash>)` line. realpath-based matching cannot work
// on Windows, where pnpm shortens `.pnpm` directory names (long-path limit)
// and the literal `patch_hash=` segment disappears from resolved paths
// (#1361).
const virtualStoreLockfile = readFileSync(join(root, "node_modules/.pnpm/lock.yaml"), "utf8");

for (const entry of entries) {
if (!existsSync(join(root, entry.patch))) throw new Error(`Missing patch: ${entry.patch}`);
Expand All @@ -40,8 +48,20 @@ for (const entry of entries) {
if (!workspace.includes(workspaceMapping)) throw new Error(`pnpm-workspace.yaml does not map ${entry.name} to ${entry.patch}`);
const packagePath = join(root, entry.packagePath);
const resolved = realpathSync(packagePath);
const patchHash = resolved.match(/patch_hash=([a-f0-9]+)/)?.[1];
if (!patchHash || !lockfile.includes(`${entry.name}@${targetVersion}(patch_hash=${patchHash}`)) {
// The installed manifest pins the exact patched version; the hash itself
// comes from the root lockfile's patchedDependencies entry and must be
// embedded in the installed virtual-store snapshot (#1361).
const installedVersion = JSON.parse(readFileSync(join(resolved, "package.json"), "utf8")).version;
if (installedVersion !== targetVersion) {
throw new Error(`${entry.packagePath} resolves to ${entry.name}@${installedVersion}, expected ${targetVersion}`);
}
const declaredHash = lockfile.match(
new RegExp(`'?${entry.name}@${targetVersion}'?:[ \\t]*([a-f0-9]{64})`),
)?.[1];
if (!declaredHash) {
throw new Error(`pnpm-lock.yaml has no patchedDependencies hash for ${entry.name}@${targetVersion}`);
}
if (!virtualStoreLockfile.includes(`(patch_hash=${declaredHash}`)) {
throw new Error(`${entry.name}@${targetVersion} installed patch hash is absent from pnpm-lock.yaml`);
}
}
Expand Down
Loading