Declare host-provided typebox in peerDependencies, not dependencies #285

Closed
opened 2026-09-29 23:13:09 +00:00 by david · 0 comments
Owner

Summary

Pi emits a startup warning because typebox — a host-provided package — is declared in dependencies in the root package.json:

[Extension issues]
  ~/Projects/pi-extensions-and-skills/package.json
    Host-provided extension packages must be declared in peerDependencies with a "*" range,
    not dependencies: typebox. Installed copies can bypass the extension loader and create
    duplicate runtime modules.

The fix is to declare typebox in peerDependencies with a "*" range and remove it from dependencies, in the root manifest and in the sub-extension manifests that repeat the violation.

Background

Pi's resource loader (dist/core/resource-loader.js) contains a hardcoded set of host-provided packages:

HOST_PROVIDED_EXTENSION_PACKAGES = new Set([
  "@earendil-works/pi-agent-core", "@earendil-works/pi-ai",
  "@earendil-works/pi-coding-agent", "@earendil-works/pi-tui",
  "@mariozechner/pi-agent-core", "@mariozechner/pi-ai",
  "@mariozechner/pi-coding-agent", "@mariozechner/pi-tui",
  "@sinclair/typebox", "typebox",
]);

For every extension package root it reads package.json and warns about any of those names found in dependencies. Only dependencies is inspected — peerDependencies is the sanctioned location.

Pi supplies those packages to extensions through a virtual-module map rather than through disk resolution:

// dist/bundle/chunks/virtual-modules-CSWRO37N.js
VIRTUAL_MODULES = { typebox: build_exports, "typebox/compile": ..., "typebox/value": ..., ... }
// dist/core/extensions/loader.js (built Node mode — a jiti alias)
typebox: require.resolve("typebox")   // resolved from the pi install, not the extension

A physical node_modules/typebox can bypass that mapping in compiled-ESM paths, producing two TypeBox instances (mismatched Kind symbols/registries, duplicate initialization work). docs/packages.md states the rule: host-provided packages belong in peerDependencies with a "*" range, must not be bundled, and must not appear in dependencies.

This repo is consumed as a local path package (~/.pi/agent/settings.json → "../../Projects/pi-extensions-and-skills"), and per the docs local packages are not installed or modified by pi, so the manifest declaration is the author's responsibility.

Current state

Manifest typebox in dependencies Notes
package.json (root) yes ("*") the manifest pi warns about
extensions/pr-comments/package.json yes (^1.3.6) pi.extensions set; would warn if loaded/installed standalone
extensions/forgejo/package.json yes (^1.3.6) same
extensions/searxng/package.json yes (^1.3.19) same
extensions/victorialogs/package.json yes (^1.3.19) same
extensions/grillme/package.json no — already peerDependencies correct reference pattern

All 11 extensions import typebox (import { Type } from "typebox"), and the penpot tests additionally import typebox/value. Every one of those specifiers is present in VIRTUAL_MODULES, so the host copy covers all usage in this repo — nothing here requires a private copy.

Five divergent typebox versions are currently resolvable from the repo:

Location Version
host global pi 0.99.1 dependency 1.3.27
node_modules/typebox (hoisted by the root dependencies entry) 1.3.13
node_modules/@earendil-works/pi-coding-agent/node_modules/typebox 1.3.7
extensions/grillme/node_modules/typebox 1.3.13
extensions/pr-comments/node_modules/typebox 1.3.6

