issue-285: declare host-provided typebox in peerDependencies #286

Merged
david merged 1 commit from bug/issue-285/declare-host-provided-typebox-in-peerdependencies into main 2026-09-29 23:17:53 +00:00
Owner

Closes #285

Problem

Pi reported an extension-package warning on startup:

[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.

Per docs/packages.md, host-provided packages (typebox among them) must be declared in peerDependencies with a "*" range, must not be bundled, and must not appear in dependencies. Pi supplies them to extensions through a virtual-module map (dist/bundle/chunks/virtual-modules-*.js → VIRTUAL_MODULES) and a jiti alias in dist/core/extensions/loader.js, so a physical copy can bypass that mapping and load a second TypeBox instance — duplicate classes, registries and initialization work.

Scope (wider than the single warning)

Straightening out the reported root manifest exposed four sub-manifests with the same violation, plus the lockfiles that record them:

Artifact Change
package.json typebox "*": dependencies → peerDependencies
extensions/pr-comments/package.json typebox ^1.3.6 → peer "*" (no other deps)
extensions/forgejo/package.json typebox ^1.3.6 → peer "*" (adm-zip stays a dependency)
extensions/searxng/package.json typebox ^1.3.19 → peer "*" (no other deps)
extensions/victorialogs/package.json typebox ^1.3.19 → peer "*" (no other deps)
package-lock.json, extensions/pr-comments/package-lock.json, extensions/forgejo/package-lock.json root entry follows the manifest; "peer": true on the typebox node
extensions/searxng/bun.lock, extensions/victorialogs/bun.lock workspace entry follows the manifest (bun's own --lockfile-only output)
README.md typebox no longer described as a runtime dependency (:89, layout list :148–155)
CHANGELOG.md entry under Unreleased → Fixed

extensions/grillme already used the correct pattern and is unchanged; README.md:148-155 annotations for vision/penpot/searxng/victorialogs now read "typebox from the host".

Per-file detail

package.json — the flagged manifest. dependencies keeps the genuine runtime deps (adm-zip, dotenv, image-size, mongodb, pg, pgpass); peerDependencies now lists the three host-provided packages (@earendil-works/pi-coding-agent, @earendil-works/pi-tui, typebox). The two existing peers are untouched.

extensions/pr-comments/package.json, extensions/searxng/package.json, extensions/victorialogs/package.json — typebox was the only dependencies entry, so the key is replaced by peerDependencies (declaration order: peerDependencies then devDependencies). No other field changes; @earendil-works/pi-coding-agent stays a devDependency (it is a compile-time types import there, not a runtime one).

extensions/forgejo/package.json — adm-zip stays in dependencies; typebox moves to a new peerDependencies block.

Lockfiles — deliberately minimal, not a whole-file rewrite. A plain npm install under npm 11 rewrites ~144 unrelated "peer": true and "license" fields in the committed package-lock.json. That rewrite reproduces from the unmodified committed manifest, so it is tooling drift (the committed lock was produced by a different npm), not part of this change. The lock diffs therefore contain only the entries this change actually affects: the root package entry, and "peer": true on node_modules/typebox — verified byte-identical to npm 11's output for those nodes. The two bun.lock files carry bun's own canonical --lockfile-only result (an 8-line move of typebox from dependencies to peerDependencies in the workspace entry).

README.md — the install:local dependency list now names the runtime deps and states the peers are declared, not bundled; the repository-layout annotations stop implying a private typebox copy is needed.

Verification

  1. Manifest check (the loader's own rule, HOST_PROVIDED_EXTENSION_PACKAGES ∩ dependencies) across every tracked package.json:
    • before: 4 flagged (root, pr-comments, forgejo, victorialogs) — plus searxng found by the exhaustive sweep
    • after: ok for all 8 manifests
  2. Pi's real warning path — loaded this repo as a local package through DefaultResourceLoader (~/.pi/agent/settings.json registration) and read getExtensions():
    • pre-fix: warnings: 1 — Host-provided extension packages must be declared in peerDependencies with a "*" range, not dependencies: typebox., at exactly the reported path
    • post-fix: warnings: 0 errors: 0, 15 extensions loaded
    • typebox-backed extensions still register their schemas: rg 1 tool, forgejo 37, penpot 21
  3. npm test — 976 tests, 975 pass / 0 fail / 1 skipped.
  4. Dev-resolution parity — typebox still resolves from node_modules (build/index.mjs, 1.3.13) and typebox/value resolves too: npm install auto-installs a root project's own peerDependencies. Confirmed independently by extensions/grillme, whose peer-only manifest already yields a physical 1.3.13.
  5. Lock sync — npm ci --dry-run clean at the root and in extensions/pr-comments + extensions/forgejo; bun install --frozen-lockfile exits 0 in extensions/searxng and extensions/victorialogs and leaves both locks untouched.
  6. Side effects — ~/.pi/agent/settings.json unchanged by the loader runs; no untracked files left behind.

Notes / out of scope

  • docs/IMPLEMENTATION_PLAN.md:24 describes typebox as a "root dep" while recording issue-112. That line was accurate when written and is left as-is rather than rewritten as history.
  • The repo still installs physical copies of host packages via npm's auto-peer-install (@earendil-works/pi-coding-agent@0.84.1, pi-tui@0.84.1). Pi only warns about dependencies, and the jiti alias wins at runtime, so this is left alone.
  • Root origin is an HTTPS URL with no credential helper; the branch was pushed over SSH (git@git.excelera.net:…). Local git config was not modified.
Closes #285 ## Problem Pi reported an extension-package warning on startup: ``` [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. ``` Per `docs/packages.md`, host-provided packages (`typebox` among them) must be declared in `peerDependencies` with a `"*"` range, must not be bundled, and must not appear in `dependencies`. Pi supplies them to extensions through a virtual-module map (`dist/bundle/chunks/virtual-modules-*.js` → `VIRTUAL_MODULES`) and a jiti alias in `dist/core/extensions/loader.js`, so a physical copy can bypass that mapping and load a second TypeBox instance — duplicate classes, registries and initialization work. ## Scope (wider than the single warning) Straightening out the reported root manifest exposed four sub-manifests with the same violation, plus the lockfiles that record them: | Artifact | Change | |---|---| | `package.json` | `typebox` `"*"`: `dependencies` → `peerDependencies` | | `extensions/pr-comments/package.json` | `typebox` `^1.3.6` → peer `"*"` (no other deps) | | `extensions/forgejo/package.json` | `typebox` `^1.3.6` → peer `"*"` (`adm-zip` stays a dependency) | | `extensions/searxng/package.json` | `typebox` `^1.3.19` → peer `"*"` (no other deps) | | `extensions/victorialogs/package.json` | `typebox` `^1.3.19` → peer `"*"` (no other deps) | | `package-lock.json`, `extensions/pr-comments/package-lock.json`, `extensions/forgejo/package-lock.json` | root entry follows the manifest; `"peer": true` on the `typebox` node | | `extensions/searxng/bun.lock`, `extensions/victorialogs/bun.lock` | workspace entry follows the manifest (bun's own `--lockfile-only` output) | | `README.md` | `typebox` no longer described as a runtime dependency (`:89`, layout list `:148–155`) | | `CHANGELOG.md` | entry under *Unreleased → Fixed* | `extensions/grillme` already used the correct pattern and is unchanged; `README.md:148-155` annotations for vision/penpot/searxng/victorialogs now read "typebox from the host". ## Per-file detail **`package.json`** — the flagged manifest. `dependencies` keeps the genuine runtime deps (`adm-zip`, `dotenv`, `image-size`, `mongodb`, `pg`, `pgpass`); `peerDependencies` now lists the three host-provided packages (`@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, `typebox`). The two existing peers are untouched. **`extensions/pr-comments/package.json`, `extensions/searxng/package.json`, `extensions/victorialogs/package.json`** — `typebox` was the only `dependencies` entry, so the key is replaced by `peerDependencies` (declaration order: `peerDependencies` then `devDependencies`). No other field changes; `@earendil-works/pi-coding-agent` stays a `devDependency` (it is a compile-time types import there, not a runtime one). **`extensions/forgejo/package.json`** — `adm-zip` stays in `dependencies`; `typebox` moves to a new `peerDependencies` block. **Lockfiles — deliberately minimal, not a whole-file rewrite.** A plain `npm install` under npm 11 rewrites ~144 unrelated `"peer": true` and `"license"` fields in the committed `package-lock.json`. That rewrite reproduces from the *unmodified* committed manifest, so it is tooling drift (the committed lock was produced by a different npm), not part of this change. The lock diffs therefore contain only the entries this change actually affects: the root package entry, and `"peer": true` on `node_modules/typebox` — verified byte-identical to npm 11's output for those nodes. The two `bun.lock` files carry bun's own canonical `--lockfile-only` result (an 8-line move of `typebox` from `dependencies` to `peerDependencies` in the workspace entry). **`README.md`** — the `install:local` dependency list now names the runtime deps and states the peers are declared, not bundled; the repository-layout annotations stop implying a private typebox copy is needed. ## Verification 1. **Manifest check** (the loader's own rule, `HOST_PROVIDED_EXTENSION_PACKAGES ∩ dependencies`) across every tracked `package.json`: - before: 4 flagged (root, pr-comments, forgejo, victorialogs) — plus searxng found by the exhaustive sweep - after: `ok` for all 8 manifests 2. **Pi's real warning path** — loaded this repo as a local package through `DefaultResourceLoader` (`~/.pi/agent/settings.json` registration) and read `getExtensions()`: - pre-fix: `warnings: 1` — `Host-provided extension packages must be declared in peerDependencies with a "*" range, not dependencies: typebox.`, at exactly the reported path - post-fix: `warnings: 0 errors: 0`, 15 extensions loaded - typebox-backed extensions still register their schemas: `rg` 1 tool, `forgejo` 37, `penpot` 21 3. **`npm test`** — 976 tests, **975 pass / 0 fail** / 1 skipped. 4. **Dev-resolution parity** — `typebox` still resolves from `node_modules` (`build/index.mjs`, 1.3.13) and `typebox/value` resolves too: `npm install` auto-installs a root project's own `peerDependencies`. Confirmed independently by `extensions/grillme`, whose peer-only manifest already yields a physical 1.3.13. 5. **Lock sync** — `npm ci --dry-run` clean at the root and in `extensions/pr-comments` + `extensions/forgejo`; `bun install --frozen-lockfile` exits 0 in `extensions/searxng` and `extensions/victorialogs` and leaves both locks untouched. 6. **Side effects** — `~/.pi/agent/settings.json` unchanged by the loader runs; no untracked files left behind. ## Notes / out of scope - `docs/IMPLEMENTATION_PLAN.md:24` describes `typebox` as a "root dep" while recording issue-112. That line was accurate when written and is left as-is rather than rewritten as history. - The repo still installs physical copies of host packages via npm's auto-peer-install (`@earendil-works/pi-coding-agent@0.84.1`, `pi-tui@0.84.1`). Pi only warns about `dependencies`, and the jiti alias wins at runtime, so this is left alone. - Root `origin` is an HTTPS URL with no credential helper; the branch was pushed over SSH (`git@git.excelera.net:…`). Local git config was not modified.
Pi's resource loader warns when an extension package declares a
host-provided package (`typebox` among them) in `dependencies`, because a
physical copy can bypass the extension loader's virtual-module mapping and
load a second TypeBox instance (duplicate classes, registries and
initialization work).

Move `typebox` to `peerDependencies` with a `"*"` range in the root
manifest and in the four sub-extension manifests that repeated the
violation (pr-comments, forgejo, searxng, victorialogs), matching the
pattern grillme already used. Refuse to ship a private copy; pi supplies
its own at load time.

Refresh the lockfiles that record the changed declarations
(`package-lock.json`, the two npm sub-locks, and the two affected
`bun.lock` files) structurally rather than accepting npm 11's unrelated
whole-file `"peer"`/`"license"` metadata rewrite, which reproduces from the
unmodified manifest too. Update the README dependency descriptions.

Verified: no manifest lists a host-provided package in `dependencies`; pi's
real loader reports 0 warnings and 0 errors for this repo (1 warning before,
matching the report); npm test 975 pass / 0 fail; npm ci --dry-run in sync
at the root and in pr-comments/forgejo; bun install --frozen-lockfile
leaves both bun locks untouched; typebox-backed extensions still register
their tools (rg 1, forgejo 37, penpot 21).

Closes #285
david merged commit ba3817615f into main 2026-09-29 23:17:53 +00:00
david deleted branch bug/issue-285/declare-host-provided-typebox-in-peerdependencies 2026-09-29 23:17:53 +00:00
Sign in to join this conversation.
No reviewers
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!286
No description provided.