diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1bff46648..93f794f7f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Fixed + +- Fixed all extension loading silently failing on the cross-compiled `omp-darwin-arm64` release binary (downloaded directly or via a Homebrew tap wrapper) because `__computeBunfsPackageRoot` mis-handled `import.meta.dir = "//root/omp-darwin-arm64"`. Bun 1.3.14 reports `/` for the compiled entry's `import.meta.dir`, but the pre-fix function joined `metaDir + "packages"` and produced `/root/omp-darwin-arm64/packages` — the binary basename was baked into every bunfs path, so the TypeBox/legacy-pi shims and every `@oh-my-pi/pi-*` package-root override failed `existsSync` validation and `resolveCanonicalPiSpecifier` fell through to a bunfs `Bun.resolveSync` that also could not find the module. The function now detects the bunfs-root + binary-basename shape (`path.basename(path.dirname(metaDir)) === "root"`) and strips the trailing binary segment by slicing the original `metaDir`; the production bunfs shim join path also preserves Bun's bunfs-native `//root` / `B:\~BUN\root` prefix that `path.join` would otherwise collapse. ([#3329](https://github.com/can1357/oh-my-pi/issues/3329)) ## [16.1.16] - 2026-06-23 ### Breaking Changes diff --git a/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts b/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts index dd024c390..bc69f2b43 100644 --- a/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts +++ b/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts @@ -93,14 +93,22 @@ const TYPEBOX_SPECIFIER_FILTER = /^(?:@sinclair\/typebox|typebox)$/; // so they keep working when on-disk layout differs from the monorepo tree. /** * Compute the bunfs package root from the compiled binary's `import.meta.dir` - * (or any stand-in supplied by tests). Bun 1.3 reports the bunfs mount root - * (`/$bunfs/root` or `:\~BUN\root`) for imported modules as well as the - * entrypoint, so the normal path is `/packages`. + * (or any stand-in supplied by tests). Bun compiled binaries report one of: * - * The suffix branch preserves correctness if a future Bun release switches to - * module-specific `import.meta.dir` values inside compiled binaries, matching - * the source layout: - * `/packages/coding-agent/src/extensibility/plugins`. + * - the bunfs mount root itself — `/$bunfs/root` or `:\~BUN\root` (Bun + * 1.2.x and early 1.3.x). Append `packages` for the canonical layout. + * - the bunfs mount root followed by the binary's basename — `//root/` + * on POSIX or `:\~BUN\root\.exe` on Windows (observed on Bun + * 1.3.14 with the cross-compiled `omp-darwin-arm64` release asset — issue + * #3329). The trailing segment is stripped so the result still lands on + * `/packages`. + * - the module's own source directory if a future Bun release switches to + * module-specific `import.meta.dir` values: + * `/packages/coding-agent/src/extensibility/plugins`. + * The bunfs-root-with-binary branch slices the original `metaDir`, and + * `bunfsPath` uses a matching double-slash-preserving join, so the bunfs-native + * prefix is preserved verbatim — `path.posix.join` collapses `//root` to + * `/root`, but Bun's bunfs lookup is keyed on the exact `//root` form. * * Exported for tests; production callers use `BUNFS_PACKAGE_ROOT` below. */ @@ -110,6 +118,10 @@ export function __computeBunfsPackageRoot(metaDir: string, pathImpl: typeof path if (normalizedMetaDir.endsWith(pluginsDirSuffix)) { return pathImpl.resolve(metaDir, "..", "..", "..", ".."); } + const parent = pathImpl.dirname(metaDir); + if (pathImpl.basename(pathImpl.normalize(parent)) === "root") { + return `${parent + pathImpl.sep}packages`; + } return pathImpl.join(metaDir, "packages"); } @@ -137,11 +149,31 @@ export function __computeBundledSelfPackageRoot(metaDir: string, pathImpl: typeo const BUNFS_PACKAGE_ROOT = IS_COMPILED_BINARY ? __computeBunfsPackageRoot(import.meta.dir) : null; +/** + * Join a computed bunfs package root with descendants without collapsing + * Bun's POSIX `//root` mount prefix. + * + * Exported for tests; production callers use `bunfsPath` below. + */ +export function __joinBunfsPath(root: string, segments: readonly string[], pathImpl: typeof path = path): string { + const joined = pathImpl.join(root, ...segments); + const doubleRootPrefix = pathImpl.sep + pathImpl.sep; + const tripleRootPrefix = doubleRootPrefix + pathImpl.sep; + if ( + root.startsWith(doubleRootPrefix) && + !root.startsWith(tripleRootPrefix) && + !joined.startsWith(doubleRootPrefix) + ) { + return pathImpl.sep + joined; + } + return joined; +} + function bunfsPath(...segments: string[]): string { if (!BUNFS_PACKAGE_ROOT) { throw new Error("bunfsPath is only valid in compiled-binary mode"); } - return path.join(BUNFS_PACKAGE_ROOT, ...segments); + return __joinBunfsPath(BUNFS_PACKAGE_ROOT, segments); } function resolveBundledSelfPackageRoot(): string | undefined { diff --git a/packages/coding-agent/test/extensibility/legacy-pi-bunfs-root.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-bunfs-root.test.ts index a2e7ebf26..b9a4af64d 100644 --- a/packages/coding-agent/test/extensibility/legacy-pi-bunfs-root.test.ts +++ b/packages/coding-agent/test/extensibility/legacy-pi-bunfs-root.test.ts @@ -3,6 +3,7 @@ import * as path from "node:path"; import { __computeBundledSelfPackageRoot, __computeBunfsPackageRoot, + __joinBunfsPath, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat"; // Regression for issue #1514: legacy pi compat shim paths were built from a @@ -35,6 +36,24 @@ describe("legacy pi compat bunfs root computation (issue #1514)", () => { expect(__computeBunfsPackageRoot(posixMetaDir, path.posix)).toBe("/$bunfs/root/packages"); }); + it("preserves the double-slash bunfs prefix through production shim path joins (issue #3329)", () => { + // Bun 1.3.14 on the cross-compiled `omp-darwin-arm64` release asset + // reports `import.meta.dir` as `//root/omp-darwin-arm64`. The leading + // `//` is part of Bun's bunfs identifier, so both the root computation + // and the production shim-path join must preserve it. + const root = __computeBunfsPackageRoot("//root/omp-darwin-arm64", path.posix); + expect(root).toBe("//root/packages"); + expect(__joinBunfsPath(root, ["coding-agent", "src", "extensibility", "typebox.js"], path.posix)).toBe( + "//root/packages/coding-agent/src/extensibility/typebox.js", + ); + + // `$bunfs`-prefixed variant is also handled. + expect(__computeBunfsPackageRoot("/$bunfs/root/omp", path.posix)).toBe("/$bunfs/root/packages"); + // Windows variant: bunfs mount at `:\~BUN\root` with the binary + // basename appended. + expect(__computeBunfsPackageRoot("B:\\~BUN\\root\\omp.exe", path.win32)).toBe("B:\\~BUN\\root\\packages"); + }); + it("uses the current host path implementation for production calls", () => { const metaDir = path.join("/", "anywhere", "root"); expect(__computeBunfsPackageRoot(metaDir)).toBe(path.join("/", "anywhere", "root", "packages"));