From 0643a64f0d3f9f06265e6fd2b9516e8221b8bbe1 Mon Sep 17 00:00:00 2001 From: Phil Larson Date: Sun, 26 Jul 2026 07:34:50 -0700 Subject: [PATCH] fix(desktop): pin complete Pi runtime closure (#2439) ## Summary - pin `pi-agent-core`, `pi-ai`, `pi-coding-agent`, and `pi-tui` to one exact 0.82.0 workspace override set - extend the Pi version policy guard to reject missing, ranged, or mismatched desktop runtime closure overrides - add a patch changeset for the legacy desktop packaging fix ## Test plan - `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs` (5 passed) - `node scripts/check-pi-versions-pinned.mjs` - `corepack pnpm check:changesets --strict` - focused engine fixtures: 4 files / 47 tests passed - GitHub: Desktop packaging, Lint, Typecheck, Build, Gate, and Greptile Review passed --- .changeset/desktop-pi-runtime-closure.md | 7 ++ pnpm-lock.yaml | 2 + pnpm-workspace.yaml | 5 ++ .../check-pi-versions-pinned.test.mjs | 30 ++++++++ scripts/check-pi-versions-pinned.mjs | 70 ++++++++++++++++++- 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 .changeset/desktop-pi-runtime-closure.md diff --git a/.changeset/desktop-pi-runtime-closure.md b/.changeset/desktop-pi-runtime-closure.md new file mode 100644 index 0000000000..3b57110298 --- /dev/null +++ b/.changeset/desktop-pi-runtime-closure.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep legacy desktop builds on one packageable Pi runtime dependency closure. +category: fix +dev: Pins pi-agent-core and pi-tui with the 0.82.0 Pi runtime pair and extends the dependency policy guard to workspace overrides. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 783205c06a..01aa080380 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,10 @@ settings: overrides: '@types/node': ^25.5.2 protobufjs: ^7.5.8 + '@earendil-works/pi-agent-core': 0.82.0 '@earendil-works/pi-ai': 0.82.0 '@earendil-works/pi-coding-agent': 0.82.0 + '@earendil-works/pi-tui': 0.82.0 importers: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1b29762219..48b33ae7b4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,8 +25,13 @@ overrides: # to match guarded package manifests (cli/core/engine/dashboard/pi-claude-cli). # FNXC:ModelCatalog 2026-07-24-12:00: FN-8564 advances the matched runtime pair # to 0.82.0. Wildcard consumers must resolve the exact guarded Pi API surface. + # FNXC:DesktopPackaging 2026-07-25-17:15: Legacy `pnpm deploy` resolves pi-coding-agent's + # transitive ranges without the workspace lockfile. Pin the complete Pi runtime closure to one + # exact version so new agent-core or tui patches cannot split the packageable desktop tree. + '@earendil-works/pi-agent-core': 0.82.0 '@earendil-works/pi-ai': 0.82.0 '@earendil-works/pi-coding-agent': 0.82.0 + '@earendil-works/pi-tui': 0.82.0 # FNXC:DesktopEmbeddedPostgres 2026-07-14-09:30: # Desktop release jobs cross-build Intel and ARM64 artifacts on one host. Install # optional native payloads for both CPU families on the current OS so each diff --git a/scripts/__tests__/check-pi-versions-pinned.test.mjs b/scripts/__tests__/check-pi-versions-pinned.test.mjs index f18f95299d..7d68226d81 100644 --- a/scripts/__tests__/check-pi-versions-pinned.test.mjs +++ b/scripts/__tests__/check-pi-versions-pinned.test.mjs @@ -1,8 +1,10 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { + PI_RUNTIME_PACKAGES, scanTrackedManifests, validateManifestSet, + validateWorkspaceOverrides, } from "../check-pi-versions-pinned.mjs"; const pinnedManifest = { @@ -42,4 +44,32 @@ describe("check-pi-versions-pinned", () => { it("accepts a clean exact matched pair", () => { assert.deepEqual(validate(pinnedManifest), []); }); + + /* + FNXC:DesktopPackaging 2026-07-25-17:15: + Legacy desktop deploy resolves transitive pi-mono ranges without the workspace + lockfile. Guard every Pi runtime package in the staged closure, not only the + direct pi-ai and pi-coding-agent declarations, so a newly published patch + cannot split agent-core or tui from the exact runtime version. + */ + it("requires one exact workspace override for every staged Pi runtime package", () => { + const coherentOverrides = Object.fromEntries(PI_RUNTIME_PACKAGES.map((packageName) => [packageName, "0.82.0"])); + + assert.deepEqual(validateWorkspaceOverrides(coherentOverrides), []); + assert.equal( + validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-tui": undefined }) + .some((violation) => violation.includes("pi-tui") && violation.includes("must be pinned")), + true, + ); + assert.equal( + validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-agent-core": "^0.82.0" }) + .some((violation) => violation.includes("pi-agent-core") && violation.includes("exact semver")), + true, + ); + assert.equal( + validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-tui": "0.82.1" }) + .some((violation) => violation.includes("one exact version")), + true, + ); + }); }); diff --git a/scripts/check-pi-versions-pinned.mjs b/scripts/check-pi-versions-pinned.mjs index 2e110d1ce8..a003d60056 100644 --- a/scripts/check-pi-versions-pinned.mjs +++ b/scripts/check-pi-versions-pinned.mjs @@ -9,12 +9,27 @@ manifest declaration must be an exact semver and all declarations must agree. import { readFileSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; +import { parse as parseYaml } from "yaml"; export const PI_DEPENDENCIES = [ "@earendil-works/pi-ai", "@earendil-works/pi-coding-agent", ]; +/* +FNXC:DesktopPackaging 2026-07-25-17:15: +The legacy desktop deploy resolves pi-coding-agent's transitive pi-mono ranges +without the workspace lockfile. Keep every Pi package in that staged runtime +closure on one exact override so a new agent-core or tui patch cannot be hoisted +beside older direct ai/coding-agent dependencies. +*/ +export const PI_RUNTIME_PACKAGES = [ + "@earendil-works/pi-agent-core", + "@earendil-works/pi-ai", + "@earendil-works/pi-coding-agent", + "@earendil-works/pi-tui", +]; + export const GUARDED_MANIFESTS = [ "packages/cli/package.json", "packages/core/package.json", @@ -30,6 +45,31 @@ export function isExactSemver(version) { return typeof version === "string" && EXACT_SEMVER.test(version); } +export function validateWorkspaceOverrides(overrides = {}) { + const violations = []; + const versions = []; + + for (const packageName of PI_RUNTIME_PACKAGES) { + const version = overrides?.[packageName]; + if (version === undefined) { + violations.push(`pnpm-workspace.yaml: overrides.${packageName} must be pinned for the staged desktop closure`); + continue; + } + if (!isExactSemver(version)) { + violations.push(`pnpm-workspace.yaml: overrides.${packageName} must be an exact semver, found ${JSON.stringify(version)}`); + continue; + } + versions.push(version); + } + + const uniqueVersions = [...new Set(versions)]; + if (uniqueVersions.length > 1) { + violations.push(`${PI_RUNTIME_PACKAGES.join(", ")} must use one exact version in pnpm-workspace.yaml overrides; found ${uniqueVersions.join(", ")}`); + } + + return violations; +} + function listTrackedManifests() { const result = spawnSync("git", ["ls-files", "--", ...GUARDED_MANIFESTS], { encoding: "utf8", @@ -99,13 +139,37 @@ export function scanTrackedManifests(files = listTrackedManifests(), options = { return [`${filePath}: invalid JSON (${error instanceof Error ? error.message : String(error)})`]; } } - return validateManifestSet(manifests); + const violations = validateManifestSet(manifests); + let workspaceConfig; + try { + workspaceConfig = parseYaml(readFile("pnpm-workspace.yaml", "utf8")); + } catch (error) { + return [...violations, `pnpm-workspace.yaml: invalid YAML (${error instanceof Error ? error.message : String(error)})`]; + } + + const workspaceOverrides = workspaceConfig?.overrides ?? {}; + violations.push(...validateWorkspaceOverrides(workspaceOverrides)); + + const manifestVersions = new Set( + manifests.flatMap(({ manifest }) => + DEPENDENCY_BLOCKS.flatMap((blockName) => + PI_DEPENDENCIES.map((packageName) => manifest?.[blockName]?.[packageName]).filter(isExactSemver), + ), + ), + ); + const overrideVersions = new Set(PI_RUNTIME_PACKAGES.map((packageName) => workspaceOverrides[packageName]).filter(isExactSemver)); + const allRuntimeVersions = [...new Set([...manifestVersions, ...overrideVersions])]; + if (allRuntimeVersions.length > 1) { + violations.push(`guarded Pi manifests and workspace overrides must use one exact runtime version; found ${allRuntimeVersions.join(", ")}`); + } + + return violations; } export function formatFailureMessage(violations) { return [ - "[check-pi-versions-pinned] pi runtime dependencies must be exact, matched versions.", - "npm global installs do not use pnpm-lock.yaml; ranges can resolve an incompatible pi-mono patch set.", + "[check-pi-versions-pinned] Pi runtime dependencies and workspace overrides must be exact, matched versions.", + "npm global installs and legacy desktop deploy do not reliably use pnpm-lock.yaml; ranges can resolve an incompatible pi-mono patch set.", ...violations.map((violation) => `- ${violation}`), ].join("\n"); }