Rejoined split Windows extension module paths before launch parsing finishes and stripped extended-length Win32 prefixes before Bun import and worker spawn APIs see them.
Fixes#3804
Codex discovery surfaced every `~/.codex/hooks/*.{ts,js}` file as an OMP
hook (silently defaulting untyped names to `pre:<basename>`), and
`discoverExtensionPaths` then handed those paths to `loadExtension` for
dynamic import. A standalone Codex hook script with a top-level
`process.exit(0)` terminated the host CLI cleanly — `try/catch` around
`await import()` cannot intercept a synchronous exit, so OMP died at the
`loadExtensions:start` startup marker with no error surface.
Two-layer fix:
- `packages/coding-agent/src/extensibility/utils.ts`: new
`withExitGuard` helper patches `process.exit` for the duration of a
guarded callback so an exit raises `ExtensionExitError` instead of
terminating the process; nested and concurrent guards restore correctly
via depth counter.
- `extensibility/extensions/loader.ts`, `extensibility/hooks/loader.ts`,
and `extensibility/plugins/manager.ts` wrap their dynamic-import sites
in `withExitGuard` so the existing per-module `try/catch` records the
intercepted exit as a load error and OMP keeps starting.
- `discovery/codex.ts:loadHooks` no longer treats arbitrary files as
OMP hooks: only `pre-<tool>.{ts,js}` and `post-<tool>.{ts,js}` are
registered. Files like `memory-bank-reminder.ts` are silently skipped
rather than imported as extension factories.
Adds regression coverage:
- `test/extension-loader-process-exit.test.ts` — `loadExtensions` /
`loadHooks` return errors and leave `process.exit` restored when a
module exits at import time; sibling modules still load.
- `test/discovery/codex-hooks-discovery.test.ts` — codex provider
registers `pre-*` / `post-*` files and drops everything else.
Fixes#3680
- Migrated 288 lines of scattered error classification logic from `utils/error-id.ts` into a cohesive `packages/ai/src/error/` module with 13 specialized submodules covering flags, classes, OAuth, providers, rate-limiting, and finalization.
- Replaced 100+ generic `Error` throws across 60+ provider and registry files with semantic `AIError.*` classes (e.g., `AIError.MissingApiKeyError`, `AIError.OAuthError`, `AIError.ProviderResponseError`), improving error diagnostics and retry logic.
- Consolidated error utility imports from `pi-utils` and scattered classification functions into a single `AIError` namespace, reducing coupling and simplifying error handling across all packages.
Coerced missing scope on installed_plugins.json entries to user before suppressing them, matching listClaudePluginRoots semantics, so users carrying registries written before the scope field still see marketplace plugins hidden from plugin list and doctor.
Fixes#3628
Derived marketplace runtime package names from plugin IDs when package.json is absent, preserving suppression for config-only marketplace installs. Added regression coverage for package-less LSP plugin installs in plugin list and doctor.
Fixes#3628
Compared marketplace runtime entries by realpath before suppressing link-only plugin-manager entries. Added a regression where a local runtime link reuses a marketplace package name but points at a different path.
Fixes#3628
Filtered marketplace-managed runtime symlinks out of the npm plugin listing and OMP extension-package status provider while keeping marketplace installs available to the runtime loader. Added regressions for both duplicate surfaces.
Fixes#3628
- Renamed the `find` and `search` tools to `glob` and `grep` respectively across the codebase to improve command clarity.
- Implemented full-stack support for the renamed tools, including CLI arguments, system prompts, SDK exports, and tool registration.
- Added automated migration logic in `settings` to transform legacy `find` and `search` configuration keys to their new equivalents.
- Updated the `collab-web` renderer registry to ensure backwards compatibility with legacy tool outputs.
- Centralized JSON parsing and stream processing logic by moving utilities from `packages/ai` to the shared `@oh-my-pi/pi-utils` package.
- Standardized import paths for JSON parsing and streaming across the agent, ai, and coding-agent packages.
- Refactored SSE stream handling to use consolidated `parseStreamingJson` logic and introduced robust error recovery for malformed container-shaped tail events.
- Cleaned up legacy bundled registry references and updated related module exports and tests to reflect the new utility structure.
Restored the pi-ai OAuth device-code helper expected by legacy provider packages and rewrote extension-owned bare dependencies to file URLs during validation so compiled binaries do not rely on Bun's runtime bare resolver.
Excluded worker entry modules from the compiled legacy bundled registry so validation does not import worker-only code on the main thread.
Fixes#3508
The first pass only enumerated non-wildcard `exports` entries, so
patterns like pi-ai's `./oauth/*` left every concrete target
(`@oh-my-pi/pi-ai/oauth/anthropic` and friends) outside the
bundled registry. Compiled-mode resolution then fell back through
`Bun.resolveSync` → original peer specifier → missing peer dep,
reproducing the original `Cannot find module` failure for any
plugin that imports a wildcard-only subpath (e.g.
`@mariozechner/pi-ai/utils/oauth/anthropic`, remapped via
PI_SUBPATH_REMAPS).
The generator now runs a second pass over wildcard exports,
parses each single-asterisk pattern into prefix/suffix halves,
globs the matching source directory, and emits a registry entry
per concrete `.ts` file. Root catch-all wildcards (`./*` /
`./*.js`) are skipped on purpose — they'd static-import top-level
files like the coding-agent's own `cli.ts` and explode the bundle
through the binary entry's transitive graph. Test, `.d`,
`.generated`, `.bench` files and `index` basenames are filtered
out so the registry stays focused on importable surfaces.
A new test case in
test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts
asserts the reviewer's cited `@oh-my-pi/pi-ai/oauth/anthropic`
key now routes through the virtual namespace and that root
catch-all wildcards remain unbundled.
Fixes#3442
Compiled-binary extension validation rewrote @(scope)/pi-ai/oauth →
@oh-my-pi/pi-ai/oauth, but LEGACY_PI_PACKAGE_ROOT_OVERRIDES only
covered bare package roots. resolveCanonicalPiSpecifier therefore
fell through to Bun.resolveSync, which fails inside bunfs on Bun
1.3.14+, then the rewriteLegacyPiImports catch left the original
specifier alone. Bun's native resolver then failed because most
plugins (e.g. @charmland/pi-hyper-provider) declare @(scope)/pi-ai
as a peerDependency only and never materialize a real install.
A new scripts/generate-legacy-pi-bundled-registry.ts reads every
bundled pi-* package's non-wildcard exports field and emits both
the heavy legacy-pi-bundled-registry.ts (static imports + map) and
a light legacy-pi-bundled-keys.ts. legacy-pi-compat.ts statically
imports the keys file to seed the override map without paying the
legacy-pi-coding-agent-shim → ../index → export/html/... cascade,
so subpath imports now route to the same omp-legacy-pi-bundled:
virtual namespace that already serves the roots.
scripts/build-binary.ts runs the generator before bun build
--compile so new pi-* subpaths added under packages/*/package.json
ship without manual regeneration; --check verifies the committed
output stays in sync.
Fixes#3442
Remove `__getLegacyPiBundledRegistry` and `__synthesizeLegacyPiBundledSource`:
both reject outside compiled-binary mode, so no unit test can call them — they
were untested test seams. Real coverage runs through the pure
`__synthesizeLegacyPiBundledSourceWithRegistry` + `__getLegacyPiBundledRegistryGlobal`.
Also document why the synthesized virtual module must bridge back to the host
registry through `globalThis` (separate ES module, no shared closure scope,
live non-serializable exports) so the indirection isn't mistaken for cruft.
Serve bundled pi-* and TypeBox through an in-process virtual namespace
on Bun 1.3.14+ where `--compile` extras are unreachable via any
filesystem API (issue #3423).
Merge resolution:
- Reconciled `TYPEBOX_SHIM_PATH` with main's #3414 fall-through:
`__resolveTypeBoxShimPath(isCompiled, sourcePath, exists)` returns the
`omp-legacy-pi-bundled:` virtual specifier in compiled mode (no FS
probe) and the on-disk source path otherwise, dropping to null when
the shim file is missing so bare typebox imports fall through to native
resolution.
- Removed the now-dead `--compile` extras path: dropped
`LEGACY_COMPAT_BUILD_ENTRYPOINTS` usage from both build-binary.ts and
ci-release-build-binaries.ts and deleted scripts/binary-entrypoints.ts;
the bundler reaches every surface via legacy-pi-bundled-registry.ts.
- Deleted obsolete tests for the removed bunfs machinery
(legacy-pi-compat-entrypoints, legacy-pi-typebox-shim-validation) and
added regression coverage for __resolveTypeBoxShimPath.
- Dropped the binary-compiling smoke driver per maintainer request.
Verified: bun check clean; 77 extensibility tests pass; instrumented
compiled-binary run confirmed onLoad fires for all bundled specifiers.
Bun 1.3.14 stopped exposing `--compile` extras through every filesystem-style API: `fs.existsSync`, `Bun.file().exists()`, `Bun.resolveSync`, and `await import()` on `/$bunfs/...` or `file:///$bunfs/...` all fail; only `/$bunfs/root/<binary-name>` itself answers. The pre-existing legacy-pi rewrite emitted `file:///$bunfs/...` URLs that Bun then could not load, so every legacy extension that imported `@oh-my-pi/pi-*` or `@sinclair/typebox` failed on the `omp-darwin-arm64` release binary.
`legacy-pi-compat.ts` now keeps a JS-heap reference to every bundled pi-* surface in a lazy-loaded sibling `legacy-pi-bundled-registry.ts` and serves them through an `omp-legacy-pi-bundled:` virtual namespace whose `Bun.plugin().onLoad` synthesizes a re-export module — no bunfs path ever leaves the module in compiled mode. Dev / source-link / installed-package modes keep the historical `file://` rewrite (source files exist on disk). The matching `--compile` extras in `scripts/build-binary.ts` are gone; `BUNFS_PACKAGE_ROOT`, `bunfsPath`, `__computeBunfsPackageRoot`, and `__joinBunfsPath` are deleted as dead code. `scripts/smoke-3423.ts` compiles a tiny binary that loads a fixture extension end-to-end through the new path.
Fixes#3423
- Extracted the legacy `--compile` entrypoint list to `scripts/binary-entrypoints.ts` and consumed it from both the release CI script and the local dev `build-binary.ts`, so the two cannot drift apart.
- `scripts/ci-release-build-binaries.ts` no longer ships release binaries without the typebox shim, legacy pi shims, and `@oh-my-pi/{agent,natives,tui,utils}` package barrels in bunfs. Commit dc5c93462f removed worker entrypoints and false-comment-claimed the legacy entrypoints were "still" listed, so every published `omp-<platform>-<arch>` since shipped without them and the resolver emitted bunfs URLs to missing files.
- Validated TYPEBOX_SHIM_PATH at module init via __resolveTypeBoxShimPath, mirroring __validateLegacyPiPackageRootOverrides (#2168). When the shim is absent the rewriter leaves bare typebox / @sinclair/typebox imports alone so Bun falls through to native node_modules resolution.
- Pinned both halves of the contract with tests: release+dev scripts must source from the shared constant, every shim path computed in legacy-pi-compat.ts must appear in it, and __resolveTypeBoxShimPath drops missing candidates.
Fixes#3414
`__computeBunfsPackageRoot` now returns `//root/packages` for the Bun 1.3.14
`//root/<binary>` import.meta.dir shape, but production immediately joined that
root with shim and package segments through `path.join`, which collapses the
POSIX double-slash bunfs mount back to `/root`. That still made override
validation miss the embedded shim files.
Added a bunfs join helper that preserves the `//root` mount prefix after joining
production descendants, wired `bunfsPath` through it, and extended the #3329
regression test to assert the full typebox shim path stays under
`//root/packages/...`.
Fixes#3329
The reporter clarified that the failing binary is the pre-built
`omp-darwin-arm64` release asset from GitHub Releases; Homebrew is only a
local-tap wrapper that downloads that asset. The fix already covers every
cross-compiled `<bunfs-root>/<binary>` shape, but the source/test docstrings
and changelog blurb framed it as a Homebrew-build-specific bug. Updated those
three call sites to name the release asset and note the Homebrew tap as a
downstream consumer of the same binary; no code change.
Bun 1.3.14 reports `import.meta.dir` as `<bunfs-mount>/<binary-basename>` for
the compiled entry on some hosts — e.g. the Homebrew darwin-arm64 build sees
`//root/omp-darwin-arm64` instead of the bunfs root alone. The pre-fix path
joined `metaDir` with `"packages"` and baked the binary basename into every
bunfs path, so the typebox / legacy-pi shim overrides failed `existsSync`
validation, `resolveCanonicalPiSpecifier` fell through to a bunfs
`Bun.resolveSync` that also could not find the module, and every third-party
`@oh-my-pi/pi-*` extension was silently dropped.
`__computeBunfsPackageRoot` now detects the trailing binary-basename segment
(`path.basename(path.dirname(metaDir)) === "root"`) and strips it off the
original `metaDir` via string slicing rather than `path.join`, so Bun's
bunfs-native `//root` and `B:\~BUN\root` prefixes survive verbatim
(`path.posix.join` would collapse `//root` to `/root`). The single-segment
`<bunfs-root>` and deep `<bunfs>/packages/coding-agent/src/extensibility/plugins`
paths keep their existing branches.
Regression test added in `legacy-pi-bunfs-root.test.ts` for the POSIX
`//root/<bin>`, POSIX `/$bunfs/root/<bin>`, and Win32 `<drive>:\~BUN\root\<bin>.exe`
shapes.
Fixes#3329
Made getEnabledPlugins walk both the user plugins root and the active project plugins root, with project entries shadowing same-named user entries. This makes `omp plugin install --scope project name@marketplace` discoverable to slash commands and the extension loader, matching the existing user-scope path. Added regression coverage that exercises the project-scope install through the runtime loader.\n\nFollow-up to #3244 review.
Added runtime symlink and lockfile bookkeeping for marketplace installs and removals so installed plugins are visible to the plugin loader. Updated manager coverage for install, uninstall, enablement, and loader discovery.\n\nFixes #3244
The #3063 fix introduced a second mutating step — `bun update <name>` —
that rewrites bun.lock before extension validation runs. Three failure
paths could still leave the rejected commit pinned in the lockfile or
active tree:
- Extension validation throwing after `bun update` had refreshed
bun.lock — rollback restored package.json and node_modules/<name>
but never touched bun.lock.
- Feature validation (`omp plugin install pkg[ghost]`) throwing
outside the rollback block entirely.
- Runtime-config save failing after a successful install with no
rollback path.
Snapshot bun.lock alongside package.json before `bun install` runs and
route every post-install step (resolution, update, package.json read,
feature validation, extension validation, runtime-config save) through
one outer catch that restores all three (package.json + bun.lock +
node_modules/<name> from snapshot). `#rollbackFailedInstall` now
tolerates an unresolved `actualName` for failures that throw before
the dep key is known.
Three regression tests in plugin-install-validation.test.ts pin the
new contract: bun.lock restoration after a git reinstall fails
validation, bun.lock removal when it didn't exist pre-install, and
rollback on an unknown feature request.
Addresses review feedback on #3069.
bun install <spec> respects the existing bun.lock pin when the spec is
unchanged and never re-resolves the remote ref, so re-running
`omp plugin install github:owner/repo` on an already-installed plugin
reported success while silently keeping the user on the original
resolved commit (1ms no-op, no network).
PluginManager.install now follows a git re-install with
`bun update <name>` to force re-resolution of the ref against the
upstream. First-time installs (no prior dep entry) skip the update —
the initial bun install already fetches HEAD. bun update failures
trigger the same rollback path as validation failures.
Fixes#3063
Added compatibility exports for legacy plugin validation, including defineTool, StringEnum, TypeBox bare imports, frontmatter helpers, SettingsManager, and createCodingTools.
Updated SDK settingsManager alias handling and regression coverage for the affected shim surfaces.
Fixes#2858
Taught plugin doctor to treat lockfile-only plugins with node_modules entries as installed instead of orphaned.
Added coverage for plugin doctor --fix after linking a local plugin without package dependencies.
Fixes#2742
Included lockfile-only linked plugins when building plugin list output so local symlink installs are visible after successful link operations.
Added regression coverage for plugin link followed by plugin list --json.
Fixes#2742
Installed pi packages declare their entry as `pi.extensions: ["./extensions"]` with the real module at `extensions/<name>/index.ts`, but the plugin manifest resolver only matched a file or a directory containing a direct index.{ts,js,mjs,cjs}. A directory whose entry sits one level below resolved to null, so `omp plugin install` rejected it ("declared extension entry not found on disk") and runtime load silently skipped it.
For the extensions key, resolveManifestEntryFiles now resolves a directory like OMP's configured-directory (-e) scanner: the directory's own package.json omp/pi extensions (authoritative -- a declared-but-missing entry is reported, not replaced by a decoy index), then a direct index.{ts,js,mjs,cjs}, then a one-level scan where each child directory is itself resolved manifest-first plus direct *.{ts,js,mjs,cjs} files. The directory expansion is gated to the extensions key; tools/hooks/commands keep direct-index resolution so a directory entry like `tools: "."` is unaffected.
Adds CHANGELOG Unreleased entry and regression tests for subdir-index, nested-manifest-over-index, missing-declared-entry, and key-aware (tools vs extensions) resolution. Verified: omp plugin install @zosmaai/pi-llm-wiki registers all 14 wiki_* tools; pi-mcp-adapter loads.
Redirected legacy pi-ai utils/oauth subpaths through the compatibility loader so background workers load the relocated OAuth registry modules.\n\nFixes #2566