Commit Graph
8137 Commits
Author SHA1 Message Date
can1357 2b9d2ca44e merge PR #3968: fix(browser): reap Chromium/Puppeteer on aborted open and session dispose 2026-07-01 04:40:46 +02:00
can1357 68a2c023bc merge PR #3967: fix(lsp): honor tool signal in cold-start initialize and notification writes 2026-07-01 04:40:46 +02:00
can1357 446d5dcba4 merge PR #3965: fix(coding-agent): streamed write progress 2026-07-01 04:40:46 +02:00
can1357 b105782744 merge PR #3957: fix(eval): bound python shell helper output 2026-07-01 04:40:45 +02:00
can1357 7f6b48068c merge PR #3956: fix(mcp): route stdio write/flush failures without parking request() 2026-07-01 04:40:39 +02:00
can1357 8cc7520e47 merge PR #3954: fix(pi-shell): wired fd directory walk to observe scope cancellation 2026-07-01 04:40:39 +02:00
can1357 0e37bd7212 merge PR #3951: fix(extensions): bound tool_call handlers by extensionHandlerTimeoutMs 2026-07-01 04:40:39 +02:00
roboomp 769a9e5807 fix(lsp): only teardown clients on in-flight flush aborts
Distinguish aborts that race an active sink.flush() from aborts that happen
before a queued write starts. Only the former leaves the sink flush pending
and requires killing/evicting the LSP client; pre-write aborts should reject
that caller without disrupting unrelated in-flight operations.

Add a regression with one notification blocked in flush and a second queued
notification whose signal aborts before its write starts, asserting the shared
client is not killed and only the first message is written.
2026-07-01 02:21:40 +00:00
roboomp 0a1392de4b fix(lsp): skip init failure cache on caller abort
Caller cancellations and tool timeout signals are transient initialize failures.
Do not put them in the three-minute init failure backoff, so a later normal
LSP call can retry the server/cwd instead of failing fast as recently failed.

Add a regression that aborts a wedged initialize and then retries the same
server/cwd with a short explicit timeout, asserting it does not hit the
negative-cache error.
2026-07-01 02:13:59 +00:00
roboomp 7282230094 style: bun run fix 2026-07-01 02:09:17 +00:00
roboomp 19b85bca70 fix(browser): reap Chromium/Puppeteer on aborted open and session dispose
Two termination boundaries in the browser tool leaked browser-owned OS resources into the long-lived coding-agent process.

1. Aborted 'open' published an orphan. #open wrapped acquisition in untilAborted, which rejects its outer wrapper on abort but lets the inner launch resolve in the background; acquireBrowser then unconditionally stored the resolved handle in the module-global browsers map. releaseAllTabs walks tabs, not browsers, so the refCount:0 handle stayed alive to process exit.

2. Session dispose had no browser teardown. Browser/tab state lives in module-global maps, and AgentSession.dispose() had no hook to walk them, so headless/spawned Chromium the session opened survived it.

acquireBrowser now short-circuits before launch on a pre-aborted signal and disposes the handle when the launch completes after abort. TabSession records the creating session's id (opts.ownerSessionId, threaded through BrowserTool.#open), preserved across reuse so a subagent re-driving an existing tab does not yank teardown responsibility. AgentSession.dispose() invokes releaseTabsForOwner bounded by withTimeout(3s), mirroring the async-job/MCP disposal pattern.

Regression tests exercise both boundaries via spied CmuxSocketClient (no real puppeteer/socket) and cover: pre-aborted open short-circuit, aborted-mid-launch cleanup, releaseTabsForOwner reaping only owned tabs, and reuse preserving original ownership.

Fixes #3963
2026-07-01 02:09:04 +00:00
roboomp e06fe92649 fix(lsp): normalize sink.flush return to Promise for strict typecheck
sink.flush()'s type is number | Promise<number>, so calling .then/.catch
directly failed under tsgo. Wrap in Promise.resolve() and annotate the
rejection handler's err parameter to satisfy strict noImplicitAny.
2026-07-01 02:06:59 +00:00
roboomp 11edbe01c2 style: bun run fix 2026-07-01 02:06:16 +00:00
roboomp 442ee57283 fix(lsp): honor tool signal in cold-start initialize and notification writes
Two client-level paths in the LSP tool bypassed the combined tool-timeout/
caller abort signal built in `LspTool.execute`, so a wedged server hung
past the advertised tool deadline and past user cancellation:

- `getOrCreateClient` took no `AbortSignal` and its `initialize`
  `sendRequest` was invoked with `signal = undefined`. With no signal
  and no explicit `timeoutMs`, `sendRequest` fell back to the hard-coded
  `DEFAULT_REQUEST_TIMEOUT_MS = 30000` internal timer, so a first-use
  `lsp` call against a server that wedged in `initialize` ignored the
  20s tool default (and any user-supplied shorter `timeout`) until the
  30s internal timer fired.
- `writeMessage`/`queueWriteMessage`/`sendNotification` had no timeout
  and no signal, so a `textDocument/didOpen`/`didChange`/`didSave` sent
  to a server that stopped draining stdin awaited `sink.flush()`
  forever. Because writes serialize through `client.writeQueue`, every
  later op on the client stalled behind the stuck flush too.

Thread the caller `AbortSignal` through `getOrCreateClient` (initialize
+ initialized notification) and through `sendNotification` /
`queueWriteMessage` / `writeMessage` so the sink flush is raced against
the signal. On abort, tear the client down: kill the process and evict
it from the active-clients map so the next `getOrCreateClient` call
spawns a fresh server instead of queueing behind the wedged sink.

Update the LSP tool callsites and internal helpers
(`captureDiagnosticVersions`, `captureOpenFileVersions`,
`syncFileContent`, `notifyFileSaved`, `formatContent`,
`getDiagnosticsForFile`, `reloadServer`, and the rename didClose /
didRenameFiles path) to forward their operation signal.

Warmup keeps its short explicit `initTimeoutMs` and passes no caller
signal; `sendRequest`'s existing `timeoutMs ?? (signal ? undefined : DEFAULT)`
policy still uses that fixed timer.

Fixes #3962
2026-07-01 02:06:03 +00:00
roboomp 58c84e4c94 fix(coding-agent): streamed write progress
Emitted partial write-tool updates before filesystem, archive, SQLite, internal URL, and conflict writes so the TUI can render execution-phase progress instead of waiting for the final result.

Updated the write renderer to keep partial results pending, show the progress snapshot, and suppress diagnostics until the final result.

Fixes #3960
2026-07-01 01:57:14 +00:00
roboomp 822314dea5 fix(mcp): attach stdin.write rejection handler before flush()
In the request() send path, stdin.write() could return a pending thenable while stdin.flush() threw synchronously. Control jumped to the outer catch before wrote.then(undefined, failFromSend) was ever installed, so a later async EPIPE on the write would fire as an unhandled rejection — reintroducing the very orphan path the parent fix removes.

Attach the rejection handler on the write result immediately after dispatching it, before calling flush(). If flush() then throws synchronously, wrote is already covered; if write() itself throws, flush() was never called and there is no flushed thenable to orphan.
2026-07-01 01:16:19 +00:00
roboomp e348e8d9b5 fix(eval): bound python shell helper output
Stream Python eval shell helper stdout in fixed-size chunks instead of buffering through subprocess.run or newline-bound text iteration.

Added regression coverage for !cmd and newline-free %%bash streaming.

Fixes #3950
2026-07-01 01:15:38 +00:00
roboomp 379539b3b2 fix(extensions): clear pending timer when a handler wins the timeout race
Bun.sleep(timeoutMs).then(...) leaves an uncancellable timer registered
with the event loop, so every successful handler race in the runner
leaked one — a completed tool_call/tool_result handler could delay
non-interactive CLI exit by up to the 30s default cap. Verified with a
subprocess exit-time probe: buggy pattern exits in ~5000ms for a 5s
timeout, setTimeout+clearTimeout pattern exits in ~17ms.

Extract a raceHandlerWithTimeout helper backed by setTimeout with a
finally-scoped clearTimeout, and route both #runHandlerWithTimeout
(pre-existing latent leak) and emitToolCall (introduced in the same PR)
through it. No behavior change on the timeout branch.

