fix(desktop): advance Pi runtime pin to 0.82.1 for packaging PR lane (#2465)

## Summary
- Advance the matched Pi runtime pin (`pi-ai`, `pi-coding-agent`,
`pi-agent-core`, `pi-tui`) from **0.82.0 → 0.82.1** so
electron-builder's production-dependency walk accepts `pi-agent-core`'s
`pi-ai@^0.82.1` requirement.
- Fixes the Desktop packaging PR-lane failure:
`Production dependency @earendil-works/pi-ai not found for package
@earendil-works/pi-agent-core` (required `^0.82.1`).
- Keep the workspace override guard; update pin-policy fixtures and CLI
package-config expectations.
- Tighten the advisory packaging step-order test so it asserts against
the real `electron-builder --dir` step (not a missing release-only step
name that previously passed via `indexOf === -1`).
- Run `pnpm dedupe` so the packaging lane's lockfile dedupe
early-warning is clean.

## Context
#2439 pinned the full Pi closure at 0.82.0 and made recent main-based
packaging runs green. This advances to the current upstream patch so
deploy + electron-builder stay aligned with `pi-agent-core@0.82.1`'s
declared dependency range.

## Test plan
- [x] `node scripts/check-pi-versions-pinned.mjs`
- [x] `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs`
- [x] `pnpm --filter @runfusion/fusion exec vitest run
src/__tests__/package-config.test.ts`
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm dedupe --check`
- [ ] GitHub: Desktop packaging (should run full packaging walk —
lockfile/package.json touched)
- [ ] GitHub: PR Checks (Lint, Typecheck, Build, Gate)
This commit is contained in:
gsxdsm
2026-07-26 23:47:49 -07:00
committed by GitHub
parent 3c5d07ab01
commit 8b039a543e
15 changed files with 366 additions and 657 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Keep desktop packaging on a packageable Pi 0.82.1 runtime closure.
category: fix
dev: Advances the matched Pi runtime pin (pi-ai, pi-coding-agent, pi-agent-core, pi-tui) from 0.82.0 to 0.82.1 so electron-builder accepts pi-agent-core's pi-ai@^0.82.1 dependency during the Desktop packaging PR lane.

View File

@@ -23,22 +23,37 @@ runs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
# Do NOT cache the linked node_modules tree. pnpm builds node_modules from
# symlinks/junctions into the content-addressable store, and actions/cache does
# not preserve Windows junctions across its tar/restore — a restored tree has
# broken peer links (e.g. @vitejs/plugin-react can't resolve its `vite` peer →
# ERR_MODULE_NOT_FOUND), which silently broke the Windows release build. The
# pnpm *store* is already cached by actions/setup-node (`cache: pnpm`), so
# `pnpm install --frozen-lockfile` is fast with a warm store and relinks
# correctly per-OS/arch every time (also fixing the prior arch-key and per-job
# cache-race issues this node_modules cache was patched for).
#
# FNXC:CI 2026-07-26-23:05:
# skip-install callers (agent-browser pack fixture, version.yml) never create a
# pnpm store. setup-node's post-job cache save then fails hard with
# "Path Validation Error: Path(s) specified in the action for caching do(es)
# not exist" and marks an otherwise-successful pack job failed. Only enable
# the pnpm store cache when this composite will actually install.
- name: Setup Node.js - name: Setup Node.js
if: ${{ inputs.skip-install != 'true' }}
uses: actions/setup-node@v5 uses: actions/setup-node@v5
with: with:
node-version: ${{ inputs.node-version }} node-version: ${{ inputs.node-version }}
cache: pnpm cache: pnpm
registry-url: ${{ inputs.registry-url }} registry-url: ${{ inputs.registry-url }}
# Do NOT cache the linked node_modules tree. pnpm builds node_modules from - name: Setup Node.js (no pnpm store cache)
# symlinks/junctions into the content-addressable store, and actions/cache does if: ${{ inputs.skip-install == 'true' }}
# not preserve Windows junctions across its tar/restore — a restored tree has uses: actions/setup-node@v5
# broken peer links (e.g. @vitejs/plugin-react can't resolve its `vite` peer → with:
# ERR_MODULE_NOT_FOUND), which silently broke the Windows release build. The node-version: ${{ inputs.node-version }}
# pnpm *store* is already cached by actions/setup-node above (`cache: pnpm`), so registry-url: ${{ inputs.registry-url }}
# `pnpm install --frozen-lockfile` is fast with a warm store and relinks
# correctly per-OS/arch every time (also fixing the prior arch-key and per-job
# cache-race issues this node_modules cache was patched for).
- name: Install dependencies - name: Install dependencies
if: ${{ inputs.skip-install != 'true' }} if: ${{ inputs.skip-install != 'true' }}
shell: bash shell: bash

View File

@@ -67,8 +67,8 @@
"dependencies": { "dependencies": {
"@agentclientprotocol/sdk": "0.24.0", "@agentclientprotocol/sdk": "0.24.0",
"agent-browser": "0.26.0", "agent-browser": "0.26.0",
"@earendil-works/pi-ai": "0.82.0", "@earendil-works/pi-ai": "0.82.1",
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": "0.82.1",
"claude-code-cli-acp": "0.1.1", "claude-code-cli-acp": "0.1.1",
"dockerode": "^4.0.12", "dockerode": "^4.0.12",
"electron": "^33.4.11", "electron": "^33.4.11",

View File

@@ -259,6 +259,25 @@ describe("Merge gate (.github/workflows/pr-checks.yml)", () => {
expect(compositeAction.inputs?.["install-args"]?.default).toBe("--frozen-lockfile"); expect(compositeAction.inputs?.["install-args"]?.default).toBe("--frozen-lockfile");
}); });
/*
FNXC:CI 2026-07-26-23:05:
skip-install pack jobs never create a pnpm store; setup-node must not enable
cache: pnpm on that path or post-job cache save fails the whole job after a
successful pack (agent-browser-install pack-fixture).
*/
it("disables pnpm store cache when skip-install is true", () => {
const setupSteps = (compositeAction.runs?.steps ?? []).filter(
(step: any) => typeof step.uses === "string" && step.uses.startsWith("actions/setup-node@"),
);
const withCache = setupSteps.find((step: any) => step.with?.cache === "pnpm");
const withoutCache = setupSteps.find((step: any) => step.with?.cache === undefined || step.with?.cache === "");
expect(withCache?.if).toContain("skip-install");
expect(withCache?.if).toContain("!=");
expect(withoutCache?.if).toContain("skip-install");
expect(withoutCache?.if).toContain("==");
expect(withoutCache?.with?.cache).toBeUndefined();
});
it("keeps lint as install + lint only, without Bun/setup build coupling", () => { it("keeps lint as install + lint only, without Bun/setup build coupling", () => {
const lintSteps = workflow.jobs?.lint?.steps ?? []; const lintSteps = workflow.jobs?.lint?.steps ?? [];
expect( expect(
@@ -826,6 +845,8 @@ describe("Cross-platform agent-browser install workflow", () => {
const packFixture = workflow.jobs?.["pack-fixture"]; const packFixture = workflow.jobs?.["pack-fixture"];
const installSmoke = workflow.jobs?.["install-smoke"]; const installSmoke = workflow.jobs?.["install-smoke"];
expect(packFixture?.["runs-on"]).toBe("ubuntu-latest"); expect(packFixture?.["runs-on"]).toBe("ubuntu-latest");
// FNXC:CI 2026-07-26-23:05: pack fixture must keep skip-install so the composite
// takes the no-store-cache setup-node path (see setup-node-pnpm action).
expect(findCompositeSetupStep(packFixture?.steps ?? [])?.with?.["skip-install"]).toBe("true"); expect(findCompositeSetupStep(packFixture?.steps ?? [])?.with?.["skip-install"]).toBe("true");
expect(installSmoke?.needs).toBe("pack-fixture"); expect(installSmoke?.needs).toBe("pack-fixture");
expect(installSmoke?.["runs-on"]).toBe("${{ matrix.os }}"); expect(installSmoke?.["runs-on"]).toBe("${{ matrix.os }}");

View File

@@ -53,10 +53,12 @@ function assertRuntimeDepsAreNotOptionalPeers(pkg: any, label: string): void {
).not.toBe(true); ).not.toBe(true);
} }
// FNXC:DesktopPackaging 2026-07-26-22:55: Exact matched Pi runtime pin — keep in sync with
// pnpm-workspace.yaml overrides and check-pi-versions-pinned.mjs (currently 0.82.1).
for (const dependencyName of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai"]) { for (const dependencyName of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai"]) {
expect(dependencies, `${label}: ${dependencyName} must remain a required runtime dependency`).toHaveProperty( expect(dependencies, `${label}: ${dependencyName} must remain a required runtime dependency`).toHaveProperty(
dependencyName, dependencyName,
"0.82.0", "0.82.1",
); );
expect(dependencies[dependencyName], `${label}: ${dependencyName} must be a clean exact semver`).toMatch( expect(dependencies[dependencyName], `${label}: ${dependencyName} must be a clean exact semver`).toMatch(
EXACT_SEMVER, EXACT_SEMVER,

View File

@@ -51,7 +51,7 @@
"test:pg-gate": "vitest run --config vitest.pg.config.ts src/__tests__/postgres/handoff-to-review-atomicity.pg.test.ts src/__tests__/postgres/task-lifecycle-e2e.pg.test.ts --silent=passed-only --reporter=dot" "test:pg-gate": "vitest run --config vitest.pg.config.ts src/__tests__/postgres/handoff-to-review-atomicity.pg.test.ts src/__tests__/postgres/task-lifecycle-e2e.pg.test.ts --silent=passed-only --reporter=dot"
}, },
"devDependencies": { "devDependencies": {
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": "0.82.1",
"@types/dockerode": "^3.3.41", "@types/dockerode": "^3.3.41",
"@types/node": "^25.5.0", "@types/node": "^25.5.0",
"@vitest/coverage-v8": "^4.1.0", "@vitest/coverage-v8": "^4.1.0",

View File

@@ -107,7 +107,7 @@
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.36.4", "@codemirror/view": "^6.36.4",
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": "0.82.1",
"@fusion-plugin-examples/claude-runtime": "workspace:*", "@fusion-plugin-examples/claude-runtime": "workspace:*",
"@fusion-plugin-examples/cli-printing-press": "workspace:*", "@fusion-plugin-examples/cli-printing-press": "workspace:*",
"@fusion-plugin-examples/compound-engineering": "workspace:*", "@fusion-plugin-examples/compound-engineering": "workspace:*",

View File

@@ -106,16 +106,31 @@ describe("desktop release workflow wiring", () => {
expect(verifier).not.toContain("found ${platform}; expected ${expectedPlatform}"); expect(verifier).not.toContain("found ${platform}; expected ${expectedPlatform}");
const advisoryPackaging = await readRepoFile(".github/workflows/desktop-packaging.yml"); const advisoryPackaging = await readRepoFile(".github/workflows/desktop-packaging.yml");
for (const workflow of [release, testRelease, advisoryPackaging]) { /*
FNXC:DesktopEmbeddedPostgres 2026-07-15-11:55:
This verifier reads electron-builder's unpacked tree, so invoking it before
the Linux packaging command would only validate stale output.
FNXC:DesktopPackaging 2026-07-26-22:55:
Release/test-release package via the named "Package Linux desktop artifacts"
step; the advisory PR lane uses electron-builder --dir ("Validate packageable
closure"). Assert order against each workflow's real packaging step so a
missing release step cannot silently pass via indexOf === -1.
*/
for (const workflow of [release, testRelease]) {
expect(workflow).toContain("Verify Linux AppImage embedded Postgres packaging"); expect(workflow).toContain("Verify Linux AppImage embedded Postgres packaging");
expect(workflow).toContain("node scripts/verify-desktop-linux-pg-packaging.mjs"); expect(workflow).toContain("node scripts/verify-desktop-linux-pg-packaging.mjs");
// FNXC:DesktopEmbeddedPostgres 2026-07-15-11:55: expect(workflow).toContain("Package Linux desktop artifacts");
// This verifier reads electron-builder's unpacked tree, so invoking it
// before the Linux packaging command would only validate stale output.
expect(workflow.indexOf("node scripts/verify-desktop-linux-pg-packaging.mjs")).toBeGreaterThan( expect(workflow.indexOf("node scripts/verify-desktop-linux-pg-packaging.mjs")).toBeGreaterThan(
workflow.indexOf("Package Linux desktop artifacts"), workflow.indexOf("Package Linux desktop artifacts"),
); );
} }
expect(advisoryPackaging).toContain("Verify Linux AppImage embedded Postgres packaging");
expect(advisoryPackaging).toContain("node scripts/verify-desktop-linux-pg-packaging.mjs");
expect(advisoryPackaging).toContain("Validate packageable closure (electron-builder --dir)");
expect(advisoryPackaging.indexOf("node scripts/verify-desktop-linux-pg-packaging.mjs")).toBeGreaterThan(
advisoryPackaging.indexOf("Validate packageable closure (electron-builder --dir)"),
);
}); });
it("wires release aggregation to include desktop assets across platforms", async () => { it("wires release aggregation to include desktop assets across platforms", async () => {

View File

@@ -38,8 +38,8 @@
"test:watch": "vitest src/__tests__/executor-*.test.ts --watch" "test:watch": "vitest src/__tests__/executor-*.test.ts --watch"
}, },
"dependencies": { "dependencies": {
"@earendil-works/pi-ai": "0.82.0", "@earendil-works/pi-ai": "0.82.1",
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": "0.82.1",
"@fusion/core": "workspace:*", "@fusion/core": "workspace:*",
"@fusion/pi-claude-cli": "workspace:*", "@fusion/pi-claude-cli": "workspace:*",
"@modelcontextprotocol/sdk": "^1.0.0", "@modelcontextprotocol/sdk": "^1.0.0",

View File

@@ -224,12 +224,12 @@ plus a per-provider read-modify-merge (FileAuthStorageBackend.persistProviderCha
refreshOAuthTokenWithLock re-read the file under a lock and spread refreshOAuthTokenWithLock re-read the file under a lock and spread
{...currentData, [provider]: credential} rather than flushing a whole-file in-memory {...currentData, [provider]: credential} rather than flushing a whole-file in-memory
snapshot). packages/engine/package.json already pins this at snapshot). packages/engine/package.json already pins this at
"@earendil-works/pi-coding-agent": "0.82.0" (exact matched runtime pair; locked per-provider "@earendil-works/pi-coding-agent": "0.82.1" (exact matched runtime pair; locked per-provider
merge requires >=0.80.3) — do not downgrade below 0.80.x, and re-verify this comment against merge requires >=0.80.3) — do not downgrade below 0.80.x, and re-verify this comment against
dist/core/auth-storage.js if the pin is ever lowered. See dist/core/auth-storage.js if the pin is ever lowered. See
FNXC:ProviderAuth 2026-07-24-12:00: FNXC:ProviderAuth 2026-07-24-12:00:
FN-8564 retains Fusion's credential-store adapter on Pi 0.82.0. The release adds Kimi Code and FN-8564 retains Fusion's credential-store adapter on Pi 0.82.x. The release adds Kimi Code and
OpenRouter OAuth, so new upstream login providers must still use the same queued, cross-process OpenRouter OAuth, so new upstream login providers must still use the same queued, cross-process
credential persistence contract rather than bypassing Fusion auth storage. credential persistence contract rather than bypassing Fusion auth storage.
packages/engine/src/__tests__/auth-storage-concurrency.test.ts for the regression coverage. packages/engine/src/__tests__/auth-storage-concurrency.test.ts for the regression coverage.

View File

@@ -2627,7 +2627,7 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
ModelRuntime path already adopted in 0.80.8. ModelRuntime path already adopted in 0.80.8.
FNXC:ModelCatalog 2026-07-24-12:00: FNXC:ModelCatalog 2026-07-24-12:00:
FN-8564 advances the exact matched pair to 0.82.0. Its catalog now exposes only FN-8564 advances the exact matched pair to 0.82.x. Its catalog now exposes only
provider-verified reasoning levels and adds Kimi/OpenRouter OAuth plus constrained tools; provider-verified reasoning levels and adds Kimi/OpenRouter OAuth plus constrained tools;
retain Fusion's ModelRuntime session construction and supplemental catalog merges rather retain Fusion's ModelRuntime session construction and supplemental catalog merges rather
than duplicating upstream provider behavior. than duplicating upstream provider behavior.

View File

@@ -23,12 +23,12 @@
"@agentclientprotocol/sdk": "0.24.0" "@agentclientprotocol/sdk": "0.24.0"
}, },
"peerDependencies": { "peerDependencies": {
"@earendil-works/pi-ai": "0.82.0", "@earendil-works/pi-ai": "0.82.1",
"@earendil-works/pi-coding-agent": "0.82.0" "@earendil-works/pi-coding-agent": "0.82.1"
}, },
"devDependencies": { "devDependencies": {
"@earendil-works/pi-ai": "0.82.0", "@earendil-works/pi-ai": "0.82.1",
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": "0.82.1",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"typescript": "^5.7.0", "typescript": "^5.7.0",
"vitest": "^4.1.0" "vitest": "^4.1.0"

875
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,10 +28,13 @@ overrides:
# FNXC:DesktopPackaging 2026-07-25-17:15: Legacy `pnpm deploy` resolves pi-coding-agent's # 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 # 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. # exact version so new agent-core or tui patches cannot split the packageable desktop tree.
'@earendil-works/pi-agent-core': 0.82.0 # FNXC:DesktopPackaging 2026-07-26-22:55: Advance the matched closure 0.82.0 → 0.82.1 so
'@earendil-works/pi-ai': 0.82.0 # electron-builder's production-dependency walk accepts pi-agent-core's pi-ai@^0.82.1 requirement
'@earendil-works/pi-coding-agent': 0.82.0 # (Desktop packaging PR-lane failure when deploy resolved a newer agent-core patch beside 0.82.0 ai).
'@earendil-works/pi-tui': 0.82.0 '@earendil-works/pi-agent-core': 0.82.1
'@earendil-works/pi-ai': 0.82.1
'@earendil-works/pi-coding-agent': 0.82.1
'@earendil-works/pi-tui': 0.82.1
# FNXC:DesktopEmbeddedPostgres 2026-07-14-09:30: # FNXC:DesktopEmbeddedPostgres 2026-07-14-09:30:
# Desktop release jobs cross-build Intel and ARM64 artifacts on one host. Install # 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 # optional native payloads for both CPU families on the current OS so each

View File

@@ -7,10 +7,19 @@ import {
validateWorkspaceOverrides, validateWorkspaceOverrides,
} from "../check-pi-versions-pinned.mjs"; } from "../check-pi-versions-pinned.mjs";
/*
FNXC:DesktopPackaging 2026-07-26-22:55:
Fixture versions track the workspace's exact matched Pi runtime pin (currently 0.82.1).
Keep these in sync when advancing overrides/manifests so the policy guard still exercises
matched-set and range rejection against the live pin surface.
*/
const PINNED_VERSION = "0.82.1";
const OTHER_VERSION = "0.82.0";
const pinnedManifest = { const pinnedManifest = {
dependencies: { dependencies: {
"@earendil-works/pi-ai": "0.82.0", "@earendil-works/pi-ai": PINNED_VERSION,
"@earendil-works/pi-coding-agent": "0.82.0", "@earendil-works/pi-coding-agent": PINNED_VERSION,
}, },
}; };
@@ -24,7 +33,7 @@ describe("check-pi-versions-pinned", () => {
}); });
it("rejects caret, tilde, wildcard, x, and comparator ranges", () => { it("rejects caret, tilde, wildcard, x, and comparator ranges", () => {
for (const version of ["^0.82.0", "~0.82.0", "*", "0.82.x", ">=0.82.0"]) { for (const version of [`^${PINNED_VERSION}`, `~${PINNED_VERSION}`, "*", "0.82.x", `>=${PINNED_VERSION}`]) {
const violations = validate({ const violations = validate({
...pinnedManifest, ...pinnedManifest,
dependencies: { ...pinnedManifest.dependencies, "@earendil-works/pi-ai": version }, dependencies: { ...pinnedManifest.dependencies, "@earendil-works/pi-ai": version },
@@ -36,7 +45,7 @@ describe("check-pi-versions-pinned", () => {
it("rejects a matched-set version mismatch", () => { it("rejects a matched-set version mismatch", () => {
const violations = validate({ const violations = validate({
...pinnedManifest, ...pinnedManifest,
dependencies: { ...pinnedManifest.dependencies, "@earendil-works/pi-coding-agent": "0.82.1" }, dependencies: { ...pinnedManifest.dependencies, "@earendil-works/pi-coding-agent": OTHER_VERSION },
}); });
assert.equal(violations.some((violation) => violation.includes("same exact version")), true); assert.equal(violations.some((violation) => violation.includes("same exact version")), true);
}); });
@@ -53,7 +62,7 @@ describe("check-pi-versions-pinned", () => {
cannot split agent-core or tui from the exact runtime version. cannot split agent-core or tui from the exact runtime version.
*/ */
it("requires one exact workspace override for every staged Pi runtime package", () => { 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"])); const coherentOverrides = Object.fromEntries(PI_RUNTIME_PACKAGES.map((packageName) => [packageName, PINNED_VERSION]));
assert.deepEqual(validateWorkspaceOverrides(coherentOverrides), []); assert.deepEqual(validateWorkspaceOverrides(coherentOverrides), []);
assert.equal( assert.equal(
@@ -62,12 +71,12 @@ describe("check-pi-versions-pinned", () => {
true, true,
); );
assert.equal( assert.equal(
validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-agent-core": "^0.82.0" }) validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-agent-core": `^${PINNED_VERSION}` })
.some((violation) => violation.includes("pi-agent-core") && violation.includes("exact semver")), .some((violation) => violation.includes("pi-agent-core") && violation.includes("exact semver")),
true, true,
); );
assert.equal( assert.equal(
validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-tui": "0.82.1" }) validateWorkspaceOverrides({ ...coherentOverrides, "@earendil-works/pi-tui": OTHER_VERSION })
.some((violation) => violation.includes("one exact version")), .some((violation) => violation.includes("one exact version")),
true, true,
); );