A Refresh button next to the sensitive-data toggle reloads the channel
list with the current search, filters, sorting and page. It is marked
busy and its icon spins while the list is loading.
The token menu now opens an edit dialog instead of a rename prompt.
Renaming still needs no verification. Changing the permissions requires
step-up verification bound to the token id and the new grant, so a
proof cannot be replayed for another token or another set of
permissions; the grant is checked against what the owner can do, the
same as at creation. PATCH /api/user/access_tokens/:id takes an
optional scopes list and is rate limited like the other token routes.
A token that holds permissions the catalog no longer offers can still
be renamed and keeps them; a new grant only contains offered
permissions. Edit stays disabled until the permission catalog has
loaded.
Grant changes are audited as access_token.update with the previous and
new scopes, and audit details show both as permission names.
The permission picker for tokens and admin users now uses toggle
buttons. Token permissions are grouped by personal, administration and
system, each group collapsible with a selected count and select-all or
clear.
Each user can now hold up to 20 named access tokens in the
user_access_tokens table instead of a single token on the user row.
Tokens are shown once at creation (nap_ prefix) and stored as a SHA-256
digest. They expire after 30 days by default; a token without expiry is
allowed with a warning.
A token only reaches the routes its scopes allow. Scopes are picked from
the same permission matrix as admin permissions and are limited to what
the owner can do; every management route declares the scope it needs,
and routes without a declaration reject tokens. Token management itself
always requires a browser session.
Tokens can complete step-up verification, so verified actions such as
changing a password or 2FA settings work with a token; such changes sign
out all browser sessions. Disabling a user keeps their tokens, deleting
the account removes them.
The existing dashboard token keeps working for 30 days after the
upgrade. It can be revoked but no longer regenerated, and afterwards it
is rejected with ACCESS_TOKEN_LEGACY_RETIRED. The security page lists
tokens with their access records, and audit details show scope names.
Deleting a user, changing their status or role, setting a password or
admin permissions, creating an admin, resetting a passkey, disabling 2FA
and clearing a binding now need a single-use verification proof. The
proof context names the managed user (and the action or binding where
relevant), so it cannot be reused for another user or operation. Quota
adjustments and edits to display name, group or remark stay unchanged.
The admin.user.* scopes are issued only to admins. An admin with 2FA or
a passkey must use one of them; otherwise password or a bound OAuth
account is accepted. The users page asks for verification before sending
each gated request.
An unchanged model expression or provider override of a single-plugin
model stays saveable after the plugin drops a fact from its profile, so
a full pricing save is no longer blocked by existing prices. Shared
models still follow each provider's profile.
The task pricing table reads a dropped fact at the value a plugin keeps
reporting: price terms on dropped counts are omitted, tiers requiring a
dropped boolean to be true are skipped, and other conditions on dropped
facts are removed with the first matching tier kept. Existing prices
open in the table with only the remaining fields.
Marketplace hosts serve versioned plugin sources as immutable. When a file
is republished in place, a browser that cached the earlier bytes keeps
reading them, while the index is fetched fresh and declares the new hash,
so the install dialog reports a sha256 mismatch for a correct source.
Fetch the plugin source, the sidecar icon and the index with
cache: 'no-cache' so each request revalidates with the host (a 304 when
unchanged).
* fix(logs): prevent model badge descenders from clipping
* fix(logs): constrain wrapped model badge width
* fix(logs): preserve compact model name line spacing
* fix(logs): give wrapped model names sufficient line height
A bare TEXT column holds 64 KiB on MySQL, and the shipped alibaba plugin
is already 73 KB, so uploading an override of it failed with "Data too
long for column 'source'" or was silently truncated. The 1 MiB upload
cap also blocks the larger plugins planned next.
Add model.LongText, a string type that picks longtext on MySQL and text
on PostgreSQL and SQLite through GormDBDataType, and use it for source
and icon. Neither type carries a length, so AutoMigrate leaves an
up-to-date column alone and widens the shipped MySQL text column once
without data loss. Raise the source cap to 8 MiB on the backend and in
the frontend picker, importers and locale strings.
Verified fresh and rc.39-upgrade startups twice each on SQLite, MySQL
5.7.44 and 8.4.11, PostgreSQL 9.6.24 and 17.11; the second start issues
no DDL and a 73 KB source round-trips on every engine.
When a task plugin stops declaring an enum value (Seedance 2.0 mini now
offers 480p and 720p only), an expression saved with 1080p and 4k
branches no longer opened in the visual price table: the parser rejected
the whole tier chain on the first undeclared value, the editor fell back
to expression mode with a "cannot be represented" notice, and switching
to the visual editor reset every price to zero.
Treat a branch on an undeclared enum value as unreachable and skip it,
so the remaining tiers stay editable with their prices preserved. Nested
conditions in the pricing display treat such a comparison as never
matching. Editing a price regenerates the expression without the
unreachable branches; opening the editor alone leaves the stored
expression unchanged.
Add the synchronous openai_image host protocol so a task plugin can claim
POST /v1/images/generations and /v1/images/edits; the host submits the
task, polls it inside the request and lets the plugin render the OpenAI
image response. Unclaimed models still fall back to the HTTP image relay.
- Migrate Ali (Bailian) image models to the alibaba plugin and remove the
ali channel image/wan adaptors and the qwen model setting
- Add Doubao Seedream image generation to the doubao plugin
- Support retainResult on routes and indexed multipart file references
- Settle image counts from upstream usage or image payloads; document the
rule in the billing conventions and plugin API docs
- Show billing source and task artifacts in usage logs; rename the
DoubaoVideo channel display name to Doubao and refresh the favicon
A New API (type 60) channel proxies another gateway that may host many
task plugins, so the channel setting gains task_extend_plugin_keys; the
single task_plugin_key stays valid there as well. Channel selection,
shared-model candidate rebinding, the task_plugin.bind permission gate,
plugin usage counting and the disable cascade (which now unbinds gateway
channels instead of disabling them) all honour the union of both fields,
and plugins may no longer claim channel type 60 in channelTypes.
Plugins declare meta.upstreams and the host injects ctx.upstream into
every driver hook context. On a type-60 channel the driver addresses its
own prefixed native routes (doubao, alibaba, kling, jimeng) and the host
sends the channel key as a Bearer header for every auth type; polling
threads the channel type through ChannelMeta and tasks keep the key
that submitted them. Only plugins declaring new_api can be bound.
The channel form offers an "Upstream task plugins" selector for type-60
channels, MultiSelect options can carry an icon, and the plugin API docs,
type declarations and schema describe the additive apiVersion 1
extension.
Run discovery only when its URL changes or OIDC is enabled. Show URL validation errors beside the field and keep save actions disabled throughout submission. Correct Well-Known terminology in translations and cover provider-independent saves, validation, discovery failures, and loading state.
Validation from web/: bun run test src/features/system-settings/auth/__tests__/oauth-settings.test.tsx src/features/system-settings/auth/__tests__/passkey-settings.test.tsx (30 passed); bun run typecheck; bunx oxlint -c .oxlintrc.json src/features/system-settings/auth/oauth-section.tsx src/features/system-settings/auth/__tests__/oauth-settings.test.tsx; bunx oxfmt --check on both files; bun run build; bun run i18n:sync. In-app browser checked GitHub save and inline OIDC error using an isolated fixture with mocked API responses.
Security scope: settings form only. Consulted OWASP ASVS 5.0.0 section 8.3.1 and the Authentication, Session Management, and OAuth2 Cheat Sheets. Server authorization and authentication protocol flows are unchanged; this is not a full authentication compliance assessment.
When the security verification dialog only offers OAuth, the provider
button was a no-op selector for an already selected provider and the
actual OAuth popup only opened from the footer Verify button.
Make the provider button trigger verification directly, hide the footer
Verify button on the OAuth tab, and let onVerify accept an explicit
input so the clicked provider is used instead of stale state.
* fix(web): accept fractional group ratios and tool prices
* fix(web): mask portaled group options in usage log privacy mode
* fix(web): limit pricing input steps to four decimal places
* feat(log): flag upstream response model mismatches
* fix(log): refine response model mismatch warnings
* fix(log): skip model details when no mapping or response difference exists
* fix(log): omit redundant response model metadata
Gate the toggle, the quick option and the configuration marker through
one shared predicate that mirrors the backend allow list, add the
Sub2API channel type constant, and explain under the toggle that
advanced custom channels only use converter-free /v1/responses routes.
Lay out the channel health form with the shared settings grid so switch
rows span the full width and inputs align in two columns instead of a
fixed three-column grid that stretched the switches. Nest the test mode
and interval under the scheduled tests switch, make the mode select fill
its column, and replace the alert boxes and channels link at the top with
plain hint text. Add a layout regression test and the new group label
translation.
Enforce toIntlLocale() for every Intl call through a project oxlint
plugin with its own tests, adopt it in the billing time fields, add the
missing floating-window labels, and refresh the agent instructions.