Addresses review on #3951 from chatgpt-codex-connector[bot].
2026-07-01 01:12:30 +00:00
roboomp 797bbe667e style: bun run fix 2026-07-01 01:10:12 +00:00
roboomp 0e90d57be6 fix(mcp): route stdio write/flush failures without parking request()
StdioTransport.request() awaited stdin.write() and stdin.flush() before returning the internal deferred promise. When the child stopped draining stdin (wedged process, or full OS pipe buffer with no reader), Bun's FileSink returned a pending Promise that never settled — the async function got stuck above 'return promise', past the timeout timer and the abort handler. cleanup() + reject() still ran on the inner deferred, but the outer async-function promise never adopted it, so the caller's await hung forever and the deferred rejection surfaced as an unhandled promise rejection.

Send the frame without awaiting: sync EPIPE throws (Windows) still reject the request immediately; async EPIPE rejections (POSIX processTicksAndRejections) are wired to the same reject() via a guarded failFromSend handler that no-ops after cleanup(). The returned promise now settles from the response, the timer, the abort signal, or the read loop's transport-close broadcast.

Regression test spawns 'sleep 60' (POSIX only), sends a 1MB tools/call payload past the pipe buffer, and asserts the deferred rejects with the timeout error before the outer window elapses and produces no orphaned unhandled rejections.

Fixes #3945
2026-07-01 01:08:56 +00:00
roboomp 3bb2ade643 fix(pi-shell): wired fd directory walk to observe scope cancellation
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
2026-07-01 01:08:48 +00:00
can1357 b2a859a7c5 chore: bump version to 16.2.11 2026-07-01 03:06:34 +02:00
roboomp 6c56d39dc5 fix(extensions): bound tool_call handlers by extensionHandlerTimeoutMs
emitToolCall awaited each extension handler directly (runner.ts:704-706),
bypassing the #runHandlerWithTimeout wrapper every other subscribed event
routes through. A tool_call handler that never resolves parked
ExtensionToolWrapper.execute indefinitely, freezing tool dispatch even
though the symmetric emitToolResult path has always been timeout-protected.

