Skip to content

refactor: replace the experimental pnpm-agent server with pnpr#12151

Merged
zkochan merged 1 commit into
mainfrom
deprecate-agent
Jun 2, 2026
Merged

refactor: replace the experimental pnpm-agent server with pnpr#12151
zkochan merged 1 commit into
mainfrom
deprecate-agent

Conversation

@zkochan

@zkochan zkochan commented Jun 2, 2026

Copy link
Copy Markdown
Member

What

The experimental TypeScript pnpm-agent install-accelerator server (agent/server) is superseded by the pnpr server, which implements the same /v1/install + /v1/files protocol. This PR removes pnpm-agent and routes its e2e test through pnpr.

The pnpm-side client @pnpm/agent.client (which powers --config.agent) is kept — it is server-agnostic and now works against pnpr.

Why it needed more than a swap

The pnpm TypeScript client and pnpr had never been exercised together (pnpr's /v1/install was only tested against the pacquet Rust client), and two real parity gaps surfaced when pointing the e2e test at pnpr:

  1. Lockfile shape. The wire protocol carries pnpm's on-disk lockfile format (split packages/snapshots, {specifier, version} importer deps), while pnpm keeps an in-memory LockfileObject in process. Without bridging, linking failed with ref.startsWith is not a function. Handled in @pnpm/agent.client, reusing pnpm's own converters:
    • Incoming: the agent's response lockfile → convertToLockfileObject.
    • Outgoing: the existing lockfile is read in its on-disk shape via the new readWantedLockfileFile and forwarded as-is — no in-memory→on-disk round-trip.
  2. Multi-project workspaces. pnpr's /v1/install hard-rejected projects.length > 1 with a 400. It now reconstructs the workspace on disk (root manifest + pnpm-workspace.yaml + member manifests) and lets pacquet's install path discover and resolve every importer. Importer dirs are validated against path traversal (sanitized_importer_dir).

Changes

  • Deleted agent/server/ (the pnpm-agent package) and scrubbed all references (pnpm/package.json, .meta-updater, tsconfig project reference, docs/comments).
  • Converted pnpm/test/install/pnpmRegistry.ts to drive the pnpr server the test harness already starts; kept the counting proxy so the "agent was used" assertions stay meaningful. Added a case that forwards an existing lockfile to the agent on a second resolution.
  • @pnpm/lockfile.fs: added readWantedLockfileFile, returning the raw on-disk LockfileFile (the _read path now exposes the pre-conversion file).
  • @pnpm/agent.client: incoming on-disk → in-memory conversion; outgoing now sends the raw on-disk lockfile.
  • pnpr: dir added to the install-request protocol; multi-project workspace resolution; path-traversal guard with unit tests.

Verification

  • pnpm/test/install/pnpmRegistry.ts: 10/10 pass against pnpr (single-project, workspace, and incremental-with-existing-lockfile cases).
  • @pnpm/lockfile.fs: 46/46 unit tests pass.
  • pnpr Rust tests pass (incl. 4 new sanitizer unit tests); cargo fmt, cargo doc, cargo dylint, taplo all clean.
  • pnpm --filter pnpm run compile (tsc + bundle) and pnpm run lint:meta pass; ESLint clean on changed files.

No changeset (removal of an experimental published package; the pnpm CLI's --config.agent behavior is unchanged).


Written by an agent (Claude Code, claude-opus-4-8).

Summary by CodeRabbit

  • New Features

    • Multi-project workspace resolution for pnpr installs.
    • Agent requests now forward the on-disk lockfile format.
    • Generated manifests now set license to "MIT".
  • Refactor

    • Server-side workspace reconstruction for accurate multi-importer resolution.
    • Lockfile handling expanded to return on-disk serialized form alongside in-memory shape.
  • Chores

    • CI: cache/restore prebuilt pnpr binaries to speed tests.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c37ad932-6753-480e-b1d7-d34175bd4a05

📥 Commits

Reviewing files that changed from the base of the PR and between 333bf9c and e40d59c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (38)
  • .github/workflows/test.yml
  • .meta-updater/src/index.ts
  • __utils__/jest-config/package.json
  • __utils__/jest-config/with-registry/globalSetup.js
  • agent/client/package.json
  • agent/client/src/fetchFromPnpmRegistry.ts
  • agent/client/tsconfig.json
  • agent/server/CHANGELOG.md
  • agent/server/CONTRIBUTING.md
  • agent/server/Dockerfile
  • agent/server/LICENSE.md
  • agent/server/README.md
  • agent/server/package.json
  • agent/server/src/bin.ts
  • agent/server/src/createRegistryServer.ts
  • agent/server/src/diff.ts
  • agent/server/src/fileStore.ts
  • agent/server/src/index.ts
  • agent/server/src/metadataStore.ts
  • agent/server/src/protocol.ts
  • agent/server/test/diff.ts
  • agent/server/test/integration.ts
  • agent/server/test/tsconfig.json
  • agent/server/tsconfig.json
  • agent/server/tsconfig.lint.json
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/src/install/index.ts
  • lockfile/fs/src/read.ts
  • pnpm-workspace.yaml
  • pnpm/package.json
  • pnpm/test/install/pnpmRegistry.ts
  • pnpm/tsconfig.json
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pnpr/crates/pnpr/src/install_accelerator/diff.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs
  • worker/src/start.ts
💤 Files with no reviewable changes (22)
  • agent/server/README.md
  • agent/server/Dockerfile
  • pnpm/package.json
  • agent/server/package.json
  • agent/server/CONTRIBUTING.md
  • agent/server/tsconfig.lint.json
  • agent/server/src/index.ts
  • utils/jest-config/package.json
  • agent/server/CHANGELOG.md
  • agent/server/src/bin.ts
  • agent/server/test/integration.ts
  • agent/server/test/tsconfig.json
  • agent/server/LICENSE.md
  • agent/server/tsconfig.json
  • pnpm/tsconfig.json
  • agent/server/src/metadataStore.ts
  • agent/server/test/diff.ts
  • agent/server/src/protocol.ts
  • agent/server/src/diff.ts
  • agent/server/src/createRegistryServer.ts
  • agent/server/src/fileStore.ts
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (2)
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • pnpr/crates/pnpr/src/install_accelerator/diff.rs
🚧 Files skipped from review as they are similar to previous changes (13)
  • agent/client/tsconfig.json
  • worker/src/start.ts
  • utils/jest-config/with-registry/globalSetup.js
  • installing/deps-installer/src/install/index.ts
  • pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs
  • agent/client/package.json
  • agent/client/src/fetchFromPnpmRegistry.ts
  • .github/workflows/test.yml
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpm/test/install/pnpmRegistry.ts
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • lockfile/fs/src/read.ts
  • .meta-updater/src/index.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Code Coverage
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pnpr/**/pnpr/**/*.rs

📄 CodeRabbit inference engine (pnpr/AGENTS.md)

pnpr/**/pnpr/**/*.rs: Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions

Files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
🧠 Learnings (28)
📓 Common learnings
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Match how the same feature is implemented in the TypeScript pnpm CLI — any change in pacquet must match pnpm's behavior, logic, edge cases, config resolution, error messages, file/lockfile formats, and existing tests
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:08:06.093Z
Learning: Pacquet (pnpm's Rust port) has a cardinal rule: "match pnpm exactly — do not fix pnpm quirks unless the same fix has landed in pnpm first." Review comments should not suggest behavioral deviations from upstream pnpm, even when the upstream behavior appears buggy. If a real bug is identified, it must be fixed upstream first.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Reference the upstream pnpm commit/PR when porting code from pnpm in commit messages
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11640
File: pacquet/AGENTS.md:1-6
Timestamp: 2026-05-14T17:38:39.470Z
Learning: In the pnpm/pnpm repository, `AGENTS.md` and `pacquet/AGENTS.md` are agent guides in the agentsmd.net convention — they contain instructions FOR AI coding tools (Claude Code, Codex, Cursor, etc.) working on the repo. They are NOT documentation about software agents defined by the project. Do not apply "document agent capabilities / inputs / outputs / integration points" review rules to these files; such rules are for agentic-framework documentation, not for repo-level AI-tool guides.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.760Z
Learning: Read the monorepo-wide AGENTS.md (../AGENTS.md) first for GitHub PR workflow, signing agent-authored content, conventional commit messages, code-reuse philosophy, and test failure handling
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Applies to pacquet/**/tests/**/*.rs : Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Do not change lockfile format, store layout, `.npmrc` semantics, or CLI surface unless pnpm changed them first
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In pacquet (pnpm/pnpm repo), `ResolvedPackage.optional` AND-folding intentionally mirrors pnpm's resolveDependencies.ts:1627-1648 revisit behavior: only the directly-visited package's `optional` flag is updated on revisit, not transitive descendants. pnpm CLI corrects stale optional flags via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`. Pacquet does not yet have this pruner equivalent, so raw `node.optional` flows directly into snapshot/virtual-store via `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up issue to port `copyDependencySubGraph` is planned.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.760Z
Learning: Use Conventional Commits with 'pnpr' as the scope in commit messages (e.g., feat(pnpr): ..., fix(pnpr): ...)
📚 Learning: 2026-05-25T14:58:11.105Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11931
File: pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs:560-589
Timestamp: 2026-05-25T14:58:11.105Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs`, all per-`(registry, name[, version])` caches in `NpmResolutionVerifier` (`published_at`, `full_meta`, `full_meta_for_trust`, `abbreviated_meta`, `local_meta`) intentionally use the same pattern: lock → miss-check → release lock → await fetch/load → re-acquire lock → insert. This uniform pattern is deliberate; do not flag individual caches for using it. The known follow-up improvement (replacing the pattern with `tokio::sync::OnceCell` per key inside a `Mutex<HashMap<…>>`) is tracked as a future structural change to cover all five caches simultaneously.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not invoke `BuildModules` and discards `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput`. This is pre-existing, documented behavior (mirroring upstream `link.ts:167-170`): `importing_done` fires once extraction and symlink linking are complete, and the fresh-lockfile path does not run lifecycle scripts. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Do not flag this omission as a bug; wiring lifecycle scripts into the fresh-lockfile path is tracked as future work separate from perf refactors.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-24T21:11:04.272Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not run lifecycle scripts (`BuildModules` is not invoked, and `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput` is discarded). This is pre-existing behavior documented by an in-code comment mirroring upstream `link.ts:167-170`. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Wiring lifecycle scripts into the fresh-lockfile path is tracked as future work, separate from this file's concern.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T21:18:55.266Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11778
File: pacquet/crates/resolving-local-resolver/src/parse_bare_specifier.rs:253-278
Timestamp: 2026-05-20T21:18:55.266Z
Learning: In `pacquet/crates/resolving-local-resolver/src/parse_bare_specifier.rs`, the `resolve_path` function intentionally short-circuits absolute specifiers verbatim (returns them unchanged without normalizing `..` components), mirroring the upstream TypeScript `resolvePath` in `resolving/local-resolver/src/parseBareSpecifier.ts` at ef87f3ccff. The OS resolves `..` at `fs.read` time. Do not suggest normalizing the absolute branch — it would invent behavior pnpm doesn't have, violating the pacquet AGENTS.md cardinal rule of fidelity to upstream.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T23:07:40.413Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11783
File: pacquet/crates/engine-runtime-node-resolver/src/node_resolver.rs:167-191
Timestamp: 2026-05-20T23:07:40.413Z
Learning: In `pacquet/crates/engine-runtime-node-resolver/src/node_resolver.rs`, the offline guard in `resolve_impl` (NodeResolver) is intentionally absent from `resolve_latest_impl`. This mirrors upstream pnpm's behavior: `resolveNodeRuntime` checks offline, but `resolveLatestNodeRuntime` does not carry `offline` in its context and never checks it. The asymmetry is required so `pnpm outdated` / `pnpm update --latest` can still query for newer runtime versions even in offline mode.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-21T00:33:05.035Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11789
File: pacquet/crates/resolving-npm-resolver/src/resolve_from_workspace.rs:145-146
Timestamp: 2026-05-21T00:33:05.035Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/resolve_from_workspace.rs`, the guard `bare.starts_with("workspace:.")` is intentionally broad — matching pnpm upstream's identical `startsWith('workspace:.')` check. All dot-prefixed workspace forms including `workspace:.foo` are intentionally handed off to the local-resolver, which handles them as `link:`-style directory specs via its prefix-stripping regex. Do not suggest narrowing this to `workspace:./` or `workspace:../` only.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-21T00:33:05.035Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11789
File: pacquet/crates/resolving-npm-resolver/src/resolve_from_workspace.rs:145-146
Timestamp: 2026-05-21T00:33:05.035Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/resolve_from_workspace.rs`, the guard `bare.starts_with("workspace:.")` is intentionally broad — matching pnpm upstream's identical `startsWith('workspace:.')` check at `resolving/npm-resolver/src/index.ts`. All dot-prefixed workspace forms including `workspace:.foo` are intentionally passed through to the local-resolver, which handles them as `link:`-style directory specs via its prefix-stripping regex. Do not suggest narrowing this to `workspace:./` or `workspace:../` only.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-24T21:11:04.272Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In pacquet (pnpm/pnpm repo), `ResolvedPackage.optional` AND-folding intentionally mirrors pnpm's resolveDependencies.ts:1627-1648 revisit behavior: only the directly-visited package's `optional` flag is updated on revisit, not transitive descendants. pnpm CLI corrects stale optional flags via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`. Pacquet does not yet have this pruner equivalent, so raw `node.optional` flows directly into snapshot/virtual-store via `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up issue to port `copyDependencySubGraph` is planned.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T21:18:56.391Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11778
File: pacquet/crates/resolving-local-resolver/tests/resolve.rs:365-372
Timestamp: 2026-05-20T21:18:56.391Z
Learning: In `pacquet/crates/resolving-local-resolver/tests/resolve.rs`, the test `fail_when_resolving_from_not_existing_directory_an_injected_dependency` intentionally uses `injected: false`. The test is a verbatim port of the upstream pnpm TypeScript test (resolving/local-resolver/test/index.ts at ef87f3ccff). The `injected` flag only affects the file/link protocol choice for plain directory paths; when the `file:` scheme is explicit in the bare specifier, the flag has no effect on the resolution code path. The misleading test name is inherited from upstream.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T22:49:17.652Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11787
File: pacquet/crates/catalogs-resolver/src/lib.rs:156-167
Timestamp: 2026-05-20T22:49:17.652Z
Learning: In pacquet's `catalogs-resolver` crate (`pacquet/crates/catalogs-resolver/src/lib.rs`), the protocol detection pattern `catalog_lookup.split(':').next().unwrap_or("")` is an intentional byte-for-byte port of pnpm's upstream JavaScript `getProtocol`/split logic in `catalogs/resolver/src/resolveFromCatalog.ts#L95`. A bare value like `"workspace"` (without a colon) is deliberately classified as the `"workspace"` protocol, matching upstream behavior. pacquet's cardinal rule is to match upstream pnpm behavior including quirks; any behavioral change must land in pnpm first and then be ported here.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-19T19:23:00.981Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11752
File: pacquet/crates/config/src/lib.rs:1062-1073
Timestamp: 2026-05-19T19:23:00.981Z
Learning: In `pacquet/crates/config/src/lib.rs`, `modules_dir` does not need a `!virtual_store_dir_explicit` guard on its workspace re-anchor because `modules_dir` is in pnpm's `excludedPnpmKeys` (filtered out by `WorkspaceSettings::clear_workspace_only_fields`) and therefore can only be set by workspace yaml (applied immediately after) or env vars (applied later in the cascade) — not by global `config.yaml`. `virtual_store_dir`, by contrast, IS settable from global config and requires the `if !virtual_store_dir_explicit` guard to survive the workspace-root re-anchor.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-06-02T14:39:46.617Z
Learnt from: KSXGitHub
Repo: pnpm/pnpm PR: 11938
File: pacquet/crates/cli/src/cli_args/dlx.rs:281-288
Timestamp: 2026-06-02T14:39:46.617Z
Learning: In pacquet's `pacquet/crates/cli/src/cli_args/dlx.rs` (and the analogous exec path), the `shell_mode` branch intentionally uses `joined.join(" ")` (no per-token shell-escaping) when building the command string passed to the platform shell. This matches pnpm's observable behavior: pnpm passes the command to execa with `shell: true`, and execa explicitly does NOT escape arguments in shell mode — it joins them and delegates entirely to the shell. Applying any shell-escaping here would diverge from pnpm's behavior and violates the pnpm-compatibility rule for the pacquet Rust port.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T23:24:24.022Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11783
File: pacquet/crates/crypto-shasums-file/src/lib.rs:156-160
Timestamp: 2026-05-20T23:24:24.022Z
Learning: In `pacquet/crates/crypto-shasums-file/src/lib.rs`, the `pick_file_checksum_from_shasums_file` function intentionally uses `format!("  {file_name}")` with **two** spaces. This matches upstream pnpm's `pickFileChecksumFromShasumsFile` which uses `` `  ${fileName}` `` (two-space template literal). Do not suggest changing to a single space — that would diverge from pnpm behavior.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-19T00:19:52.808Z
Learnt from: KSXGitHub
Repo: pnpm/pnpm PR: 11730
File: pacquet/crates/workspace/src/root_finder.rs:127-129
Timestamp: 2026-05-19T00:19:52.808Z
Learning: In `pacquet/crates/workspace/src/root_finder.rs`, the function `find_workspace_dir_from_env_with` uses `.or_else(|| Sys::var_os(WORKSPACE_DIR_ENV_VAR_LOWER)).filter(|v| !v.is_empty())` to mirror upstream pnpm's JS behavior: `process.env['NPM_CONFIG_WORKSPACE_DIR'] ?? process.env['npm_config_workspace_dir']` followed by `if (workspaceDir)`. JS `??` falls through only on null/undefined (not ""), so when the uppercase var is set to "" the lowercase is NOT consulted — the trailing `.filter` then discards the empty string. Applying `.filter` before `.or_else` would be wrong because it would let the lowercase var win when the uppercase is explicitly set to "" — contradicting upstream behavior.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-06-02T14:39:19.809Z
Learnt from: KSXGitHub
Repo: pnpm/pnpm PR: 11938
File: pacquet/crates/config/src/lib.rs:959-966
Timestamp: 2026-06-02T14:39:19.809Z
Learning: In the pnpm/pnpm pacquet Rust port (`pacquet/crates/config/src/lib.rs`), `Config.extra_bin_paths` is intentionally left empty (`Vec::new()`) until workspace fan-out support for `run`/`exec` is implemented. It mirrors pnpm's `Config.extraBinPaths` (the workspace-root `node_modules/.bin`), which is also empty outside a workspace. Populating it before the workspace-root is resolved would put a non-existent path on `PATH`, so it should only be derived once workspace support lands. Do not flag this as a bug or missing derivation in reviews.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-23T17:30:06.849Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: resolving/npm-resolver/src/createNpmResolutionVerifier.ts:381-418
Timestamp: 2026-05-23T17:30:06.849Z
Learning: In `resolving/npm-resolver/src/pickPackage.ts` (pnpm/pnpm), the resolver's `PackageMetaCache` keys by `name` (abbreviated) and `name:full` (full metadata) only — no registry component is included. This is a pre-existing limitation meaning that if two different registries serve packages of the same name in one install, the cache will only hold the first fetched entry. The `createNpmResolutionVerifier.ts` shares this same cache and inherits the limitation; a `validateSharedMeta` name-check guards against cross-package contamination but cannot distinguish same-named packages from different registries. Tightening to a registry-qualified key would require a coordinated change to the resolver's cache key shape. The Pacquet/Rust side is already registry-qualified (`{registry}\x00{name}:full`).

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T20:41:50.322Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/package-manager/src/install_package_from_registry.rs:111-114
Timestamp: 2026-05-20T20:41:50.322Z
Learning: In pacquet (pnpm/pnpm repo, Rust codebase), `install_package_from_registry` is the npm-only install path. The npm resolver always stamps `ResolveResult.id` (a `PkgResolutionId`) as `nameversion`. Parsing it back through `PkgNameVer` with `.expect()` is intentional — a parse failure means a mis-dispatch bug, not malformed external input. Per pacquet's CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." Do not suggest replacing such `.expect()` calls with graceful error handling.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-28T16:19:30.483Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12025
File: pacquet/crates/deps-path/src/link_path_to_peer_version.rs:0-0
Timestamp: 2026-05-28T16:19:30.483Z
Learning: In `pacquet/crates/deps-path/src/link_path_to_peer_version.rs`, the `link_path_to_peer_version` function intentionally deviates from upstream pnpm/JS behavior for non-BMP Unicode code points: the JavaScript `filenamify` v4 implementation sees UTF-16 code units and emits two surrogate fragments for a single non-BMP scalar, while the Rust port iterates `chars()` and emits one clean Unicode scalar. pnpm has no tests for non-ASCII link paths, so the behavior was undefined upstream; the Rust scalar form is the intentional, preferred behavior for pacquet.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-18T20:35:22.917Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11729
File: pacquet/crates/resolving-npm-resolver/src/fetch_attestation_published_at.rs:55-57
Timestamp: 2026-05-18T20:35:22.917Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/fetch_attestation_published_at.rs`, the npm attestation endpoint (`/-/npm/v1/attestations/{pkg_name}@{version}`) intentionally does NOT percent-encode the package name — the endpoint accepts literal `/` in scoped package names (e.g. `scope/pkg`). This matches upstream pnpm's `fetchAttestationPublishedAt.ts` behavior. Do not flag missing URL encoding here. By contrast, the full-metadata fetch paths (`fetch_full_metadata`, `fetch_full_metadata_cached`) DO percent-encode via the `registry_url::to_registry_url` helper, matching upstream's `toUri` behavior.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T10:06:55.749Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11760
File: pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs:15-18
Timestamp: 2026-05-20T10:06:55.749Z
Learning: In `pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs`, the `DirectDep.id`, `ResolvedPackage.id`, and `ResolvedTree.packages` HashMap keys are intentionally plain `String` for now. The natural branded type would be `pacquet_lockfile::PkgNameVer`, but it cannot be used as a HashMap key because `node_semver::Version` does not derive `Hash`. The upstream parity type is `PkgResolutionId` (which carries an optional peer-dep suffix), and the branded type should be introduced alongside peer-dep resolution and lockfile generation work to avoid locking the seam too early.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-29T18:03:15.354Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Applies to pacquet/**/*.rs : Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using `#[serde(try_from = "String")]` for deserialization and `#[serde(into = "String")]` for serialization

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-07T20:38:01.796Z
Learnt from: camcima
Repo: pnpm/pnpm PR: 11159
File: deps/compliance/license-checker/src/utils.ts:42-59
Timestamp: 2026-05-07T20:38:01.796Z
Learning: In `deps/compliance/license-checker/src/utils.ts`, `collectDirectDeps` intentionally uses name-only keys (not `nameversion`) for the shallow-depth filter. This is a deliberate design decision: being over-inclusive (auditing extra versions of a direct dep) is safer for compliance than being under-inclusive. Tightening to `nameversion` would require unsafe `(lockfile.packages as any)?.[ref]` casts through untyped lockfile internals. Do not flag this as a bug.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-14T07:57:23.823Z
Learnt from: mandarini
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/src/pickPackage.ts:183-221
Timestamp: 2026-05-14T07:57:23.823Z
Learning: In `resolving/npm-resolver/src/pickPackage.ts`, the error-fallback `catch` block (the `loadMeta(pkgMirror)` path that fires when the primary network fetch throws) intentionally does NOT call `maybeUpgradeAbbreviatedMetaForReleaseAge` or retry with `fullMetadata: true`. This is by design: the network is already known-iffy at that point, so an extra fetch would risk compounding the failure. The `ignoreMissingTimeField` warn-and-skip path is the accepted graceful degradation here.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-29T18:03:15.354Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.354Z
Learning: Match how the same feature is implemented in the TypeScript pnpm CLI — any change in pacquet must match pnpm's behavior, logic, edge cases, config resolution, error messages, file/lockfile formats, and existing tests

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T23:07:43.668Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11783
File: pacquet/crates/engine-runtime-bun-resolver/src/bun_resolver.rs:70-88
Timestamp: 2026-05-20T23:07:43.668Z
Learning: In `pacquet/crates/engine-runtime-bun-resolver/src/bun_resolver.rs`, `resolve_impl` intentionally passes `ResolveOptions::default()` (not the caller-provided `opts`) when delegating to the npm resolver. This mirrors the upstream TypeScript code at `engine/runtime/bun-resolver/src/index.ts#L46` which passes an empty `{}` literal; forwarding outer opts would diverge from pnpm's behavior.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
📚 Learning: 2026-05-20T01:42:44.958Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11755
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:370-380
Timestamp: 2026-05-20T01:42:44.958Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/pick_package.rs`, the fetch-error fallback that returns `Ok(PickPackageResult { meta: disk, picked_package: picked })` — even when `picked` is `None` — is intentional upstream parity with pnpm's `pickPackage.ts` catch block (https://github.com/pnpm/pnpm/blob/f657b5cb44/resolving/npm-resolver/src/pickPackage.ts#L420-L431). When a fetch fails and a disk mirror exists, the stale-mirror pick (including null/None) is returned verbatim; the transport error is logged via `tracing::debug!`. Do not flag this as a bug.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
🔇 Additional comments (8)
pnpr/crates/pnpr/src/install_accelerator/resolve.rs (8)

1-7: LGTM!


60-69: LGTM!


75-108: LGTM!


110-117: LGTM!


119-135: LGTM!


332-354: LGTM!


356-369: LGTM!


371-372: LGTM!


📝 Walkthrough

Walkthrough

This PR moves the agent lockfile wire to on-disk LockfileFile, updates the agent client and install path to use that format, implements pnpr multi-project importer normalization and temp-workspace reconstruction, and updates tests/CI and repo wiring to target pnpr while removing server-side agent artifacts.

Changes

Agent migration to pnpr with lockfile and workspace updates

Layer / File(s) Summary
Lockfile on-disk format contract and serialization
lockfile/fs/src/read.ts
readWantedLockfileFile added; _read and helpers now return both LockfileFile (on-disk) and LockfileObject (in-memory); conversion helpers used for merged results.
Agent client and install-path wiring (LockfileFile)
agent/client/src/fetchFromPnpmRegistry.ts, agent/client/package.json, agent/client/tsconfig.json, installing/deps-installer/src/install/index.ts, installing/deps-installer/src/install/extendInstallOptions.ts, worker/src/start.ts
Agent client option types and streamed payloads switched to transmit LockfileFile; installFromPnpmRegistry now reads the wanted lockfile via readWantedLockfileFile; tsconfig/package updated and agent option docs/comments expanded.
pnpr protocol updates for multi-project and importer normalization
pnpr/crates/pnpr/src/install_accelerator/protocol.rs
InstallRequestProject gains dir; InstallRequest::projects_normalized() returns per-importer ProjectDeps and replaces single-project helpers.
pnpr workspace reconstruction and resolve flow
pnpr/crates/pnpr/src/install_accelerator/resolve.rs, pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs
Resolve reconstructs temporary workspace from normalized projects, writes per-importer synthetic manifests and optional root/pnpm-workspace.yaml, validates importer dirs, runs pacquet lockfile-only resolution, and loads the produced lockfile; tests for sanitization and manifest naming added.
pnpr install-accelerator endpoint and diff docs
pnpr/crates/pnpr/src/install_accelerator.rs, pnpr/crates/pnpr/src/install_accelerator/diff.rs
Handler no longer rejects multi-project requests; documentation clarified for workspace handling and diff computation.
Meta-updater removal of pnpm-agent special handling
.meta-updater/src/index.ts
Removes pnpm-agent from SOURCE_AVAILABLE_PKGS, deletes special-case branch in manifest update, and writes license: 'MIT' unconditionally in generated manifests.
Test registry migration and repo cleanup
pnpm/test/install/pnpmRegistry.ts, pnpm/package.json, pnpm/tsconfig.json, __utils__/jest-config/*, pnpm-workspace.yaml
Tests now proxy to the jest-preset pnpr server and add an e2e lockfile-reuse test; pnpm-agent dev dep and TS project reference removed; @pnpm/pnpr test helper dep removed; jest globalSetup simplified; workspace catalog entry updated.
Server-side cleanup
agent/server/src/fileStore.ts, agent/server/src/index.ts
Deletes the SQLite-backed FileStore implementation and removes re-exports of server utilities and protocol types from the server package entrypoint.
CI: prebuilt pnpr binary cache and build gating
.github/workflows/test.yml
Adds cache restore for prebuilt pnpr and pnpr-prepare binaries, conditionally builds them on cache miss, and exports PNPR_BIN/PNPR_PREPARE_BIN paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pnpm#11964: Overlapping changes to pnpr binary discovery and test workflow wiring.
  • pnpm/pnpm#11662: Related lockfile/fs format and conversion fixes.

Suggested reviewers

  • KSXGitHub

Poem

🐰 I hopped through locks both raw and neat,
Sent on-disk notes for pnpr to meet.
Workspaces stitched in a temp-dir song,
Tests now hum where agents moved along.
Hooray — a carrot for every merge!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deprecate-agent

@codecov-commenter

codecov-commenter commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.62162% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.57%. Comparing base (f429f93) to head (e40d59c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...npr/crates/pnpr/src/install_accelerator/resolve.rs 74.13% 15 Missing ⚠️
...pr/crates/pnpr/src/install_accelerator/protocol.rs 62.50% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12151      +/-   ##
==========================================
+ Coverage   87.56%   87.57%   +0.01%     
==========================================
  Files         267      267              
  Lines       30649    30710      +61     
==========================================
+ Hits        26837    26894      +57     
- Misses       3812     3816       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zkochan zkochan force-pushed the deprecate-agent branch from 7299648 to 318bce6 Compare June 2, 2026 21:01
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.841 ± 0.115 1.700 2.089 1.01 ± 0.07
pacquet@main 1.826 ± 0.050 1.754 1.935 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.84146457696,
      "stddev": 0.11546319578162129,
      "median": 1.83398734886,
      "user": 2.8256139399999998,
      "system": 2.22499222,
      "min": 1.69999182736,
      "max": 2.08871451536,
      "times": [
        1.96108029136,
        1.75695415536,
        1.8584042203600002,
        1.83675816636,
        1.73827639336,
        1.76899599936,
        1.87425366936,
        1.83121653136,
        1.69999182736,
        2.08871451536
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.82623376396,
      "stddev": 0.050159105814385305,
      "median": 1.8309992483600002,
      "user": 2.7895607399999998,
      "system": 2.25749502,
      "min": 1.75440231736,
      "max": 1.93454062736,
      "times": [
        1.75440231736,
        1.85275939336,
        1.84531259036,
        1.8370814913600002,
        1.79529454436,
        1.79707966436,
        1.8249170053600001,
        1.93454062736,
        1.84410830536,
        1.7768417003599999
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 560.1 ± 15.5 541.3 595.4 1.03 ± 0.06
pacquet@main 542.8 ± 27.5 523.8 617.9 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.5601436452599999,
      "stddev": 0.015508849208250597,
      "median": 0.55462221286,
      "user": 0.40976973999999994,
      "system": 0.8882183600000001,
      "min": 0.54132907086,
      "max": 0.5954423948600001,
      "times": [
        0.5954423948600001,
        0.55179974286,
        0.5628258688600001,
        0.5746557668600001,
        0.54861304786,
        0.55527966886,
        0.56503639986,
        0.55396475686,
        0.5524897348600001,
        0.54132907086
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.54282169906,
      "stddev": 0.02746739030790533,
      "median": 0.53440828486,
      "user": 0.39042694,
      "system": 0.8757921599999999,
      "min": 0.52380020886,
      "max": 0.61793769086,
      "times": [
        0.61793769086,
        0.5393872468600001,
        0.52902304986,
        0.5492576598600001,
        0.53303400186,
        0.52380020886,
        0.5258981888600001,
        0.54173050786,
        0.53578256786,
        0.5323658678600001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.350 ± 0.041 2.292 2.404 1.01 ± 0.03
pacquet@main 2.316 ± 0.056 2.231 2.394 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.3500621144,
      "stddev": 0.04100243045715337,
      "median": 2.3547028715,
      "user": 4.3350221399999995,
      "system": 2.19584242,
      "min": 2.2916353634999997,
      "max": 2.4044917115,
      "times": [
        2.4044917115,
        2.3947882915,
        2.3981878045,
        2.3552189045,
        2.3231394405,
        2.3541868385,
        2.3607685215,
        2.3190899055,
        2.2991143624999997,
        2.2916353634999997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.3159471673,
      "stddev": 0.05568266499372405,
      "median": 2.3031269415,
      "user": 4.25996144,
      "system": 2.19718362,
      "min": 2.2305551484999997,
      "max": 2.3937561735,
      "times": [
        2.2612207255,
        2.2305551484999997,
        2.2883707995,
        2.3661568655,
        2.2749817365,
        2.2987452155,
        2.3937561735,
        2.3862539305,
        2.3075086675,
        2.3519224105
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.517 ± 0.023 1.483 1.552 1.00
pacquet@main 1.588 ± 0.084 1.487 1.789 1.05 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.51675569952,
      "stddev": 0.022693026034219164,
      "median": 1.52020203682,
      "user": 1.9360488,
      "system": 1.2369958,
      "min": 1.48281644432,
      "max": 1.55246308732,
      "times": [
        1.52187795932,
        1.53234785532,
        1.48884597432,
        1.48281644432,
        1.50266166532,
        1.49927185832,
        1.51852611432,
        1.53041297132,
        1.53833306532,
        1.55246308732
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.5876469764199999,
      "stddev": 0.08391295576892865,
      "median": 1.58704422282,
      "user": 1.9828265000000003,
      "system": 1.2849792,
      "min": 1.48695008432,
      "max": 1.7890921803200002,
      "times": [
        1.52970950532,
        1.58574493932,
        1.50004764232,
        1.48695008432,
        1.7890921803200002,
        1.5736942663200002,
        1.58834350632,
        1.61908893932,
        1.59813167232,
        1.60566702832
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12151
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,350.06 ms
(+0.30%)Baseline: 2,342.99 ms
2,811.59 ms
(83.58%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,516.76 ms
(-0.12%)Baseline: 1,518.65 ms
1,822.38 ms
(83.23%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,841.46 ms
(-10.27%)Baseline: 2,052.16 ms
2,462.59 ms
(74.78%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
560.14 ms
(-13.77%)Baseline: 649.59 ms
779.51 ms
(71.86%)
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan marked this pull request as ready for review June 2, 2026 21:20
Copilot AI review requested due to automatic review settings June 2, 2026 21:20
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Replace pnpm-agent server with pnpr and add multi-project workspace support

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Replace experimental pnpm-agent TypeScript server with pnpr Rust server
• Add multi-project workspace support to pnpr's install resolution
• Bridge on-disk/in-memory lockfile format mismatch between client and server
• Add path-traversal validation for importer directories in pnpr
• Update pnpm client to read and forward on-disk lockfile format
Diagram
flowchart LR
  A["pnpm Client"] -->|"on-disk lockfile format"| B["pnpr Server"]
  B -->|"resolve workspace"| C["Reconstruct temp workspace"]
  C -->|"root + members"| D["pacquet install"]
  D -->|"resolved lockfile"| E["Convert to in-memory"]
  E -->|"LockfileObject"| A
  F["readWantedLockfileFile"] -->|"skip conversion"| B
  G["sanitized_importer_dir"] -->|"path validation"| C

Loading

Grey Divider

File Changes

1. pnpr/crates/pnpr/src/install_accelerator/resolve.rs ✨ Enhancement +94/-19

Multi-project workspace resolution with path validation

pnpr/crates/pnpr/src/install_accelerator/resolve.rs


2. pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs 🧪 Tests +30/-0

Unit tests for importer dir sanitization and manifest naming

pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs


3. pnpr/crates/pnpr/src/install_accelerator/protocol.rs ✨ Enhancement +30/-18

Add importer dir field to install request protocol

pnpr/crates/pnpr/src/install_accelerator/protocol.rs


View more (32)
4. pnpr/crates/pnpr/src/install_accelerator.rs 📝 Documentation +6/-10

Update module docs for multi-project workspace support

pnpr/crates/pnpr/src/install_accelerator.rs


5. pnpr/crates/pnpr/src/install_accelerator/diff.rs 📝 Documentation +3/-3

Remove outdated pnpm-agent reference from comments

pnpr/crates/pnpr/src/install_accelerator/diff.rs


6. lockfile/fs/src/read.ts ✨ Enhancement +35/-6

Add readWantedLockfileFile to expose on-disk lockfile format

lockfile/fs/src/read.ts


7. agent/client/src/fetchFromPnpmRegistry.ts 🐞 Bug fix +17/-7

Convert on-disk lockfile to in-memory format on response

agent/client/src/fetchFromPnpmRegistry.ts


8. pnpm/test/install/pnpmRegistry.ts 🧪 Tests +25/-25

Route e2e tests through pnpr server instead of pnpm-agent

pnpm/test/install/pnpmRegistry.ts


9. .meta-updater/src/index.ts ⚙️ Configuration changes +1/-10

Remove pnpm-agent from experimental and source-available packages

.meta-updater/src/index.ts


10. installing/deps-installer/src/install/index.ts ✨ Enhancement +5/-3

Use readWantedLockfileFile for on-disk lockfile format

installing/deps-installer/src/install/index.ts


11. worker/src/start.ts 📝 Documentation +1/-1

Update comment to reference pnpr instead of pnpm-agent

worker/src/start.ts


12. installing/deps-installer/src/install/extendInstallOptions.ts 📝 Documentation +5/-1

Update agent option documentation to reference pnpr

installing/deps-installer/src/install/extendInstallOptions.ts


13. agent/client/package.json Dependencies +1/-0

Add @pnpm/lockfile.fs dependency for format conversion

agent/client/package.json


14. agent/client/tsconfig.json ⚙️ Configuration changes +3/-0

Add lockfile.fs to TypeScript project references

agent/client/tsconfig.json


15. pnpm-lock.yaml Dependencies +3/-49

Update lockfile with new agent/client dependency

pnpm-lock.yaml


16. agent/server/CHANGELOG.md Additional files +0/-227

...

agent/server/CHANGELOG.md


17. agent/server/CONTRIBUTING.md Additional files +0/-35

...

agent/server/CONTRIBUTING.md


18. agent/server/Dockerfile Additional files +0/-23

...

agent/server/Dockerfile


19. agent/server/LICENSE.md Additional files +0/-91

...

agent/server/LICENSE.md


20. agent/server/README.md Additional files +0/-192

...

agent/server/README.md


21. agent/server/package.json Additional files +0/-65

...

agent/server/package.json


22. agent/server/src/bin.ts Additional files +0/-56

...

agent/server/src/bin.ts


23. agent/server/src/createRegistryServer.ts Additional files +0/-574

...

agent/server/src/createRegistryServer.ts


24. agent/server/src/diff.ts Additional files +0/-151

...

agent/server/src/diff.ts


25. agent/server/src/fileStore.ts Additional files +0/-75

...

agent/server/src/fileStore.ts


26. agent/server/src/index.ts Additional files +0/-3

...

agent/server/src/index.ts


27. agent/server/src/metadataStore.ts Additional files +0/-133

...

agent/server/src/metadataStore.ts


28. agent/server/src/protocol.ts Additional files +0/-22

...

agent/server/src/protocol.ts


29. agent/server/test/diff.ts Additional files +0/-262

...

agent/server/test/diff.ts


30. agent/server/test/integration.ts Additional files +0/-222

...

agent/server/test/integration.ts


31. agent/server/test/tsconfig.json Additional files +0/-18

...

agent/server/test/tsconfig.json


32. agent/server/tsconfig.json Additional files +0/-46

...

agent/server/tsconfig.json


33. agent/server/tsconfig.lint.json Additional files +0/-8

...

agent/server/tsconfig.lint.json


34. pnpm/package.json Additional files +0/-1

...

pnpm/package.json


35. pnpm/tsconfig.json Additional files +0/-3

...

pnpm/tsconfig.json


Grey Divider

Qodo Logo

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the experimental TypeScript pnpm-agent server from the monorepo and switches the pnpm e2e “agent” coverage to exercise the Rust pnpr server implementing the same /v1/install + /v1/files protocol. It also bridges the lockfile wire-format mismatch by introducing a “raw/on-disk” wanted-lockfile reader and converting server responses into pnpm’s in-memory lockfile object.

Changes:

  • Remove agent/server (pnpm-agent) and scrub references (workspace deps, tsconfig project refs, meta-updater behavior).
  • Update @pnpm/agent.client + deps-installer to send/receive the on-disk lockfile format over the wire, converting only at the client boundary.
  • Extend pnpr install-accelerator to support multi-project workspace requests by reconstructing a workspace on disk, including path traversal protection + tests.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
worker/src/start.ts Updates agent URL example comment to reference pnpr.
pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs Adds unit tests for importer-dir sanitization + manifest name uniqueness.
pnpr/crates/pnpr/src/install_accelerator/resolve.rs Reconstructs workspace installs in a temp dir; adds importer dir sanitization and synthetic manifest naming.
pnpr/crates/pnpr/src/install_accelerator/protocol.rs Adds dir to project requests and normalizes legacy vs multi-project request shapes.
pnpr/crates/pnpr/src/install_accelerator/diff.rs Updates module docs to remove pnpm-agent wording.
pnpr/crates/pnpr/src/install_accelerator.rs Removes hard rejection for multi-project requests and updates docs to reflect workspace support.
pnpm/tsconfig.json Drops tsconfig project reference to removed agent/server.
pnpm/test/install/pnpmRegistry.ts Routes agent e2e tests through the pnpr server and adds an incremental-resolution case (forward existing lockfile).
pnpm/package.json Removes pnpm-agent workspace dependency.
pnpm-lock.yaml Removes agent/server importer; adds @pnpm/lockfile.fs dependency for agent/client.
lockfile/fs/src/read.ts Introduces readWantedLockfileFile() and plumbs raw on-disk lockfile shape through the internal read path.
installing/deps-installer/src/install/index.ts Reads and forwards the raw lockfile shape to the agent instead of the in-memory lockfile object.
installing/deps-installer/src/install/extendInstallOptions.ts Updates agent option docs to be server-agnostic and mention pnpr.
agent/server/tsconfig.lint.json Deletes pnpm-agent server config (package removed).
agent/server/tsconfig.json Deletes pnpm-agent server config (package removed).
agent/server/test/tsconfig.json Deletes pnpm-agent server tests config (package removed).
agent/server/test/integration.ts Deletes pnpm-agent integration tests (package removed).
agent/server/test/diff.ts Deletes pnpm-agent diff unit tests (package removed).
agent/server/src/protocol.ts Deletes pnpm-agent server protocol types (package removed).
agent/server/src/metadataStore.ts Deletes pnpm-agent server metadata store (package removed).
agent/server/src/index.ts Deletes pnpm-agent server exports (package removed).
agent/server/src/fileStore.ts Deletes pnpm-agent server file store (package removed).
agent/server/src/diff.ts Deletes pnpm-agent server diff logic (package removed).
agent/server/src/createRegistryServer.ts Deletes pnpm-agent server implementation (package removed).
agent/server/src/bin.ts Deletes pnpm-agent server CLI (package removed).
agent/server/README.md Deletes pnpm-agent documentation (package removed).
agent/server/package.json Deletes pnpm-agent package manifest (package removed).
agent/server/LICENSE.md Deletes pnpm-agent license file (package removed).
agent/server/Dockerfile Deletes pnpm-agent Dockerfile (package removed).
agent/server/CONTRIBUTING.md Deletes pnpm-agent contributing terms (package removed).
agent/server/CHANGELOG.md Deletes pnpm-agent changelog (package removed).
agent/client/tsconfig.json Adds project reference to lockfile/fs (needed for lockfile conversion import).
agent/client/src/fetchFromPnpmRegistry.ts Converts server-returned lockfile from on-disk to in-memory format; updates lockfile option type to on-disk.
agent/client/package.json Adds @pnpm/lockfile.fs dependency for lockfile conversion.
.meta-updater/src/index.ts Removes pnpm-agent special-casing (experimental + license handling) and normalizes license handling.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pnpr/crates/pnpr/src/install_accelerator/resolve.rs Outdated
Comment thread pnpr/crates/pnpr/src/install_accelerator/resolve.rs Outdated
Comment thread .meta-updater/src/index.ts
@zkochan zkochan force-pushed the deprecate-agent branch 2 times, most recently from 1147b13 to 333bf9c Compare June 2, 2026 22:25
Copilot AI review requested due to automatic review settings June 2, 2026 22:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

pnpr/crates/pnpr/src/install_accelerator/resolve.rs:343

  • sanitized_importer_dir currently normalizes a string of only slashes (e.g. "/" or "////") to "." because trim_end_matches('/') runs before the absolute-path check. That can silently accept an absolute path and also collide with the real root importer (".") in the same request.

Comment thread pnpr/crates/pnpr/src/install_accelerator/resolve.rs
Comment thread pnpr/crates/pnpr/src/install_accelerator/resolve/tests.rs Outdated
The experimental TypeScript `pnpm-agent` install-accelerator server is
superseded by the `pnpr` server, which implements the same protocol.
Remove `agent/server` and route the agent e2e test through pnpr.

The pnpm TypeScript client (`@pnpm/agent.client`) is kept and made
compatible with pnpr. The wire protocol carries the on-disk lockfile
format, while pnpm keeps an in-memory `LockfileObject` in process:

- Incoming: the agent's response lockfile is converted to the in-memory
  shape via `convertToLockfileObject`.
- Outgoing: the existing lockfile is read in its on-disk shape with the
  new `readWantedLockfileFile` and forwarded as-is — no in-memory
  round-trip.

pnpr now resolves multi-project workspaces by reconstructing the
workspace on disk (root manifest + `pnpm-workspace.yaml` + member
manifests) and letting pacquet's install path discover every importer.
Member dirs are written as quoted YAML scalars; importer dirs are
validated against path traversal (rejecting absolute, `..`, backslash,
and slashes-only inputs) and de-duplicated; synthetic manifest names
map injectively from dirs.

The CI test job builds the `pnpr` server from source (cached on the
Rust sources) so the agent e2e tests run against the current server.
The published `@pnpm/pnpr` is dropped as a test dependency: running the
suite already requires building `pnpr-prepare` from source (no npm
fallback), so the toolchain to build `pnpr` is always present, and the
published binary can predate the server protocol the tests exercise.
@zkochan zkochan force-pushed the deprecate-agent branch from 333bf9c to e40d59c Compare June 2, 2026 22:35
@zkochan zkochan merged commit 2b788d5 into main Jun 2, 2026
26 checks passed
@zkochan zkochan deleted the deprecate-agent branch June 2, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants