parseSlashCommand treats ':' as a name/args separator, so an extension
command like 'model:foo' was advertised in available_commands_update but
dispatched to the '/model' builtin. Filter such names via
isAcpBuiltinShadowedName, and fix the FakeAgentSession prompt stubs in
acp-agent.test.ts to return true now that AgentSession.prompt() reports
whether the agent was invoked (6 tests were failing against the new
early-finish path).
Addresses review feedback on #2052.
Extension commands (e.g. /sonnet) and TypeScript custom commands that
consume the input without calling the LLM return early from
session.prompt() with no agent turn. In ACP mode this left the pending
prompt promise unresolved, hanging the client forever.
Change session.prompt() to return Promise<boolean>: true when the LLM
was invoked, false when the command was fully handled locally.
#runPromptOrCommand calls #finishPrompt immediately on a false return so
the ACP turn completes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dispatch in AgentSession runs #tryExecuteExtensionCommand before
#tryExecuteCustomCommand, so the palette must reflect the same order.
Moving the extension-runner block before session.customCommands ensures
that on a name collision the advertised command matches what will
actually execute.
Update the test to assert the extension description wins over the
colliding custom TS description.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update ordering comment in #buildAvailableCommands to document the
extension tier and explain why skills/custom TS commands intentionally
shadow extension commands (unlike interactive mode)
- Add CHANGELOG entry under [Unreleased]
- Add regression test: verifies extension commands surface in
available_commands_update and that a builtin-colliding extension
command is excluded via the reserved-set, with no duplicates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The native arboard backend cannot retain X11 / Wayland selection ownership
after the calling process exits, and for short-lived napi calls it can drop
ownership before any consumer sees the selection — leaving the clipboard
empty even though set_text returned success. tmux + QTerminal made this
visible because OSC 52 is also dropped by libqtermwidget, so both backends
fail and the UI's 'Copied to clipboard' status reads as a lie.
utils/clipboard.ts now:
- Tries wl-copy / xclip / xsel before arboard on Linux. These CLIs fork
after reading stdin and serve the selection until another app claims it,
so the payload survives our process exit.
- Honors OMP_CLIPBOARD_COMMAND as a shell-string escape hatch (e.g.
`xclip -selection clipboard -in -silent`).
- Logs a single warning when every backend fails, so the silent-success
regression from #2075 cannot recur unnoticed.
Tests assert dispatch order: Linux+X11 prefers xclip, Wayland prefers
wl-copy, xclip→xsel fallback works, all-fail falls back to native,
macOS still goes straight to native, and OMP_CLIPBOARD_COMMAND wins
over the CLI chain.
Fixes#2075
runEphemeralTurn (IRC//btw) called streamSimple directly with the raw
system prompt, bypassing the SDK-level obfuscateProviderContext wrapper;
and #obfuscatePreparationForProvider skipped previousPreserveData, so a
pre-fix openaiRemoteCompaction.replacementHistory could resend raw
secrets on the next remote compaction.
Addresses review feedback on #2147.
Obfuscated preparation.previousSummary, hook prompt/context, before forwarding to compact() so prior pi- or extension-supplied summaries do not leak verbatim secrets on subsequent compactions.
Fixes#2146
Obfuscated custom instructions before handoff and related side-request provider calls, then deobfuscated generated handoff output before persistence.
Fixes#2146
Converted provider-facing tool parameters to wire JSON Schema before redaction so live Zod instances are not deep-cloned into plain objects.
Fixes#2146
Redacted configured secrets across provider-facing system prompts, tool definitions, developer reminders, and assistant tool-call payloads before LLM requests.
Fixes#2146
After rebasing onto a13e9827f, #createFileSink's head-retention flush
wrote directly to the sink, bypassing #emitToSink's budget accounting —
the on-disk artifact could grow past artifactMaxBytes by up to the head
window. Route the flush through #emitToSink and pin it with a
regression test (fails 24B vs 16B cap without the fix).
Addresses review feedback on #2083.
Codex review on PR #2083 caught a corruption case in `OutputSink`:
when a stream exceeds `artifactHeadBytes` but still fits below
`artifactMaxBytes`, post-head bytes flow into the tail ring without any
eviction (`droppedBytes === 0`) — yet `#flushArtifactTailIfCapped`
unconditionally injected `[ARTIFACT TRUNCATED: kept first … + last … of
…; 0 B elided from the middle]` between head and tail. The resulting
artifact-on-disk is then no longer verbatim and falsely advertises
truncation for outputs that actually fit. With the default 4 MiB / 3 MiB
split, every ~3–4 MiB bash capture in this band tripped the bug.
The notice is now gated on `droppedBytes > 0`. The tail ring is still
flushed unconditionally so head + tail still equal the verbatim stream
in this band. Regression pinned by a new test in
`test/streaming-output.test.ts` that pushes 24 bytes into a 16-head /
16-tail cap and asserts the file equals the payload byte-for-byte with
no `[ARTIFACT TRUNCATED:` marker.
Refs #2081
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
- Raised Anthropic provider retries to 10 attempts and used a shared jittered exponential backoff for each retry.
- Updated coding-agent retry defaults and session delay calculation to a 500ms base with an 8,000ms jittered cap.
- Added tests that verify capped ten-step backoff sequences and recovery after repeated 502 errors.
- 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.
- Added "available" status so recognized servers show under lazy mode without warmup.
- Rendered full welcome box as pre-TUI splash with fixed slot heights to avoid layout shift.
- Reported lazy servers as available in /status instead of omitting the section.
- Replaced minLevel/maxLevel range with explicit efforts array plus baked effortMap/supportsDisplay wire facts.
- Removed runtime enrichment layer and modelOmitsReasoningEffort; providers now read baked fields.
- Fixed dotted Opus 4.7/4.8 ids missing adaptive display via classifier-based predicates (#1373).
- Bumped model cache schema to v4 to invalidate pre-efforts rows.
Route direct model role resolution through the configured pattern normalizer so comma-separated fallbacks are parsed before thinking selectors. Add resolver coverage for preserving :off and trying later entries.\n\nFixes #2228
- 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.
- Centralized catalog and registry handling on `ModelSpec` and `buildModel`, resolving compatibility at model build time.
- Removed runtime compatibility detectors and switched provider request flows to direct `model.compat` reads.
- Added compat fields (`supportsReasoningParams`, `alwaysSendMaxTokens`, `strictResponsesPairing`, `whenThinking`).
- Persisted explicit compatibility overrides through `compatConfig` in discovery and cache merge paths.
- Added Windows batch-command detection and COMSPEC-based cmd.exe resolution for MCP stdio spawns.
- Escaped and quoted batch command arguments, then routed .cmd and .bat commands through cmd /d /s /c.
- Updated the stdio transport tests to assert wrapped cmd.exe command arrays and escaping behavior.
- Added catalog-level host/model predicates and compat resolvers.
- Extended compatibility types and model schema with timeout and replay flags.
- Replaced provider-specific heuristics with shared resolver-based checks.
- Updated host/identity and resolver tests to validate the new behavior.
- Added a `pasteText` handler to `HookEditorComponent` to forward non-bracketed OSC 5522 paste text into its inner editor.
- Added a `pasteText` handler to `HookInputComponent` that forwards text to the input and resets the interaction timeout.
- Extended hook editor and timeout tests to cover enhanced-paste payload absorption and behavior retention.
- Added type-to-search filtering across setting labels, IDs, values, and descriptions.
- Preserved selected item focus by ID when replacing settings during an active filter.
- Displayed search status, empty-filter hints, and no-match messaging while searching.
- Updated Escape handling to clear an active query before cancelling the list.
- Applied selection and navigation to filtered items to keep behavior consistent under search.
Resolved the Windows stdio MCP regression by keeping resolved .cmd shims on the direct argv spawn path instead of rewriting them through cmd.exe /c. Added regression coverage for explicit and PATHEXT-resolved codegraph.cmd commands.\n\nFixes #2220
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.