chore(deps): update dependency can1357/oh-my-pi to v15.10.12 #55
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "renovate/can1357-oh-my-pi-15.x"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
15.10.10→15.10.12Release Notes
can1357/oh-my-pi (can1357/oh-my-pi)
v15.10.12Compare Source
@oh-my-pi/pi-agent-core
Added
AgentLoopConfig.getDisableReasoningso callers can overridedisableReasoningper LLM call, mirroringgetReasoning.transformProviderContexttoAgentOptions/AgentLoopConfig: an optional hook applied to the assembled provider context after conversion, normalization, and append-only handling, but before telemetry capture and provider send.Fixed
Agentruns so explicit reasoning disablement is forwarded to provider stream options and re-resolved per continuation, keeping mid-run thinking-off changes in sync with the next provider request.@oh-my-pi/pi-ai
Added
antigravityRankingStrategyand registered it forgoogle-antigravityinDEFAULT_RANKING_STRATEGIES, so new sessions are routed to OAuth credentials with quota headroom for the requested model backend (lowest relevantremainingFractioncounter as the sole ranked window, 24hwindowDefaultsmatchingdaily-cloudcode-pa.googleapis.comresets). Without it, the existingantigravityUsageProviderdata never reached credential selection. (#2198)Changed
MiniMax-M3and refreshed Token Plan login copy/links (#1725).Fixed
response.completed/response.incompleteevent. Both providers now detect premature stream closure and throw withstopReason: "error"(#2184)isUsageLimitErrormissing Antigravity / Cloud Code Assist'sIndividual quota reached429 phrasing. TheUSAGE_LIMIT_PATTERNonly knewquota.?exceeded/limit_reached, soauth-retryandAuthStorage.markUsageLimitReachedtreated the response as a terminal provider error and pinned sessions to the exhausted OAuth account instead of rotating to a sibling credential. The pattern now also matchesquota.?reached. (#2198)gemini-*/gemma-*→ Google,claude-*→ Anthropic,gpt-*/openai/*→ OpenAI), so an exhausted Gemini counter no longer makes a healthy Claude/OpenAI Antigravity credential unavailable until reset. (#2198)scopeLimitsnow returns no limits without a concrete backend counter, andblockScopealways returns a counter scope so missing model context can never fall through to AuthStorage's provider-wide block bucket. (#2198)@oh-my-pi/pi-catalog
Added
grok-composer-2.5-fast(Cursor "Composer 2.5 Fast") to the xAI Grok OAuth (SuperGrok) catalog: non-reasoning, text-only, 200K context.Changed
grok-build,grok-4.3,grok-4.20-0309-{reasoning,non-reasoning},grok-4.20-multi-agent-0309,grok-composer-2.5-fast), replacing the8888UNK_MAX_TOKENSplaceholder (and a stale30000on three grok-4.x entries). xAI's OAuth/v1/modelsreports no per-request output limit, so the curated catalog now ownsmaxTokenslikecontextWindow, deterministic on both the static-seed and online-overlay paths; theopenai-responseswire still clamps the actual request toOPENAI_MAX_OUTPUT_TOKENS(64k).Fixed
xai-oauthsubscription entries from the model reference indexes (buildModelReferenceIndex,createReferenceResolver), so their zero pricing and context-window-sizedmaxTokenscannot outrank paid/public Grok references when resolving custom-provider model identities.@oh-my-pi/pi-coding-agent
Added
omp --mode rpc.shellMinimizer.sourceOutlineLevelandshellMinimizer.legacyFilterssettings so shell minimization can tune source outlining and selectively fall back to conservative legacy routing.--config <path>CLI overlays for temporaryconfig.yml-style settings without editing the persistent global config (#1733).python.interpreterto pin eval's Python backend to an explicit interpreter and skip automatic runtime discovery (#1802).!commandresolution formodels.ymlproviderapiKeyvalues and provider/model headers (#1888).TITLE_SYSTEM.mddiscovery so users can override the automatic session-title generation prompt for online and local tiny title models without patching installed prompt files. The override is re-discovered when the session working directory changes via/cwd.reftablestorage format by detectingextensions.refStorage = reftablein the repository configuration and falling back to shelling out to Git commands (git symbolic-ref,git rev-parse) for reference and HEAD resolution./setup providers(also available as/setupor/providers) to reopen the interactive provider setup scene from an active TUI session, letting users sign in and choose a web search provider without rerunning the full onboarding flow.Changed
artifact://…footer when shell minimization rewrites a command's output.OutputSinkartifact files (~/.omp/agent/artifacts/<id>.<tool>.log) are unbounded by default again, soartifact://<id>references preserve the complete raw stream. The head + rolling-tail capping machinery from #2081 (with its[ARTIFACT TRUNCATED: …]close notice) remains available as an opt-in viaartifactMaxBytes, and the head window now closes permanently on first overflow so later small chunks cannot be written out of order before the tail replay.Fixed
:offthinking selector so requests explicitly send reasoning disablement instead of falling back to the provider default (#2239).getSessionSlashCommands()now filter against the shared builtin reserved-name registry.pi.sendUserMessage()prompts ran: the turn now drains scheduled extension prompts and prompt-event handlers before reportingend_turn, and prompts queued on a closed or disposed session fail fast with anACP_SESSION_CLOSEDerror instead of running against a dead session.transformProviderContexthook so request telemetry also captures the redacted context (#2146).Settings.init()permanently poisoning subsequent initialization: the cached init promise is now cleared on error so a retry can succeed.enabledModelsbeing ignored by the ACP model picker (Zed and other ACP clients) —AgentSession.getAvailableModels()now applies the configured allow-list, so only the models listed inenabledModelsappear in the UI. Also applies consistently to the RPCget_available_modelsendpoint and the/modelslash command. Glob selectors (anthropic/*), provider-scoped fuzzy patterns, and substring selectors now resolve in this synchronous path too, using the same scope semantics as startup resolution instead of being skipped with a warning.--yolo/--auto-approveor a configuredtools.approvalMode: yolo) and the effective per-tool policy is "allow"; default-config sessions keep the gate (#2097 by @Mokto)Thinking...lines in the transcript (#2068).per-project-taggedscoping siloing retains/recalls per linked git worktree:projectLabel()now resolves the primary checkout root (or shared bare-repo common dir) via the new syncgit.repo.primaryRootSynchelper, so every worktree of one repo shares the sameproject:<name>tag andper-projectbank id (#2232)./loginin headless environments (#2122).ModelRegistryAPI-key resolvers and Antigravity usage-limit rotation sopi-aican apply model-family-scoped OAuth quota backoff instead of treating allgoogle-antigravitycounters as credential-wide. (#2198)omp extensionsbeing treated as a chat prompt instead of returning an actionable plugin-command error (#2089).editcalls. The hashline executor previously surfaced a soft "your body row(s) are byte-identical to the file" hint that some models ignored; one captured session emitted 182 such repeats in 205 calls over 16 minutes before the user aborted. A new per-ToolSessionnoopLoopGuardnow tracks consecutive identical no-op payloads per canonical path and escalates to a thrownToolErrorafterNOOP_HARD_LIMIT(3) repeats, so the agent loop sees a tool failure and breaks the cycle (#2081).split/replaceTabs/truncateToVisualLines) on every TUI repaint, which scaled with both transcript length and per-row output size. With a long captured session every keystroke walked hundreds of bash rows; the reporter on issue #2081 observed Ctrl+X/Ctrl+C feeling unresponsive because the main thread was pinned re-styling scrollback. The result renderer now caches its produced lines keyed by(width, previewLines, expanded, rawOutput, isPartial), mirroring the existing eval-renderer cache;invalidate()clears the cache as before. Hot-path repaints with unchanged inputs are now O(1) (#2081).available_commands_updateto include extension-registered slash commands so clients like Zed surface them in the slash-command palette.session/cancelis processed) now implicitly cancels the running turn and queues the new message, instead of throwing an error that blocks further interaction.!/!!shell shortcuts to run non-bash commands through the configured user shell, including interactive startup for zsh/fish aliases and functions (#1816).@oh-my-pi/pi-mnemopi
Changed
@oh-my-pi/pi-natives
Added
executeShell().minimizedfor callers that want compact inline output plus a separately persisted original capture.Fixed
PI_CONFIG_DIRis absolute: the config root now mirrorspath.join(homedir, PI_CONFIG_DIR)semantics (absolute values re-rooted under$HOME,./..components normalized), and an emptyPI_CODING_AGENT_DIRno longer disables XDG state-dir resolution.pyright/basedpyright--outputjsonruns into a diagnostics summary; machine-readable JSON output now passes through untouched.pi-nativesaborting Bun on Windows withmemory allocation of N bytes failedand no backtrace whenever the native cdylib hit a Rust panic or out-of-memory condition. The release profile usespanic = "abort", so neither default handler emitted any context — Bun received only the bare message and tore down the TUI session before flushing. Module load now installsstd::panic::set_hookandstd::alloc::set_alloc_error_hookvia#[napi::module_init]; both hooks captureBacktrace::force_capture()(so it works withoutRUST_BACKTRACE=1) and write a structured report — pid, thread, size/alignment for OOM, source location and message for panics, full backtrace — to the same logs directory the JS logger uses ($XDG_STATE_HOME/omp/logs/on Linux/macOS when the user has migrated to XDG andPI_CODING_AGENT_DIRisn't customized, otherwise~/.omp/logs/) and to stderr before the host process exits. The OOM hook prints the canonical allocation-failure line before any allocation-prone diagnostics and aborts immediately on re-entry, so real process-wide OOM still surfaces the fallback message instead of recursing in the report path (#2211).What's Changed
New Contributors
Full Changelog: https://github.com/can1357/oh-my-pi/compare/v15.10.11...v15.10.12
v15.10.11Compare Source
@oh-my-pi/pi-agent-core
Changed
<read-files>/<modified-files>/<previous-summary>) and all output-format headings left byte-identical@oh-my-pi/pi-catalogpackage: subpath imports (calculateCost, Codex wire constants) plus catalog values previously taken from the@oh-my-pi/pi-airoot (getBundledModel,clampThinkingLevelForModel), which pi-ai no longer re-exports; type-onlyModel/Api/Effortimports from pi-ai are unchanged@oh-my-pi/pi-ai
Breaking Changes
@oh-my-pi/pi-catalogpackage. Deep subpath exports@oh-my-pi/pi-ai/models.json,/models,/model-cache,/model-manager,/model-thinking,/effort,/provider-models*,/utils/discovery*,/providers/openai-codex/constants,/providers/google-gemini-headers, and/providers/openai-completions-compatare gone — import the@oh-my-pi/pi-catalogequivalents (/models.json,/models,/model-cache,/model-manager,/model-thinking,/effort,/provider-models*,/discovery*,/wire/codex,/wire/gemini-headers,/compat/openai). The pi-ai root barrel re-exports only the model/effort types its own signatures use (Model,Api,ThinkingConfig,Effort,Usage, compat interfaces) — catalog values (getBundledModel(s),calculateCost,modelsAreEqual,clampThinkingLevelForModel,DEFAULT_MODEL_PER_PROVIDER, …) must be imported from@oh-my-pi/pi-catalog.ProviderDefinitionis now auth-only:defaultModel,createModelManagerOptions,catalogDiscovery,dynamicModelsAuthoritative,allowUnauthenticated, andspecialModelManagermoved to pi-catalog'sCATALOG_PROVIDERStable, andKnownProviderIdwas replaced by pi-catalog'sKnownProvider(registry completeness is enforced by a compile-time check against that union). The pure GitHub Copilot key/endpoint helpers moved fromregistry/oauth/github-copilotto@oh-my-pi/pi-catalog/wire/github-copilot.Added
wrapFetchForCchso non-streaming OAuth callers (e.g. the web-search provider) can patch the Claude Code billing-headercchattestation into their request bodies instead of shipping thecch=00000placeholder.Changed
setTimeoutcreate/destroy pair per delta, and the persistent race promises are re-minted every 1024 items so per-race reaction records cannot accumulate for the stream's whole life.write-class call no longer echoes hundreds of KB of payload back to the model as the error message.envKeysliterals (including the pure$pickenvpickers forhuggingface/qwen-portal/xai-oauth),getEnvApiKeynow derives those fallbacks fromCATALOG_PROVIDERS[].envVars, andenvKeysremains only for computed resolvers (Anthropic Foundry, Vertex ADC, Bedrock credential chains) and non-catalog providers (kagi,tavily,parallel,perplexity)model.compatreaders — the per-requestresolve*Compat/detect*Compatcalls (anthropic ×11, responses ×3, completions wrappers), inlinestrictResponsesPairinghost detection, the OpenCodereasoning_contentmutation block, and allresolvedBaseUrlthreading are gone. Compat is materialized once at model build time (@oh-my-pi/pi-catalogbuildModel); the OpenCode thinking-mode quirk is a precomputedcompat.whenThinkingpointer swap, and request-time base-URL overrides only feed the HTTP client. Behavior is unchanged (the AnthropicsupportsLongCacheRetentionofficial-endpoint gate is folded into detection).model.compat.supportsSamplingParamsand adaptivedisplayonmodel.thinking.supportsDisplay(Bedrock too), adaptive effort tiers come from the bakedthinking.effortMap, the GooglethinkingLevelmap is static, and effort-dial-less reasoners (thinking: undefined, e.g.xai-oauth/grok-build) short-circuitresolveOpenAiReasoningEffortwithout the removedmodelOmitsReasoningEffortpredicate.retry-afterhints still win, and retryable pre-content failures such as 502s no longer stop after three tries.Fixed
omitMaxOutputTokens, sendingthink: falsewhen reasoning is explicitly disabled, and preserving HTTP 400 response bodies in surfaced errors.AuthStorage.markUsageLimitReachedcollapsing "every sibling is momentarily blocked" into "no sibling exists": it now returnsUsageLimitMarkResultwith the earliest sibling block expiry (retryAtMs), so retry layers can wait out a short-lived block (60s post-401, 5-min usage-probe) instead of adopting the provider's multi-hour retry-after.rotateSessionCredentialand the auth-gateway adapt to the new shape.stop: in-band{"error":{...}}events andpromptFeedback.blockReasonchunks were never inspected, and a stream ending without anyfinishReasonkept the initializedstop— all three now surface as errors (both the API-key and gemini-cli/Antigravity consumers), and thetoolUsestop-reason override no longer masksSAFETY/MALFORMED_FUNCTION_CALLfinishes that arrive after a valid tool call.MALFORMED_FUNCTION_CALL,RECITATION,guardrail_intervened, …) is now recorded into the surfaced error message.retry-afteron 429/529 — it now waitsmax(headerDelay, backoff)instead of hammering a rate-limited endpoint three times within ~14s of guaranteed failures.errorevents being thrown as raw JSON envelopes; the structurederror.type/messageis parsed out, keeping retry classification on the typed token instead of accidental regex hits.message_start, replayedcontent_block_startevents for already-closed indexes are now consumed silently instead of appending duplicate text/tool calls.{type:"text", text:123}) through the unknown-block catch-all, corrupting history and surfacing later as an opaque TypeError — they now fail validation with a clean 400. The gateway's encode stream also emitspingkeepalives every 15s and a completemessage_start/message_delta/message_stopenvelope when the inner stream ends without a terminal event, so strict clients no longer classify slow or empty streams as protocol errors.claude-opus-4.7/4.8on GitHub Copilot, Vercel AI Gateway, Zenmux) missing adaptive thinkingdisplaysupport — streamed reasoning stayed hidden on those entries because the display predicate only matched dash-form ids (same failure class as #1373).requiresThinkingAsTextreplay path calling.unshift()on string assistant content — an unconditional TypeError that failed any same-model history turn carrying both thinking and text.encrypted_contentfrom inbound reasoning items (strip-mode schema), which broke codex-style stateless replay; the schema is now loose, restoring the symmetry the outbound encoder already preserved. Composite internalcallId|itemIdids are also split before hitting the wire so third-party clients that validatecall_idcharsets no longer reject them.response.completedhandler, so a lostoutput_item.donecan no longer persist a tool call with stale{}arguments and transient parser fields into session history.content_part.added: the missing part is now synthesized on the firstoutput_text/refusaldelta (shared and codex decoders).content/tool_callsdeltas fragmenting a tool call into a truncated call plus a nameless phantom: text/thinking transitions no longer finish open tool-call blocks, so index-only continuation deltas re-find them.AZURE_OPENAI_DEPLOYMENT_NAME_MAP(only the Responses provider honored it), producing opaque 404s when deployment names differ from catalog model ids.reasoning_content, which fed DeepSeek/Kimi exact-replay upstreams a placeholder instead of the model's actual reasoning; it now round-trips as a thinking block, andtoolcall_endemits a corrective id/name chunk when the streamed start carried empty values.EventStream.end()without a terminal result leaving.result()pending forever (reachable via extension streams and the lazy wrapper); it now rejects with a synthesized error.Retry-After(capped at 30s) and other statuses are not retried, while status-less transport blips keep the linear retry.<in model output for up to 256 chars: idle-state holding now only triggers on a strict DSML section-open prefix, and blowing the 1MB parameter cap no longer leaks the closing envelope tags as visible text; a capped parameter value also carries an explicit…[parameter truncated]marker instead of executing the tool with silently corrupted input.{}: the visited-set cycle guard treated a subschema object reused across two properties as a cycle; path-trackingenter/exitnow allows sharing while still short-circuiting true cycles, frozen input schemas no longer throw, and the path counter no longer leaks depth on the cycle branch (which made every later normalization of the same object misreport a cycle).AbortSignal, failing every concurrent waiter when one parallel Vertex call was cancelled; callers now race their own signal against a detached refresh, which is bounded by its own 30s timeout so a hung fetch cannot pin the in-flight slot until process restart.functionCall.argsto{}like the shared consumer.toolConfigentirely whentoolChoiceis"none"while history still contains tool blocks — the Converse API rejects such requests, so tool specs are kept and only the choice is omitted.credential_process/SSO fetches — the shared resolution is detached from the first caller's abort signal (one cancelled request no longer fails every waiter) and bounded by its own 30s timeout. The eventstream reader also cancels the response body on abnormal exit instead of leaving the HTTP connection draining.websocket_connection_limit_reached: the no-content reconnect path never consulted the retry budget and never waited, hammering the endpoint forever when the limit is account-scoped. Reconnects are now budgeted and delayed like every other WS retry path, falling back to a single SSE replay when exhausted.toolcall_endwas delivered, surfacing the error instead of re-emitting the same tool calls.web_search_call) from the failed attempt into the replayed turn'sproviderPayloadand append baseline.websocket closed before open) is classified fatal and disabled WebSockets for the whole session. Failure cleanup now skips CONNECTING sockets and the pool re-joins replacement handshakes (bounded).custom_tool_call_outputitems (onlyfunction_call_outputwas folded into an assistant note) — a compaction splice that dropped anapply_patchcall while keeping its result produced a hard 400 on the default GPT-5 Codex toolset.processResponsesStreamfinalizing reasoning items via a bareitemIdcontent scan instead of the routed entry: with id-less reasoning items (local hosts), everyoutput_item.donematched the FIRST thinking block — the second item's text clobbered it and the second block was never finalized or signed.processResponsesStreamdropping tool calls and message text whoseoutput_item.addedevent was lost (lossy proxies):toolcall_endwas emitted with a dangling contentIndex while the call never enteredmessage.content, so the agent loop silently never executed it. The done handler now synthesizes the missing block; still-open tool-call blocks are also final-parsed atresponse.completedso thetoolUseoverride cannot hand the agent stale{}arguments.response.incompletewithincomplete_details.reason: "content_filter"being reported as a token-cap truncation (stopReason: "length") — the agent loop's length recovery then asked the model to "shorten" a filtered prompt. Content-filtered turns now surface as errors; usage is also populated fromresponse.failedevents, and an unknown terminal status degrades to"stop"with a logged anomaly instead of throwing away a fully-streamed response.premiumRequestsaccounting being dropped from failed/cancelled responses:populateResponsesUsageFromResponsereplacedusagewholesale and the error path threw before the success-path re-apply. The populate now preserves the field.deduplicateToolCallIdssuffixing the whole composite Responses id (callId|itemId) —normalizeResponsesToolCallIdextracts the first segment as the wirecall_idat encode time, so both copies collapsed back onto onecall_idand the request carried duplicate call/output pairs. The suffix and length budget now apply per segment.transformMessages' same-model trust rule.store: falsewhile requestingreasoning.encrypted_content(stateless-only per OpenAI), replaying custom tool calls paired with mismatchedfunction_call_outputitems (customCallIds was never threaded through), letting the SDK's internal retries (maxRetries 5) silently re-POST inside the explicit first-event deadline, and sending aprompt_cache_keywhen the caller opted out viacacheRetention: "none".onResponsenotification callback (a slow callback aborted an already-connected stream), Copilot transient-model retries re-attempting on an already-aborted signal (instant dead retry surfacing the scheduler's AbortError), CodexreasoningSummary: nullbeing coerced to"auto"(the documented omit-summary contract was unreachable), nested Codex error codes (response.error.code) being invisible to the connection-limit/previous-response recovery matchers, and the session id leaking unredacted intoPI_CODEX_DEBUGlogs via thex-client-request-idheader.processResponsesStream(shared byopenai-responsesandazure-openai-responses) ignoring the terminalresponse.incompleteevent: a max-output-tokens-truncated response ended withstopReason: "stop", zero usage, and no cost instead of"length"with the reported token counts.response.incompleteis now handled alongsideresponse.completedand counts as stream progress for the idle watchdogs.partialJsonaccumulation buffer (and a potentially stalearguments.input) afterresponse.output_item.donein the shared Responses stream processor — the function_call branch already cleaned these up.providerPayload— stale reasoning items completed before the failure were re-sent as history input on subsequent requests alongside the retry's own items.response.completedqueued just before an eager server close was discarded, turning a finished response into a spuriouswebsocket closedfailure and a full request replay. Errors now append behind pending data frames.getOrCreateCodexWebSocketConnectioncallers (prewarm racing the first request) tearing down each other's in-flight handshake — closing a CONNECTING socket rejected the other caller with a fatalwebsocket closed before open, disabling WebSockets for the entire session. Callers now join the pending handshake.toolcall_endhad already been delivered to the consumer (canSafelyReplayWebsocketOverSseguard was bypassed, re-emitting the same tool calls); the error now surfaces instead.custom_tool_call_input.deltaframes, which counted as stream progress and could keep a degenerate response alive forever with no cap on buffer growth.response.createdevent resetting the recorded time-to-first-token.xhigh; Fable/Mythos and Opus 4.7+ requests now map userhigh/xhighonto OpenRouter's Anthropicxhigh/maxeffort scale.stop_reasonfailing the whole turn after the response had fully streamed.mapStopReasonthrew on unrecognized values, and since the reason arrives on the trailingmessage_deltathe error was unretryable — the livemodel_context_window_exceededstop reason (default on Sonnet 4.5+) hit this path. It now maps tolength, and any future unknown reason degrades to a logged anomaly plus a normalstopinstead of an error.CLAUDE_CODE_MAX_OUTPUT_TOKENSclamp exists to match the OAuth wire fingerprint, butbuildParamsapplied it unconditionally, silently halving the output budget of 128k-output models (e.g. Opus 4.8) for API-key callers. OAuth requests keep the clamp.errorMessageon astopReason: "stop"assistant message. After a grammar-too-large 400 triggered the non-strict retry, the original 400 text was kept on the final message even when the retry succeeded — consumers that treaterrorMessagepresence as failure (e.g. balance probes) misclassified the turn, and the stale text suppressed later refusal explanations. The fallback is now logged instead.User-Agentheaders being silently dropped on non-OAuth Anthropic requests.enforcedHeaderKeysfiltered the header out ofmodelHeadersin every branch but only the OAuth branch set one back; the Cloudflare-gateway, bearer-gateway, andX-Api-Keybranches now forward the caller's value verbatim.fast-mode-2026-02-01beta header once a session has learned the endpoint+model rejects fast mode (fastModeDisabledprovider state), matching the already-droppedspeedparam.buildAnthropicHeadersdefaulting API-key requests onto the full Claude Code OAuth beta list (oauth-2025-04-20,claude-code-20250219, …). TheclaudeCodeBetasdefault is now OAuth-gated, matching the streaming path — the web-search header builder was the only caller hitting the default, so API-key search requests now carry just their own betas (e.g.web-search-2025-03-05). An emptyanthropic-betaheader is omitted entirely instead of being sent as an empty string.developermessages being upgraded to mid-conversationsystemturns on Opus 4.8+/Fable/Mythos 5. System content is text-only on the wire, so a developer turn carrying image blocks in an upgrade-eligible position produced a 400; it now stays ausermessage.message_deltawas not gated by the terminal-stop flag (content events and duplicatemessage_startwere), so the splice'sstop_reason/usage replaced the finished turn's — atool_useturn could be relabeledstop, and the harness then never executed the streamed tool calls. Post-terminal deltas are now logged as envelope anomalies and skipped.pingarriving beforemessage_startconsuming the Anthropic first-event watchdog: the stall was then classified as a terminal mid-stream idle timeout instead of a retryable first-event timeout. Pings no longer count as the first item but still refresh the idle deadline once content is flowing.usage/deltaobjects frommessage_start/message_delta/content_block_*envelopes crashing the turn with an unretryableTypeError; the missing payloads now degrade to logged envelope anomalies like every other malformed-frame case.applyPromptCachingplacingcache_controlonthinking/redacted_thinkingblocks — Anthropic rejects that with a 400. A thinking-only assistant turn inside the trailing cache window (e.g. followed by the syntheticContinue.pad) no longer receives a breakpoint.assistantparams reaching the wire when an empty user/developer turn between two assistant turns was dropped by the converter (e.g. an empty "nudge" submission after a length-truncated reply); Anthropic 400s on non-alternating assistant turns, and the broken triple replayed on every subsequent request. Auser: "Continue."separator is now inserted, mirroring the trailing-prefill fallback.supportsAdaptiveThinkingDisplaymisparsing bare dated Opus ids:claude-opus-4-20250514(Opus 4.0) parsed as minor20250514≥ 4.7, which silently dropped theinterleaved-thinking-2025-05-14beta for API-key Opus 4.0 requests.output_config.effortshipping without theeffort-2025-11-24beta on thinking-off requests against adaptive-only Claude models (the effort:"low" pin), and the mid-conversationsystemrole shipping withoutmid-conversation-system-2026-04-07on API-key and OAuth-utility requests; both betas are now added whenever the request can carry the corresponding field.Content-Typeand noanthropic-versionheader — the copilot branch builds its headers from scratch and Bun's fetch does not defaultContent-Typefor string bodies. Both headers are now pinned to match every other branch.PI_STREAM_FIRST_EVENT_TIMEOUT_MS=0), the client's internalmaxRetries: 5reactivated and stacked with the provider loop's 3 retries — up to 24 wire attempts with double backoff. The provider now pins per-requestmaxRetries: 0unconditionally.AnthropicMessagesClientspreadingfetchOptionsafter the core request fields, letting a caller-suppliedsignal/method/bodysilently disconnect the timeout controller or corrupt the request. Transport extras (TLS) still pass through; core fields now always win.claude-cli/2.1.160) and OAuth bootstrap (claude-code/2.1.160) pinned a stale version while/v1/messagesreported 2.1.165; both now derive fromclaudeCodeVersion.x-anthropic-billing-header:mid-text suppressing the entire Claude Code system-block injection (billing header, instruction, and cch attestation); the resumed-session guard now anchors withstartsWith.tool_use.inputstring leaves are now deep-sanitized withtoWellFormed(), while same-API Anthropic arguments stay byte-identical to keep prompt-cache prefixes stable.mergeHeadersmerging case-sensitively on the Copilot/client-options path, where a miscased user-configured header (e.g.authorizationnext to the synthesizedAuthorization) survived as two keys that theHeadersconstructor joins comma-separated on the wire.buildCopilotDynamicHeaders) and error-finalization failures now surface as anerrorevent instead of an unhandled rejection that leftstream.result()hanging forever; the spurious "cch billing placeholder not patched" warning no longer fires when the placeholder only appears in user content.Removed
iterateUntilAborthelper (superseded byiterateWithIdleTimeout); it leaked the upstream iterator when the consumer abandoned mid-yield and had no production call sites.@oh-my-pi/pi-catalog
Added
hostMatchesUrl,modelMatchesHost, and endpoint-shape helpers in the newhostsmodule for consistent provider/baseUrl matchingbuildModel(spec)(build.ts) is now the single Model constructor: it materializes the fully-resolved compat record and canonical thinking metadata exactly once (compat first, thinking derived from identity + resolved compat), soModel.compatis a required, completeCompatOf<TApi>(ResolvedOpenAICompat/ResolvedOpenAIResponsesCompat/ResolvedAnthropicCompat) and request-path code reads fields with zero URL parsing and zero per-request allocation. Sparse user/config overrides live on the newModelSpec<TApi>input shape and survive onModel.compatConfigfor introspection.ResolvedAnthropicCompat.supportsSamplingParams(Opus 4.7+/Fable/Mythos rejecttemperature/top_p/top_kwith a 400), baked at build time from model identity so the request path stops re-parsing model ids.supportsReasoningParams,alwaysSendMaxTokens,isOpenRouterHost,isVercelGatewayHost,streamIdleTimeoutMs, and a precomputedwhenThinkingalternate view (OpenCodereasoning_contentgating, #1071/#1484); responsesstrictResponsesPairing,supportsLongPromptCacheRetention,supportsReasoningEffort; anthropicofficialEndpoint,requiresToolResultId,replayUnsignedThinking.@oh-my-pi/pi-catalogpackage: the model catalog extracted from@oh-my-pi/pi-ai. Owns the bundledmodels.jsonand its generation pipeline (scripts/generate-models.ts), the core model data types (Model,Api,ThinkingConfig,Effort,Usage, compat interfaces), thinking metadata enrichment and generated policies (model-thinking.ts), the SQLite model cache and model manager, per-provider discovery factories (provider-models/), the discovery protocol clients (discovery/), and the newCATALOG_PROVIDERStable — the single source of truth for provider ids, default models, and discovery wiring (KnownProvider,PROVIDER_DESCRIPTORS, andDEFAULT_MODEL_PER_PROVIDERare derived from it).identity/module centralizing model-identity concerns that were previously duplicated across packages: family classification and version parsing (identity/classify.ts, extracted from pi-ai'smodel-thinkinginternals), canonical model equivalence with injected reference data (identity/equivalence.ts, from coding-agent'smodel-equivalence), proxy/reseller reference lookup (identity/reference.ts, from coding-agent'smodel-registry), bracket-affix and id-segment helpers (identity/id.ts), a single trailing-marker vocabulary with canonical vs reference flavors (identity/markers.ts—searchstays reference-only so Perplexity'ssonar-pro-searchremains canonical-distinct), and provider priority ordering (identity/priority.ts).getBundledCanonicalReferenceData/getBundledModelReferenceIndexinidentity/bundled.ts): one lazy walk of the bundled catalog feeds both canonical equivalence and proxy-reference lookup, so consumers no longer hand-roll the glue.identity/selection.ts: pure canonical-variant selection (resolveCanonicalVariant,buildCanonicalModelOrder,CanonicalVariantPreferences) extracted from the coding-agent registry — provider rank, then exact-id match, variant source, id length, and candidate order.Changed
modelMatchesHost/hostMatchesUrl) with normalized matching instead of raw URL substring checkshostMatchesUrl/modelMatchesHostusage in compatibility detection to reduce mismatches across case variants and provider alias hostsenvVarslist;catalogDiscovery.envVarsbecame an optional generation-time override (onlycursorandvercel-ai-gatewaydiffer) andPROVIDER_DESCRIPTORSmaterializes the resolved list forgenerate-models.ts.Model's api parameter now defaults toApiinstead ofany(Model<TApi extends Api = Api>), so bareModelno longer behaves asModel<any>at call sites.ThinkingConfigis now explicit and total: an orderedeffortsarray replaces theminLevel/maxLevel/levelsrange encoding, and the wire facts are baked alongside it —effortMap(anthropic-adaptive 4-tier vs 5-tier scale, shared with the OpenRouter completions remap) andsupportsDisplay(adaptivedisplayfield support). Explicit spec thinking owns the capability surface (mode/efforts/defaultLevel) and wins over inference; missing wire facts are backfilled from identity so configs never need to know Anthropic's tier tables. Reasoning models that reject the wire effort param (compat.supportsReasoningEffort: falseon openai-responses*) are encoded asthinking: undefined("thinks, no control surface") instead of the removedmodelOmitsReasoningEffortspecial case.models.jsonwas re-baked in the new vocabulary behind a 3196-model behavioral parity gate, and the model cache schema bumped to v4 to invalidate old-shape rows.mapEffortToGoogleThinkingLevel(effort)is now a static map (model parameter dropped — validation stays at therequireSupportedEffortcall sites), andmapEffortToAnthropicAdaptiveEffortreads the bakedthinking.effortMapinstead of re-classifying the model id per request.scripts/generated-policies.ts:applyGeneratedModelPolicies(now policy fixups + thinking re-bake via the shared deriver),linkOpenAIPromotionTargets, the Copilot context-window table, minimax/opencode-go compat fixups, andCLOUDFLARE_FALLBACK_MODEL. The anthropic id predicates (hasOpus47ApiRestrictions,supportsMidConversationSystemMessages,isAnthropicFableOrMythosModel) moved toidentity/familyfor build-time use by the compat/thinking derivers only.Fixed
@oh-my-pi/pi-cataloginto the release publish package list, tarball install smoke test, and rootbun generate-modelsscript.supportsAdaptiveThinkingDisplayonly matching dash-form version ids: dotted ids (claude-opus-4.7) now classify throughidentity/classifylike every other anthropic predicate, so six bundled dotted Opus 4.7/4.8 entries (github-copilot, vercel-ai-gateway, zenmux) regain adaptivedisplaysupport; bare dated ids (claude-opus-4-20250514= Opus 4.0) stay excluded.claude-opus-4-20250514parsed as version 4.20 → wrongly adaptive); the map now derives from the shared classifier and the shared 4-/5-tier tables.Removed
enrichModelThinking(and its non-enumerable memo-slot cache),refreshModelThinking,modelOmitsReasoningEffort, and themodel-thinkingre-exports of generator-only policies. Thinking metadata is resolved exactly once insidebuildModel; runtime helpers (getSupportedEfforts,clampThinkingLevelForModel,requireSupportedEffort, the effort mappers) are pure field reads.@oh-my-pi/pi-coding-agent
Added
supportsReasoningParams,alwaysSendMaxTokens,strictResponsesPairing, and a recursivewhenThinkingoverlay (alongsidestreamIdleTimeoutMs/supportsLongPromptCacheRetention/requiresToolResultId/replayUnsignedThinking) to the OpenAI/Anthropiccompatschema so custom model entries can configure those provider-specific capabilitiesthinkingconfig now uses the catalog's explicit vocabulary:efforts(ordered list) plus optionaldefaultLevel,effortMap, andsupportsDisplayoverrides; the legacyminLevel/maxLevel/levelsrange shape is still accepted and normalized at parse time. Wire facts (effortMap/supportsDisplay) are backfilled from model identity when not set, so existing claude-proxy configs keep the 5-tier adaptive scale and summarized display without changes.omp usagecommand: a detailed per-account breakdown of provider usage limits (bars, windows, reset times, plan metadata) covering every stored credential — accounts with no usage endpoint are listed as "no usage data" rows. Each provider section ends with per-window capacity stats ("capacity: 5h → 2.40/5 accounts used (2.60× quota left)"). Flags:--providerto filter,--jsonfor the broker-shaped report payload, and--redactto mask account emails/ids down to a two-char anchor plus a minimal middle-out differentiator (ca*9*) for screenshot-safe sharing.omp -h" report class): a watchdog prints a stderr line every 10s naming the deepest in-flight startup phase (vialogger.openSpanPath()) until a mode runner takes over, pausing around legitimate interactive waits (fork/move prompts, the--resumesession picker);PI_DEBUG_STARTUPis restored as streaming synchronous[startup]phase markers covering command-module imports and the native addon load, which the post-startupPI_TIMINGtree structurally cannot show for a hang; and waiting on piped-stdin EOF announces itself after 1s instead of blocking silently.bin.omppoints atdist/cli.js(built byscripts/bundle-dist.tsduringprepack, ~18MB minified, natives/transformers/mupdf external), cutting npm-install cold start by roughly 3x versus transpiling the raw TypeScript graph per launch;src/**stays published for SDK consumers and worker fallbacks. The on-repo manifest keepsbin.ompatsrc/cli.ts— release rewrites it via thepublishBinoverride inscripts/ci-release-publish.ts— so source installs (bun link,install.sh --source) keep working without a build stepomp <version>/Initializing session…); resume/fork/continue flows, quiet mode,PI_TIMING, and non-TTY stdio still skip it/statsto launch the local stats dashboard from an active session, syncing session files first and opening the same browser dashboard asomp stats./settingsnow supports type-to-search filtering on setting labels, paths, descriptions, and values; Escape clears an active search before closing the panel.viewop to thetodotool that echoes the current list without mutating state, so the agent can recover exact task text instead of guessing it from memory.Changed
@oh-my-pi/pi-catalogpackage:config/model-equivalence.ts,config/model-id-affixes.ts, andconfig/model-provider-priority.tswere removed in favor of@oh-my-pi/pi-catalog/identity, and the registry's proxy-reference lookup now shares the catalog's single lazily-built bundled-model walk (@oh-my-pi/pi-catalog/identitybundled accessors) with the canonical-equivalence index instead of walking the ~12K bundled models twice into duplicate mapsconfig/model-registry.tsintoconfig/model-discovery.ts; the registry keeps orchestration (caching, status tracking, merging) while the protocol clients take an injected fetch/auth contextmodelsAreEqual,clampThinkingLevelForModel,getSupportedEfforts,DEFAULT_MODEL_PER_PROVIDER, Gemini/Antigravity wire headers) are now imported from@oh-my-pi/pi-catalog/<module>instead of the@oh-my-pi/pi-aibarrel, which no longer re-exports them; the resolver'sdefaultModelPerProvideralias was removed and its duplicated default-model fallback / scoped-model dedupe blocks were factored intopickDefaultAvailableModeland a sharedaddScopedModelhelperOMP_AUTH_BROKER_*resolution instead of re-running config/token discoveryTaskTool.createcalls in the same working directory to avoid repeated plugin scans during subagent startuplinkedom(web fetch feed parsing and scrapers),puppeteer-core/@puppeteer/browsers(browser launch),@mozilla/readability(page extraction),@xterm/headless(interactive bash PTY),@babel/parser(JS eval import rewriting), and the mnemopi memory engine (backend/state construction)PI_TIMINGstartup phasediscoverModelstodiscoverAuthStorage— the timer only ever wrapped auth storage discovery__omp_*,--tiny-worker) via the declared worker-host entry (workerHostEntry()), collapsing the per-distribution spawn branches; outside a CLI host (bun test, SDK embedding) spawn sites fall back to loading the worker module directly, and both binary build scripts dropped their per-worker--compileentrypoint listsrunCli— the floating call reports rejections to stderr and exits 1, keeping the entry module CJS-lowerable and the bundle parse-friendlyghsearch ops, dropped a deadrsedreference and an internaltool-timeouts.tspointer, and pruned internal mechanism the agent can't act on (screenshot temp-file/downscaling pipeline, browser spawn lifecycle,gh"replaces former op" history and run-watch grace period, output-minimizer heuristics, BM25 ranking name,task.maxConcurrencypointer)<PLAN_TITLE>placeholder in the plan-approval reminder, deduped intra-file restatements, and corrected thetodoop table's claim thatrmrequires atask/phase(barermclears the whole list)sed -i/cat-heredoc commands that the bash interceptor blocks; its bash-alternatives table now only lists non-intercepted commandspty: true) no longer injects the non-interactive environment (TERM=dumb,GIT_EDITOR=true,PAGER=cat,NO_COLOR=1) that defeated its purpose — the PTY child now gets a realTERM=xterm-256color; and when a PTY is requested but unavailable (headless/RPC), the result now carries an explicit downgrade notice instead of silently running through a dumb pipe.?q=queries are now capped at 1000 rows with an "add a LIMIT clause" notice —statement.all()on a multi-million-row table previously materialized every row, blocking the process for minutes.gh run_watchnow polls adaptively (3s for the first minute, then 15s), survives rate-limit errors with backoff instead of dying and discarding accumulated context, reuses job data for completed runs, and gives up with a clear message after ~90s when a commit has no workflow runs at all (previously an infinite 3-second poll loop).Buffer.concatper chunk plus whole-buffer byte-scans per 1KB trim, freezing the session.hosts.ymlmtime (was a blockingreadFileSyncon everyissue:///pr://read including cache hits), background refreshes are deduped by row identity, and PR diffs are stored once per row instead of twice (unified + rendered copies).structuredClone-ing nested tool payloads (up to 500KB) on every progress event; streaming assistant-message reveal caches per-block grapheme counts and skips the markdown render LRU for in-flight partials, eliminating 2-3 full Intl.Segmenter walks per 33ms tick and tens of MB of retained stale partial snapshots on long replies.config/model-registry.tsfurther: model roles (MODEL_ROLES,getRoleInfo,getKnownRoleIds) moved toconfig/model-roles.ts, themodels.jsonconfig handle and provider validation moved toconfig/models-config.ts, the two provider+id merge scaffolds collapsed into onemergeByModelKeyhelper, the four ~15-field override/overlay enumerations now share aModelPatchtype applied by a singleapplyModelPatch(base, patch, transport)core (themergevsreplacetransport policies preserve the same-id custom-definition replacement semantics), and canonical-variant selection delegates to@oh-my-pi/pi-catalog/identity's newresolveCanonicalVariant:levelsuffix parses collapsed intosplitThinkingSuffix, the matching engine is now the documentedmatchModelcore with the selector grammar and entry points layered on top, andresolveCliModel's hand-rolled decomposed provider/id lookup reusesfindExactModelReferenceMatch; runtime discovery tests split out oftest/model-registry.test.tsintotest/model-discovery.test.tsTranscriptContainerassembles the transcript incrementally: each block's render is reference-compared and its stripped contribution, separator, and row placement are reused when unchanged, with the persistent row array truncated and re-pushed only from the first divergent block; the leading byte-identical row count is reported to the renderer through pi-tui's newRenderStablePrefixseam so off-screen transcript rows are no longer re-rendered, re-prepared, or re-audited every frame. Block components became reference-stable to make this effective:UserMessageComponentmemoizes its OSC 133 zone wrapping,WelcomeComponentandDynamicBordercache their renders, and dashboards copy before padding (render results arereadonlyunder the new pi-tui contract)wc -l,sort | uniq -c,comm,diff) are legitimate bash, while commands that merely move, page, or trim bytes a dedicated tool can fetch remain banned — output trimming destroys data theartifact://capture would have saved.Fixed
askquestion/result renders so option and answer rows are no longer duplicated when the component is re-renderedwrite/diffpreviews to keep line-number gutter widths stable while content grows, preventing already-rendered preview rows from being reflowed mid-streamlsp.lazyis enabled: recognized servers are now still discovered at startup and listed with a dim "available" dot (no warmup), and/statusreports them asavailableinstead of omitting the section...markers around inserted block-context rows (each row added its own gap markers from a snapshot of the diff, so neighboring insertions doubled them, and a marker could be left stranded between contiguous lines): non-contiguous regions are now separated by a single blank row, normalized after insertion, and rendered as one dim…in the TUI and HTML exportquestions.map is not a functionTUI crash in the ask tool's call renderer when a model double-encoded thequestionsarray as a JSON string (a bare string passes a truthy.lengthcheck but has no.map): the renderer now normalizes untrusted call args — parsing double-encodedquestions, dropping malformed entries/options, and falling back to the "No question provided" frame instead of throwingmodelRolesconsumers so comma-separated fallback chains are split before model parsing, preserving explicit thinking selectors instead of treating the comma tail as an invalid suffix.pasteTexthook, and the dialog wrappers had none, so the payload was stuffed into the main prompt editor hidden behind the dialog.HookEditorComponentandHookInputComponentnow forwardpasteTextto their inner editor/input (pasting also resets the input dialog's timeout countdown like any keystroke).per-project-taggedmental-model seeding so each project gets its own conventions/decisions models and session context only injects active-project or untagged models (#2218)..cmdcommands by wrapping batch shims withcmd.exe /d /s /cusing the outer command quotes required bycmd /s, while preserving literal%and quoted JSON arguments for Codegraph MCP (#2220).exploreagent'sthinking-level: medfrontmatter — not a valid effort (minimal/low/medium/high/xhigh), so it silently parsed to undefined and the agent ran without its intended thinking level~/.claude,~/.cursor, project trees,@-imports) now stat-gate to regular files before reading: a FIFO/socket/char device dropped where a context file is expected previously blocked startup forever on a read that can never see EOF.pathschema and docs so web URLs and internal URI targets (omp://,issue://,pr://, etc.) are advertised alongside local files (#2215).artifact://advertised as the "full capture" was permanently missing its head — the agent re-reading it got truncated data presented as lossless.vault://writes bypassing both the approval ladder and plan mode: internal-URL writes were uniformly rated tierread(auto-allowed even in always-ask) and the internal-router branch returned before the plan-mode guard, so the model could silently overwrite real Obsidian notes; writes through schemes with a mutating handler are now tierwriteand plan-mode-enforced..tar.gz/.tgzarchives silently stripping gzip compression (the rewritten archive was a bare tar under the.gzname — masked on re-read because Bun auto-detects, broken fortar xzf/CI consumers), and made archive rewrites atomic via temp-file + rename so a crash mid-write can no longer destroy every other member; symlinked archive paths resolve to their target before the swap so the rename writes through instead of replacing the link with a regular file.\nand compared=== "=======", so=======\rnever matched and the agent edited around live conflict markers without warning; CRLF files now detect, splice, and round-trip their line endings correctly.\nin the pattern) silently returning zero matches: the native searcher was never switched to multi-line mode (only the regex flag was set), so the advertised feature matched nothing on real files while reporting a confident "No matches found".skip(now capped per file with the footer hedgingof N+when truncated), paginating past the last page returned "No matches found" instead of "No more results", directory scans now report how many >4MB files were skipped instead of silently excluding them, adjacent matches in virtual resources no longer emit duplicated backwards-numbered context lines, and patterns are no longertrim()ed (only all-whitespace is rejected — leading/trailing whitespace is meaningful regex).artifact://3:-100) silently dumping the whole resource instead of erroring, selectors directly on an archive root (a.zip:500,a.zip:raw) being misparsed as member names, archive members minting editable hashline tags keyed to the archive path (they are immutable resources), URL selector tokens being case-sensitive (:RAW404ed),artifact://Nresolving into another session's artifacts in multi-session hosts, and not-found paths with archive/sqlite extensions stacking multiple 5s workspace-wide suffix globs (now shared per read, with glob metachars escaped sofoo[1].tscan match itself).cd X &&extraction breaking shell-expanded paths —cd "$(git rev-parse --show-toplevel)" && makefailed with "Working directory does not exist" because the captured path was resolved literally; extraction now defers to the shell when the path contains$, backticks, or(.>inside quotes (echo "a -> b",printf 'use 2>&1'); the rule is now quote-aware, and also catches>|clobber redirects and$VARtargets it previously missed.Shellin the process-global session map, and the running-job cap failing all bash commands outright — at capacity, commands now degrade to direct foreground execution (explicitasync: truestill errors).askreporting timeout auto-selection as "User selected: X" — fabricated consent for consequential questions; the result now says "(auto-selected after timeout)" with atimedOutdetail flag, the transcript card marks the auto-selection distinctly, and a deliberate Esc seconds past the deadline is treated as a cancel instead of being reclassified as a timeout.todoaccepting duplicate task content/phase names ininit(duplicates were permanently unaddressable — every targeting op hit the first match while auto-promotion kept resurrecting the twin) and persisting half-applied batches on error; failed batches no longer mutate state.bumpFileMutationVersion, shebang chmod), so mutation-version consumers saw stale state depending on whether an editor was attached.conflict://*resolution failing spuriously when an out-of-band edit shifted a conflict block (stale duplicate registrations are now tolerated as already-resolved — but a DISTINCT conflict block that is merely byte-identical and still present in the file stays addressable), and partial conflict-resolution failures now setisErrorinstead of burying failed files mid-text in a success result.# %% [markdown]marker text being silently split into extra cells on any edit; marker-shaped source lines are now escaped on render and restored on parse.initializecompleted (concurrent callers hit "server not initialized" flakes on first use), reader-loop death leaving a permanent zombie client where every request times out at 30s forever (bad messages are now isolated per-message and a dead reader tears the client down for respawn), framing stalls on header blocks withoutContent-Length(now resynced past the junk in both LSP and DAP),lsp statushardcodingreadyfor every client including wedged ones, and shutdown skipping clients still mid-initialize (their server processes outlived exit).query: "2"could apply a different quickfix whose title contained "2"; numeric queries now select strictly by index.file://URIs built without percent-encoding: a%in a path threwURIErroron round-trip and a#truncated the server-side path, desynchronizing diagnostics and workspace edits; URIs from lax servers carrying a raw#/?now route to the lenient parser instead of parsing "successfully" as fragment/query and misrouting edits.applyWorkspaceEditnow overlap-validates every file before writing any, so a conflicting rename no longer leaves the workspace half-renamed.lsp reloadhanging for the whole tool timeout (didChangeConfigurationwas sent as a request; it is a notification), biome failures being silently reported as "no diagnostics", a hung language server adding up to 30s to every edit (writethrough init is now deadline-bounded at 5s with deterministic spawn failures negative-cached for 3 minutes), and DAPpause()burning its full timeout when the stopped event raced the subscription; concurrent DAP breakpoint mutations are also serialized per session (last-writer no longer silently drops the other's breakpoints), queued mutations honor the caller's abort at dequeue, and the DAP output buffer retains a full 128KB tail instead of dropping whole chunks below the cap.git stash push/pop+ cherry-pick on the shared repository — the merge sequence now runs under the repo lock, eliminating a lost-uncommitted-changes race; a stash-pop failure after successful cherry-picks also no longer reports merged branches as "unmerged" (the duplicate-commit trap) and instead tells the user to pop the stash manually.completed, semaphore-queued tasks counting against the 15-job global cap (batches >15 dropped the remainder and starved other async work), duplicate task ids skipping validation on the async path, and an abort racing subagent session startup leaking the late-created session's LSP/MCP processes.$@​command expansion interpreting$-replacement patterns in user input.parallel()early-rejecting in violation of its documented barrier (orphaning in-flightagent()thunks with worker-side promises hung forever), Python child subprocesses inheriting the NDJSON frame pipe (their stdout was dropped and could corrupt protocol frames — it is now captured and forwarded), JS cell timeouts silently wiping persistent VM state without annotation, and the JS console bridge throwing onconsole.dir/time/group/assert/trace.pr_pushnever invalidating the PR/diff cache (the canonical push-then-verify flow read a pre-push diff for up to 5 minutes), current-branchgh pr merge/closewith no positional never invalidating at all (exactly the staleness the cache layer claims to eliminate; numeric flag values like--milestone 3also no longer steal the positional), multi-PR checkouts discarding successful checkouts and racing in-flight git mutations on first failure (allSettledwith per-PR reporting), run-watch ending with a failure result and zero logs when an auto-retry raced the grace-period refetch, the per-watch completed-run job cache serving a rerun's FIRST-attempt jobs after the rerun completed (entries are evicted whenever a run is observed non-completed), pagination terminating on post-filter page length, millisecond precision leaking into GitHub search date qualifiers, leading-dash PR identifiers reachingghas flags, andissue://?state=typos silently coercing to the open list.202xsubstring with the current year — it corrupted CVE identifiers and made historical-year searches silently impossible.dialogspolicy disposing Chromium and then using the dead handle, a stale tab release evicting a live replacement browser from the registry (spawning duplicate Chromium processes), and concurrent same-nameopencalls leaking a worker + refcount via a check-then-set race (acquisitions are now single-flight per name); queued opens honor an abort at dequeue, and an init-payload failure releases the temporary browser hold instead of pinning the refcount forever.Content-Typeand<meta charset>are now honored viaTextDecoder), binary URLs being downloaded twice (body skipped on the first pass for convertible types), >50MB truncation being silent (now flagged in notes), all transport error detail being swallowed into a bare "Failed to fetch URL" (the cause is surfaced and 429s get oneRetry-After-honoring, abort-aware retry), MCP SSE keep-alive lines escaping as rawSyntaxErrors, MCP calls having no default timeout (now 60s), and a YouTube fetch budget expiry being misreported as a user abort that also skipped temp-file cleanup.a.zip:dir:50now starts the listing at the 50th entry instead of relisting from the top.import(...)inside functions passed to the browser tool'stab.evaluate/page.evaluatefailing with__omp_import__ is not defined. The eval/browser JS runtime rewrites dynamic-import callees to the worker-injected__omp_import__helper, but puppeteer serializes evaluate callbacks withFunction.prototype.toString()and re-runs them inside the page, where the helper does not exist. The rewriter now substitutes a guarded shim that falls back to native dynamic import when the helper is absent, so serialized code works in the page realm while in-worker imports keep resolving against the session cwd.findBlockContextLinesthen re-surfaced it under its post-edit number and the row was spliced in after the adjacent change run. New-file boundary lines are now translated back to pre-edit numbers (the compact-preview renumbering contract) and merged into a single old-numbered insertion pass — also fixing closers below a net-offset edit being dropped or renumbered incorrectly.cchattestation (viawrapFetchForCch) instead of shipping thecch=00000placeholder.deriveLiveCommitState) was all-or-nothing per block — one perpetually rewriting row (a task tool's ticking progress tree, per-agent cost/tool counters, spinner stats) suspended scrollback commits for the entire block, so once the block outgrew the viewport its static head (e.g. a task's prompt/context markdown) was neither committed nor on screen until the tool sealed, and was lost outright if the session ended mid-run. A stable-prefix ratchet now promotes leading rows that stayed visibly identical for a full 30-frame window as commit-safe, so the settled head reaches native scrollback while only the genuinely volatile tail stays deferred; a rewrite above the promoted run retreats the boundary and the engine audit recommits (duplication, never loss).</title>and cache/status lines into the interactive TUI scrollback (#2206)..claude/agents/*.mdas OMP subagents; direct task-agent discovery now only loads OMP-native.ompagent roots, while Claude marketplace plugin agents keep their existing provider path (#2209).Removed
clearOnShrinksetting and itsPI_CLEAR_ON_SHRINKenvironment variable: the rewritten renderer always clears shrunken rows exactly, so the flicker/perf tradeoff the setting controlled no longer exists. Existing config entries are ignored.@oh-my-pi/hashline
Breaking Changes
BlockResolution.isDeletetoBlockResolution.op("replace" | "delete" | "insert_after") so resolutions can describe every block-anchored opAdded
insert after block N:patch syntax to insert body rows after the last line of the tree-sitter-resolved block beginning on line N, so a statement can be placed after a construct without counting to its closing lineinsert after N:hunks: a body indented shallower than its anchor line slides past the structural closer lines below the anchor until depth returns to the body's level, with a warning naming the final landing line. The shift never crosses content lines, skips incomparable indentation styles and pure-closer bodies, and is abandoned when another hunk targets a crossed lineInMemorySnapshotStore(maxTotalBytes, default 64 MiB): the cap was previously per-file only, so a session reading many large files retained up to 30 paths × 4 full-text versions indefinitelyChanged
replace block N:ops entry in the patch prompt to grammar and pointing rules; the usage doctrine it duplicated stays in the rules sectionbuildCompactDiffPreviewto treat blank rows as gap separators alongside…markers: separators never stack (removed lines omitted from the preview no longer leave two adjacent), and leading/trailing separators are trimmedFixed
}lines repeat, a payload intentionally beginning/ending with lines identical to the range's neighbors had both edges silently dropped, writing content that differed from what was authored1: "one"dict/YAML shapes) satisfied the uniform line-prefix check and had its keys stripped from every line — blank rows are now preserved when proven interior, and the uniform strip refuses lone-literal remaindersdelete/replaceranges ending on the phantom trailing line of a newline-terminated file silently stripping the file's final newline; such anchors are now rejected with guidance towardN-1/insert tail:(inserts there remain valid, and genuine empty last lines of unterminated files stay deletable)@oh-my-pi/pi-mnemopi
Fixed
openrouterby URL host, so custom embedding endpoints are now recognized correctly instead of being misclassified by substring matchingopenrouterhosts are treated as non-custom@oh-my-pi/pi-natives
Added
maxCountPerFileoption togrepthat caps how many matches a single file may contribute, so one hot file can no longer exhaust the globalmaxCountbudget in path order and starve every file sorted after it out of the result set entirely.PI_DEBUG_STARTUPstreaming markers to the addon loader (native:loadNative:start,native:extractEmbeddedAddon:start,native:require:<file>,native:loadNative:done), written with synchronous stderr writes so a hang inside first-run extraction ordlopen()— which blocks the event loop and defeats any timer-based diagnostics — still leaves the failing step as the last marker on stderr.skippedOversizedcount toGrepResult: directory walks now report how many files were silently skipped for exceeding the 4MB per-file grep limit (previously they vanished without a trace, letting callers conclude a symbol does not exist).Changed
glob()walk (the path OMPfindalways takes): per-thread bounded top-N heaps replace the single-threaded full-stat traversal, so large trees rank in a fraction of the wall clock while keeping the deterministic mtime-desc/path ordering and bounded memory.Fixed
multilineset the(?m)flag on the regex matcher but never enabledmulti_lineon theSearcher, which stayed line-oriented, so any pattern spanning a\nreturned zero matches with no error.@oh-my-pi/omp-stats
Changed
getBundledModel,GeneratedProvider) now import from the new@oh-my-pi/pi-catalogpackage instead of the@oh-my-pi/pi-aibarrel, which no longer re-exports catalog valuesworkerHostEntry()+__omp_stats_sync_workerargv selector) when running inside omp — source, npm bundle, or compiled binary — and keeps loading its ownsync-worker.tsmodule directly for standaloneomp-stats, bun test, and SDK hosts@oh-my-pi/pi-tui
Added
SettingsListnow supports type-to-search filtering with Escape clearing an active query before canceling.Changed
[+Astyle fragments into the editor as typed textStdinBuffer.extractCompleteSequencesto index-based scanning: the previous per-iterationslice+Array.from(remaining)[0]made plain-text bursts O(n²), turning a 100KB non-bracketed paste into a multi-second freezeInput), capped the kill ring at 60 entries, cached word-wrap layout per (line, width) so each render and key handler shares one wrap pass, and batched ≤1000-char single-line pastes into one insert + one trigger-detection pass instead of per-character replayComponent.rendernow returnsreadonly string[]: results are component-owned, callers must not mutate them, and an unchanged component returns the same array reference (reference equality proves byte-identical rows).Container.rendermemoizes its concatenation on child references (children are still rendered every frame for their side effects);Boxreplaced its content-hashing cache with the same child-reference memo (no more per-frameleftPad + linerebuilds and full-content hashing);Markdown,Spacer, andTruncatedTextreturn their cached arrays by reference instead of defensive copies. The TUI composes a persistent frame from per-child segments and an opt-inRenderStablePrefixreport (consumable floor semantics for in-place mutators like the transcript), so marker extraction, line preparation (persistent prepared-frame replacing the per-frame rebuilt cache arrays), and the committed-prefix audit now run only over rows at/after the first changed row instead of every line of the transcript every frameNativeScrollbackLiveRegion) marks them final, and the visible window repaints in place with relative moves. The engine no longer probes the terminal's scroll position or guesses whether a destructive rebuild is safe — the entire ED3-risk/defer/checkpoint machinery (viewport probes, eager streaming mode, dirty-scrollback reconciliation, deferred shrink/mutation intents, streaming high-water rebuilds, ConPTY-specific defer paths) is deleted. ED3 (CSI 3 J) now fires only on explicit user gestures: session replace, resize outside multiplexers, andresetDisplay(). This structurally removes the yank / flash / duplicated-rows / invisible-until-resize failure families tracked across #1610, #1635, #1651, #1682, #1719, #1746, #1799, #1823, #1962, #1974, #2000, #2011, #2154.Fixed
Γöé/ΓöÇinstead of box-drawing borders and Nerd Font glyphs) after a console-sharing child process changed the console codepage (e.g. PHP CLI's implicitchcp, php.net request #73716): the breakage stayed latent until the next full repaint such as ctrl+o expand. The terminal now re-asserts the UTF-8 codepage (output and input) before each stdout writeemergencyTerminalRestore(andterminal.stop()) never left the alt screen nor disabled mouse tracking, so a crash during a fullscreen overlay stranded the user on the alternate buffer with any-motion mouse reporting spewing escape garbage until a manualresetESC[201~end marker (ssh/tmux truncation) silently eating all subsequent input forever while growing memory unboundedly — paste mode now has an inactivity watchdog (1s) and a byte cap (64 MiB) that exit paste mode and deliver the accumulated bytes through the paste event[Paste #​1, +30junk that no longer expanded to the pasted content on submit — delete ranges now extend over any atomic token they intersectresetDisplay()being a no-op on the alt screen: the redraw gesture could not repair a corrupted fullscreen modal because#emitAltFrameskipped identical-string repaints without consulting the force-repaint flag\x1b[36m(chalk cyan): every shipped theme emits truecolor/256-color SGR for bullets, so nested items doubled their indentation per level on all real themes; nesting is now tagged structurally by the list renderer. Ordered-list continuation lines also hang by the actual bullet width, so wrapped text under10.+ items alignsfindCommittedPrefixResync, exported for the stress harness's shadow ledger) samples the prefix tail SGR-stripped so theme restyles and single-row edits never trigger spurious recommits.Removed
TUI.setEagerNativeScrollbackRebuild(),TUI.refreshNativeScrollbackIfDirty(),TUI.setClearOnShrink()/getClearOnShrink(),RenderRequestOptions.allowUnknownViewportMutation,NativeScrollbackRefreshOptions,Terminal.isNativeViewportAtBottom(),Terminal.hasEagerEraseScrollbackRisk(), and theeagerEraseScrollbackRisk/submitPinsViewportToTailcapability fields with their detectors.PI_TUI_ED3_SAFE,PI_CLEAR_ON_SHRINK, andPI_TUI_DEBUGenvironment variables (the levers they tuned no longer exist;PI_DEBUG_REDRAWnow logs the commit-ledger state per frame).@oh-my-pi/pi-utils
Added
PI_DEBUG_STARTUPstreaming startup markers:logger.timenow writes a synchronous[startup] <op>:start/:done/:failstderr line per phase (independent ofPI_TIMING), so a startup that hangs hard still names the phase it is stuck in — thePI_TIMINGtree only prints after startup completes and is structurally unable to diagnose a hang. The CLI runner emitscli:load:<name>markers around each lazily-imported command module for the same reason.logger.openSpanPath(): ops of the currently-open timing-span chain (root → deepest), used by the coding agent's startup watchdog to name the in-flight phase of a stalled startup.declareWorkerHostEntry()/workerHostEntry()(env): self-dispatching CLI entrypoints declareBun.mainas the worker host so worker spawn sites can re-enter the single entry module withWorkerOptions.argvselectors across source, npm-bundle, and compiled distributionsChanged
prompt.compile()to cache compiled templates by the raw template string so repeated calls reuse the same compiled function without re-disambiguatingSnowflake.formatPartspacks the id as a single 64-bit BigInt hex format instead of stitching four 16-bit segments (simpler and ~1.7x faster), andgetTimestampextracts via exact double arithmetic instead of a BigInt round-trip. Output is bit-identical.prompt.format()post-processing got cheap per-line guards and a single-pass ASCII-symbol replacement (was 7 chained regex passes per line), roughly halving render post-processing cost; output is byte-identicalFixed
prompt.format()so ASCII symbol replacements such as-->and!=still run on lines containing a closing HTML comment token when not inside a commentisCompiledBinary()now also honors a define-foldedprocess.env.PI_COMPILED(onlyBun.envwas checked), so builds that constant-foldprocess.envkeep compiled-binary detection without relying onimport.meta.urlbunfs markersomp <cmd> --helpnow loads only the requested command module instead of the entire command table, so an unrelated command whose import graph hangs or crashes can no longer take down every per-command help invocation.What's Changed
Full Changelog: https://github.com/can1357/oh-my-pi/compare/v15.10.9...v15.10.11
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate.
693672390f5f9ec98e38chore(deps): update dependency can1357/oh-my-pi to v15.10.11to chore(deps): update dependency can1357/oh-my-pi to v15.10.12