- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
- 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.
The host guard's stdin restore only dropped listeners a module added; a
module that removed a snapshot listener (e.g. a factory calling
process.stdin.removeAllListeners("data") when a subagent re-runs preloaded
factories against a live parent TUI) permanently stripped ProcessTerminal's
input handler. Reconcile each guarded event back to the pre-load snapshot:
when membership changed, removeAllListeners then re-add the snapshot in
order, restoring both additions dropped and removals reinstated. Snapshot
via rawListeners so once-wrapped handlers round-trip intact.
Fixes#5618
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
Validated custom tool factory results before registering plugin-provided tools so one malformed feature entry is reported and skipped instead of crashing startup.
Added regression coverage for null entries and mixed valid/null factory arrays.
Fixes#5189
Replaces the bespoke batch-scoped process.exit interceptor with the
withExitGuard convention main established for extension/hook/plugin
loaders (500c39aa2): guard the module import and factory invocation so
a synchronous process.exit()/process.reallyExit() from a custom tool
becomes an ExtensionExitError handled as a recoverable load error,
while host exit paths stay untouched outside the guarded windows.
Tests cover the import-time exit (issue #1704 repro) and factory-time
exit; both would kill the test process without the guard.
Replaced the per-load process.exit replacement with a permanent interceptor that stays active for the entire loadCustomTools batch. The previous version restored process.exit as soon as a tool's import or factory promise resolved, so a tool that scheduled deferred async work during import (e.g. void main().catch(() => process.exit(1))) still killed the host when the deferred callback fired. The batch-scoped guard intercepts those microtask follow-ups too because the microtask drain at every await point happens before the surrounding batch promise resolves.
Added a regression test for the deferred-exit pattern alongside the existing synchronous-exit test.
Fixes#1704
Converted process.exit calls during custom tool module import or factory execution into recoverable load errors so a bad custom tool cannot terminate session startup.
Added regression coverage for a CLI-style custom tool that calls main() at import time and exits on failure.
Fixes#1704