- Replaced the fixed `CALL_PREVIEW_MAX_LINES` cap in `render-utils.ts` with a dynamic `previewWindowRows()` (terminal rows minus a chrome reserve); `capPreviewLines` now returns lines untouched when expanded and windows them otherwise.
- Applied the cap in every render state for bash and ssh command previews — streaming and final render identically instead of snapping fully open on completion — and included the preview window in bash's render cache key so terminal resizes invalidate it.
- Capped eval cell code previews via the same window; previously cell code was never capped at all, with `ctrl+o` remaining the only way to uncap.
- Added stream-vs-final parity coverage in `bash-sixel-render.test.ts`, `ssh-render.test.ts`, and the new `eval-code-preview.test.ts`, and a changelog entry.
Older GitHub CLI releases reject the optional issue stateReason JSON field. Retried single issue reads without it and stopped requesting it for issue listings.\n\nFixes #2333
- Added `fanOutFileItems` plumbing through `resolveSearchPathItems`/`resolveExplicitSearchPaths` and `ToolScopeOptions` so plain-file entries can be scanned as explicit targets instead of being folded into an unrequested ancestor walk.
- Search tool now enabled `fanOutFileTargets` and deduplicated overlapping results by tracking a `path\0line` key before merging matches.
- Expanded multi-path tests for shared non-root ancestors, explicit `.git/config` targets, and overlap deduplication behavior.
- Extended `AgentTool.concurrency` to accept per-call resolver functions and resolved concurrency mode from each tool call, falling back to exclusive on resolver errors.
- Updated BashTool to schedule non-PTY calls as shared and PTY calls as exclusive so non-interactive bash calls can run in parallel within one message.
- Tracked in-use persistent shell sessions in the bash executor and routed overlapping calls on the same session key to isolated one-shot shells while preserving owner session availability.
A subagent's send await:true to Main during a blocking task spawn was a structural deadlock: deliverIrcMessage queues mid-turn messages as step-boundary asides, but Main's next boundary requires the sender's own batch to finish, so the sender always burned the full irc.timeoutMs. Awaited sends now pass expectsReply through IrcBus.send; a mid-turn recipient with async.enabled off generates an ephemeral no-tools reply via runEphemeralTurn, records an irc:autoreply aside in its own history, and delivers it back over the bus with replyTo threading so the sender's waiter resolves.
- Removed the running count from the status meta list so only completed, failed, and cancelled counts are shown.
- Updated the waiting message to use singular/plural job labels and simplify wording when all jobs are running.
- Added transcript and assistant block version tracking for finalized segments.
- Changed committed block reuse logic to require prior finalization and same version.
- Fixed rerendering of committed finalized blocks when version values changed.
- Replaced task-simple-mode with a `task.batch` setting enabled by default.
- Updated task schema to use batch `{agent, context, tasks[]}` payloads.
- Migrated task execution to spawn one async job per task and merge outputs.
- Removed per-call schema passing while preserving legacy flat task calls.
- Added an exported `createIrcMessageCard` renderer to `tools/irc.ts` for incoming and relay IRC traffic.
- Updated IRC message rendering in `UiHelpers` to use the shared card component instead of manual transcript block construction.
- Included `replyTo` and timestamp-derived age metadata in the IRC transcript card output.
- Added imageFormatForPath to map explicit save extensions to Puppeteer image formats.
- Derived screenshot capture type from explicit save paths and used it for both element and page screenshots.
- Updated MIME and extension handling so saved full-resolution image bytes and names match the chosen format.
- Removed `resume` from task params and schema, requiring agent and assignment inputs.
- Dropped resume continuation paths in task execution and call rendering, always spawning a new agent.
- Removed the `irc.enabled` setting and computed IRC availability by task-depth rules.
- Updated task follow-up guidance to use IRC messaging/history links instead of `task(resume:)`.
- Added a helper to strip model-facing `<task-result>` wrappers from job result text.
- Updated the job tool renderer to preview the unwrapped `<output>/<preview>` body instead of the envelope markup.
- Documented the settled-task preview fix in the coding-agent changelog.
Tracks successful file reads per resolved base path (selector stripped) for the session; once a path has been whole-file-read three times, every subsequent read for that path appends a one-line nudge suggesting narrower line-range re-reads or the context echoed in edit results. Non-file sources (URLs, internal resources, directories, archives, SQLite, images) are never counted.
Adds enforceInlineByteCap() in streaming-output and applies it to bash and browser tool results: oversized outputs are elided head/tail with an artifact:// footer pointing at the full capture, closing paths that previously let 100KB+ inline results past the minimizer. Defense at the tool-result boundary (no-op for already-bounded output).
A job poll whose watched jobs are all still running and nothing was cancelled is pure 'still waiting' noise once a newer block exists. ToolExecutionComponent now detects isWaitingPollDetails on the result, leaves the block displaceable, and keeps its rows out of scrollback. EventController tracks the most recent displaceable poll and resolves it (sealing the block) on every event that proves another tool now owns the live region: a new tool call/result, a streaming arg start, an end-of-turn signal, or another job result that itself isn't displaceable. UiHelpers' transcript rebuild applies the same rule. Docs and the new job-poll-displacement test pin the behavior.
Shared background now flows through a '/Users/can/.omp/agent/sessions/-Projects-.tree-pi-commit/2026-06-10T15-36-32-782Z_019eb22d-970e-7000-8964-72c98becf3e8/local' file referenced in each prompt instead of a context string forwarded into the subagent's system prompt. The JS and Python preludes drop the context kwarg from agent(), the subagent system prompt drops the {{#if context}} block and the conversation-context file pointer, and runEvalAgent no longer writes a per-call conversation context file. AgentSession sheds the now-unused formatCompactContext() helper that supplied the file's body, and ToolSession.getCompactContext is removed alongside it.
Replaces the blocking auto-reply IRC turn with a process-global IrcBus and a four-op tool (send/wait/inbox/list). send is fire-and-forget with per-recipient delivery receipts (injected/woken/revived/failed); replies become real turns by the recipient, observed via wait (or the send await:true sugar). Bounded per-agent mailboxes (cap 100) drop oldest on overflow; AgentSession.deliverIrcMessage folds an in-flight delivery in as a non-interrupting aside at the next step boundary, or starts a real wake turn when idle. AgentRegistry/lifecycle handle the idle→woken / parked→revived transitions, so messaging a non-running peer brings it back. Adds a dedicated TUI renderer (directional headers, delivery-outcome coloring, quoted bodies, per-recipient receipt trees, status-badged peer lists with unread counts). irc.timeoutMs is now the default timeout for wait / send await:true. AgentSession sheds the agentRegistry config field, the dedupeIrcReply → dedupeEphemeralReply rename (now used by /btw and /omfg), and the background-channel exchange queue / forwardIrcRelayToMain plumbing the auto-reply model needed.
Removes isBackgroundJobSupportEnabled and JobTool.createIf; the tool is now registered unconditionally via `new JobTool(s)`. `async.enabled` now gates async bash commands only — the task tool runs asynchronously regardless. Deletes the async/support module and its barrel re-export.
Resolve the explicit interpreter from the session's Settings instance
(ToolSession.settings / AgentSession.settings) instead of re-reading the
process-global Settings.init() singleton, so project-scoped and cloned
session settings take effect. The availability cache is now keyed by
cwd + interpreter, and PythonKernel.start/executePython accept the
resolved interpreter as an option. Also expand home-relative paths
(~/...) before resolving against cwd, and document the contract of
resolveExplicitPythonRuntime.
Addresses review feedback on #2204.
main's CachedOutputBlock.render now returns readonly string[]; the
cache slot added by this PR was still mutable, failing check:types.
Addresses review feedback on #2083.
- Added CredentialRankingStrategy scope hooks so providers can rank and block only the limits relevant to the requested model.
- Scoped Antigravity usage reports by model family: Gemini/Gemma use Google counters, Claude uses Anthropic counters, and GPT/OpenAI models use OpenAI counters.
- Added scoped backoff keys so a Gemini quota block no longer suppresses healthy Claude/OpenAI Antigravity sessions on the same OAuth credential.
- Threaded modelId through coding-agent API-key resolvers and usage-limit rotation paths.
- Added regression coverage proving a Google/Gemini exhaustion block still allows Claude selection on the same credential.
Fixes#2198
Follow-up to the loop guard + artifact cap that addressed the root cause
of issue #2081's runaway captures. The reporter then noted Ctrl+X/Ctrl+C
remaining unresponsive — confirming the secondary symptom: per-keystroke
TUI repaints walked every visible bash row and re-ran `split` /
`replaceTabs` / `truncateToVisualLines` over the stored output. With a
1,000+ message transcript and a 50KB-tail per row, that string work was
what pinned the main thread, not the loop itself.
The eval renderer already caches its computed lines keyed by `(width,
previewLines)` — see `eval-render.ts:709-752`. Mirrored that pattern in
the bash result renderer with a slightly wider key (`width`,
`previewLines`, `expanded`, `rawOutput`, `isPartial`) so the cache is
busted whenever any input that affects the produced lines actually
changes. `invalidate()` continues to clear `CachedOutputBlock` and now
also clears the lines cache, so callers that already drive invalidation
keep working unchanged.
A render() with cache-equivalent inputs is now an array-reference
return; the `CachedOutputBlock` round trip is skipped entirely. New
test in `test/tools/bash-sixel-render.test.ts` pins the contract:
identical inputs → same array reference; width change → cache miss;
invalidate() → fresh array.
Refs #2081
Two pathologies surfaced in the same captured failure (#2081): a subagent
spent 16 minutes hammering 205 `edit` calls (182 byte-identical no-ops)
against a file that already matched its payload, while a sibling bash
invocation persisted 7.6MB of PowerShell rich-object metadata to
`~/.omp/agent/artifacts/<id>.bash.log` from what was intended as a small
tail. Both are addressed independently here:
- Hashline executor now consults a per-ToolSession `noopLoopGuard` that
hashes the raw patch input and tracks consecutive no-ops per canonical
path. After NOOP_HARD_LIMIT (3) repeats of the same payload the soft
"byte-identical" hint escalates to a thrown ToolError, which the agent
loop surfaces as a tool failure rather than success-with-text — far
more effective at breaking the loop than the soft hint alone. A
non-noop commit (or any variant payload) resets the counter; state is
isolated per ToolSession so subagents cannot inherit each other's
history.
- OutputSink artifact-on-disk writes are now bounded by
`artifactMaxBytes` (default 4 MiB = 3 MiB head + 1 MiB rolling tail).
Once the head budget is exhausted, subsequent chunks divert into a
fixed-size tail ring; `dump()` replays the ring behind a single
`[ARTIFACT TRUNCATED: kept first … + last … of …; … elided from the
middle]` notice before closing the sink. Setting `artifactMaxBytes: 0`
restores the historical unbounded behavior. Sized comfortably above
anything a model would reasonably scroll through via the artifact URL
scheme while preventing the captured 7.6MB spray from sitting on disk.
The terminal-typing lag the reporter observed has multiple compounding
causes (transcript-render freezing is disabled on win32; the bash result
renderer lacks the per-render cache that the eval renderer already has).
Those land in a follow-up — the loop guard + artifact cap address the
root pathologies that turned the session into a multi-MB transcript in
the first place.
Fixes#2081
- Separated non-contiguous diff regions with a single blank gap row, normalized after block-context insertion.
- Rendered gap rows as one dim ellipsis in the TUI and HTML export.
- Applied the same blank-separator dedupe and edge-trimming to hashline's compact diff preview.
- Normalized untrusted `questions` arguments by parsing double-encoded JSON strings and skipping invalid question entries before rendering.
- Added option normalization that dropped malformed option items while preserving valid entries in multi-choice rendering.
- Expanded ask tool renderer tests to verify malformed or unparsable questions no longer crash and now fall back safely.
Move bundled models, model cache/manager, thinking metadata, effort helpers,
provider descriptors/discovery, wire constants, and model identity utilities
into the new @oh-my-pi/pi-catalog package.
Update pi-ai to keep provider runtime/auth concerns, move catalog provider
metadata into CATALOG_PROVIDERS, and migrate coding-agent, agent, stats, docs,
and tests to import catalog values from pi-catalog.
Split coding-agent model registry helpers into discovery, roles, and models
config modules while preserving registry orchestration.
BREAKING CHANGE: @oh-my-pi/pi-ai no longer exports catalog subpaths such as
/models, /model-cache, /model-manager, /model-thinking, /effort,
/provider-models*, discovery helpers, and provider wire constants; use the
matching @oh-my-pi/pi-catalog subpaths instead.
- Added lazy async module loaders for @babel/parser, linkedom, puppeteer/browsers, @mozilla/readability, @xterm/headless, and mnemopi to avoid loading them during cold startup.
- Added an interactive startup splash before session construction and skipped it for resume/fork/continue, quiet mode, timing mode, or non-TTY runs.
- Updated JS import-rewrite and memory tests to match async parser loading and preloaded mnemopi modules for sync state helpers.
- Rerouted sync, tab, js-eval, and tiny workers to re-enter CLI modes via `__omp_*` selectors.
- Adjusted `cli.ts` startup to dispatch worker entrypoints before parsing and exit 1 on uncaught errors.
- Bundled CLI as `dist/cli.js` in prepack, switching `omp` binary and published files.
- Removed explicit Bun `--compile` worker entrypoints from build/release scripts in favor of host-entry dispatch.
- Added `declareWorkerHostEntry()` and `workerHostEntry()` environment helpers and `PI_COMPILED` binary detection.
- Introduced memoized dynamic import loaders for Babel parser, mnemopi modules, puppeteer, and HTML-related packages.
- Refactored eval import-rewrite helpers and runtime call sites to use asynchronous wrapping and parsing flows.
- Shifted fetch and web-scraper linkedom usage to on-demand imports so heavy modules load only when needed.
Updated the read tool's provider-visible path schema, prompt docs, CLI help, and internal URL docs so URL and internal URI targets are advertised consistently. Added schema coverage for the read path description.\n\nFixes #2215
- Rewrote prescriptive prose to MUST/NEVER/SHOULD/MAY phrasing.
- Pruned internal mechanism the agent can't act on from tool prompts.
- Fixed garbled grammar and a stale plan-title placeholder.
- Made ssh tool description synchronous via cached host info.