diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b641fc7..14d28711e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -288,6 +288,7 @@ jobs: run: | runtime_dir="$(mktemp -d)" HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --version + HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --smoke-test - name: Smoke release binary (Windows) if: runner.os == 'Windows' shell: pwsh @@ -297,6 +298,7 @@ jobs: $env:HOME = Join-Path $runtimeDir "home" $env:XDG_DATA_HOME = Join-Path $runtimeDir "xdg" & "${{ matrix.binary_path }}" --version + & "${{ matrix.binary_path }}" --smoke-test - name: Upload release binary artifact uses: actions/upload-artifact@v4 with: diff --git a/packages/coding-agent/test/issue-1150-repro.test.ts b/packages/coding-agent/test/issue-1150-repro.test.ts new file mode 100644 index 000000000..777c58869 --- /dev/null +++ b/packages/coding-agent/test/issue-1150-repro.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "bun:test"; +import * as path from "node:path"; + +/** + * Regression for https://github.com/can1357/oh-my-pi/issues/1150 + * + * In v15.1.3 `omp stats` crashed in the published Linux/macOS/Windows + * binaries with `BuildMessage: ModuleNotFound resolving + * "./packages/stats/src/sync-worker.ts" (entry point)`. The dev-mode build + * script `packages/coding-agent/scripts/build-binary.ts` listed the three + * worker entrypoints required by AGENTS.md, but the release script + * `scripts/ci-release-build-binaries.ts` — the one that actually builds the + * shipped artifacts — did not. The `new Worker("./packages//src/...")` + * literal at the spawn site fooled Bun's `--compile` static analyzer into + * keeping the call site, but without the matching `--compile` entrypoint + * the worker module was never emitted into bunfs and the runtime tried to + * bundle it on the fly, which fails in `$bunfs`. + * + * The contract from AGENTS.md is symmetric: **every** worker spawned via + * the `isCompiledBinary()` hybrid pattern must be listed as an extra + * `--compile` entry in **both** scripts. This test pins that contract for + * the release script; the dev script is covered by `issue-1011-repro` for + * the tab worker entry. Runtime coverage lives in `omp --smoke-test`, + * which the release-binary CI step now invokes. + */ +describe("issue #1150 — release-build script must list all worker --compile entrypoints", () => { + const repoRoot = path.resolve(import.meta.dir, "../../.."); + const ciScriptPath = path.join(repoRoot, "scripts/ci-release-build-binaries.ts"); + const devScriptPath = path.join(repoRoot, "packages/coding-agent/scripts/build-binary.ts"); + + // Repo-root-relative literals — both the runtime `new Worker(...)` + // spawn site and the `--compile` entry must use this exact string for + // Bun's static analyzer to match them up. + const workerEntrypoints = [ + "./packages/stats/src/sync-worker.ts", + "./packages/coding-agent/src/tools/browser/tab-worker-entry.ts", + "./packages/coding-agent/src/eval/js/worker-entry.ts", + ]; + + it("scripts/ci-release-build-binaries.ts lists every worker as an explicit --compile entrypoint", async () => { + const source = await Bun.file(ciScriptPath).text(); + for (const entry of workerEntrypoints) { + expect( + source.includes(`"${entry}"`), + `scripts/ci-release-build-binaries.ts must include "${entry}" as a --compile entrypoint so Bun emits the worker into bunfs in the published binary`, + ).toBe(true); + } + }); + + it("packages/coding-agent/scripts/build-binary.ts lists every worker as an explicit --compile entrypoint", async () => { + // Dev script's cwd is packages/coding-agent and its `--root ../..` + // resolves to repo root, so its entry strings are package-relative + // (not repo-relative) but produce the same bunfs path. + const devEntrypoints = [ + "../stats/src/sync-worker.ts", + "./src/tools/browser/tab-worker-entry.ts", + "./src/eval/js/worker-entry.ts", + ]; + const source = await Bun.file(devScriptPath).text(); + for (const entry of devEntrypoints) { + expect( + source.includes(`"${entry}"`), + `packages/coding-agent/scripts/build-binary.ts must include "${entry}" as a --compile entrypoint so dev binaries match release binaries`, + ).toBe(true); + } + }); +}); diff --git a/packages/stats/CHANGELOG.md b/packages/stats/CHANGELOG.md index 95192b06d..fe833234a 100644 --- a/packages/stats/CHANGELOG.md +++ b/packages/stats/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `omp stats` crashing on first session sync in published `omp-{linux,darwin,windows}-*` binaries with `BuildMessage: ModuleNotFound resolving "./packages/stats/src/sync-worker.ts"`; the release build script now lists the stats sync, browser tab, and JS eval workers as explicit `--compile` entrypoints so Bun emits them into bunfs, matching the dev build script and the AGENTS.md worker spawn contract. ([#1150](https://github.com/can1357/oh-my-pi/issues/1150)) + ## [15.1.0] - 2026-05-15 ### Fixed diff --git a/scripts/ci-release-build-binaries.ts b/scripts/ci-release-build-binaries.ts index 177b7a4dc..ab72a8ec1 100644 --- a/scripts/ci-release-build-binaries.ts +++ b/scripts/ci-release-build-binaries.ts @@ -14,6 +14,20 @@ interface BinaryTarget { const repoRoot = path.join(import.meta.dir, ".."); const binariesDir = path.join(repoRoot, "packages", "coding-agent", "binaries"); const entrypoint = "./packages/coding-agent/src/cli.ts"; +// Worker entrypoints. Bun's `--compile` static analyzer discovers the +// literal in `new Worker("…", …)` at each spawn site, but only actually +// emits the worker into the bunfs root when it is also listed here as an +// explicit additional entry. Paths are repo-root-relative (matching +// `--root .` below) so the workers land at +// `/$bunfs/root/packages//src/.js`, which is exactly what the +// literals at the spawn sites resolve to. Keep this in sync with the dev +// script at `packages/coding-agent/scripts/build-binary.ts`; the +// `issue-1150-repro` test pins both halves of the contract. +const workerEntrypoints = [ + "./packages/stats/src/sync-worker.ts", + "./packages/coding-agent/src/tools/browser/tab-worker-entry.ts", + "./packages/coding-agent/src/eval/js/worker-entry.ts", +]; const isDryRun = process.argv.includes("--dry-run"); const targets: BinaryTarget[] = [ { @@ -106,7 +120,7 @@ async function buildBinary(target: BinaryTarget): Promise { console.log(`Building ${target.outfile}...`); await embedNative(target); if (isDryRun) { - console.log(`DRY RUN bun build --compile --no-compile-autoload-bunfig --no-compile-autoload-dotenv --no-compile-autoload-tsconfig --no-compile-autoload-package-json --keep-names --define process.env.PI_COMPILED="true" --root . --external mupdf --target=${target.target} ${entrypoint} --outfile ${target.outfile}`); + console.log(`DRY RUN bun build --compile --no-compile-autoload-bunfig --no-compile-autoload-dotenv --no-compile-autoload-tsconfig --no-compile-autoload-package-json --keep-names --define process.env.PI_COMPILED="true" --root . --external mupdf --target=${target.target} ${entrypoint} ${workerEntrypoints.join(" ")} --outfile ${target.outfile}`); return; } @@ -132,6 +146,7 @@ async function buildBinary(target: BinaryTarget): Promise { "--target", target.target, entrypoint, + ...workerEntrypoints, "--outfile", target.outfile, ],