fix(test): repair auth-storage-rotation merge resolution and normalize changelogs
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed compaction summary inputs escaping Harmony control tokens so Copilot `gpt-5.6-*` models no longer reject serialized analysis-channel markers. ([#5184](https://github.com/can1357/oh-my-pi/issues/5184))
|
||||
|
||||
## [16.5.0] - 2026-07-13
|
||||
|
||||
### Added
|
||||
@@ -14,9 +18,6 @@
|
||||
### Added
|
||||
|
||||
- Added a process-global pause gate (`agentPauseGate`) to safely pause agent loops before model calls or tool executions, allowing them to be resumed later or aborted cleanly.
|
||||
### Fixed
|
||||
|
||||
- Fixed compaction summary inputs escaping Harmony control tokens so Copilot `gpt-5.6-*` models no longer reject serialized analysis-channel markers. ([#5184](https://github.com/can1357/oh-my-pi/issues/5184))
|
||||
|
||||
## [16.4.3] - 2026-07-11
|
||||
|
||||
|
||||
+20
-26
@@ -5,6 +5,26 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed OpenAI Responses `content_filter` terminal events being auto-retried as provider finish errors; content-filtered turns now remain hard failures without the same-model retry loop.
|
||||
- Fixed provider-agnostic replay-safe usage/account-quota failures to rotate through every distinct eligible credential instead of stopping after the fixed a/b/c ladder, while preserving transient-429 backoff, cycle/abort guards, exact failed-credential targeting despite stale session stickiness, and a finite safety ceiling.
|
||||
- Healed GLM in-band tool calls whose `<arg_value>` closer is missing or mistyped as `</arg_key>`; the scanner now ends the value at the next-pair signature instead of swallowing the remaining arguments into one field.
|
||||
- Healed the same `arg_key`/`arg_value` spill when it arrives through native tool calling (provider parses the in-band syntax server-side): as a last resort after validation and coercion fail, contaminated string arguments are split at the spill boundary and the swallowed pairs restored.
|
||||
- Fixed Anthropic logins silently replacing the stored credential when one account email holds multiple organizations (e.g. a Team seat plus a personal Max plan). Credentials are now identified by email + organization: the login flow captures the organization from the token exchange (with a `claude_cli/bootstrap` fallback), both subscriptions store side by side, and the existing multi-account rotation treats them as separate accounts. Legacy email-keyed rows are claimed in place by the first org-scoped login with the same email, and an org-less credential never clobbers org-scoped rows. Usage reports and the per-credential usage cache also partition by organization so the two subscriptions' limit pools no longer merge into one confused row.
|
||||
- Fixed broker-served usage routing for org-scoped credentials: `RemoteAuthCredentialStore` now matches aggregate reports and keys header-ingest overlays by organization first, so with a Team seat exhausted and a personal Max healthy under one email, each credential receives its own pool instead of whichever report appeared first. The Anthropic usage-cache key version was bumped so pre-org cache entries (including the 24h last-good fallback) cannot be replayed across organizations.
|
||||
- Fixed OAuth access results (`getOAuthAccess`, `getOAuthAccesses`, `getOAuthAccessAt`) and `checkCredentials` health results dropping the organization: they now carry `orgId`/`orgName` so consumers that key or label per-account results (e.g. `omp dry-balance --bench`, `omp auth-gateway check`) can tell two same-email subscriptions apart. Active-account matching is org-decisive whenever either side carries an organization — an org-scoped session no longer flags the legacy bare-email row, and a legacy-row session no longer flags org-scoped siblings, via the shared email. `getOAuthAccountIdentity` also preserves org-only identities instead of discarding them.
|
||||
- Fixed usage-path OAuth refreshes on broker-backed credentials persisting the rotated token into the wrong row: the shared `REMOTE_REFRESH_SENTINEL` refresh value is no longer treated as row identity when locating the row to update, so with two same-email organizations the refreshed token lands on the org that was actually refreshed.
|
||||
- Fixed the org qualifier only riding on email-based Anthropic identities: when the login email cannot be recovered (token response omits it and the bootstrap fallback fails), the account/project fallback keys are now org-qualified too — the account UUID is identical across the orgs of one login account, so a second subscription could otherwise still replace the first on the no-email path. The same one-way legacy upgrade applies to bare account/project keys, usage-report dedupe falls back to the org-qualified account for no-email reports, and broker report routing is org-decisive on either side (an org-less legacy credential no longer receives an org-attributed sibling's pool).
|
||||
- Fixed an org-only Anthropic credential row (stored when login recovered neither email nor account) never being claimed by a later login of the same organization that does recover the identity — the row is now upgraded and re-keyed in place instead of duplicating the subscription.
|
||||
- Fixed broker usage report routing and header-overlay keying/merging to require the member's own identity (account/email/project) within a shared organization, so two Team members sharing one org id no longer receive each other's per-user pools — a member's missing report surfaces as "no usage data" instead of a sibling's numbers, while an org-only credential still matches its lone same-org report.
|
||||
- Fixed an Anthropic credential row stored under a fallback identity (e.g. account-keyed because email recovery failed) never being claimed when a later login of the same subscription recovers the email: an org-scoped login now matches an existing row by any of its base identities (email/account/project), bare or org-qualified, instead of only its primary key — org-less logins keep matching by exact key only.
|
||||
- Fixed the remaining asymmetries of the org-scoped claiming and cache rules: a same-org login now also claims an existing row when the STORED credential shares a base identity with the incoming one (e.g. an email-keyed row whose credential carries the account UUID is updated in place by a later login that loses the email but keeps the account), and org-only credentials treat the organization as a stable usage-cache identity so token rotation no longer churns their cache keys and fragments usage history.
|
||||
- Fixed OpenAI Responses and Codex Responses message finalization preserving streamed text when `output_item.done` arrives with empty content. ([#5146](https://github.com/can1357/oh-my-pi/issues/5146))
|
||||
- Fixed OpenAI Chat Completions request parsing to accept assistant tool-call replay messages with `content: null` as absent content. ([#5121](https://github.com/can1357/oh-my-pi/issues/5121))
|
||||
- Fixed provider credential changes leaving persisted session-sticky OAuth credential mappings active, so existing sessions reselect accounts after login/logout instead of reusing stale `session:sticky:<provider>:<sessionId>` rows. ([#4982](https://github.com/can1357/oh-my-pi/issues/4982))
|
||||
- Fixed concurrent reasoning summaries to ignore legacy streaming events under cutoff contract
|
||||
- Fixed Codex saved-reset redemption to include the selected account in the consume request body, so `/usage reset` applies to the chosen OpenAI account in multi-account setups. ([#5054](https://github.com/can1357/oh-my-pi/issues/5054))
|
||||
- Fixed the OAuth completion page copy to tell users they can close the tab manually when browsers such as Firefox ignore best-effort `window.close()` calls. ([#4855](https://github.com/can1357/oh-my-pi/issues/4855))
|
||||
- Fixed Cursor `max_mode` requests to send discovered max-mode metadata on both model payload fields. ([#4797](https://github.com/can1357/oh-my-pi/issues/4797))
|
||||
- Fixed `auth-broker` config discovery ignoring nested `auth.broker.url` / `auth.broker.token` YAML keys. `readConfigYaml` only read the literal flat dotted key, so standard nested YAML was silently dropped; it now resolves both nested and flat forms (nested wins). ([#4734](https://github.com/can1357/oh-my-pi/issues/4734))
|
||||
|
||||
## [16.5.0] - 2026-07-13
|
||||
|
||||
@@ -23,9 +43,6 @@
|
||||
### Removed
|
||||
|
||||
- Removed automatic /interactions chaining for follow-up turns in Google provider calls, along with the useInteractionsApi, storeInteraction, and previousInteractionId stream options.
|
||||
### Fixed
|
||||
|
||||
- Fixed provider-agnostic replay-safe usage/account-quota failures to rotate through every distinct eligible credential instead of stopping after the fixed a/b/c ladder, while preserving transient-429 backoff, cycle/abort guards, exact failed-credential targeting despite stale session stickiness, and a finite safety ceiling.
|
||||
|
||||
## [16.4.6] - 2026-07-12
|
||||
|
||||
@@ -43,17 +60,6 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue in GLM tool calling where missing or malformed argument closers (such as `<arg_value>` mistyped as `</arg_key>`) caused subsequent arguments to be swallowed or merged into a single field, affecting both in-band and native tool calling.
|
||||
- Healed GLM in-band tool calls whose `<arg_value>` closer is missing or mistyped as `</arg_key>`; the scanner now ends the value at the next-pair signature instead of swallowing the remaining arguments into one field.
|
||||
- Healed the same `arg_key`/`arg_value` spill when it arrives through native tool calling (provider parses the in-band syntax server-side): as a last resort after validation and coercion fail, contaminated string arguments are split at the spill boundary and the swallowed pairs restored.
|
||||
- Fixed Anthropic logins silently replacing the stored credential when one account email holds multiple organizations (e.g. a Team seat plus a personal Max plan). Credentials are now identified by email + organization: the login flow captures the organization from the token exchange (with a `claude_cli/bootstrap` fallback), both subscriptions store side by side, and the existing multi-account rotation treats them as separate accounts. Legacy email-keyed rows are claimed in place by the first org-scoped login with the same email, and an org-less credential never clobbers org-scoped rows. Usage reports and the per-credential usage cache also partition by organization so the two subscriptions' limit pools no longer merge into one confused row.
|
||||
- Fixed broker-served usage routing for org-scoped credentials: `RemoteAuthCredentialStore` now matches aggregate reports and keys header-ingest overlays by organization first, so with a Team seat exhausted and a personal Max healthy under one email, each credential receives its own pool instead of whichever report appeared first. The Anthropic usage-cache key version was bumped so pre-org cache entries (including the 24h last-good fallback) cannot be replayed across organizations.
|
||||
- Fixed OAuth access results (`getOAuthAccess`, `getOAuthAccesses`, `getOAuthAccessAt`) and `checkCredentials` health results dropping the organization: they now carry `orgId`/`orgName` so consumers that key or label per-account results (e.g. `omp dry-balance --bench`, `omp auth-gateway check`) can tell two same-email subscriptions apart. Active-account matching is org-decisive whenever either side carries an organization — an org-scoped session no longer flags the legacy bare-email row, and a legacy-row session no longer flags org-scoped siblings, via the shared email. `getOAuthAccountIdentity` also preserves org-only identities instead of discarding them.
|
||||
- Fixed usage-path OAuth refreshes on broker-backed credentials persisting the rotated token into the wrong row: the shared `REMOTE_REFRESH_SENTINEL` refresh value is no longer treated as row identity when locating the row to update, so with two same-email organizations the refreshed token lands on the org that was actually refreshed.
|
||||
- Fixed the org qualifier only riding on email-based Anthropic identities: when the login email cannot be recovered (token response omits it and the bootstrap fallback fails), the account/project fallback keys are now org-qualified too — the account UUID is identical across the orgs of one login account, so a second subscription could otherwise still replace the first on the no-email path. The same one-way legacy upgrade applies to bare account/project keys, usage-report dedupe falls back to the org-qualified account for no-email reports, and broker report routing is org-decisive on either side (an org-less legacy credential no longer receives an org-attributed sibling's pool).
|
||||
- Fixed an org-only Anthropic credential row (stored when login recovered neither email nor account) never being claimed by a later login of the same organization that does recover the identity — the row is now upgraded and re-keyed in place instead of duplicating the subscription.
|
||||
- Fixed broker usage report routing and header-overlay keying/merging to require the member's own identity (account/email/project) within a shared organization, so two Team members sharing one org id no longer receive each other's per-user pools — a member's missing report surfaces as "no usage data" instead of a sibling's numbers, while an org-only credential still matches its lone same-org report.
|
||||
- Fixed an Anthropic credential row stored under a fallback identity (e.g. account-keyed because email recovery failed) never being claimed when a later login of the same subscription recovers the email: an org-scoped login now matches an existing row by any of its base identities (email/account/project), bare or org-qualified, instead of only its primary key — org-less logins keep matching by exact key only.
|
||||
- Fixed the remaining asymmetries of the org-scoped claiming and cache rules: a same-org login now also claims an existing row when the STORED credential shares a base identity with the incoming one (e.g. an email-keyed row whose credential carries the account UUID is updated in place by a later login that loses the email but keeps the account), and org-only credentials treat the organization as a stable usage-cache identity so token rotation no longer churns their cache keys and fragments usage history.
|
||||
|
||||
## [16.4.3] - 2026-07-11
|
||||
|
||||
@@ -63,7 +69,6 @@
|
||||
- Fixed an issue in the Responses API where empty tool results were incorrectly serialized with a "(see attached image)" placeholder, causing models to look for non-existent attachments.
|
||||
- Fixed OpenAI Responses server non-streaming envelopes to always include the required "incomplete_details" field, using null for completed responses.
|
||||
- Preserved Cloud Code Assist tool schemas when mixed-type unions carry branch-local validation descriptions.
|
||||
- Fixed OpenAI Responses and Codex Responses message finalization preserving streamed text when `output_item.done` arrives with empty content. ([#5146](https://github.com/can1357/oh-my-pi/issues/5146))
|
||||
|
||||
## [16.4.2] - 2026-07-10
|
||||
|
||||
@@ -72,7 +77,6 @@
|
||||
- Fixed compatibility with xAI by automatically downgrading OpenAI-specific tool calls and image detail settings during message history replays.
|
||||
- Fixed a race condition in shared SQLite OAuth token refreshes by implementing durable credential ownership and compare-and-set persistence to prevent stale refresh failures.
|
||||
- Fixed OpenAI Codex requests to include the required version header for newly gated models.
|
||||
- Fixed OpenAI Chat Completions request parsing to accept assistant tool-call replay messages with `content: null` as absent content. ([#5121](https://github.com/can1357/oh-my-pi/issues/5121))
|
||||
|
||||
## [16.4.1] - 2026-07-10
|
||||
|
||||
@@ -101,11 +105,6 @@
|
||||
- Fixed xai-oauth/grok-4.5 Responses requests to omit the unsupported reasoning.summary field while preserving the reasoning.effort payload.
|
||||
- Fixed Codex OAuth credential selection to re-check blocked accounts during ranking and clear stale usage-limit blocks once live usage indicates recovery.
|
||||
- Fixed sequential-cutoff reasoning summaries duplicating section headers across Codex reasoning items by tracking the cumulative summary response-globally, so replayed sections and replay-only items no longer re-emit text earlier thinking blocks already streamed.
|
||||
### Fixed
|
||||
|
||||
- Fixed provider credential changes leaving persisted session-sticky OAuth credential mappings active, so existing sessions reselect accounts after login/logout instead of reusing stale `session:sticky:<provider>:<sessionId>` rows. ([#4982](https://github.com/can1357/oh-my-pi/issues/4982))
|
||||
- Fixed concurrent reasoning summaries to ignore legacy streaming events under cutoff contract
|
||||
- Fixed Codex saved-reset redemption to include the selected account in the consume request body, so `/usage reset` applies to the chosen OpenAI account in multi-account setups. ([#5054](https://github.com/can1357/oh-my-pi/issues/5054))
|
||||
|
||||
## [16.3.15] - 2026-07-09
|
||||
|
||||
@@ -170,12 +169,7 @@
|
||||
- Fixed OpenAI Codex WebSocket continuations to treat proxy stale-anchor codes such as `codex_previous_response_stale` as an expired `previous_response_id` chain — same recovery class as the OpenAI-standard `previous_response_not_found` — so the turn is retried with full context instead of surfacing the error to the user ([#4624](https://github.com/can1357/oh-my-pi/issues/4624)).
|
||||
- Fixed Azure Foundry Anthropic utility requests to omit the structured-output beta whenever strict tools are disabled, preventing `structured_outputs not supported in your workspace` failures for Sonnet 5 compaction ([#4679](https://github.com/can1357/oh-my-pi/issues/4679)).
|
||||
- Fixed OAuth `launchUrl` advertisement for flows whose redirect never returns to the local callback server: custom-scheme redirects (e.g. GitLab Duo's `vscode://` URI, which `new URL` parses without complaint) and fixed non-loopback hosts no longer receive a `http://localhost:<port>/launch` copy target that misrepresents the callback endpoint and resolves nowhere for remote users.
|
||||
- Fixed the OAuth completion page copy to tell users they can close the tab manually when browsers such as Firefox ignore best-effort `window.close()` calls. ([#4855](https://github.com/can1357/oh-my-pi/issues/4855))
|
||||
### Fixed
|
||||
|
||||
- Codex load balancing: clear stale persisted and in-memory usage-limit blocks for an `openai-codex` account when a fresh live usage report shows it is allowed and below all limits, including broker-backed gateway snapshots, so traffic returns to recovered accounts instead of funneling to one sibling.
|
||||
- Fixed Cursor `max_mode` requests to send discovered max-mode metadata on both model payload fields. ([#4797](https://github.com/can1357/oh-my-pi/issues/4797))
|
||||
- Fixed `auth-broker` config discovery ignoring nested `auth.broker.url` / `auth.broker.token` YAML keys. `readConfigYaml` only read the literal flat dotted key, so standard nested YAML was silently dropped; it now resolves both nested and flat forms (nested wins). ([#4734](https://github.com/can1357/oh-my-pi/issues/4734))
|
||||
|
||||
## [16.3.11] - 2026-07-06
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Z.ai GLM-5.2 on the `anthropic-messages` coding endpoint deriving `mode: "budget"` with five synthetic effort tiers instead of the wire-exact `anthropic-budget-effort` with `[high, max]`. The catalog now matches Z.ai's Anthropic proxy to the same two-tier reasoning scale as Umans, so `output_config.effort` is emitted on the wire.
|
||||
- Fixed stale cached model limits overriding updated static catalog limits after a static catalog fingerprint mismatch. ([#4956](https://github.com/can1357/oh-my-pi/issues/4956))
|
||||
- Fixed Cursor discovery to preserve `GetUsableModels` max-mode metadata for premium models and invalidate stale pre-max-mode cache rows. ([#4797](https://github.com/can1357/oh-my-pi/issues/4797))
|
||||
|
||||
## [16.4.3] - 2026-07-11
|
||||
|
||||
### Fixed
|
||||
@@ -14,9 +20,6 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed OpenAI Codex model discovery to include the Codex version header alongside the client_version query parameter.
|
||||
### Fixed
|
||||
|
||||
- Fixed Z.ai GLM-5.2 on the `anthropic-messages` coding endpoint deriving `mode: "budget"` with five synthetic effort tiers instead of the wire-exact `anthropic-budget-effort` with `[high, max]`. The catalog now matches Z.ai's Anthropic proxy to the same two-tier reasoning scale as Umans, so `output_config.effort` is emitted on the wire.
|
||||
|
||||
## [16.4.1] - 2026-07-10
|
||||
|
||||
@@ -96,9 +99,6 @@
|
||||
|
||||
- Updated cost and token configurations for various models across providers
|
||||
- Renamed several models for consistency (e.g., MiniMax M3, Gemma 4 31B, Qwen variants)
|
||||
### Fixed
|
||||
|
||||
- Fixed stale cached model limits overriding updated static catalog limits after a static catalog fingerprint mismatch. ([#4956](https://github.com/can1357/oh-my-pi/issues/4956))
|
||||
|
||||
## [16.3.12] - 2026-07-08
|
||||
|
||||
@@ -107,9 +107,6 @@
|
||||
- Fixed LiteLLM discovery stopping at `/model_group/info` when that endpoint omitted `supports_vision`; it now continues to `/model/info` and preserves `model_info.supports_vision=true` for vision-capable proxy models. ([#4747](https://github.com/can1357/oh-my-pi/issues/4747))
|
||||
- Fixed LiteLLM discovery to fall back to bundled catalog metadata when `models.dev` lacks a model reference, preserving reasoning and thinking support for models such as `glm-5.2`. ([#4695](https://github.com/can1357/oh-my-pi/issues/4695))
|
||||
- Detected Azure AI Inference / Foundry Anthropic routes as strict-tool-incompatible so resolved Anthropic compat disables strict tools before request construction ([#4679](https://github.com/can1357/oh-my-pi/issues/4679)).
|
||||
### Fixed
|
||||
|
||||
- Fixed Cursor discovery to preserve `GetUsableModels` max-mode metadata for premium models and invalidate stale pre-max-mode cache rows. ([#4797](https://github.com/can1357/oh-my-pi/issues/4797))
|
||||
|
||||
## [16.3.11] - 2026-07-06
|
||||
|
||||
|
||||
@@ -2,10 +2,64 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- `omp usage` and the in-session `/usage` view now show the Anthropic organization next to the account for org-scoped credentials (with `--redact` masking applied per part in the CLI, falling back to the org id when no display name is available), attribute "no usage data" rows per organization, and match the "in use by this session" marker by organization so only the active subscription is flagged. The OAuth login success message names the account and organization that was stored — a login landing on an unintended subscription is visible immediately.
|
||||
- `/logout` labels Anthropic accounts with their organization and marks only the credential of the active organization as active; `omp token --list` shows the organization next to each account. Two subscriptions sharing one email are distinguishable when selecting which to remove or mint a token for.
|
||||
- `omp auth-broker migrate --from-local` dedupes Anthropic OAuth identities per organization, so a Team seat already on the broker no longer blocks uploading the personal plan under the same email.
|
||||
- The status line invalidates its cached usage when the session rotates to a different Anthropic organization (previously the old subscription's quota could linger for the cache TTL), and `omp auth-gateway check` labels each credential with its organization so a failing row says which subscription needs re-login.
|
||||
- `omp usage` "no usage data" attribution is org-decisive whenever either the stored account or a report carries an organization: an org-less legacy credential whose own fetch failed is no longer hidden by an org-attributed sibling report sharing the same email.
|
||||
- Active-account matching for `/usage`, `/logout`, and `omp token --list` now treats a shared organization as a qualifier rather than a match: two Anthropic Team seats in one org (same org id, per-user pools) no longer flag each other's rows or reports as "in use by this session" — the base identity (account/email/project) is still required, with org-only sessions matching on the org alone.
|
||||
- `omp usage` "no usage data" coverage now requires the member's own identity within a shared organization: a sibling Team member's same-org report no longer counts as coverage for an account whose own report is missing, while an org-only account remains covered by any same-org report.
|
||||
- `omp auth-broker migrate --from-local` reruns now recognize an already-migrated org-only Anthropic row (login recovered neither email nor account) by its organization id instead of re-uploading it, which could overwrite the broker's newer refresh token with the stale local one.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Codex web search requests for GPT-5.6 Responses-Lite models, including `gpt-5.6-sol`. ([#5286](https://github.com/can1357/oh-my-pi/issues/5286))
|
||||
- Fixed subagent model and thinking level intermittently resolving to the parent session's model instead of the configured `modelRoles.task` selector ([#5325](https://github.com/can1357/oh-my-pi/issues/5325)). The pre-flight auth check in `resolveModelOverrideWithAuthFallback` called `getApiKey` without a session id, so providers with session-sticky OAuth credentials returned `undefined` even though the credential was usable once the subagent session started. The subagent's id is now forwarded as the session id so session-sticky credentials resolve correctly. Model resolution warnings (e.g. invalid thinking level in a pattern) are also now logged instead of silently dropped.
|
||||
- Bounded transcript render retention after finalized rows enter native scrollback, while rehydrating the complete transcript before destructive resize/reset replays, so long resumed TUI sessions no longer retain every committed row or lose history on repaint ([#4820](https://github.com/can1357/oh-my-pi/issues/4820)).
|
||||
- Fixed `/tan` and `/fork` clones cold-missing the provider prompt cache: the per-turn supersede/useless-result prune rewrote the live context without persisting it, so file-based forks and resume rebuilt a divergent (un-pruned) prefix and re-wrote the entire cache
|
||||
- Fixed `/tan` pinning the clone's prompt-cache key to the parent's session id instead of the parent's effective cache key, dropping shard affinity when the parent was itself a fork or tan
|
||||
- Fixed eval kernels inheriting the interactive terminal session, allowing subprocesses to take the foreground process group and suspend the CLI with `zsh: suspended (tty input)` ([#5327](https://github.com/can1357/oh-my-pi/pull/5327) by [@masonc15](https://github.com/masonc15))
|
||||
- Fixed the JS eval subprocess killing itself — erasing all session state — when a cell passed a non-serializable value (e.g. a function) into a bridge tool call; the `DataCloneError` now rejects that call inside the cell, matching Worker `postMessage` semantics ([#5327](https://github.com/can1357/oh-my-pi/pull/5327) by [@masonc15](https://github.com/masonc15))
|
||||
- Fixed inconsistent history rendering when toggling the display setting for compacted items
|
||||
- Fixed configured `retry.fallbackChains` never engaging on non-retryable provider errors (e.g. "Cloud Code Assist API returned an empty response"): a hard error on a model covered by a fallback chain now switches to the next candidate instead of failing the turn, while still never backoff-retrying the failing model itself
|
||||
- Fixed transcript rebuilds (compaction, `/compact`, and toggling history display) repainting content below stale scrollback when collapsing history; rebuilds now correctly clear the scrollback buffer when history is collapsed
|
||||
- Improved auto-compaction to automatically drop images and elide content when context is tight, and added persistent warning badges to the compaction divider when manual intervention is required
|
||||
- Fixed backgrounded Bash blocks continuing to repaint with live and final job output; they now freeze with a compact job notice while completion is delivered separately
|
||||
- Fixed the downshift plan nudge silently ending the run with no code written when the model answered with a text-only reply (no tool call): the agent loop treats a tool-call-free turn as a natural stop and never prompts again, which the nudge's own "write the plan in your next reply" instruction makes common. The nudge now explicitly tells the model this is a checkpoint, not a final answer, and the session forces one more turn whenever a post-nudge reply lands with zero tool calls
|
||||
- Fixed launch tool rendering stacking a stale pending header over a bare `✓ Launch` line and raw text: the tool now uses a merged registry renderer with one per-op status header (op, target, `state · pid · uptime` meta), stripped log cursor suffixes, capped collapsed log/list previews, and a launch tool glyph
|
||||
- Fixed confusing launch start/wait results when readiness timed out with the log pattern already matched (readiness needs log AND port): the result printed a contradictory `Ready: <match>` next to `Readiness timed out` without naming the failing condition. Daemon snapshots now carry the unmet conditions (`readyPending`), and start/wait results state exactly what never happened (e.g. `port 3100 on 127.0.0.1 never accepted connections`); the TUI shows a `waiting on port` badge on starting daemons
|
||||
- Fixed the in-process `stat` builtin mangling BSD-style invocations like `stat -f "%Sm %N" file` (macOS muscle memory): GNU `-f` means `--file-system`, so the format string was treated as a file operand — printing filesystem info for the real operands and erroring with `cannot read file system information for '%Sm %N'`. A `-f` whose format value contains `%` is now detected as BSD syntax and translated to the GNU equivalent (`%Sm`→`%y`, `%N`→`%n`, `%z`→`%s`, epoch/`S`-form times, owner/group/permission and `H`/`L` sub-field directives, `-L`/`-n`/`-q`/`-F` flag clusters, with `%n`/`%t` as literal newline/tab); directives with no GNU counterpart fail with a clear `unsupported BSD format directive` error
|
||||
- Fixed the remaining GNU-flavored shell builtins that broke under macOS/BSD muscle memory, using the same unambiguous-detection approach as the `stat` fix (only invocations that are invalid or nonsensical under GNU semantics are reinterpreted; unsupported BSD forms fail loudly instead of producing wrong output): `date -r <epoch>` formats the epoch when no such file exists (GNU `-r FILE` mtime preserved), signed `date -v±N<unit>` adjustments translate to `-d` relative dates and `-j` is accepted (`-j -f` strptime parse mode and field-set `-v` error clearly); `sed -i '' 's/…/…/' file` drops the BSD empty backup-suffix token instead of treating it as the script; `mktemp -t prefix` without X's creates `$TMPDIR/prefix.XXXXXXXXXX` (the GNU `too few X's` error path); `tail -r` reverses input by delegating to `tac` (with `-n`/`-c`/`-f` combinations erroring clearly); `find -E` maps to `-regextype posix-extended` ahead of the expression; `base64 -D` decodes as an alias of `-d`; and `ln -sfh` works via a `-h` alias of `--no-dereference` (clap's `-h` help short is dropped to match real GNU/BSD ln; `--help` unchanged)
|
||||
- Fixed temporary model picks (`Alt+P`, `/switch`, `/model --temporary`) ignoring explicit thinking suffixes from matching configured model roles. ([#5290](https://github.com/can1357/oh-my-pi/issues/5290))
|
||||
- Fixed ModelRegistry/AuthStorage resolvers to continue replay-safe usage/account-quota turns across every distinct eligible credential and return exhaustion when no sibling switches, instead of re-resolving the same failed credential.
|
||||
- Fixed preferred web search providers failing before execution when an unrelated fallback provider could not initialize. ([#5182](https://github.com/can1357/oh-my-pi/pull/5182) by [@wolfiesch](https://github.com/wolfiesch))
|
||||
- Fixed Advisor containment so hallucinated unavailable tool calls and output-only destructive directives quarantine the Advisor response and reset its private context instead of feeding contaminated text into later advice. ([#5181](https://github.com/can1357/oh-my-pi/issues/5181))
|
||||
- Fixed the built-in advisor treating empty `stop` completions without advice as successful reviews, so silent provider failures now enter the advisor retry/drop path. ([#5212](https://github.com/can1357/oh-my-pi/issues/5212))
|
||||
- Fixed `autolearn.autoContinue` treating the hidden capture turn's terminal empty assistant stop as a retryable empty response instead of successful completion. ([#5211](https://github.com/can1357/oh-my-pi/issues/5211))
|
||||
- Fixed advisor flagging issues the primary agent already resolved by coalescing late-arriving transcript deltas into the current batch before the advisor model call (instead of deferring them a full cycle), annotating advice with a staleness caveat when newer primary turns arrived during the model call, and tagging mid-turn `[in progress]` updates so the advisor withholds premature critique ([#4850](https://github.com/can1357/oh-my-pi/issues/4850)). Also fixed pre-existing unchecked inline casts in `#renderDelta` and `#dedupContextMessage` that suppressed type errors instead of using role-discriminant narrowing.
|
||||
- Fixed sub-agent progress rendering leaking raw terminal control bytes into the parent TUI. ([#5159](https://github.com/can1357/oh-my-pi/issues/5159))
|
||||
- Fixed `--continue <session-id>` falling back to an unrelated latest session when the requested session does not exist.
|
||||
- Fixed resumed sessions retaining an unterminated turn after an abnormal process exit.
|
||||
- Fixed macOS runtime diagnostics (e.g. `MallocStackLogging: can't turn off malloc stack logging because it was not enabled`) written directly to fd 2 by libmalloc painting into the TUI viewport. While the TUI owns the terminal, stderr is now redirected to the omp log file and restored at every ownership handoff (external editor, Ctrl+Z suspend, shutdown, crash restore); fatal crash reports still reach the real terminal.
|
||||
- Fixed custom model/provider config discovery so `~/.omp/agent/models.yaml` loads when `models.yml` is absent, while preserving `.yml` precedence and only migrating legacy `models.json` when neither YAML file exists. ([#5145](https://github.com/can1357/oh-my-pi/issues/5145))
|
||||
- Fixed throttled live command output holding a quiet final chunk until the command exited.
|
||||
- Fixed rpc-ui extension UI and host tool responses deadlocking login, session lifecycle, and queued commands while an active command awaited the same side channel. ([#5153](https://github.com/can1357/oh-my-pi/issues/5153))
|
||||
- Fixed `write` treating read-only internal URLs like `memory://root/memory_summary.md` as project-relative filesystem paths, prevented leaks such as `{cwd}/memory:/root/memory_summary.md`, and made `memory://root` prefer the calling session's cwd-specific memory root when multiple agents are live. ([#5075](https://github.com/can1357/oh-my-pi/issues/5075))
|
||||
- Fixed `memory://root` resolution leaking between live agents with different working directories by resolving file-backed memory roots from the calling session cwd before falling back to the global session registry. ([#5079](https://github.com/can1357/oh-my-pi/issues/5079))
|
||||
- Fixed todo reminders auto-continuing over interactive skill questions; text-only assistant questions now yield to the user without firing the incomplete-todo reminder loop. ([#5089](https://github.com/can1357/oh-my-pi/issues/5089))
|
||||
- Fixed MiniMax-M3 task subagents retrying empty `yield` results forever; repeated untyped `result: {}` submissions now abort the child with guidance instead of leaving the parent blocked. ([#5095](https://github.com/can1357/oh-my-pi/issues/5095))
|
||||
- Fixed `omp update` on npm-managed Windows installs so npm `.cmd`/`.ps1`/`.bat` launchers update through npm instead of being overwritten by downloaded release binaries. ([#5053](https://github.com/can1357/oh-my-pi/issues/5053))
|
||||
- Fixed Python eval `agent()` bridge calls losing in-flight subagent work when the parent cell receives an external abort; the kernel abort is now deferred until already-started bridge calls settle, and new bridge calls are rejected after the abort is pending. ([#5005](https://github.com/can1357/oh-my-pi/issues/5005))
|
||||
- Fixed `--max-time` duration values like `5s`, `10m`, and `1h` being ignored instead of configuring a session deadline. ([#5041](https://github.com/can1357/oh-my-pi/issues/5041))
|
||||
- Fixed `ultrathink`, `orchestrate`, and `workflowz` magic keywords not triggering when adjacent to sentence punctuation or quotes while still ignoring inflections and path/file-extension occurrences. ([#4965](https://github.com/can1357/oh-my-pi/issues/4965))
|
||||
- Fixed `omp plugin install github:owner/repo --force` failing with Bun `DependencyLoop` when replacing an existing pinned git plugin source for the same repository; the installer now removes the stale pinned dependency edge before invoking Bun and restores it on rollback. ([#4960](https://github.com/can1357/oh-my-pi/issues/4960))
|
||||
- Fixed Cursor-provider turns whose assistant message contains intro text, tool calls, and trailing final text rendering the final answer above the tool output instead of at the transcript tail ([#4871](https://github.com/can1357/oh-my-pi/issues/4871)).
|
||||
- Fixed `omp -p` looking hung while a text-mode prompt is in flight by writing a one-shot working indicator to stderr before awaiting the model response. ([#4901](https://github.com/can1357/oh-my-pi/issues/4901))
|
||||
- Fixed autolearn auto-continue firing a capture turn after an aborted stop (Esc/cancel): the controller now skips any `agent_end` whose last assistant message has `stopReason: "aborted"`.
|
||||
- Fixed MCP tools receiving session image attachments as raw `local://...` URIs by resolving them to session-local filesystem paths before `tools/call` is sent ([#4946](https://github.com/can1357/oh-my-pi/issues/4946)).
|
||||
- Fixed late advisor concerns after a terminal primary answer starting a duplicate primary turn when no queued work remains ([#4840](https://github.com/can1357/oh-my-pi/issues/4840)).
|
||||
|
||||
## [16.5.0] - 2026-07-13
|
||||
|
||||
@@ -50,24 +104,6 @@
|
||||
- Removed the `--prewalk-boomerang` feature and its associated configuration setting.
|
||||
- Removed the unreliable Bing and Yahoo HTML-scraping web search providers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bounded transcript render retention after finalized rows enter native scrollback, while rehydrating the complete transcript before destructive resize/reset replays, so long resumed TUI sessions no longer retain every committed row or lose history on repaint ([#4820](https://github.com/can1357/oh-my-pi/issues/4820)).
|
||||
- Fixed `/tan` and `/fork` clones cold-missing the provider prompt cache: the per-turn supersede/useless-result prune rewrote the live context without persisting it, so file-based forks and resume rebuilt a divergent (un-pruned) prefix and re-wrote the entire cache
|
||||
- Fixed `/tan` pinning the clone's prompt-cache key to the parent's session id instead of the parent's effective cache key, dropping shard affinity when the parent was itself a fork or tan
|
||||
- Fixed eval kernels inheriting the interactive terminal session, allowing subprocesses to take the foreground process group and suspend the CLI with `zsh: suspended (tty input)` ([#5327](https://github.com/can1357/oh-my-pi/pull/5327) by [@masonc15](https://github.com/masonc15))
|
||||
- Fixed the JS eval subprocess killing itself — erasing all session state — when a cell passed a non-serializable value (e.g. a function) into a bridge tool call; the `DataCloneError` now rejects that call inside the cell, matching Worker `postMessage` semantics ([#5327](https://github.com/can1357/oh-my-pi/pull/5327) by [@masonc15](https://github.com/masonc15))
|
||||
- Fixed inconsistent history rendering when toggling the display setting for compacted items
|
||||
- Fixed configured `retry.fallbackChains` never engaging on non-retryable provider errors (e.g. "Cloud Code Assist API returned an empty response"): a hard error on a model covered by a fallback chain now switches to the next candidate instead of failing the turn, while still never backoff-retrying the failing model itself
|
||||
- Fixed transcript rebuilds (compaction, `/compact`, and toggling history display) repainting content below stale scrollback when collapsing history; rebuilds now correctly clear the scrollback buffer when history is collapsed
|
||||
- Improved auto-compaction to automatically drop images and elide content when context is tight, and added persistent warning badges to the compaction divider when manual intervention is required
|
||||
- Fixed backgrounded Bash blocks continuing to repaint with live and final job output; they now freeze with a compact job notice while completion is delivered separately
|
||||
- Fixed the downshift plan nudge silently ending the run with no code written when the model answered with a text-only reply (no tool call): the agent loop treats a tool-call-free turn as a natural stop and never prompts again, which the nudge's own "write the plan in your next reply" instruction makes common. The nudge now explicitly tells the model this is a checkpoint, not a final answer, and the session forces one more turn whenever a post-nudge reply lands with zero tool calls
|
||||
- Fixed launch tool rendering stacking a stale pending header over a bare `✓ Launch` line and raw text: the tool now uses a merged registry renderer with one per-op status header (op, target, `state · pid · uptime` meta), stripped log cursor suffixes, capped collapsed log/list previews, and a launch tool glyph
|
||||
- Fixed confusing launch start/wait results when readiness timed out with the log pattern already matched (readiness needs log AND port): the result printed a contradictory `Ready: <match>` next to `Readiness timed out` without naming the failing condition. Daemon snapshots now carry the unmet conditions (`readyPending`), and start/wait results state exactly what never happened (e.g. `port 3100 on 127.0.0.1 never accepted connections`); the TUI shows a `waiting on port` badge on starting daemons
|
||||
- Fixed the in-process `stat` builtin mangling BSD-style invocations like `stat -f "%Sm %N" file` (macOS muscle memory): GNU `-f` means `--file-system`, so the format string was treated as a file operand — printing filesystem info for the real operands and erroring with `cannot read file system information for '%Sm %N'`. A `-f` whose format value contains `%` is now detected as BSD syntax and translated to the GNU equivalent (`%Sm`→`%y`, `%N`→`%n`, `%z`→`%s`, epoch/`S`-form times, owner/group/permission and `H`/`L` sub-field directives, `-L`/`-n`/`-q`/`-F` flag clusters, with `%n`/`%t` as literal newline/tab); directives with no GNU counterpart fail with a clear `unsupported BSD format directive` error
|
||||
- Fixed the remaining GNU-flavored shell builtins that broke under macOS/BSD muscle memory, using the same unambiguous-detection approach as the `stat` fix (only invocations that are invalid or nonsensical under GNU semantics are reinterpreted; unsupported BSD forms fail loudly instead of producing wrong output): `date -r <epoch>` formats the epoch when no such file exists (GNU `-r FILE` mtime preserved), signed `date -v±N<unit>` adjustments translate to `-d` relative dates and `-j` is accepted (`-j -f` strptime parse mode and field-set `-v` error clearly); `sed -i '' 's/…/…/' file` drops the BSD empty backup-suffix token instead of treating it as the script; `mktemp -t prefix` without X's creates `$TMPDIR/prefix.XXXXXXXXXX` (the GNU `too few X's` error path); `tail -r` reverses input by delegating to `tac` (with `-n`/`-c`/`-f` combinations erroring clearly); `find -E` maps to `-regextype posix-extended` ahead of the expression; `base64 -D` decodes as an alias of `-d`; and `ln -sfh` works via a `-h` alias of `--no-dereference` (clap's `-h` help short is dropped to match real GNU/BSD ln; `--help` unchanged)
|
||||
|
||||
## [16.4.8] - 2026-07-12
|
||||
|
||||
### Added
|
||||
@@ -94,7 +130,6 @@
|
||||
- Fixed tab reuse issues where hung navigation or unhandled modals would cause initialization to stall and trigger a force-kill
|
||||
- Improved search reliability for Perplexity provider by forcing retrieval for all queries
|
||||
- Fixed JS eval cells losing top-level `function` and `var` declarations across cells when the defining cell contained top-level `await` — the async wrapper scoped them to the cell's IIFE instead of publishing them to the worker global
|
||||
- Fixed temporary model picks (`Alt+P`, `/switch`, `/model --temporary`) ignoring explicit thinking suffixes from matching configured model roles. ([#5290](https://github.com/can1357/oh-my-pi/issues/5290))
|
||||
|
||||
## [16.4.7] - 2026-07-12
|
||||
|
||||
@@ -113,9 +148,6 @@
|
||||
|
||||
- Fixed PageUp/PageDown in the model browser wrapping past the list edges instead of clamping
|
||||
- Fixed the hover highlight sticking to the last hovered model row when the pointer moved into the provider sidebar
|
||||
### Fixed
|
||||
|
||||
- Fixed ModelRegistry/AuthStorage resolvers to continue replay-safe usage/account-quota turns across every distinct eligible credential and return exhaustion when no sibling switches, instead of re-resolving the same failed credential.
|
||||
|
||||
## [16.4.6] - 2026-07-12
|
||||
|
||||
@@ -145,7 +177,6 @@
|
||||
- Fixed the Model Hub role-assignment strip hiding the selected chip once the row overflowed; the strip now scrolls horizontally, truncating passed chips behind a leading ellipsis so the selection (plus one chip of lookahead) stays visible.
|
||||
- Fixed mouse hover and clicks in the /models Roles view landing one row above the pointer (the row mapping subtracted the status row twice).
|
||||
- Fixed model search keeping the most-recently-used model on top of the results: match quality now ranks first (an exact `gpt-5.5` beats the active `gpt-5.6-sol`), with MRU order only breaking ties between equally good matches.
|
||||
- Fixed preferred web search providers failing before execution when an unrelated fallback provider could not initialize. ([#5182](https://github.com/can1357/oh-my-pi/pull/5182) by [@wolfiesch](https://github.com/wolfiesch))
|
||||
|
||||
## [16.4.5] - 2026-07-11
|
||||
|
||||
@@ -179,10 +210,6 @@
|
||||
- Fixed agents getting stuck waiting for messages from peers that have already stopped running.
|
||||
- Fixed compiled Linux binary extension loading when bundled web-search header generation cannot read `header-generator` data files from the build-time path. ([#5178](https://github.com/can1357/oh-my-pi/issues/5178))
|
||||
- Fixed plugin custom tool loading to skip and report invalid feature entries instead of crashing startup when a plugin dependency tree leaves one feature unresolved. ([#5189](https://github.com/can1357/oh-my-pi/issues/5189))
|
||||
- Fixed Advisor containment so hallucinated unavailable tool calls and output-only destructive directives quarantine the Advisor response and reset its private context instead of feeding contaminated text into later advice. ([#5181](https://github.com/can1357/oh-my-pi/issues/5181))
|
||||
- Fixed the built-in advisor treating empty `stop` completions without advice as successful reviews, so silent provider failures now enter the advisor retry/drop path. ([#5212](https://github.com/can1357/oh-my-pi/issues/5212))
|
||||
- Fixed `autolearn.autoContinue` treating the hidden capture turn's terminal empty assistant stop as a retryable empty response instead of successful completion. ([#5211](https://github.com/can1357/oh-my-pi/issues/5211))
|
||||
- Fixed advisor flagging issues the primary agent already resolved by coalescing late-arriving transcript deltas into the current batch before the advisor model call (instead of deferring them a full cycle), annotating advice with a staleness caveat when newer primary turns arrived during the model call, and tagging mid-turn `[in progress]` updates so the advisor withholds premature critique ([#4850](https://github.com/can1357/oh-my-pi/issues/4850)). Also fixed pre-existing unchecked inline casts in `#renderDelta` and `#dedupContextMessage` that suppressed type errors instead of using role-discriminant narrowing.
|
||||
|
||||
## [16.4.4] - 2026-07-11
|
||||
|
||||
@@ -196,25 +223,6 @@
|
||||
- Fixed native Windows binary compatibility on older Windows 10 CPUs by building the `omp-windows-x64.exe` release asset with a baseline x64 runtime instead of AVX2. (#5172)
|
||||
- Fixed `GenerateImage` rejecting OpenAI Codex-compatible proxy bearer keys when the token does not expose a `chatgpt-account-id`. (#5174)
|
||||
- Fixed context promotion documentation to accurately reflect the `contextPromotionTarget` runtime behavior and `contextPromotion.enabled` default. (#5163)
|
||||
### Changed
|
||||
|
||||
- `omp usage` and the in-session `/usage` view now show the Anthropic organization next to the account for org-scoped credentials (with `--redact` masking applied per part in the CLI, falling back to the org id when no display name is available), attribute "no usage data" rows per organization, and match the "in use by this session" marker by organization so only the active subscription is flagged. The OAuth login success message names the account and organization that was stored — a login landing on an unintended subscription is visible immediately.
|
||||
- `/logout` labels Anthropic accounts with their organization and marks only the credential of the active organization as active; `omp token --list` shows the organization next to each account. Two subscriptions sharing one email are distinguishable when selecting which to remove or mint a token for.
|
||||
- `omp auth-broker migrate --from-local` dedupes Anthropic OAuth identities per organization, so a Team seat already on the broker no longer blocks uploading the personal plan under the same email.
|
||||
- The status line invalidates its cached usage when the session rotates to a different Anthropic organization (previously the old subscription's quota could linger for the cache TTL), and `omp auth-gateway check` labels each credential with its organization so a failing row says which subscription needs re-login.
|
||||
- `omp usage` "no usage data" attribution is org-decisive whenever either the stored account or a report carries an organization: an org-less legacy credential whose own fetch failed is no longer hidden by an org-attributed sibling report sharing the same email.
|
||||
- Active-account matching for `/usage`, `/logout`, and `omp token --list` now treats a shared organization as a qualifier rather than a match: two Anthropic Team seats in one org (same org id, per-user pools) no longer flag each other's rows or reports as "in use by this session" — the base identity (account/email/project) is still required, with org-only sessions matching on the org alone.
|
||||
- `omp usage` "no usage data" coverage now requires the member's own identity within a shared organization: a sibling Team member's same-org report no longer counts as coverage for an account whose own report is missing, while an org-only account remains covered by any same-org report.
|
||||
- `omp auth-broker migrate --from-local` reruns now recognize an already-migrated org-only Anthropic row (login recovered neither email nor account) by its organization id instead of re-uploading it, which could overwrite the broker's newer refresh token with the stale local one.
|
||||
### Fixed
|
||||
|
||||
- Fixed sub-agent progress rendering leaking raw terminal control bytes into the parent TUI. ([#5159](https://github.com/can1357/oh-my-pi/issues/5159))
|
||||
### Fixed
|
||||
|
||||
- Fixed `--continue <session-id>` falling back to an unrelated latest session when the requested session does not exist.
|
||||
### Fixed
|
||||
|
||||
- Fixed resumed sessions retaining an unterminated turn after an abnormal process exit.
|
||||
|
||||
## [16.4.3] - 2026-07-11
|
||||
|
||||
@@ -257,12 +265,6 @@
|
||||
### Removed
|
||||
|
||||
- Removed the bundled plan subagent from available task agents.
|
||||
### Fixed
|
||||
|
||||
- Fixed macOS runtime diagnostics (e.g. `MallocStackLogging: can't turn off malloc stack logging because it was not enabled`) written directly to fd 2 by libmalloc painting into the TUI viewport. While the TUI owns the terminal, stderr is now redirected to the omp log file and restored at every ownership handoff (external editor, Ctrl+Z suspend, shutdown, crash restore); fatal crash reports still reach the real terminal.
|
||||
- Fixed custom model/provider config discovery so `~/.omp/agent/models.yaml` loads when `models.yml` is absent, while preserving `.yml` precedence and only migrating legacy `models.json` when neither YAML file exists. ([#5145](https://github.com/can1357/oh-my-pi/issues/5145))
|
||||
- Fixed throttled live command output holding a quiet final chunk until the command exited.
|
||||
- Fixed rpc-ui extension UI and host tool responses deadlocking login, session lifecycle, and queued commands while an active command awaited the same side channel. ([#5153](https://github.com/can1357/oh-my-pi/issues/5153))
|
||||
|
||||
## [16.4.2] - 2026-07-10
|
||||
|
||||
@@ -282,10 +284,6 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed MCP OAuth dynamic client registration omitting discovered scopes on the RFC 7591 registration body. Providers such as Clerk bind DCR-created clients to only the scopes declared at registration, then reject the subsequent authorize request when it asks for `openid` (from `scopes_supported`). Registration now includes `config.scopes` when present, matching Claude Code and the scopes already sent on authorize.
|
||||
- Fixed `write` treating read-only internal URLs like `memory://root/memory_summary.md` as project-relative filesystem paths, prevented leaks such as `{cwd}/memory:/root/memory_summary.md`, and made `memory://root` prefer the calling session's cwd-specific memory root when multiple agents are live. ([#5075](https://github.com/can1357/oh-my-pi/issues/5075))
|
||||
- Fixed `memory://root` resolution leaking between live agents with different working directories by resolving file-backed memory roots from the calling session cwd before falling back to the global session registry. ([#5079](https://github.com/can1357/oh-my-pi/issues/5079))
|
||||
- Fixed todo reminders auto-continuing over interactive skill questions; text-only assistant questions now yield to the user without firing the incomplete-todo reminder loop. ([#5089](https://github.com/can1357/oh-my-pi/issues/5089))
|
||||
- Fixed MiniMax-M3 task subagents retrying empty `yield` results forever; repeated untyped `result: {}` submissions now abort the child with guidance instead of leaving the parent blocked. ([#5095](https://github.com/can1357/oh-my-pi/issues/5095))
|
||||
|
||||
## [16.4.0] - 2026-07-10
|
||||
|
||||
@@ -314,9 +312,6 @@
|
||||
- Fixed subagent yield tool calls being discarded when a soft request budget aborts the assistant turn before the yield event completes.
|
||||
- Fixed --tools filtering in interactive sessions incorrectly disabling deferred MCP tools from configured servers.
|
||||
- Fixed kept-alive task subagents entering infinite provider-call loops after an IRC wake and terminal yield.
|
||||
- Fixed `omp update` on npm-managed Windows installs so npm `.cmd`/`.ps1`/`.bat` launchers update through npm instead of being overwritten by downloaded release binaries. ([#5053](https://github.com/can1357/oh-my-pi/issues/5053))
|
||||
- Fixed Python eval `agent()` bridge calls losing in-flight subagent work when the parent cell receives an external abort; the kernel abort is now deferred until already-started bridge calls settle, and new bridge calls are rejected after the abort is pending. ([#5005](https://github.com/can1357/oh-my-pi/issues/5005))
|
||||
- Fixed `--max-time` duration values like `5s`, `10m`, and `1h` being ignored instead of configuring a session deadline. ([#5041](https://github.com/can1357/oh-my-pi/issues/5041))
|
||||
|
||||
## [16.3.15] - 2026-07-09
|
||||
|
||||
@@ -333,8 +328,6 @@
|
||||
- Improved rendering of raw thinking blocks by stripping empty HTML comment noise
|
||||
- Fixed display of thinking blocks consisting entirely of hidden comment noise
|
||||
- Fixed gpt-5.6 reasoning summaries rendering literal `<!-- -->` sentinel lines in thinking blocks; empty HTML comments (and the unterminated `<!--` tail while streaming) are now dropped from the thinking display, and blocks reduced to pure comment noise are hidden entirely.
|
||||
- Fixed `ultrathink`, `orchestrate`, and `workflowz` magic keywords not triggering when adjacent to sentence punctuation or quotes while still ignoring inflections and path/file-extension occurrences. ([#4965](https://github.com/can1357/oh-my-pi/issues/4965))
|
||||
- Fixed `omp plugin install github:owner/repo --force` failing with Bun `DependencyLoop` when replacing an existing pinned git plugin source for the same repository; the installer now removes the stale pinned dependency edge before invoking Bun and restores it on rollback. ([#4960](https://github.com/can1357/oh-my-pi/issues/4960))
|
||||
|
||||
## [16.3.13] - 2026-07-09
|
||||
|
||||
@@ -351,12 +344,6 @@
|
||||
- Fixed the streamed `write` tool's collapsed pending tail preview leaving stale rows above the first partial-result frame in the TUI; the first result now replays the viewport like the SSH placeholder seam already did ([#4477](https://github.com/can1357/oh-my-pi/issues/4477))
|
||||
- Fixed first-run setup ignoring a pre-seeded `config.yaml`: the settings loader now treats `config.yml` and `config.yaml` as equivalent existing main config files, writes back to the existing extension, and only creates canonical `config.yml` for fresh installs. ([#4914](https://github.com/can1357/oh-my-pi/issues/4914))
|
||||
- Fixed extension `sendUserMessage()` without `deliverAs` surfacing `AgentBusyError` during active streams; omitted `deliverAs` now queues a steer through the normal prompt flow, and ACP/RPC skill-command prompts queue while streaming (RPC honors the prompt command's `streamingBehavior`, defaulting to steer) ([#4923](https://github.com/can1357/oh-my-pi/issues/4923)).
|
||||
### Fixed
|
||||
|
||||
- Fixed Cursor-provider turns whose assistant message contains intro text, tool calls, and trailing final text rendering the final answer above the tool output instead of at the transcript tail ([#4871](https://github.com/can1357/oh-my-pi/issues/4871)).
|
||||
- Fixed `omp -p` looking hung while a text-mode prompt is in flight by writing a one-shot working indicator to stderr before awaiting the model response. ([#4901](https://github.com/can1357/oh-my-pi/issues/4901))
|
||||
- Fixed autolearn auto-continue firing a capture turn after an aborted stop (Esc/cancel): the controller now skips any `agent_end` whose last assistant message has `stopReason: "aborted"`.
|
||||
- Fixed MCP tools receiving session image attachments as raw `local://...` URIs by resolving them to session-local filesystem paths before `tools/call` is sent ([#4946](https://github.com/can1357/oh-my-pi/issues/4946)).
|
||||
|
||||
## [16.3.12] - 2026-07-08
|
||||
|
||||
@@ -406,7 +393,6 @@
|
||||
- Fixed retry fallback model recovery by exposing `retry.fallbackChains` in `/settings`, adding a `/model` action to assign the selected default fallback model, and clearing a selected model's retry cooldown marker on manual model switches. ([#4533](https://github.com/can1357/oh-my-pi/issues/4533))
|
||||
- Fixed `/handoff` and auto-handoff skipping extension lifecycle hooks by emitting cancellable `session_before_switch` hooks and a `session_switch` with `reason: "handoff"` after the replacement session is ready ([#4434](https://github.com/can1357/oh-my-pi/issues/4434)).
|
||||
- Fixed TTSR stream interrupts so only the tool call whose stream matched a rule receives the rule-named abort result; sibling tool-call placeholders now use a neutral abort reason ([#2783](https://github.com/can1357/oh-my-pi/issues/2783)).
|
||||
- Fixed late advisor concerns after a terminal primary answer starting a duplicate primary turn when no queued work remains ([#4840](https://github.com/can1357/oh-my-pi/issues/4840)).
|
||||
|
||||
## [16.3.11] - 2026-07-06
|
||||
|
||||
|
||||
@@ -2269,10 +2269,14 @@ describe("advisor", () => {
|
||||
state: { messages: [] },
|
||||
};
|
||||
const messages: AgentMessage[] = [{ role: "user", content: "aaa", timestamp: 1 } as AgentMessage];
|
||||
const runtime = new AdvisorRuntime(agent, {
|
||||
snapshotMessages: () => messages,
|
||||
enqueueAdvice: () => {},
|
||||
}, 0);
|
||||
const runtime = new AdvisorRuntime(
|
||||
agent,
|
||||
{
|
||||
snapshotMessages: () => messages,
|
||||
enqueueAdvice: () => {},
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
runtime.onTurnEnd(messages);
|
||||
await firstPromptStarted;
|
||||
|
||||
@@ -449,130 +449,135 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
||||
// runtime is parked waiting for the result, and the cell timeout must
|
||||
// not abort us mid-cherry-pick or mid-nested-commit. The clock restarts
|
||||
// only after we hand control back to the runtime.
|
||||
const { result, mergeSummary, changesApplied } = await withBridgeTimeoutPause(options.emitStatus, async () => {
|
||||
let isolationContext: IsolationContext | null = null;
|
||||
if (isIsolated) {
|
||||
try {
|
||||
isolationContext = await prepareIsolationContext(options.session.cwd);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new ToolError(`Isolated agent() execution requires a git repository. ${message}`);
|
||||
}
|
||||
}
|
||||
const preferredBackend = isIsolated ? parseIsolationMode(isolationMode) : undefined;
|
||||
|
||||
const result = await (async () => {
|
||||
if (!isolationContext) {
|
||||
return taskExecutor.runSubprocess(baseRunOptions);
|
||||
}
|
||||
const taskStart = Date.now();
|
||||
return runIsolatedSubprocess({
|
||||
baseOptions: baseRunOptions,
|
||||
context: isolationContext,
|
||||
preferredBackend,
|
||||
agentId: id,
|
||||
mergeMode,
|
||||
artifactsDir,
|
||||
description: trimToUndefined(parsed.label),
|
||||
buildCommitMessage,
|
||||
buildFailureResult: err => {
|
||||
const { result, mergeSummary, changesApplied } = await withBridgeTimeoutPause(
|
||||
options.emitStatus,
|
||||
async () => {
|
||||
let isolationContext: IsolationContext | null = null;
|
||||
if (isIsolated) {
|
||||
try {
|
||||
isolationContext = await prepareIsolationContext(options.session.cwd);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
index: 0,
|
||||
id,
|
||||
agent: effectiveAgent.name,
|
||||
agentSource: effectiveAgent.source,
|
||||
task: renderSubagentPrompt(assignment),
|
||||
assignment,
|
||||
description: trimToUndefined(parsed.label),
|
||||
exitCode: 1,
|
||||
output: "",
|
||||
stderr: message,
|
||||
truncated: false,
|
||||
durationMs: Date.now() - taskStart,
|
||||
tokens: 0,
|
||||
requests: 0,
|
||||
modelOverride,
|
||||
error: message,
|
||||
};
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
if (result.exitCode !== 0 || result.error || result.aborted) {
|
||||
const failureMessage = buildSubagentFailureMessage(agentName, result);
|
||||
const recoveryHint = isIsolated ? await buildIsolationRecoveryHint(result, artifactsDir) : "";
|
||||
throw new ToolError(`${failureMessage}${recoveryHint}`);
|
||||
}
|
||||
|
||||
let mergeSummary = "";
|
||||
let changesApplied: boolean | null = null;
|
||||
if (isIsolated && isolationContext) {
|
||||
if (applyChanges) {
|
||||
const outcome = await mergeIsolatedChanges({
|
||||
result,
|
||||
repoRoot: isolationContext.repoRoot,
|
||||
mergeMode,
|
||||
});
|
||||
mergeSummary = outcome.summary;
|
||||
changesApplied = outcome.changesApplied;
|
||||
if (outcome.changesApplied === false) {
|
||||
const summaryText = outcome.summary.trim();
|
||||
const recoveryHint = await buildIsolationRecoveryHint(result, artifactsDir);
|
||||
throw new ToolError(
|
||||
`agent() isolated apply failed for ${result.id}${summaryText ? `: ${summaryText}` : ""}${recoveryHint}`,
|
||||
);
|
||||
}
|
||||
|
||||
const nestedSummary = await applyEligibleNestedPatches({
|
||||
result,
|
||||
repoRoot: isolationContext.repoRoot,
|
||||
mergeMode,
|
||||
changesApplied: outcome.changesApplied,
|
||||
mergedBranchForNestedPatches: outcome.mergedBranchForNestedPatches,
|
||||
commitMessage: buildCommitMessage(),
|
||||
});
|
||||
mergeSummary += nestedSummary;
|
||||
if (structured && nestedSummary.trim()) {
|
||||
const recoveryHint = await buildIsolationRecoveryHint(
|
||||
{ ...result, patchPath: undefined, branchName: undefined },
|
||||
artifactsDir,
|
||||
);
|
||||
throw new ToolError(
|
||||
`agent() isolated nested patch apply failed for ${result.id}: ${plainIsolationSummary(nestedSummary)}${recoveryHint}`,
|
||||
);
|
||||
}
|
||||
} else if (result.branchName) {
|
||||
mergeSummary = `\n\nIsolation: changes captured on branch \`${result.branchName}\` (apply=false). Not merged.`;
|
||||
} else if (result.patchPath) {
|
||||
mergeSummary = `\n\nIsolation: changes captured at \`${result.patchPath}\` (apply=false). Not applied.`;
|
||||
} else {
|
||||
const nestedPatches = result.nestedPatches ?? [];
|
||||
if (nestedPatches.length > 0) {
|
||||
mergeSummary = `\n\nIsolation: changes captured for ${nestedPatches.length} nested repositor${nestedPatches.length === 1 ? "y" : "ies"} (apply=false). Not applied.`;
|
||||
} else {
|
||||
mergeSummary = "\n\nIsolation: no changes captured.";
|
||||
throw new ToolError(`Isolated agent() execution requires a git repository. ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const preferredBackend = isIsolated ? parseIsolationMode(isolationMode) : undefined;
|
||||
|
||||
// Clean up the temp artifacts dir we created for this call only when the
|
||||
// caller will not need files from it later. Keep it when the runtime helper
|
||||
// will return an `agent://` handle (the `.md`/`.jsonl` backing files live
|
||||
// here) and on `apply=false` (`changesApplied === null`) where the caller
|
||||
// consumes `details.patchPath` / `details.branchName` /
|
||||
// `details.nestedPatches` out of band. Failed isolated applies throw
|
||||
// earlier with a recovery hint, so they never reach this gate.
|
||||
const shouldCleanupTempArtifacts = tempArtifactsDir && !parsed.handle && (!isIsolated || changesApplied === true);
|
||||
if (shouldCleanupTempArtifacts) {
|
||||
await fs.rm(artifactsDir, { recursive: true, force: true });
|
||||
unregisterArtifactsDir?.();
|
||||
}
|
||||
const result = await (async () => {
|
||||
if (!isolationContext) {
|
||||
return taskExecutor.runSubprocess(baseRunOptions);
|
||||
}
|
||||
const taskStart = Date.now();
|
||||
return runIsolatedSubprocess({
|
||||
baseOptions: baseRunOptions,
|
||||
context: isolationContext,
|
||||
preferredBackend,
|
||||
agentId: id,
|
||||
mergeMode,
|
||||
artifactsDir,
|
||||
description: trimToUndefined(parsed.label),
|
||||
buildCommitMessage,
|
||||
buildFailureResult: err => {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
index: 0,
|
||||
id,
|
||||
agent: effectiveAgent.name,
|
||||
agentSource: effectiveAgent.source,
|
||||
task: renderSubagentPrompt(assignment),
|
||||
assignment,
|
||||
description: trimToUndefined(parsed.label),
|
||||
exitCode: 1,
|
||||
output: "",
|
||||
stderr: message,
|
||||
truncated: false,
|
||||
durationMs: Date.now() - taskStart,
|
||||
tokens: 0,
|
||||
requests: 0,
|
||||
modelOverride,
|
||||
error: message,
|
||||
};
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
options.session.recordEvalSubagentUsage?.(result.usage?.output ?? 0);
|
||||
if (result.exitCode !== 0 || result.error || result.aborted) {
|
||||
const failureMessage = buildSubagentFailureMessage(agentName, result);
|
||||
const recoveryHint = isIsolated ? await buildIsolationRecoveryHint(result, artifactsDir) : "";
|
||||
throw new ToolError(`${failureMessage}${recoveryHint}`);
|
||||
}
|
||||
|
||||
return { result, mergeSummary, changesApplied };
|
||||
}, { deferExternalAbort: true });
|
||||
let mergeSummary = "";
|
||||
let changesApplied: boolean | null = null;
|
||||
if (isIsolated && isolationContext) {
|
||||
if (applyChanges) {
|
||||
const outcome = await mergeIsolatedChanges({
|
||||
result,
|
||||
repoRoot: isolationContext.repoRoot,
|
||||
mergeMode,
|
||||
});
|
||||
mergeSummary = outcome.summary;
|
||||
changesApplied = outcome.changesApplied;
|
||||
if (outcome.changesApplied === false) {
|
||||
const summaryText = outcome.summary.trim();
|
||||
const recoveryHint = await buildIsolationRecoveryHint(result, artifactsDir);
|
||||
throw new ToolError(
|
||||
`agent() isolated apply failed for ${result.id}${summaryText ? `: ${summaryText}` : ""}${recoveryHint}`,
|
||||
);
|
||||
}
|
||||
|
||||
const nestedSummary = await applyEligibleNestedPatches({
|
||||
result,
|
||||
repoRoot: isolationContext.repoRoot,
|
||||
mergeMode,
|
||||
changesApplied: outcome.changesApplied,
|
||||
mergedBranchForNestedPatches: outcome.mergedBranchForNestedPatches,
|
||||
commitMessage: buildCommitMessage(),
|
||||
});
|
||||
mergeSummary += nestedSummary;
|
||||
if (structured && nestedSummary.trim()) {
|
||||
const recoveryHint = await buildIsolationRecoveryHint(
|
||||
{ ...result, patchPath: undefined, branchName: undefined },
|
||||
artifactsDir,
|
||||
);
|
||||
throw new ToolError(
|
||||
`agent() isolated nested patch apply failed for ${result.id}: ${plainIsolationSummary(nestedSummary)}${recoveryHint}`,
|
||||
);
|
||||
}
|
||||
} else if (result.branchName) {
|
||||
mergeSummary = `\n\nIsolation: changes captured on branch \`${result.branchName}\` (apply=false). Not merged.`;
|
||||
} else if (result.patchPath) {
|
||||
mergeSummary = `\n\nIsolation: changes captured at \`${result.patchPath}\` (apply=false). Not applied.`;
|
||||
} else {
|
||||
const nestedPatches = result.nestedPatches ?? [];
|
||||
if (nestedPatches.length > 0) {
|
||||
mergeSummary = `\n\nIsolation: changes captured for ${nestedPatches.length} nested repositor${nestedPatches.length === 1 ? "y" : "ies"} (apply=false). Not applied.`;
|
||||
} else {
|
||||
mergeSummary = "\n\nIsolation: no changes captured.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the temp artifacts dir we created for this call only when the
|
||||
// caller will not need files from it later. Keep it when the runtime helper
|
||||
// will return an `agent://` handle (the `.md`/`.jsonl` backing files live
|
||||
// here) and on `apply=false` (`changesApplied === null`) where the caller
|
||||
// consumes `details.patchPath` / `details.branchName` /
|
||||
// `details.nestedPatches` out of band. Failed isolated applies throw
|
||||
// earlier with a recovery hint, so they never reach this gate.
|
||||
const shouldCleanupTempArtifacts =
|
||||
tempArtifactsDir && !parsed.handle && (!isIsolated || changesApplied === true);
|
||||
if (shouldCleanupTempArtifacts) {
|
||||
await fs.rm(artifactsDir, { recursive: true, force: true });
|
||||
unregisterArtifactsDir?.();
|
||||
}
|
||||
|
||||
options.session.recordEvalSubagentUsage?.(result.usage?.output ?? 0);
|
||||
|
||||
return { result, mergeSummary, changesApplied };
|
||||
},
|
||||
{ deferExternalAbort: true },
|
||||
);
|
||||
|
||||
return {
|
||||
text: structured ? result.output : result.output + mergeSummary,
|
||||
|
||||
@@ -78,7 +78,6 @@ function readPendingToolCalls(value: unknown): PendingToolCallDiagnostic[] | und
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
function readSessionExit(entry: SessionEntry): SessionExitData | undefined {
|
||||
if (entry.type !== "custom" || entry.customType !== SESSION_EXIT_CUSTOM_TYPE || !isObject(entry.data)) {
|
||||
return undefined;
|
||||
|
||||
@@ -810,7 +810,9 @@ function createContextSectionRenderer(
|
||||
args: Partial<TaskParams> | undefined,
|
||||
theme: Theme,
|
||||
): AssignmentSectionRenderer | undefined {
|
||||
const context = sanitizeText(repairDoubleEncodedJsonString(typeof args?.context === "string" ? args.context : "")).trim();
|
||||
const context = sanitizeText(
|
||||
repairDoubleEncodedJsonString(typeof args?.context === "string" ? args.context : ""),
|
||||
).trim();
|
||||
if (!context) return undefined;
|
||||
return createMarkdownSectionRenderer(context, theme);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,11 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
1: import { type OAuthCredential, type UsageProvider, withAuth } from "@oh-my-pi/pi-ai";
|
||||
2: import type { OAuthCredentials, OAuthProviderId } from "@oh-my-pi/pi-ai/oauth/types";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
||||
3: @both
|
||||
import { type OAuthCredential, type UsageProvider, withAuth } from "@oh-my-pi/pi-ai";
|
||||
import * as oauth from "@oh-my-pi/pi-ai/oauth";
|
||||
1: import { type OAuthCredential, type UsageProvider, withAuth } from "@oh-my-pi/pi-ai";
|
||||
2: import type { OAuthCredentials, OAuthProviderId } from "@oh-my-pi/pi-ai/oauth/types";
|
||||
import type { OAuthCredentials, OAuthProviderId } from "@oh-my-pi/pi-ai/oauth/types";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
||||
3: @both
|
||||
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
|
||||
import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { createApiKeyResolver } from "../src/config/api-key-resolver";
|
||||
@@ -201,10 +195,189 @@ describe("AuthStorage account rotation", () => {
|
||||
expect(await authStorage.getApiKey("openai-codex", sessionId)).toBe(stickyKey);
|
||||
});
|
||||
|
||||
1: import { type OAuthCredential, type UsageProvider, withAuth } from "@oh-my-pi/pi-ai";
|
||||
2: import type { OAuthCredentials, OAuthProviderId } from "@oh-my-pi/pi-ai/oauth/types";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
||||
3: @both
|
||||
test("API key resolver re-resolves after a concurrent OAuth refresh makes a 401 bearer stale", async () => {
|
||||
const resolvedKeys = ["stale-access", "refreshed-access"];
|
||||
const rotationTargets: Array<string | undefined> = [];
|
||||
const registry: Parameters<typeof createApiKeyResolver>[0] = {
|
||||
async getApiKeyForProvider() {
|
||||
return resolvedKeys.shift();
|
||||
},
|
||||
authStorage: {
|
||||
async rotateSessionCredential(_provider, _sessionId, options) {
|
||||
rotationTargets.push(options?.apiKey);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
const resolver = createApiKeyResolver(registry, "openai-codex", {
|
||||
sessionId: "concurrent-oauth-refresh",
|
||||
});
|
||||
|
||||
const initial = await resolver({ lastChance: false, error: undefined });
|
||||
const refreshed = await resolver({
|
||||
lastChance: true,
|
||||
error: Object.assign(new Error("401 authentication_error"), { status: 401 }),
|
||||
previousKey: initial,
|
||||
});
|
||||
|
||||
expect(initial).toBe("stale-access");
|
||||
expect(refreshed).toBe("refreshed-access");
|
||||
expect(rotationTargets).toEqual(["stale-access"]);
|
||||
});
|
||||
|
||||
test("API key resolver stops when a usage-limit rotation has no unblocked sibling", async () => {
|
||||
const resolvedKeys = ["quota-blocked-B", "quota-blocked-A"];
|
||||
const registry: Parameters<typeof createApiKeyResolver>[0] = {
|
||||
async getApiKeyForProvider() {
|
||||
return resolvedKeys.shift();
|
||||
},
|
||||
authStorage: {
|
||||
async rotateSessionCredential() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
const attemptedKeys: string[] = [];
|
||||
|
||||
await expect(
|
||||
withAuth(createApiKeyResolver(registry, "openai-codex"), async key => {
|
||||
attemptedKeys.push(key);
|
||||
throw Object.assign(new Error("You have hit your ChatGPT usage limit (pro plan). Try again later."), {
|
||||
status: 429,
|
||||
});
|
||||
}),
|
||||
).rejects.toThrow("usage limit");
|
||||
|
||||
expect(attemptedKeys).toEqual(["quota-blocked-B"]);
|
||||
expect(resolvedKeys).toEqual(["quota-blocked-A"]);
|
||||
});
|
||||
|
||||
test("withAuth reaches a fourth healthy Codex OAuth sibling through ModelRegistry", async () => {
|
||||
await authStorage.set("openai-codex", [
|
||||
{
|
||||
type: "oauth",
|
||||
access: "access-a",
|
||||
refresh: "refresh-a",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "acct-a",
|
||||
},
|
||||
{
|
||||
type: "oauth",
|
||||
access: "access-b",
|
||||
refresh: "refresh-b",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "acct-b",
|
||||
},
|
||||
{
|
||||
type: "oauth",
|
||||
access: "access-c",
|
||||
refresh: "refresh-c",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "acct-c",
|
||||
},
|
||||
{
|
||||
type: "oauth",
|
||||
access: "access-d",
|
||||
refresh: "refresh-d",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "acct-d",
|
||||
},
|
||||
]);
|
||||
|
||||
const model = getBundledModel("openai-codex", "gpt-5.5");
|
||||
if (!model) {
|
||||
throw new Error("Expected bundled Codex test model to exist");
|
||||
}
|
||||
|
||||
const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir, "models.yml"));
|
||||
const attemptedKeys: string[] = [];
|
||||
const result = await withAuth(modelRegistry.resolver(model, "codex-four-oauth-session"), async key => {
|
||||
attemptedKeys.push(key);
|
||||
if (key !== "access-d") {
|
||||
throw new Error("You have hit your ChatGPT usage limit (pro plan). Try again later.");
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
expect(result).toBe("access-d");
|
||||
expect(attemptedKeys.at(-1)).toBe("access-d");
|
||||
expect([...attemptedKeys].sort()).toEqual(["access-a", "access-b", "access-c", "access-d"]);
|
||||
expect(new Set(attemptedKeys).size).toBe(4);
|
||||
});
|
||||
|
||||
test("provider login invalidates only that provider's persisted session stickiness", async () => {
|
||||
const targetInitialCredentials: OAuthCredential[] = [
|
||||
{
|
||||
type: "oauth",
|
||||
access: "target-access-a",
|
||||
refresh: "target-refresh-a",
|
||||
expires: Date.now() + 3600_000,
|
||||
accountId: "target-acct-a",
|
||||
email: "target-a@example.com",
|
||||
},
|
||||
{
|
||||
type: "oauth",
|
||||
access: "target-access-b",
|
||||
refresh: "target-refresh-b",
|
||||
expires: Date.now() + 3600_000,
|
||||
accountId: "target-acct-b",
|
||||
email: "target-b@example.com",
|
||||
},
|
||||
];
|
||||
const targetAddedCredential: OAuthCredential = {
|
||||
type: "oauth",
|
||||
access: "target-access-c",
|
||||
refresh: "target-refresh-c",
|
||||
expires: Date.now() + 3600_000,
|
||||
accountId: "target-acct-c",
|
||||
email: "target-c@example.com",
|
||||
};
|
||||
const targetFinalCredentials = [...targetInitialCredentials, targetAddedCredential];
|
||||
const { sessionId, stickyKey, freshKey } = await findSessionWhereFreshSelectionChanges(
|
||||
targetProvider,
|
||||
targetInitialCredentials,
|
||||
targetFinalCredentials,
|
||||
);
|
||||
|
||||
await authStorage.set(unrelatedProvider, [
|
||||
{
|
||||
type: "oauth",
|
||||
access: "unrelated-access-a",
|
||||
refresh: "unrelated-refresh-a",
|
||||
expires: Date.now() + 3600_000,
|
||||
accountId: "unrelated-acct-a",
|
||||
email: "unrelated-a@example.com",
|
||||
},
|
||||
{
|
||||
type: "oauth",
|
||||
access: "unrelated-access-b",
|
||||
refresh: "unrelated-refresh-b",
|
||||
expires: Date.now() + 3600_000,
|
||||
accountId: "unrelated-acct-b",
|
||||
email: "unrelated-b@example.com",
|
||||
},
|
||||
]);
|
||||
const unrelatedSessionId = "issue-4982-unrelated-session";
|
||||
const unrelatedStickyKey = await authStorage.getApiKey(unrelatedProvider, unrelatedSessionId);
|
||||
expect(unrelatedStickyKey).toMatch(/^unrelated-access-/);
|
||||
|
||||
const { type: _type, ...loginCredential } = targetAddedCredential;
|
||||
nextLoginCredential = loginCredential;
|
||||
await authStorage.login(targetProvider, {
|
||||
onAuth: () => {},
|
||||
onPrompt: async () => "",
|
||||
});
|
||||
nextLoginCredential = undefined;
|
||||
|
||||
authStorage.close();
|
||||
authStorage = await AuthStorage.create(path.join(tempDir, "testauth.db"), {
|
||||
usageProviderResolver: provider => (provider === "openai-codex" ? usageProvider : undefined),
|
||||
});
|
||||
await authStorage.reload();
|
||||
|
||||
const reloadedTargetKey = await authStorage.getApiKey(targetProvider, sessionId);
|
||||
expect(reloadedTargetKey).toBe(freshKey);
|
||||
expect(reloadedTargetKey).not.toBe(stickyKey);
|
||||
expect(await authStorage.getApiKey(unrelatedProvider, unrelatedSessionId)).toBe(unrelatedStickyKey);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -231,10 +231,7 @@ describe("issue #5325: sessionId forwarded to getApiKey for session-sticky OAuth
|
||||
"subagent-session-456",
|
||||
);
|
||||
|
||||
expect(receivedSessionIds).toEqual([
|
||||
"opencode-zen:subagent-session-456",
|
||||
"deepseek:subagent-session-456",
|
||||
]);
|
||||
expect(receivedSessionIds).toEqual(["opencode-zen:subagent-session-456", "deepseek:subagent-session-456"]);
|
||||
expect(result.authFallbackUsed).toBe(true);
|
||||
expect(result.model?.provider).toBe("deepseek");
|
||||
});
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { debugCatchError, debugError } from "puppeteer-core/lib/puppeteer/common/util.js";
|
||||
import { resolve } from "node:path";
|
||||
import { debugCatchError, debugError } from "puppeteer-core/lib/puppeteer/common/util.js";
|
||||
|
||||
const patchPath = resolve(import.meta.dir, "../../../../patches/puppeteer-core@25.3.0.patch");
|
||||
|
||||
describe("Puppeteer stealth patch", () => {
|
||||
it("uses the safe debug handler for all added rejection paths", async () => {
|
||||
const patch = await Bun.file(patchPath).text();
|
||||
const addedLines = patch
|
||||
.split("\n")
|
||||
.filter(line => line.startsWith("+") && !line.startsWith("+++"));
|
||||
const addedLines = patch.split("\n").filter(line => line.startsWith("+") && !line.startsWith("+++"));
|
||||
|
||||
expect(addedLines.filter(line => line.includes(".catch(debugError)"))).toEqual([]);
|
||||
expect(addedLines.filter(line => /\bdebugError\(/.test(line))).toEqual([]);
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bounded the Markdown L2 render cache by weighted output size and excluded oversized renders, preventing a few large documents from occupying ordinary cache slots indefinitely ([#4820](https://github.com/can1357/oh-my-pi/issues/4820)).
|
||||
- Fixed unmanaged macOS stderr writes (libmalloc/framework diagnostics) corrupting the viewport: `ProcessTerminal` now suppresses fd 2 via the pi-utils stderr guard while it owns the terminal and restores it in `stop()` and the emergency-restore path.
|
||||
- Fixed streamed diff code fences retaining unhighlighted rows in native scrollback when long transient blocks leave the viewport before finalization ([#5126](https://github.com/can1357/oh-my-pi/issues/5126)).
|
||||
- Fixed native Windows Terminal sessions missing mid-run light/dark theme changes when Mode 2031 appearance notifications are unavailable by polling OSC 11 only on that host path ([#5091](https://github.com/can1357/oh-my-pi/issues/5091)).
|
||||
- Hid empty HTML comment separators in Markdown-rendered TUI output instead of showing `<!-- -->` literally ([#4911](https://github.com/can1357/oh-my-pi/issues/4911)).
|
||||
|
||||
## [16.5.0] - 2026-07-13
|
||||
|
||||
### Changed
|
||||
@@ -12,9 +20,6 @@
|
||||
|
||||
- Fixed a rendering issue where resizing the terminal during forced renders (such as tool finalization or image reconciliation) caused the entire transcript to visibly replay and flicker. Forced renders are now consolidated into a single paint once the resize settles.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bounded the Markdown L2 render cache by weighted output size and excluded oversized renders, preventing a few large documents from occupying ordinary cache slots indefinitely ([#4820](https://github.com/can1357/oh-my-pi/issues/4820)).
|
||||
## [16.4.7] - 2026-07-12
|
||||
|
||||
### Fixed
|
||||
@@ -37,12 +42,6 @@
|
||||
|
||||
- Fixed an issue where the mid-prompt `/` autocomplete popup lingered indefinitely on non-path and non-skill tokens. Autocomplete matching is now properly gated to explicit skill namespaces, queries, and prefixes, preventing stale popups from incorrectly rewriting input on Tab or Enter.
|
||||
- Fixed idle Loader animation driving the full TUI render pipeline on every spinner tick by directly rewriting the Loader's visible rows when geometry is unchanged, reducing idle render work while preserving fallback repaint paths ([#5192](https://github.com/can1357/oh-my-pi/issues/5192)).
|
||||
### Fixed
|
||||
|
||||
- Fixed unmanaged macOS stderr writes (libmalloc/framework diagnostics) corrupting the viewport: `ProcessTerminal` now suppresses fd 2 via the pi-utils stderr guard while it owns the terminal and restores it in `stop()` and the emergency-restore path.
|
||||
### Fixed
|
||||
|
||||
- Fixed streamed diff code fences retaining unhighlighted rows in native scrollback when long transient blocks leave the viewport before finalization ([#5126](https://github.com/can1357/oh-my-pi/issues/5126)).
|
||||
|
||||
## [16.4.1] - 2026-07-10
|
||||
|
||||
@@ -58,9 +57,6 @@
|
||||
|
||||
- Improved row alignment and spacing for `align`, `gather`, and `array` environments
|
||||
- Updated matrix environments to render as baseline-aligned grids with stretched brackets
|
||||
### Fixed
|
||||
|
||||
- Fixed native Windows Terminal sessions missing mid-run light/dark theme changes when Mode 2031 appearance notifications are unavailable by polling OSC 11 only on that host path ([#5091](https://github.com/can1357/oh-my-pi/issues/5091)).
|
||||
|
||||
## [16.4.0] - 2026-07-10
|
||||
|
||||
@@ -80,9 +76,6 @@
|
||||
|
||||
- Fixed late terminal appearance subscribers missing the already-detected OSC 11 light/dark result, so theme auto-detection picks up the terminal appearance even when the response arrives before the UI subscribes ([#4731](https://github.com/can1357/oh-my-pi/issues/4731)).
|
||||
- Fixed slash command Tab completion reopening the file autocomplete drawer after accepting no-argument commands ([#4808](https://github.com/can1357/oh-my-pi/issues/4808)).
|
||||
### Fixed
|
||||
|
||||
- Hid empty HTML comment separators in Markdown-rendered TUI output instead of showing `<!-- -->` literally ([#4911](https://github.com/can1357/oh-my-pi/issues/4911)).
|
||||
|
||||
## [16.3.12] - 2026-07-08
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added a terminal stderr guard (`suppressTerminalStderr`/`restoreTerminalStderr`): dup2-redirects fd 2 to the omp log file while a TUI owns the terminal so macOS runtime diagnostics cannot paint into the viewport. The postmortem fatal handlers restore fd 2 before printing so crash reports stay visible.
|
||||
|
||||
## [16.4.6] - 2026-07-12
|
||||
|
||||
### Added
|
||||
|
||||
- Added `AsyncDrain`, the deferred write-batching helper previously private to the coding-agent's prompt-history storage; now shared with model-perf recording.
|
||||
### Added
|
||||
|
||||
- Added a terminal stderr guard (`suppressTerminalStderr`/`restoreTerminalStderr`): dup2-redirects fd 2 to the omp log file while a TUI owns the terminal so macOS runtime diagnostics cannot paint into the viewport. The postmortem fatal handlers restore fd 2 before printing so crash reports stay visible.
|
||||
|
||||
## [16.4.2] - 2026-07-10
|
||||
|
||||
|
||||
Reference in New Issue
Block a user