The admin user edit modal rejected concurrency < 1, so a user whose
concurrency is already 0 could not be saved at all — the guard runs
before the request, blocking notes, password, role and RPM edits on that
user too.
Everywhere else already treats 0 as unlimited: the gateway skips slot
limiting when maxConcurrency <= 0 (ConcurrencyService.AcquireUserSlot),
the batch limits endpoint binds concurrency with min=0, and the bulk edit
modal only rejects negative values.
Reject negative and non-integer values instead, mirror the RPM field with
min/step and a "0 = unlimited" placeholder and hint, and rename the error
key to match its new meaning. Account concurrency is unchanged.
The quota cell used the noun label "5-hour window/weekly window" and the
balance cell the balance value itself as the click target for a manual
refresh. Both read as passive captions, so users could not discover the
manual probe and reported the feature as missing ("only OpenAI has a
refresh button").
- Split data display from the refresh action: bars/balance render as
static rows (snapshot already paints on mount), with a dedicated
action button below, aligned with the OpenAI "Query" and Grok
"Probe" buttons (same icon, blue action styling).
- Label the control with a verb (cnProviders.probe: 查询 / Query) and
give the balance cell a tooltip (it previously had none).
- Keep the tier/label layout classes and data-test hooks intact.
- Extend the quota cell spec and add a balance cell spec covering
snapshot rendering, explicit action labeling and failure passthrough.
- ChannelsView platformOrder now includes the three CN provider
platforms so channel pricing can be configured for them; composite
group expansion/attachment stays limited to the five main platforms
(matches backend isConcreteRequestPlatform and composite-routes
target_platform validation)
- SyncPricingModels maps kimi->moonshot, zhipu->zhipu,
deepseek->deepseek; also fixes gemini mapping to "google" which
matched zero catalog entries (provider key is "gemini")
- Add CN platform colors to channel pricing model tag classes
Default codex_fingerprint_mode to off. v0.1.175 treated a missing key as
"session", so upgrading silently rewrote installation/session/thread/turn/
window identifiers for every existing OAuth account that had never configured
this field. The quota regressions in #5555, #5556 and #5582 line up with that
version boundary, with A/B reports that rolling back to v0.1.173 restores
quota. Convergence is now explicit opt-in (#5610).
Only accounts that never set the field change behaviour; explicit off /
device / session / full keep working exactly as configured. That required
flipping the persistence condition in all three account modals from
"!== 'session'" to "!== 'off'": the old rule deleted the key when it equalled
the default, which after the flip would have silently discarded an
administrator's explicit opt-in to session.
Also extend convergence to the passthrough path, which previously left client
identifiers untouched:
- resolve the ids once in forwardOpenAIPassthrough and rewrite
client_metadata on the raw bytes (gjson extract + sjson splice) because
passthrough is a hot path that must not fully unmarshal multi-MB bodies;
a shared core keeps the raw and map variants from drifting
- both request builders apply the staged ids at the same relative position
(after session isolation, before identity enforcement) so headers and body
share one id set and turn_id stays consistent
- stage the ids unconditionally, including nil: a failover from a converged
account to an off account must not leave the previous account's ids behind
The security audit group (Risk Control + Prompt Audit) was hidden from
the sidebar via `hideInSimpleMode`, but nothing else in the stack
restricts it in simple mode:
- `router/index.ts` simple-mode `restrictedPaths` does not cover
`/admin/risk-control` or `/admin/prompt-audit`
- the `/risk-control` and `/prompt-audit` admin route groups have no
`RunMode` gate, and neither does `internal/securityaudit/` nor
`service/content_moderation.go`
- `SettingsView.vue` renders the risk control toggle together with a
`<router-link to="/admin/risk-control">` shortcut, and the settings
page stays visible in simple mode
The feature is therefore fully usable in simple mode and already
reachable through the settings page — only the sidebar entry was
missing. Drop the flag so the menu matches actual behaviour.
The group remains gated by `risk_control_enabled` (opt-in, default
false) through `featureFlag: flagRiskControl`.