Race each tool_call handler against Bun.sleep(extensionHandlerTimeoutMs)
inline (the shared wrapper swallows errors, and this callsite is
fail-closed). On timeout: emit an ExtensionError with event: 'tool_call',
log a warning, and return { block: true, reason: 'Extension <path>
timed out after <ms>ms' } — symmetric with the existing per-handler error
branch. Fail-closed is the correct policy for a pre-execution gate: an
unresponsive extension MUST NOT be silent consent to run the tool.

Fixes #3948
2026-07-01 01:03:37 +00:00
can1357 b92bd7d02c chore: update chanelogs 2026-07-01 02:58:17 +02:00
can1357 ed9c049e15 Merge remote-tracking branch 'origin/farm/4a3419a7/sync-omp-docs-coverage' 2026-07-01 02:57:52 +02:00
can1357 2e8c012eec Merge remote-tracking branch 'origin/farm/3cd77781/bound-ast-search-pages' 2026-07-01 02:57:36 +02:00
can1357 44205d62c4 Merge remote-tracking branch 'origin/farm/cc69ac0e/fix-whisper-stt-download' 2026-07-01 02:57:31 +02:00
can1357 6e9ae7adc7 Merge remote-tracking branch 'origin/farm/a032fc7d/grep-rg-walker-cancel-heartbeat' 2026-07-01 02:57:24 +02:00
can1357 a64749fe1f Merge remote-tracking branch 'origin/farm/d23f8157/bound-async-job-dispose' 2026-07-01 02:57:06 +02:00
can1357 c30526fe39 fix(ai): required non-empty name and id for native streaming tool calls
- Added `hasUsableNativeToolCall` helper to verify that a streaming tool call has non-empty, trimmed name and id values.
- Retain projection initialization and updates on subsequent deltas if the provider emits native tool identifiers late.
- Guard tool call synchronization and late salvage logic to prevent empty or invalid placeholders from corrupting streaming state.
2026-07-01 02:54:42 +02:00
roboomp 8b683f8ccb fix(coding-agent): kept docs embed populated for bun pm pack
gen:bundle previously ran gen:docs and gen:docs:reset around the build. Under prepack that reset ran before bun pm pack assembled the tarball, so the published src/ copy of docs-index.generated.txt was empty and consumers importing via subpath (e.g. @oh-my-pi/pi-coding-agent/internal-urls/docs-index) fell through to the missing repo docs/ tree.

Restored gen:docs in prepack so postpack owns the reset (matching the original lifecycle), and made gen:bundle assert the embed is populated and fresh instead of touching it, so a direct gen:bundle outside prepack fails with an actionable message.

Fixes #3934
2026-07-01 00:25:24 +00:00
roboomp 5e5c88979f style: bun run fix 2026-07-01 00:07:30 +00:00
roboomp efcd6cafd6 fix(stt): kept whisper setup downloads alive
Kept the STT subprocess referenced while download and stream requests are pending so setup cannot exit before the worker answers.

Propagated worker download errors to setup callers and verified completed downloads leave the expected cache files.

Fixes #3939
2026-07-01 00:07:14 +00:00
roboomp 92740d3c4f docs(coding-agent): completed full omp docs sync
Added a contributor-facing native crate map (docs/native-crates.md) covering pi-natives, pi-shell, pi-ast, pi-iso, pi-walker, pi_uu_grep, pi-uutils-ctx, and vendored brush crates, and linked it from natives-architecture.md and user-facing-packages.md.

Added a docs-index tool coverage test asserting every BUILTIN_TOOL_NAMES entry and injected custom tool (generate_image, tts) has a docs/tools/<name>.md page served by omp://.

Inlined tiny fail/buildPayloadText/checkDocsIndexFreshness helpers in generate-docs-index.ts per the project rule against single-expression named functions.

Fixes #3934
2026-07-01 00:00:52 +00:00
roboomp 578a2d7626 fix(ast): bounded ast search pagination payloads
Retained only the requested AST search page window in native ast_grep/ast_match and the coding-agent multi-target wrapper while preserving exact totals.

Fixes #3935
2026-06-30 23:54:17 +00:00
roboomp 3c3cb2a76b docs(coding-agent): synced omp docs coverage
Added root omp docs for memory_edit, learn, manage_skill, generate_image, and tts, plus package-level coverage for user-facing README-only CLIs.

Added a docs-index freshness check to package check and made gen:bundle generate and reset the docs embed itself.

Fixes #3934
2026-06-30 23:53:42 +00:00
roboomp 8e2dfcc43e fix(agent): routed acquire-time abort through queued-spawn settled path
When a batched task spawn is cancelled while still queued behind task.maxConcurrency the semaphore now rejects acquire(), but the previous patch let the abort throw past the aborted handler so progress.status and onSettled never fired and buildAsyncDetails kept reporting the batch as running. The wrapper now records whether the slot was held, funnels both acquire-time and post-acquire aborts through the same aborted branch (releasing only when held), and a batch regression test pins the contract.

Fixes #3930
2026-06-30 23:46:19 +00:00
roboomp dbab0ef84d fix(uutils): wired grep/rg directory walks to observe scope cancellation
The grep and rg in-process builtins passed no-op heartbeats to pi_walker
when recursing into a directory, so the uutils scope's cancel flag was
ignored mid-walk. The shell wrapper sets that flag on abort/timeout and
then awaits the blocking task — with no heartbeat hookup, a cancelled
recursive grep/rg waited for the whole tree to be scanned before the
shell could return exit 130.

Add pi_uutils_ctx::is_cancelled() and have grep's search_dir, rg's
search_dir, and rg's collect_filtered_files supply heartbeats that
return io::ErrorKind::Interrupted when the flag is set. The walker maps
that to WalkError::Interrupted, which the utilities now silently treat
as a harness cancellation (no spurious 'native directory scan
interrupted' on the command's stderr — the shell wrapper owns the
user-visible exit code).

Regression tests pre-set the cancel flag and assert grep -r /
rg / rg --files exit without scanning the matching file in the tree.

Fixes #3933
2026-06-30 23:37:20 +00:00
roboomp 3bc8f995f6 fix(agent): bounded async job disposal
Made AsyncJobManager.dispose honor its timeout while waiting for cancelled jobs, and passed task abort signals into spawn semaphore waits.

Fixes #3930
2026-06-30 23:37:11 +00:00
can1357 ebdc7280cb chore: bump version to 16.2.10 2026-07-01 00:54:29 +02:00
can1357 c7fa7f5285 test(coding-agent): reset history storage between test runs
- Reset the HistoryStorage singleton instance in the afterEach hooks of multiple issue reproduction tests.
2026-07-01 00:52:50 +02:00
can1357 bdb33a184d feat(coding-agent/modes): tracked and migrate stream-key placeholders for tool calls
- Added `#streamTurnNonce` to prevent aborted streaming turns from corrupting content indexes of subsequent messages.
- Implemented temporary stream-key generation using content position and turn nonces for previewing tool calls without native IDs.
- Added migration logic to key pending tool previews by their real ID and rekey `ToolArgsRevealController` once the real ID is parsed.
2026-07-01 00:34:21 +02:00
can1357 3cb25ddaac fix: resolved bun segfaults by implementing custom timeout helpers
- Replaced native `AbortSignal.timeout` calls with self-clearing timeout helper functions across model discovery.
- Added `withTimeoutSignal`, `withCatalogDiscoveryTimeout`, and `withOpenAICompatibleDiscoveryTimeout` helpers to manage cancellable fetch timeouts.
- Supported `timeoutMs` options throughout the Ollama, Llama.cpp, LiteLLM, vLLM, LM Studio, and OpenAI-compatible discovery processes.
- Documented the fix addressing Bun garbage collection segfaults caused by uncancellable timeout signals.
2026-07-01 00:13:18 +02:00
can1357 e6140f1b55 Merge remote-tracking branch 'origin/farm/f8778fdf/ruff-lsp-windows-venv-scripts' 2026-06-30 23:57:27 +02:00
can1357 f56e563fa9 Merge remote-tracking branch 'origin/farm/2b86e599/agent-thinkinglevel-precedence' 2026-06-30 23:53:15 +02:00
can1357 daf3aaa508 feat(pi-natives): implemented two-pass search to partially grep large files
- Introduced a two-pass file processing architecture with `ReadPolicy` and `FileOutcome` state tracking.
- Deferred oversized files to a second pass where only their leading segment is read and searched.
- Integrated the two-pass processing logic into both sequential and native parallel grep execution paths.
- Updated agent tool definitions and user-visible messages to reflect partial coverage of large files instead of skipping them.
2026-06-30 23:37:49 +02:00
can1357 112317bc8e fix(coding-agent): resolved duplicate todo reminders in terminal scrollback
- Anchors the incomplete-todo reminder block inside the scrollback transcript instead of a floating live container.
- Eliminates duplicate reminder copies piling up in terminal scrollback during terminal reflows.
- Removes the dedicated `todoReminderContainer` and simplifies state synchronization on todo reload.
- Updates tests to verify sequential reminders commit as separate blocks and are left intact when tools succeed.
2026-06-30 23:32:58 +02:00
can1357 31d9fb1145 refactor: consolidated filesystem traversal into a dedicated library
- Replaced custom fast-walk and fs-cache implementations in pi-natives with a new dedicated pi-walker library.
- Integrated the thread-safe, parallel pi-walker library across pi-natives, pi-shell, pi-uu-grep, and uu-find.
- Rewrote file search, fuzzy finding, and glob-matching logic to leverage pi-walker configurations and visitor traits.
- Optimized shell process tracking in pi-shell by replacing global descendant-diff logic with an isolated, per-run SpawnRegistry.
- Added parallel rayon-based walking and optimized fast paths for directory scanning and entry classification.
2026-06-30 23:25:50 +02:00
roboomp ad6748ebe7 fix(lsp): covered setup.cfg and pyrightconfig venv lookup
Added setup.cfg and pyrightconfig.json to PYTHON_ROOT_MARKERS so pyright, basedpyright, and pylsp project shapes also probe Windows .venv/Scripts before PATH fallback.

Fixes #3916
2026-06-30 20:26:39 +00:00
roboomp ae0ae68e14 fix(lsp): included ruff-only roots for venv lookup
Extended Python local-bin gating to Ruff-only root marker files so Windows virtualenv Scripts launchers are found before PATH fallback.

Fixes #3916
2026-06-30 20:21:10 +00:00