Stop pi-walker from applying .gitignore/.ignore files from unrelated ancestors above an explicit non-repository search root. Preserve repo-root ignore inheritance when scanning a subdirectory inside a repository.
Fixes#4706
Updated collab-web package metadata to the current published version and aligned swarm-extension with the matching pi-coding-agent major.
Documented the pi-uu-grep clap-only fields with dead_code allow reasons so the lint exceptions remain intentional.
Fixes#4549
- Exposed `Markdown.getLastRenderSettledRows` to track streaming progress.
- Simplified scrollback architecture by migrating logic to a unified `SeamLineList` boundary.
- Removed legacy safe-end methods and redundant `findCommittedPrefixResync` test suites.
- Fixed live tool and evaluation preview duplication issues during re-layouts.
- Introduced parallel streaming grep with windowed result processing to enhance search performance and memory management.
- Implemented stateful parallel file walking with buffer pooling and directory entry record caching to minimize memory allocations.
- Optimized ignore state derivation by using directory entry names instead of stat probes.
- Added comprehensive unit and performance tests covering parallel traversal correctness, early termination, and streaming behavior.
- Collapsed seven duplicated stderr-trim + exit-format sites across diff.rs, rcopy.rs, and overlayfs.rs into one pub(crate) helper.
- Exit code stays caller-formatted so signal-death renders unchanged.
- Also covers the fuse_mount site PR #4376 left with an eager to_string.
Spawned a stderr reader before writing git apply stdin so large diagnostics cannot fill the pipe and deadlock dirty-state seeding.
Added a regression test with a fake git process that fills stderr before consuming stdin.
Fixes#4231
- Downgraded `blocking_task_panic_scope` panics from silent to logged recoverable.
- Persists panic reports of caught worker task panics to the disk crash log while keeping stderr silent.
- Consolidated panic payload message extraction by reusing `crash_handler::panic_payload` in the task runner.
- Promoted the `SilenceHook` test helper to the shared testing module for use across multiple test suites.
- Extracted the panic message before disposing the payload; disposal now runs under its own catch_unwind with mem::forget fallback so a Drop-panicking panic_any payload can no longer unwind across the napi extern C boundary and abort the host.
Introduced crate::testing::lock_panic_hook, a process-global Mutex that
every hook-mutating test now holds across its entire take -> set -> run
-> restore window. Without it, two parallel tests in the default cargo
test harness could interleave their take_hook / set_hook calls and pin
the noop hook as the process-global default, silently muting crash
diagnostics for every later test in the crate.
SilenceHook in task tests and blocking_task_panic_scope_restores_after_unwind
in crash_handler tests both acquire the lock on entry; SilenceHook keeps
the guard as a field so the hook restore in Drop happens under the same
lock. Mutex poisoning is unwrapped to inner so a single failing test
does not cascade.
Fixes#4071
Blocking task panics are now wrapped in a thread-local crash-handler scope before catch_unwind executes the worker closure. The panic hook recognizes that scope and treats the panic as silently recoverable, so it does not write a native crash report or chain to the default hook before the panic is converted into a GenericFailure Promise rejection.
The existing uutils recoverable path keeps its log-only behavior. Added crash-handler tests for the silent disposition and unwind-safe scope teardown.
Fixes#4071
napi-rs 3.9.4 registers async-work `execute` at src/async_work.rs:109 as
a plain `unsafe extern "C" fn` — not `extern "C-unwind"`. Any panic
inside a `Blocking::compute` closure unwound past that boundary and
force-aborted the host process under Rust's stabilized C-unwind rules
(RFC 2945, stable since 1.81), losing the JS Promise and session state.
Wrap the user closure in `std::panic::catch_unwind` inside
`Blocking::compute` and map the panic payload to
`Error::new(Status::GenericFailure, ...)` so it flows through napi-rs's
existing rejection path (`inner_task.reject` in `complete_impl`) and
surfaces as a rejected JS Promise instead of a host abort. Every
`task::blocking` caller (grep, ast, glob, listWorkspace, html-to-markdown,
snapcompact, fuzzy find, clipboard image read) inherits the guard.
Correct the root Cargo.toml `panic = "unwind"` comment — napi-rs's
per-call `catch_unwind` only covers the tokio-future path, not the
`Task`/`AsyncTask` async-work path we use here.
Regression test in `crates/pi-natives/src/task.rs` synchronously invokes
`Blocking::compute` with panicking closures (str literal, formatted,
`panic_any`, plus Ok/Err/double-invoke controls) and asserts the returned
`napi::Error` carries the tag and payload — proving the invariant: a
panicking closure MUST NOT unwind past this method.
Fixes#4071
Propagated the native shell working directory in ShellRunResult so AgentSession can refresh cwd without running a hidden pwd command in the persistent shell.
Added regression coverage for cd plus a failing command followed by echo $?, proving cwd sync no longer overwrites the user's last shell status.
Fixes#3958
The two search()-level cancel tests (`fallback_walk_observes_cancel_flag`
/ `fast_walk_observes_cancel_flag`) pre-set the cancel flag before
invoking search(), but search() and try_search_fast each have a
pre-loop guard that breaks out on cancelled before either walker call
is invoked. That means both tests still pass with the walker call
sites reverted to no-op heartbeats, so they don't defend the fix.
Replace them with symmetric walker-level tests that drive the two
walker APIs fd uses:
- `cancel_heartbeat_aborts_collect_with_heartbeat` — the fallback
path's `collect_with_heartbeat` call; asserts WalkError::Interrupted
(was `cancel_heartbeat_aborts_walker_when_flag_is_set`, renamed for
API-specific clarity and given a filler-file seed so the outcome
shows a real drain on regression).
- `cancel_heartbeat_aborts_for_each_entry_with_heartbeat` — the fast
path's streaming API; asserts WalkError::Interrupted AND that the
visitor never received any entry.
Both fail against the pre-fix no-op heartbeat with WalkStatus::Complete
/ a fully drained WalkOutcome. Keep
`walk_completes_normally_when_cancel_flag_is_unset` as the positive
regression pin — that path does exercise search() end to end because
the outer guard passes with cancelled=false.
The in-process fd builtin passed no-op heartbeats to pi_walker for
both its gitignore-respecting fallback path (`collect_with_heartbeat`
in `search`) and its fast path (`for_each_entry_with_heartbeat` in
`try_search_fast`), so cancellation of a large or slow directory walk
was deferred until traversal completed. The shell wrapper flips the
shared `AtomicBool` cancel flag when the runtime cancellation token
fires and then awaits the blocking task; with no heartbeat hookup the
walker had no way to observe the flag mid-walk and kept collecting the
whole tree before the wrapper could return exit 130.
Introduce `cancel_heartbeat(&AtomicBool)` — the walker-level heartbeat
that returns `io::ErrorKind::Interrupted` when the flag is set — and
plug it into both walker calls. Both call sites recognize the resulting
`WalkError::Interrupted` alongside `cancelled` and break silently
instead of surfacing an `fd:` diagnostic on stderr; the shell wrapper
owns the user-visible exit code.
Regression cover: a walker-level test pre-sets the cancel flag and
asserts `collect_with_heartbeat(cancel_heartbeat(&flag))` surfaces
`WalkError::Interrupted` instead of collecting the tree; two
higher-level `search` tests exercise the silent break for both the
fallback and fast paths; a fourth pins the non-cancelled contract so
the added heartbeat can't stall normal searches. Neuter the helper to
a no-op and the walker-level test fails with the pre-fix `WalkOutcome`
showing every entry scanned — the exact bug the issue reports.
Fixes#3949