- Introduce `@oh-my-pi/omptype` as a new ArkType-compatible schema validation package featuring a lazy JIT runtime, JSON Schema emission, and compatibility adapters.
- Replace `arktype` across workspace packages and test utilities with `@oh-my-pi/omptype`.
- Add benchmark suites, tests, and documentation for the new validation engine and adapters.
- Update workspace build, test runner, and release configurations to include the new package.
- A reload that drops a module's last require() edge leaves the permanent
hooks serving it from the synchronous snapshot map, which was only
refreshed while the path stayed flagged; an edit after the downgrade
replayed stale bytes. Ensure now re-rewrites and refreshes the snapshot
for every ever-synchronous path on each graph walk.
- Added the mirror reload regression (require edge dropped + source edited).
- A reload that adds a require() edge to an already-hooked ESM module never
re-registers hooks, and the original async onLoad filter keeps matching;
require() rejects async onLoad results, so the async hook now serves the
pre-rewritten synchronous source inline when one exists.
- Added a same-process reload regression covering the async-to-sync upgrade.
Requeued already-processed ESM modules when a later CommonJS require upgraded them to synchronous loading, propagating the sync marker through their descendants.
Added an end-to-end regression covering normal discovery before a lazy CommonJS require of the same ESM graph.
Fixes#7402
Kept pre-rewritten synchronous ESM sources available to permanent load hooks after the initial extension import settles, while refreshing them on reload.
Added an end-to-end regression for a CommonJS dependency that lazily requires a nested ESM cluster.
Fixes#7402
These four paths bypassed DirResolver's XDG-aware rootSubdir/agentSubdir
hooks, resolving directly against getConfigRootDir()/getAgentDir() and
ignoring XDG state/data layout. Add XDG-aware path helpers in dirs.ts
and route all four through them:
- secret-placeholder.key → $XDG_STATE_HOME/omp/ (state, agent flattened)
- marketplaces.json → $XDG_DATA_HOME/omp/ (data)
- run/daemons/<hash>/ → $XDG_STATE_HOME/omp/run/ (state)
- run/provider-inflight/ → $XDG_STATE_HOME/omp/run/ (state)
omp config init-xdg migrates secret-placeholder.key and marketplaces.json
from their legacy locations; run/ is ephemeral and rebuilds on restart.
Extract the first-wins global registration into an exported
ensureGraphCommonJsRequireRegistered() seam and assert its idempotent
(first-wins) contract directly, instead of copying the module and importing
the copy at runtime. Removes the inline await import() banned under
packages/coding-agent and keeps regression coverage: the test fails if the
registration reverts to an unconditional set.
Fixes#6449
On npm/source-link installs the @(scope)/pi-coding-agent root shim is served
from src/, so an extension's import evaluates a second instance of
legacy-pi-compat.ts. Its unconditional top-level
Reflect.set(globalThis, COMMONJS_REQUIRE_GLOBAL, evaluateGraphCommonJs)
clobbered the host bundle's populated CommonJS graph bridge with an empty-state
copy, breaking transitive CommonJS dependency resolution for legacy pi
extensions ("Missing graph-owned CommonJS definition").
Guard the registration to first-wins so the host-owned bridge survives a
second module instantiation.
Fixes#6449
Collect TSImportEqualsDeclaration/TSExternalModuleReference targets so
legacy .ts/.cts extensions using `import x = require("pkg")` get their
bare dependencies pinned like plain require() calls. Fold of the #6256
follow-up (comicchang/oh-my-pi@1e54b68) requested on #6324.
Custom tool/extension/hook/plugin modules under ~/.claude/tools are
evaluated with live side effects during createAgentSession. A module that
attaches a stdin consumer at import time — an MCP StdioServerTransport
built at module top level, or a bare process.stdin.resume() — steals
Bun's single stdin reader, so the TUI receives exactly one data event and
goes permanently deaf after the first keypress. Under tmux the terminal's
automatic DA1 reply is that one event, so the first user keystroke is
already dead: the input-deafness reported in #5378/#5618.
Broadened the loader's withExitGuard (renamed withHostGuard) to also
snapshot and restore process.stdin around third-party module evaluation:
any data/readable/end/close/error listener the module adds is removed, and
the stream's paused and raw-mode state is restored to the pre-load
snapshot. The exit guard already fenced process.exit; stdin is the same
class of host-state hijack.
Fixes#5618
Generated per-module loaders instead of eagerly evaluating the entire bundled compatibility graph during extension bootstrap.
This prevents appserver startup from cycling through its own retained command modules before the Unix socket is created.
Fixes#5568
Fetched current heads and tags into Bun's matching cached bare clone before running bun update.
Added an isolated HTTP git regression covering a moved branch with a stale cache.
Fixes#5401
- Removed the stale pinned dependency edge before invoking Bun for same-repository git source replacements so Bun does not construct a dependency loop.
- Added a regression test for the pinned-to-unpinned GitHub plugin replacement path.
Fixes#4960
Registered the bundled virtual resolver on the omp-legacy-pi-bundled namespace while keeping the file-namespace scheme fallback for build-time resolution.
Updated the regression test to cover registry-key resolver inputs.
Fixes#4954
Routed bundled virtual specifiers through Bun's plugin namespace so compiled-binary extensions can import @oh-my-pi value exports.
Surfaced extension load failures during session startup.
Fixes#4954
- Left JSON files imported with import attributes on Bun's native loader instead of registering them with the legacy source rewrite hook.
- Added a regression test for loadLegacyPiModule loading a JSON import-attribute target.
Fixes#4687
Included ESM extension-local bare dependency entries in the legacy extension graph so their relative children receive the same mtime cache-bust rewrite as extension source modules.
Skipped CommonJS dependency entries to preserve native Bun CJS default import behavior.
Added a regression test for a local node_modules ESM dependency whose unchanged entry re-exports an edited helper.
Fixes#4565
Collected the current extension graph on every load and registered supplemental Bun hooks for modules added after the first import.
Preserved exact-path filters by tracking covered realpaths per entry instead of widening hooks to unrelated files.
Added a regression test for an entry-only extension that later adds helper and leaf modules, then reloads an edited leaf.
Fixes#4565
Threaded the current load's mtime tag through rewriteExtensionPackageImports, rewriteExtensionBareImports, and a new relative-graph pass so ./helper.ts, #alias/*, and extension-local bare deps all rekey per reload.
Added a toGraphImportSpecifier helper that emits bare POSIX paths with ?mtime on POSIX and keeps file:// URLs on Windows/bundled targets, matching the entry loader.
Added a regression test covering same-process relative-helper reload freshness through the public loader.
Fixes#4565
Loaded legacy Pi extension entries through raw POSIX filesystem specifiers so Bun keys the cache-busting mtime query.
Allowed the extension graph onLoad hook to match and normalize the mtime query before rewriting source.
Added a regression test covering same-process reload freshness and clean fileURLToPath-derived paths.
Fixes#4565
Allowed npm:<package> install specs to validate against the resolved package name while still forwarding the original spec to Bun.
Added regression coverage for installing npm:pi-figma-remote-auth through PluginManager.
Fixes#4310
PluginManager.install (bun install + bun update), PluginManager.uninstall,
PluginManager.#fixMissingPlugin, the legacy installer.ts install/uninstall
helpers, and generate-legacy-pi-bundled-registry.ts's formatInPlace all
called Bun.spawn with stdout/stderr piped and awaited proc.exited before
touching either stream. Once a child's output exceeded the ~64 KiB OS
pipe buffer, the child would block on write(2) while the parent blocked
on exit — a classic pipe-buffer deadlock. Even where Bun's current runtime
happens to buffer eagerly, the pattern silently leaked unbounded bytes.
Each site now starts new Response(proc.stdout).text() and stderr readers
immediately after Bun.spawn and awaits them alongside proc.exited via
Promise.all. Existing error semantics are preserved: install throws with
stderr, uninstall keeps its generic error, and formatInPlace still includes
Biome's stderr in the failure message.
Adds a regression test (plugin-install-git.test.ts) that models the
OS-pipe deadlock by holding proc.exited until both mock streams are
drained — install must read them before awaiting exit, else the test hits
its 2s Promise.race timeout.
Fixes#4230
The consume-once source map kept entries for graph modules the initial
import never loaded (modules only reached via lazy dynamic imports).
Their first import - possibly long after load, and after an on-disk
edit - was served the boot-time snapshot instead of current file
content, and the unconsumed sources stayed in the plugin closure for
the process lifetime.
Clear the map once the entry import settles: everything Bun loaded at
startup was already consumed (keeping the read-once win), and anything
left must be read at its actual import time, matching pre-dedup
behavior for lazy modules. The new regression test passes on the
pre-dedup baseline and fails on the unfixed dedup.
collectExtensionModules already reads every own-source module's text to scan imports; the onLoad rewrite hook then re-read each file. Return a Map<path,source> from the collector and serve it consume-once in onLoad (delete on first hit, disk fallback on miss), so transitive modules are read once and the entry still re-reads on its ?mtime re-import. Adds a regression test asserting exactly one read per graph module.
Closes#4196
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