- Added preprocessing to quote ambiguous plain scalars containing a colon-space sequence when standard YAML parsing fails.
- Preserves the parsed types of unaffected fields and prevents fallback warnings for common unquoted description strings.
- Added tests to verify successful recovery of unquoted values and continued fallback warning coverage for unrecoverable syntax.
- Restored the fetchWithRetry early return for Retry-After and quota hints larger than maxDelayMs.
- Added coverage so oversized provider retry hints do not sleep and retry internally.
- Introduced `isProbablyBinary` utility to sniff file headers for NUL bytes or invalid UTF-8 sequences.
- Updated `ReadTool` to use the binary sniffer, preventing mojibake corruption in output when reading non-text files.
- Refined `file-mentions` auto-reads to skip binary files and mark them as `binary` in the message transcript.
- Added comprehensive unit tests for binary detection logic, covering NUL bytes, truncated multibyte characters, and path-based file sniffing.
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
Bun's `Response(stream).bytes()` returns the raw `ArrayBuffer` once the
body arrives in more than one chunk, which happens for subprocess stdout
past ~128 KB. The public contract of `ptree.ChildProcess.bytes()` is
`Promise<Uint8Array>`, and callers — most visibly the `ssh://` read
path's `decodeUtf8Text` — rely on `Uint8Array` methods such as `.indexOf`
and `.subarray`. On larger remote text files this surfaced as:
TypeError: bytes.indexOf is not a function
Normalize the result at the boundary: when `Response.bytes()` hands back
an `ArrayBuffer`, wrap it in a zero-copy `Uint8Array` view before
returning. Adds a regression test that drives a 256 KB stdout payload
through `ptree.spawn(...).bytes()` and asserts the contract.
Fixes#3712
The #3348 change is a single retry-window constant (25->50ms); the test asserted
it via mock.module("node:fs") + process.platform/Bun.sleepSync mutation, which
AGENTS.md bans (leaks across the full suite). The retry-on-EBUSY loop already has
coverage; a config constant does not warrant a global-mutating test.
- 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.
- Set logger to silent mode when no transports are active to avoid "no transports" errors during log emission.
- Added a regression test to verify that disabling all transports suppresses warnings and that log output resumes after re-enabling transports.
- Corrected TTY property restoration to properly delete injected properties instead of redefining them as truthy values.
- Updated environment restoration to modify process.env keys individually to prevent breaking environment object reference bindings.
- Prevented pollution of TTY-gated code and environment variable state across test suites.
- Pin colorMode to none in golden test suites to prevent nondeterministic ANSI escape sequences.
- Normalize render calls across test files to ensure stable output comparison.
- Prevent test flakes caused by environment-specific TTY auto-detection in the renderer.
Snapshot and restore PI_CODING_AGENT_DIR, OMP_PROFILE, PI_PROFILE, and
XDG_CACHE_HOME instead of relying on setAgentDir(originalAgentDir), which
cannot restore previously-unset or profile-derived env state. Reset the
profile snapshot and rebuild dirs from env in cleanup to prevent
suite-order pollution.
Also reject empty cached content in parseCacheEntry() to harden the
cache contract against corrupted/empty entries.
Repeated reads of unchanged PDFs, Office documents, and EPUBs re-ran the
full markit conversion every time. Add a transparent, content-addressed
cache for successful conversions keyed by SHA-256(content) + normalized
extension, so repeat reads reuse converted markdown instead of
reconverting.
- packages/utils: XDG-aware getDocumentConversionCacheDir() helper
- coding-agent: markit-cache module (bounded 256 MiB, oldest-first prune,
best-effort writes that never fail conversion) layered over the central
convertFileWithMarkit/convertBufferWithMarkit wrappers
- imageDir conversions stay uncached (cache:"skipped") to preserve PDF
image extraction side effects; failed/empty/aborted conversions are
never cached
- abort-safe: file byte reads run under untilAborted; cache I/O rechecks
the signal
- Introduced `abortableSource` as a lighter, direct-reader async generator.
- Removed the `createAbortableStream` public API to eliminate unnecessary stream wrapper layers.
- Updated internal stream processing to use `abortableSource` for improved memory and performance.
- Added `safeSend` helper wrapping `Subprocess.send()` so sync throws and async EPIPE rejections cannot escape.
- Replaced inline try/catch send wrappers in STT, TTS, and tiny-title clients with shared `safeSend`.
- Added `isIpcSendEpipe` predicate and made matching rejections non-fatal in the `unhandledRejection` handler.
- Added contract tests for `safeSend` and `isIpcSendEpipe` covering sync throws, async rejections, and edge cases.
- Removed configurable tab width support and the `display.tabWidth` setting across all packages.
- Deleted obsolete utility functions `getIndentation`, `getIndentationNoescape`, and `setDefaultTabWidth`.
- Standardized tab expansion logic to use a fixed `DEFAULT_TAB_WIDTH` globally.
- Cleaned up related configuration schemas, test suites, and internal API signatures to remove path-dependency.
- Added `WorkerInbox` and `installWorkerInbox(port)` to queue worker messages before bind.
- Added `consumeWorkerInbox()` to replay buffered messages and clear one active inbox.
- Added buffered inbox consumption in JS and tab worker transports before direct message handlers.
- Normalized worker selector arguments to the `__omp_worker_*` naming across workers and tests.
- Fixed OAuth credentials to keep unknown fields in schema while preserving existing shape checks.
- Fixed MCP OAuth IDs to be profile-scoped and avoid deleting credentials from non-active profiles.
- Fixed string-flag parsing so PROFILE_BOOTSTRAP_BOUNDARY tokens are not consumed as values.
- Fixed active-profile directory resolution to refresh after env updates so profile .env overrides apply.
Loaded Kokoro's side-installed transformers runtime by absolute path before requiring kokoro-js, avoiding host/workspace onnxruntime libraries in the worker process.
Kept runtime-cache bare module requests inside the registered runtime cache when the parent module is already inside that cache, and covered the resolver boundary with a regression test.
Fixes#2591
Adds regression coverage the PR's stated 'bounded retries' contract
needs but lacked: gives up after the 4-retry bound and rethrows the
original EBUSY (no infinite loop, no masking), skips non-transient
codes, and never retries off Windows. Also adds the missing final
newline biome required on the new test file.
Addresses review feedback on #2382.
- Added unified `omp setup speech` flow with JSON/check modes and model picker.
- Added local STT pipeline with sherpa workers, recorder/download flow, and streaming inference.
- Added local TTS pipeline with `omp say`, backend selection, and streaming vocalization.
- Replaced legacy speech settings with unified `speech`/`speechgen` configuration keys.
Always-on LoopWatchdog (armed in TUI.start/stop) logs ui.loop-blocked with blockedMs and the current loop phase on the rising edge of a late probe tick. New pushLoopPhase/popLoopPhase/currentLoopPhase stack in pi-utils feeds it; breadcrumbs at in-process subagent dispatch (subagent:<id>) and the SelectList fuzzy filter (ui.select-filter) attribute residual main-thread stalls.
- Reworked createAbortableStream to forward abort signals to the source stream reader.
- Added cleanup logic so abort/cancel/error paths release locks and emit AbortError consistently.
- Updated related tests to verify source-stream cancellation and handoff escape-handler behavior.
- Added isMacosMallocStackLoggingEnvName() function to identify MallocStackLogging and MallocStackLoggingNoCompact variables. Updated filterProcessEnv() and Bun.env initialization to skip these variables during environment filtering. Added test case to verify malloc stack logging toggles are dropped instead of forwarded.
- Moved `fastembed` and `onnxruntime-node` to optional peerDependencies.
- Fixed bundled installs that could not resolve `onnxruntime_binding.node`.
- Added shared `runtime-install` utilities for on-demand module resolution.
- Added tests for runtime-resolution parsing and exact peer-version checks.
The format-on-write path sent a hardcoded {tabSize:3, insertSpaces:true}
on every textDocument/formatting request from two duplicated DEFAULT_FORMAT_OPTIONS
constants. Servers that honour tabSize for re-indent (yaml-language-server, the
common YAML/Kustomize/Flux case) reserialized 2-space files at a 3-space stride
on every write — exactly the corruption reported on Kubernetes/Flux YAML repos.
Replace both constants with a single resolveFormatOptions(filePath, content)
helper that layers, in order:
- .editorconfig (indent_style, indent_size, tab_width) via the new
getEditorConfigFormatting() helper in pi-utils — strict, no fallback.
- Indent sniffed from the in-memory content the agent is about to write
(first indented line decides spaces vs tabs; GCD of space-indent widths
fixes the unit).
- Hardcoded 2-space fallback. The previous 3-space stride was an unusual
default that actively damaged every file with a 2/4-space convention.
Tests cover the editorconfig and content-sniffing paths plus a direct
regression check that 2-space YAML stays 2-space (the issue's repro).
Fixes#2329