Implementation details

  1. Root package.json: remove "typebox": "*" from dependencies, add "typebox": "*" to peerDependencies (alongside the existing @earendil-works/pi-coding-agent and @earendil-works/pi-tui entries).
  2. extensions/pr-comments/package.json, extensions/forgejo/package.json, extensions/searxng/package.json, extensions/victorialogs/package.json: move typebox from dependencies to peerDependencies with a "*" range. Match the grillme pattern; do not invent new ranges.
  3. Refresh the lockfiles that record the changed declarations, so npm ci / bun install --frozen-lockfile stay in sync: package-lock.json (root), extensions/pr-comments/package-lock.json, extensions/forgejo/package-lock.json (both npm), and extensions/searxng/bun.lock + extensions/victorialogs/bun.lock (bun). Keep the diffs structural (the workspace/root entry's dependencies → peerDependencies move, plus "peer": true on the typebox node in the npm locks) rather than accepting an unrelated whole-file metadata rewrite — see the note below.
  4. Update the README dependency descriptions that call typebox a runtime dependency: README.md:89 (npm install dependency list) and the repository-layout annotations at README.md:148–155.

Behaviour notes to preserve:

  • npm install auto-installs a root project's own peerDependencies, so the local node_modules/typebox still appears and npm test, tsc typechecks and bun test keep resolving. This was verified empirically (peer-only manifest ⇒ peer installed) and is confirmed by extensions/grillme, whose peer-only declaration already yields a physical 1.3.13.
  • A plain npm install under npm 11 rewrites ~144 unrelated "peer": true and "license" fields across the committed lockfile (the committed lock was produced by a different npm). The same rewrite reproduces from the unmodified committed manifest, so it is tooling drift and out of scope here; the lock diffs are limited to the entries this change actually affects, and the result is validated with npm ci --dry-run.
  • Remaining physical copies of host packages installed by local npm install (e.g. the root peers @earendil-works/pi-coding-agent@0.84.1 / pi-tui@0.84.1) are out of scope: pi does not warn on peerDependencies, and the jiti alias wins at runtime.

Acceptance criteria

  • No manifest in the repo lists a HOST_PROVIDED_EXTENSION_PACKAGES name in dependencies.
  • Root and the four sub-manifests declare typebox in peerDependencies with a "*" range.
  • The [Extension issues] warning no longer appears on pi startup for this package.
  • npm test passes.
  • package-lock.json and the affected bun.lock files are consistent with the new manifests.
  • README no longer describes typebox as a runtime dependencies entry.

Test plan

  1. Reproduce the warning pre-fix by applying the loader's check to each manifest (HOST_PROVIDED_EXTENSION_PACKAGES ∩ dependencies) — expect typebox reported for the root and the four sub-manifests.
  2. Apply the manifest changes, re-run the same check — expect no hits anywhere.
  3. Exercise pi's real warning path: load the repo as a package through DefaultResourceLoader and assert warnings.length === 0 (and errors.length === 0), against the pre-fix manifests as the control — expect exactly the reported warning before, none after.
  4. npm install and confirm node_modules/typebox still resolves (dev/test parity); npm ci --dry-run at the root and in extensions/pr-comments + extensions/forgejo.
  5. bun install --frozen-lockfile in extensions/searxng and extensions/victorialogs — expect success with the lock untouched.
  6. npm test — the full suite (scripts/local-install.test.mjs, penpot, learn-repo, vision tests).
  7. Confirm a typebox-backed extension still registers its tool schemas after the change (rg override, forgejo, penpot) and no extension error is reported.
## Summary Pi emits a startup warning because `typebox` — a host-provided package — is declared in `dependencies` in the root `package.json`: ``` [Extension issues] ~/Projects/pi-extensions-and-skills/package.json Host-provided extension packages must be declared in peerDependencies with a "*" range, not dependencies: typebox. Installed copies can bypass the extension loader and create duplicate runtime modules. ``` The fix is to declare `typebox` in `peerDependencies` with a `"*"` range and remove it from `dependencies`, in the root manifest and in the sub-extension manifests that repeat the violation. ## Background Pi's resource loader (`dist/core/resource-loader.js`) contains a hardcoded set of host-provided packages: ```js HOST_PROVIDED_EXTENSION_PACKAGES = new Set([ "@earendil-works/pi-agent-core", "@earendil-works/pi-ai", "@earendil-works/pi-coding-agent", "@earendil-works/pi-tui", "@mariozechner/pi-agent-core", "@mariozechner/pi-ai", "@mariozechner/pi-coding-agent", "@mariozechner/pi-tui", "@sinclair/typebox", "typebox", ]); ``` For every extension package root it reads `package.json` and warns about any of those names found in **`dependencies`**. Only `dependencies` is inspected — `peerDependencies` is the sanctioned location. Pi supplies those packages to extensions through a virtual-module map rather than through disk resolution: ```js // dist/bundle/chunks/virtual-modules-CSWRO37N.js VIRTUAL_MODULES = { typebox: build_exports, "typebox/compile": ..., "typebox/value": ..., ... } // dist/core/extensions/loader.js (built Node mode — a jiti alias) typebox: require.resolve("typebox") // resolved from the pi install, not the extension ``` A physical `node_modules/typebox` can bypass that mapping in compiled-ESM paths, producing two TypeBox instances (mismatched `Kind` symbols/registries, duplicate initialization work). `docs/packages.md` states the rule: host-provided packages belong in `peerDependencies` with a `"*"` range, must not be bundled, and must not appear in `dependencies`. This repo is consumed as a *local path* package (`~/.pi/agent/settings.json` → `"../../Projects/pi-extensions-and-skills"`), and per the docs local packages are not installed or modified by pi, so the manifest declaration is the author's responsibility. ## Current state | Manifest | `typebox` in `dependencies` | Notes | |---|---|---| | `package.json` (root) | yes (`"*"`) | the manifest pi warns about | | `extensions/pr-comments/package.json` | yes (`^1.3.6`) | `pi.extensions` set; would warn if loaded/installed standalone | | `extensions/forgejo/package.json` | yes (`^1.3.6`) | same | | `extensions/searxng/package.json` | yes (`^1.3.19`) | same | | `extensions/victorialogs/package.json` | yes (`^1.3.19`) | same | | `extensions/grillme/package.json` | no — already `peerDependencies` | correct reference pattern | All 11 extensions import `typebox` (`import { Type } from "typebox"`), and the penpot tests additionally import `typebox/value`. Every one of those specifiers is present in `VIRTUAL_MODULES`, so the host copy covers all usage in this repo — nothing here requires a private copy. Five divergent typebox versions are currently resolvable from the repo: | Location | Version | |---|---| | host global pi 0.99.1 dependency | 1.3.27 | | `node_modules/typebox` (hoisted by the root `dependencies` entry) | 1.3.13 | | `node_modules/@earendil-works/pi-coding-agent/node_modules/typebox` | 1.3.7 | | `extensions/grillme/node_modules/typebox` | 1.3.13 | | `extensions/pr-comments/node_modules/typebox` | 1.3.6 | ## Implementation details 1. Root `package.json`: remove `"typebox": "*"` from `dependencies`, add `"typebox": "*"` to `peerDependencies` (alongside the existing `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` entries). 2. `extensions/pr-comments/package.json`, `extensions/forgejo/package.json`, `extensions/searxng/package.json`, `extensions/victorialogs/package.json`: move `typebox` from `dependencies` to `peerDependencies` with a `"*"` range. Match the grillme pattern; do not invent new ranges. 3. Refresh the lockfiles that record the changed declarations, so `npm ci` / `bun install --frozen-lockfile` stay in sync: `package-lock.json` (root), `extensions/pr-comments/package-lock.json`, `extensions/forgejo/package-lock.json` (both npm), and `extensions/searxng/bun.lock` + `extensions/victorialogs/bun.lock` (bun). Keep the diffs structural (the workspace/root entry's `dependencies` → `peerDependencies` move, plus `"peer": true` on the `typebox` node in the npm locks) rather than accepting an unrelated whole-file metadata rewrite — see the note below. 4. Update the README dependency descriptions that call `typebox` a runtime dependency: `README.md:89` (`npm install` dependency list) and the repository-layout annotations at `README.md:148–155`. Behaviour notes to preserve: - `npm install` auto-installs a root project's own `peerDependencies`, so the local `node_modules/typebox` still appears and `npm test`, `tsc` typechecks and `bun test` keep resolving. This was verified empirically (peer-only manifest ⇒ peer installed) and is confirmed by `extensions/grillme`, whose peer-only declaration already yields a physical 1.3.13. - A plain `npm install` under npm 11 rewrites ~144 unrelated `"peer": true` and `"license"` fields across the committed lockfile (the committed lock was produced by a different npm). The same rewrite reproduces from the *unmodified* committed manifest, so it is tooling drift and out of scope here; the lock diffs are limited to the entries this change actually affects, and the result is validated with `npm ci --dry-run`. - Remaining physical copies of host packages installed by local `npm install` (e.g. the root peers `@earendil-works/pi-coding-agent@0.84.1` / `pi-tui@0.84.1`) are out of scope: pi does not warn on `peerDependencies`, and the jiti alias wins at runtime. ## Acceptance criteria - [ ] No manifest in the repo lists a `HOST_PROVIDED_EXTENSION_PACKAGES` name in `dependencies`. - [ ] Root and the four sub-manifests declare `typebox` in `peerDependencies` with a `"*"` range. - [ ] The `[Extension issues]` warning no longer appears on pi startup for this package. - [ ] `npm test` passes. - [ ] `package-lock.json` and the affected `bun.lock` files are consistent with the new manifests. - [ ] README no longer describes `typebox` as a runtime `dependencies` entry. ## Test plan 1. Reproduce the warning pre-fix by applying the loader's check to each manifest (`HOST_PROVIDED_EXTENSION_PACKAGES ∩ dependencies`) — expect `typebox` reported for the root and the four sub-manifests. 2. Apply the manifest changes, re-run the same check — expect no hits anywhere. 3. Exercise pi's real warning path: load the repo as a package through `DefaultResourceLoader` and assert `warnings.length === 0` (and `errors.length === 0`), against the pre-fix manifests as the control — expect exactly the reported warning before, none after. 4. `npm install` and confirm `node_modules/typebox` still resolves (dev/test parity); `npm ci --dry-run` at the root and in `extensions/pr-comments` + `extensions/forgejo`. 5. `bun install --frozen-lockfile` in `extensions/searxng` and `extensions/victorialogs` — expect success with the lock untouched. 6. `npm test` — the full suite (`scripts/local-install.test.mjs`, penpot, learn-repo, vision tests). 7. Confirm a typebox-backed extension still registers its tool schemas after the change (rg override, forgejo, penpot) and no extension error is reported.
david closed this issue 2026-09-29 23:17:53 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-extensions-and-skills#285
No description provided.