A hijacked session must not be able to silently add a passkey as a
persistent backdoor or remove the victim's credentials. Registration
(begin) and deletion now verify the account password server-side,
reusing the existing PASSWORD_REQUIRED / PASSWORD_INCORRECT errors.
The password is used instead of TOTP step-up so the guard also protects
deployments that never configured a TOTP encryption key. The password
key in both request bodies is covered by the audit middleware's
key-substring redaction, so no credential material reaches audit_logs.
Frontend: the add-passkey form gains a current-password field, and the
delete confirmation is now a dialog with a password input (replacing
window.confirm), mirroring the TOTP disable dialog. Backend error
messages (e.g. wrong password) are surfaced instead of the generic
failure toast. Rename remains password-free as it is cosmetic.
Flattening Codex `type:"namespace"` tool declarations into `namespace__name`
renames the tool but cannot rewrite the contract the client already handed the
model: Codex tells it to call `to=functions.collaboration.spawn_agent`. Under
`tool_mode: code_mode_only` (gpt-5.6-*) collaboration tools are the model's only
direct channel, so the rename left it unable to address them — it fell back to
`functions.exec` and looped on meaningless shell commands until the session
collapsed, with no upstream error anywhere. Fixes#4978.
Preserving is what the upstream actually wants. OAuth egress is unconditionally
chatgpt.com/backend-api/codex/responses (base_url is read only for API-key
accounts), i.e. the party that defines the extension; codex-rs builds one
request for both WS and HTTP and falls back WS->HTTP mid-session, so these
declarations already reach that host over HTTP — as they do today via the
responses-lite carrier and the WS/HTTP bridge, neither of which ever flattened.
Namespace names cannot be allowlisted: `features.multi_agent_v2.tool_namespace`
is user-configurable (openai/codex#31864 recommends renaming it to `agents`) and
MCP/connector namespaces are generated at runtime (mcp__codex_apps__gmail).
So the default is inverted rather than extended with more names.
- Preserve namespace declarations by default for OpenAI OAuth; the new
per-account `extra.openai_responses_flatten_namespaces` restores the previous
behavior for deployments routing OAuth traffic to a relay that rejects them.
- Keep `input[].namespace` on tool-call items for OAuth non-compact requests.
The upstream requires the round-trip ("Missing namespace for function_call
'...'. Round-trip the model's function_call item with its namespace field
included."), so preserving declarations while stripping the calls would 400 on
the second turn. The item-type allowlist mirrors the existing reactive strip.
- Keep compact on its current behavior: that endpoint rejects the field outright
("Unknown parameter: 'input[894].namespace'", #4761) and there is no evidence
either way about namespace declarations there, so this change does not widen
its surface. API-key accounts are untouched — their upstream is a standard
Responses API and the reactive retry only clears one index per round trip.
- Clear the flatten mapping at the start of each Forward attempt so an account
reached through failover cannot restore responses with the previous account's
mapping.
Admin UI gets an OAuth-only toggle (create/edit/bulk) plus zh/en copy noting the
compact exception.
Composite model routes support a match_type: prefix mode with an optional
upstream_model. Leaving upstream_model empty is meant to pass the concrete
requested model through to the upstream, but every request matching the prefix
collapsed to the route's public_model instead. A route with
public_model=deepseek-v4, match_type=prefix and an empty upstream_model
forwarded both deepseek-v4-flash and deepseek-v4-pro upstream as deepseek-v4.
Root cause: normalizeCompositeRouteInput backfilled an empty upstream_model with
public_model at save time, so CompositeRouteResolver.Resolve never reached its
"upstreamModel == '' -> concrete requested model" fallback.
- Skip the backfill for prefix routes only; an empty upstream_model stays empty
and Resolve passes the concrete requested model through.
- exact routes keep the backfill (model == public_model, so persisted/display
contract is unchanged).
- Explicit upstream_model still forwards that fixed model in every mode.
- Frontend: add an upstream_model hint (zh/en) documenting empty = passthrough.
Persist reasoning ceilings and exact mappings for OpenAI groups, enforce them across HTTP and WebSocket forwarding, and invalidate cached auth snapshots.
Resolve const-block conflict in openai_gateway_grok_cache.go:
keep #4590's client tool cache constants, drop grokFreeRolling24hTokenLimit
(moved to pkg/xai as IsGrokFreeRolling24hTokenLimit with legacy 2M support).
BatchImageGuideView carried ~140 user-visible strings outside vue-i18n:
hardcoded Chinese template strings (buttons, placeholders, tooltips,
table headers, dialog titles, empty states) plus an inline bilingual
batchImageText dictionary switched by isZhLocale() in script.
Move all of it into a new batchImage locale namespace (180 leaf keys
per language, zh/en verified key-aligned) registered in both locale
indexes, reusing 15 existing common.*/pagination.* keys. Column and
filter-option definitions become computed so language switches take
effect immediately; countable strings use vue-i18n plurals and the
three rich-text counters keep their inner bold spans via <i18n-t>
slots.
The Codex agent-instruction prompt template intentionally stays as a
plain string: it is an AI-facing prompt document containing literal
JSON braces that conflict with vue-i18n message syntax.
Verified: eslint, vue-tsc --noEmit, vite build, and the full vitest
suite (175 files / 1219 tests) all pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the last few user-visible hardcoded strings into vue-i18n:
- AppHeader: "Toggle Menu" / "User Menu" aria-labels (screen readers
read these in English regardless of locale)
- CustomPageView: "Page not found" error body
- PaymentProviderDialog: display-name example placeholder hardcoded in
Chinese for all locales
Adds common.toggleMenu / common.userMenu / common.pageNotFound and
admin.settings.payment.customMethodDisplayNamePlaceholder to both zh
and en locales, and registers the new placeholder key in the
PaymentProviderDialog spec's i18n mock.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>