- RpcClient: when stdout closes before ready, race child.exited (which settles only after ptree drains the stderr tail for nonzero exits) for 250ms before rejecting, so the earlier-registered exit watcher rejects with the real stderr text instead of an empty 'Stderr:'.
- mock-rpc-agent fixture: await the stderr pipe write callback before process.exit so failure text cannot be dropped unflushed.
- sdk-tool-activation: restore the default extension-handler budget once the intentionally stalled activation times out, so full-suite machine load cannot also time out the genuine recovery registration.
Both tests flaked only under concurrent full-suite chunk load; 10 concurrent stress runs pass post-fix.
- Replaced the custom MuPDF-WASM PDF extraction and rendering pipeline with the new `pdfToMarkdown` native function from `@oh-my-pi/pi-natives`.
- Removed legacy MuPDF extraction modules, WASM embedding scripts, and PDF image extraction tools.
- Added OCR warnings and browser/text redirection for unsupported PDF image reads.
- Updated native package definitions, documentation, and test suites for the new PDF inspection capability.
Passed the cmd.exe /s /c command line through Bun verbatim so configured editors and temporary paths retain their quotes.
Added command-line regression coverage and documented the fix.
Fixes#8544
The /hotkeys table described app.exit (Ctrl+D) as "Exit (when editor is
empty)", implying readline/EOF-style conditional exit. The handler exits
unconditionally and snapshots the current prompt as a resumable draft
regardless of editor content (custom-editor.ts fires onExit for app.exit
with no empty check; input-controller.ts handleCtrlD calls shutdown()
unconditionally, which persists the draft). Corrected the line to
"Exit (saves current prompt as draft)".
Fixes#8530
Moved the optional Streamable HTTP GET listener after the initialized notification so stateful servers do not terminate the session during setup.
Added regression coverage for a server that rejects pre-initialization GET traffic.
Fixes#8514
Detected npm and Bun ownership from the bin link immediate target within each precise global node_modules root. Foreign aliases now update their resolved standalone binary without replacing the alias.
Fixes#8468
- Replaced child_process spawn with Bun.spawn in packages/coding-agent/src/utils/external-editor.ts.
- Removed the browser tab evaluation test suite from packages/coding-agent/test/tools/browser-tab-evaluate.test.ts.
formatReadHashlineHeader collapsed every relative in-workspace path to its
basename, so reading a nested file (e.g. src/settings.json) emitted
[settings.json#tag]. When a same-basename file existed at the session cwd,
a verbatim follow-up edit resolved against the cwd file; Patcher.prepare only
runs snapshot-tag path recovery when the authored path is missing, so the
valid edit was deterministically rejected with "hash is not from this
session". Keep the workspace-relative path, which names the file uniquely and
stays directly resolvable against cwd. Out-of-workspace absolute paths remain
shortened; root-level files are unchanged.
Fixes#8482
Resolved npm and bun bin-entry symlinks before selecting the update method, and preserved foreign aliases by replacing their standalone target.
Fixes#8468
Kept the Bun event loop live across subagent yield drains and delayed parent result flushes. Added a timer-lifecycle regression for the idle flush.
Fixes#8462
- Bun's inotify-backed fs.watch permanently stops delivering events after
observing git's atomic HEAD.lock -> HEAD rename in the watched directory
(oven-sh/bun#24875), so the directory-watch fix for issue #8412 still froze
the status-line branch on Linux after the first switch; CI caught it as a
30s timeout in status-line-vcs-refresh.test.ts.
- Added git.head.watch: fs.watchFile stat-poll of the HEAD path (reftable dir
for reftable repos) with a disposer; path-based polling survives inode swaps
on every platform.
- Status line and footer now consume the helper; the footer previously bound
fs.watch to the HEAD file inode and died after one switch on all platforms.
- Dropped the FSWatcher error-listener plumbing (StatWatcher has no error
mode) and reworked the watcher lifecycle tests to the stat-poll contract;
verified the atomic-rename regression test passes on Linux bun 1.3.14 in
Docker where it previously timed out.
- Replaced time-based sleeps and polling loops with event-driven promise resolvers and fake timers across agent and tool tests.
- Migrated test suites to share in-memory auth storage and fixtures using lifecycle hooks.
- Updated catalog model definitions, metadata, and configurations.
- Stage transcript initialization inside a detached TranscriptContainer to keep existing messages visible during incremental rendering.
- Add fallback state restoration in InteractiveMode.renderInitialMessages when chat rendering is aborted or fails.
- Update render-initial-messages tests to assert that old transcripts remain visible until replacements are fully committed.
The executable version probe added in ecb22957 ("validate Linux browser
executables") replaced the file-only check in resolveSystemChromium with
isChromiumExecutable, which spawns the candidate `--version` for every
platform. On Windows chrome.exe is a GUI-subsystem binary: `--version`
does not print to a detached stdout and can hand off to a running
instance, opening/activating the user's normal browser window, after
which the probe rejects the candidate and falls back to cached Chrome
for Testing.
Gate the spawn probe on process.platform === "linux" (its intended
platform, where non-Chromium PATH wrappers are the real risk) and trust
the executable-file check on Windows and macOS.
Fixes#8445
Two overlapping `omp update` runs now share the target only for the
swap + stale-artifact sweep, guarded by withFileLock. This closes the
rollback race the unique-temp fix left open: a concurrent run's sweep
could delete a live run's .bak before its failed verification rolled it
back. The download stays outside the lock (unique temp path, safe to
overlap). Adds a regression covering a failed verification overlapping a
successful update.
Two overlapping `omp update` runs shared the fixed `<binary>.new` temp
path. downloadVerifiedBinary unlinks the target before writing, so the
second run's unlink deleted the first run's still-downloading temp file;
the first kept writing to its open fd (size and digest still passed),
then chmod hit the missing path and aborted with ENOENT.
Give the temp path the same unique per-attempt suffix the backup path
already uses (pid, timestamp, and a new process-local counter that also
covers same-millisecond, same-process collisions). Generalize the stale
backup sweep to also reclaim orphaned `.new` temp files, age-gated by the
download window so a concurrent run's in-progress temp is never deleted.
Fixes#8434
Moved strict --tools validation to the completed session registry so extension modules, custom tool directories, and plugin manifest tools are all eligible while unknown names still fail startup.
Require the omptype schema brand before treating a raw document's run key as the self-reference copied by a schema spread. This keeps legitimate JSON Schema properties named run intact while retaining spread-schema recovery.
Added regression coverage for a required boolean run parameter.
Legacy Pi extensions build raw tool-parameter documents against real
TypeBox, whose Type.* builders return plain JSON-Schema objects. omptype's
builders return callable schema values instead, so a legacy document that
embeds them (Type.Unsafe({ anyOf: [Type.Array(...), Other] })) or spreads
them (Type.Unsafe({ ...Schema, description })) carries functions. The shim
then structured-cloned that document during plugin install validation and
threw "The object can not be cloned.", rejecting extensions that load fine
when linked (e.g. pi-subagents, all published versions).
Type.Unsafe now lowers embedded builders to their wire JSON and rebuilds
spread documents from the copied run self-reference before cloning or
serializing, matching the plain-object schemas real TypeBox produces.
Fixes#8420
Deferred --tools validation until extension discovery, then validated against built-in and registered tool names while preserving strict rejection of unknown names.
Fixes#8421
Expose the Pi-compatible tui, rpc, json, or print host mode to every extension context and cover mode transitions in the runner regression suite.
Fixes#8419
The status-line branch watcher bound fs.watch to the .git/HEAD file.
git updates HEAD via a lock file plus an atomic rename (HEAD.lock ->
HEAD), which unlinks the watched inode, so the watch fired for the
first branch switch and then died on the stale inode. The displayed
branch froze on the previous branch while later switches went unseen.
Watch the git directory instead of the HEAD file (and, for reftable
repos, the reftable dir as before) and filter directory events for the
HEAD entry. The directory inode is stable across renames, so the watch
survives every switch.
Fixes#8412
- Deduplicated the re-imported changelog bullets from the PR #8403 merge,
keeping the condensed register with only the new #8402 entry.
- getUserHomeCandidates memoizes the WSL home candidate keyed by
platform + WSL markers + USERPROFILE, so a wedged interop pipe costs
one bounded probe per process instead of one 500ms stall per
discovery loader, while env changes (tests, SDK embeddings) still
recompute.
- Generalized thinking loop guard and helper functions to support Gemini, DeepSeek, and Grok model families.
- Replaced `withGeminiThinkingLoopGuard` and related Gemini-specific symbols with generalized counterparts.
- Removed deprecated `enableGeminiThinkingLoopGuard` options and associated tests.
- Updated test suites and agent session logic to use the generalized thinking loop guard and model family tokens.
- Displace overwritten destination into a temporary sibling directory during workspace renames.
- Restore the displaced file and clean up the temp directory if the main rename operation fails.
- applyWorkspaceEdit takes an onExecuted callback fired after each
filesystem mutation, so callers hold the executed prefix even when a
later op throws.
- applyWorkspaceEditWithLsp reconciles overlays/watchers for that prefix
best-effort before rethrowing the original apply error.
- applyWorkspaceEdit now returns { applied, executed }; ops skipped via
ignoreIfExists/ignoreIfNotExists are excluded from executed.
- applyWorkspaceEditWithLsp derives didClose/refresh/watched-file
notifications from executed ops, so a skipped rename no longer closes
the old URI overlay or emits phantom Deleted/Created events.
resolveWindowsUserProfile() ran Bun.spawnSync(cmd.exe echo %USERPROFILE%)
with no timeout during startup discovery. When the WSL->Windows interop
pipe is wedged, cmd.exe never returns and the synchronous spawn blocks
the JS thread forever, so the TUI never paints and no log is written.
Route both best-effort probes (cmd.exe and wslpath) through a shared
runHostProbe() helper that spawns under a 500ms hard timeout with SIGKILL
and reports a killed/non-zero exit as "host home unavailable", so
discovery falls back to the Linux $HOME/~/.omp candidates instead of
hanging.
Fixes#8402